using System;
using System.Runtime.InteropServices;
using RefreshCS;

namespace Nerfed.Runtime.Graphics;

/// <summary>
/// A structure that buffers commands to be submitted to the GPU.
/// These commands include operations like copying data, draw calls, and compute dispatches.
/// Commands only begin executing once the command buffer is submitted.
/// If you need to know when the command buffer is done, use GraphicsDevice.SubmitAndAcquireFence
/// </summary>
public class CommandBuffer
{
	public GraphicsDevice Device { get; }
	public IntPtr Handle { get; internal set; }

#if DEBUG
	bool swapchainTextureAcquired;

	ComputePipeline currentComputePipeline;

	bool renderPassActive;
	bool copyPassActive;
	bool computePassActive;

	internal bool Submitted;
#endif

	// called from CommandBufferPool
	internal CommandBuffer(GraphicsDevice device)
	{
		Device = device;
		Handle = IntPtr.Zero;

#if DEBUG
		ResetStateTracking();
#endif
	}

	internal void SetHandle(nint handle)
	{
		Handle = handle;
	}

#if DEBUG
	internal void ResetStateTracking()
	{
		swapchainTextureAcquired = false;

		currentComputePipeline = null;

		renderPassActive = false;
		copyPassActive = false;
		computePassActive = false;

		Submitted = false;
	}
#endif

	/// <summary>
	/// 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.
	/// It is an error to dispose the swapchain texture. If you do this your game WILL crash. DO NOT DO THIS.
	/// </summary>
	public Texture AcquireSwapchainTexture(
		Window window
	) {
#if DEBUG
		AssertNotSubmitted();

		if (!window.Claimed)
		{
			throw new System.InvalidOperationException("Cannot acquire swapchain texture, window has not been claimed!");
		}

		if (swapchainTextureAcquired)
		{
			throw new System.InvalidOperationException("Cannot acquire two swapchain textures on the same command buffer!");
		}
#endif

		IntPtr texturePtr = Refresh.Refresh_AcquireSwapchainTexture(
			Handle,
			window.Handle,
			out uint width,
			out uint height
		);

		if (texturePtr == IntPtr.Zero)
		{
			return null;
		}

		// Override the texture properties to avoid allocating a new texture instance!
		window.SwapchainTexture.Handle = texturePtr;
		window.SwapchainTexture.Width = width;
		window.SwapchainTexture.Height = height;
		window.SwapchainTexture.Format = window.SwapchainFormat;

#if DEBUG
		swapchainTextureAcquired = true;
#endif

		return window.SwapchainTexture;
	}

	/// <summary>
	/// Pushes data to a vertex uniform slot on the command buffer.
	/// Subsequent draw calls will use this uniform data.
	/// It is legal to push uniforms during a render or compute pass.
	/// </summary>
	public unsafe void PushVertexUniformData(
		void* uniformsPtr,
		uint size,
		uint slot = 0
	) {
		Refresh.Refresh_PushVertexUniformData(
			Handle,
			slot,
			(nint) uniformsPtr,
			size
		);
	}

	/// <summary>
	/// Pushes data to a vertex uniform slot on the command buffer.
	/// Subsequent draw calls will use this uniform data.
	/// It is legal to push uniforms during a render or compute pass.
	/// </summary>
	public unsafe void PushVertexUniformData<T>(
		in T uniforms,
		uint slot = 0
	) where T : unmanaged
	{
		fixed (T* uniformsPtr = &uniforms)
		{
			PushVertexUniformData(uniformsPtr, (uint) Marshal.SizeOf<T>(), slot);
		}
	}

	/// <summary>
	/// Pushes data to a fragment uniform slot on the command buffer.
	/// Subsequent draw calls will use this uniform data.
	/// It is legal to push uniforms during a pass.
	/// </summary>
	public unsafe void PushFragmentUniformData(
		void* uniformsPtr,
		uint size,
		uint slot = 0
	) {
		Refresh.Refresh_PushFragmentUniformData(
			Handle,
			slot,
			(nint) uniformsPtr,
			size
		);
	}

	/// <summary>
	/// Pushes data to a fragment uniform slot on the command buffer.
	/// Subsequent draw calls will use this uniform data.
	/// It is legal to push uniforms during a pass.
	/// </summary>
	public unsafe void PushFragmentUniformData<T>(
		in T uniforms,
		uint slot = 0
	) where T : unmanaged
	{
		fixed (T* uniformsPtr = &uniforms)
		{
			PushFragmentUniformData(uniformsPtr, (uint) Marshal.SizeOf<T>(), slot);
		}
	}

	/// <summary>
	/// Pushes data to a compute uniform slot on the command buffer.
	/// Subsequent draw calls will use this uniform data.
	/// It is legal to push uniforms during a pass.
	/// </summary>
	public unsafe void PushComputeUniformData(
		void* uniformsPtr,
		uint size,
		uint slot = 0
	) {
		Refresh.Refresh_PushComputeUniformData(
			Handle,
			slot,
			(nint) uniformsPtr,
			size
		);
	}

	/// <summary>
	/// Pushes data to a compute uniform slot on the command buffer.
	/// Subsequent draw calls will use this uniform data.
	/// It is legal to push uniforms during a pass.
	/// </summary>
	public unsafe void PushComputeUniformData<T>(
		in T uniforms,
		uint slot = 0
	) where T : unmanaged
	{
		fixed (T* uniformsPtr = &uniforms)
		{
			PushComputeUniformData(uniformsPtr, (uint) Marshal.SizeOf<T>(), slot);
		}
	}

	/// <summary>
	/// 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 during any kind of pass.
	/// </summary>
	/// <param name="colorAttachmentInfo">The color attachment to use in the render pass.</param>
	public unsafe RenderPass BeginRenderPass(
		in ColorAttachmentInfo colorAttachmentInfo
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertNotInPass("Cannot begin a render pass inside another pass!");
		AssertTextureNotNull(colorAttachmentInfo);
		AssertColorTarget(colorAttachmentInfo);
#endif

		Refresh.ColorAttachmentInfo* refreshColorAttachmentInfos = stackalloc Refresh.ColorAttachmentInfo[1];
		refreshColorAttachmentInfos[0] = colorAttachmentInfo.ToRefresh();

		IntPtr renderPassHandle = Refresh.Refresh_BeginRenderPass(
			Handle,
			refreshColorAttachmentInfos,
			1,
			(Refresh.DepthStencilAttachmentInfo*) nint.Zero
		);

		RenderPass renderPass = Device.RenderPassPool.Obtain();
		renderPass.SetHandle(renderPassHandle);

#if DEBUG
		renderPassActive = true;
		renderPass.colorAttachmentCount = 1;
		renderPass.colorAttachmentSampleCount = colorAttachmentInfo.TextureSlice.Texture.SampleCount;
		renderPass.colorFormatOne = colorAttachmentInfo.TextureSlice.Texture.Format;
		renderPass.hasDepthStencilAttachment = false;
#endif

		return renderPass;
	}

	/// <summary>
	/// 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.
	/// </summary>
	/// <param name="colorAttachmentInfoOne">The first color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoTwo">The second color attachment to use in the render pass.</param>
	public unsafe RenderPass BeginRenderPass(
		in ColorAttachmentInfo colorAttachmentInfoOne,
		in ColorAttachmentInfo colorAttachmentInfoTwo
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertNotInPass("Cannot begin a render pass inside another pass!");

		AssertTextureNotNull(colorAttachmentInfoOne);
		AssertColorTarget(colorAttachmentInfoOne);

		AssertTextureNotNull(colorAttachmentInfoTwo);
		AssertColorTarget(colorAttachmentInfoTwo);

		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoTwo.TextureSlice.Texture);
#endif

		Refresh.ColorAttachmentInfo* refreshColorAttachmentInfos = stackalloc Refresh.ColorAttachmentInfo[2];
		refreshColorAttachmentInfos[0] = colorAttachmentInfoOne.ToRefresh();
		refreshColorAttachmentInfos[1] = colorAttachmentInfoTwo.ToRefresh();

		IntPtr renderPassHandle = Refresh.Refresh_BeginRenderPass(
			Handle,
			refreshColorAttachmentInfos,
			2,
			(Refresh.DepthStencilAttachmentInfo*) nint.Zero
		);

		RenderPass renderPass = Device.RenderPassPool.Obtain();
		renderPass.SetHandle(renderPassHandle);

#if DEBUG
		renderPassActive = true;
		renderPass.colorAttachmentCount = 2;
		renderPass.colorAttachmentSampleCount = colorAttachmentInfoOne.TextureSlice.Texture.SampleCount;
		renderPass.colorFormatOne = colorAttachmentInfoOne.TextureSlice.Texture.Format;
		renderPass.colorFormatTwo = colorAttachmentInfoTwo.TextureSlice.Texture.Format;
		renderPass.hasDepthStencilAttachment = false;
#endif

		return renderPass;
	}

	/// <summary>
	/// 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.
	/// </summary>
	/// <param name="colorAttachmentInfoOne">The first color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoTwo">The second color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoThree">The third color attachment to use in the render pass.</param>
	public unsafe RenderPass BeginRenderPass(
		in ColorAttachmentInfo colorAttachmentInfoOne,
		in ColorAttachmentInfo colorAttachmentInfoTwo,
		in ColorAttachmentInfo colorAttachmentInfoThree
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertNotInPass("Cannot begin a render pass inside another pass!");

		AssertTextureNotNull(colorAttachmentInfoOne);
		AssertColorTarget(colorAttachmentInfoOne);

		AssertTextureNotNull(colorAttachmentInfoTwo);
		AssertColorTarget(colorAttachmentInfoTwo);

		AssertTextureNotNull(colorAttachmentInfoThree);
		AssertColorTarget(colorAttachmentInfoThree);

		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoTwo.TextureSlice.Texture);
		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoThree.TextureSlice.Texture);
#endif

		Refresh.ColorAttachmentInfo* refreshColorAttachmentInfos = stackalloc Refresh.ColorAttachmentInfo[3];
		refreshColorAttachmentInfos[0] = colorAttachmentInfoOne.ToRefresh();
		refreshColorAttachmentInfos[1] = colorAttachmentInfoTwo.ToRefresh();
		refreshColorAttachmentInfos[2] = colorAttachmentInfoThree.ToRefresh();

		IntPtr renderPassHandle = Refresh.Refresh_BeginRenderPass(
			Handle,
			refreshColorAttachmentInfos,
			3,
			(Refresh.DepthStencilAttachmentInfo*) nint.Zero
		);

		RenderPass renderPass = Device.RenderPassPool.Obtain();
		renderPass.SetHandle(renderPassHandle);

#if DEBUG
		renderPassActive = true;
		renderPass.colorAttachmentCount = 3;
		renderPass.colorAttachmentSampleCount = colorAttachmentInfoOne.TextureSlice.Texture.SampleCount;
		renderPass.colorFormatOne = colorAttachmentInfoOne.TextureSlice.Texture.Format;
		renderPass.colorFormatTwo = colorAttachmentInfoTwo.TextureSlice.Texture.Format;
		renderPass.colorFormatThree = colorAttachmentInfoThree.TextureSlice.Texture.Format;
		renderPass.hasDepthStencilAttachment = false;
#endif

		return renderPass;
	}

	/// <summary>
	/// 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.
	/// </summary>
	/// <param name="colorAttachmentInfoOne">The first color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoTwo">The second color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoThree">The third color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoFour">The four color attachment to use in the render pass.</param>
	public unsafe RenderPass BeginRenderPass(
		in ColorAttachmentInfo colorAttachmentInfoOne,
		in ColorAttachmentInfo colorAttachmentInfoTwo,
		in ColorAttachmentInfo colorAttachmentInfoThree,
		in ColorAttachmentInfo colorAttachmentInfoFour
	) {
#if DEBUG
		AssertNotSubmitted();

		AssertTextureNotNull(colorAttachmentInfoOne);
		AssertColorTarget(colorAttachmentInfoOne);

		AssertTextureNotNull(colorAttachmentInfoTwo);
		AssertColorTarget(colorAttachmentInfoTwo);

		AssertTextureNotNull(colorAttachmentInfoThree);
		AssertColorTarget(colorAttachmentInfoThree);

		AssertTextureNotNull(colorAttachmentInfoFour);
		AssertColorTarget(colorAttachmentInfoFour);

		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoTwo.TextureSlice.Texture);
		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoThree.TextureSlice.Texture);
		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoFour.TextureSlice.Texture);
#endif

		Refresh.ColorAttachmentInfo* refreshColorAttachmentInfos = stackalloc Refresh.ColorAttachmentInfo[4];
		refreshColorAttachmentInfos[0] = colorAttachmentInfoOne.ToRefresh();
		refreshColorAttachmentInfos[1] = colorAttachmentInfoTwo.ToRefresh();
		refreshColorAttachmentInfos[2] = colorAttachmentInfoThree.ToRefresh();
		refreshColorAttachmentInfos[3] = colorAttachmentInfoFour.ToRefresh();

		IntPtr renderPassHandle = Refresh.Refresh_BeginRenderPass(
			Handle,
			refreshColorAttachmentInfos,
			4,
			(Refresh.DepthStencilAttachmentInfo*) nint.Zero
		);

		RenderPass renderPass = Device.RenderPassPool.Obtain();
		renderPass.SetHandle(renderPassHandle);

#if DEBUG
		renderPassActive = true;
		renderPass.colorAttachmentCount = 3;
		renderPass.colorAttachmentSampleCount = colorAttachmentInfoOne.TextureSlice.Texture.SampleCount;
		renderPass.colorFormatOne = colorAttachmentInfoOne.TextureSlice.Texture.Format;
		renderPass.colorFormatTwo = colorAttachmentInfoTwo.TextureSlice.Texture.Format;
		renderPass.colorFormatThree = colorAttachmentInfoThree.TextureSlice.Texture.Format;
		renderPass.colorFormatFour = colorAttachmentInfoFour.TextureSlice.Texture.Format;
		renderPass.hasDepthStencilAttachment = false;
#endif

		return renderPass;
	}

	/// <summary>
	/// 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.
	/// </summary>
	/// <param name="depthStencilAttachmentInfo">The depth stencil attachment to use in the render pass.</param>
	public unsafe RenderPass BeginRenderPass(
		in DepthStencilAttachmentInfo depthStencilAttachmentInfo
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertValidDepthAttachment(depthStencilAttachmentInfo);
#endif

		Refresh.DepthStencilAttachmentInfo refreshDepthStencilAttachmentInfo = depthStencilAttachmentInfo.ToRefresh();

		IntPtr renderPassHandle = Refresh.Refresh_BeginRenderPass(
			Handle,
			(Refresh.ColorAttachmentInfo*) nint.Zero,
			0,
			&refreshDepthStencilAttachmentInfo
		);

		RenderPass renderPass = Device.RenderPassPool.Obtain();
		renderPass.SetHandle(renderPassHandle);

#if DEBUG
		renderPassActive = true;
		renderPass.hasDepthStencilAttachment = true;
		renderPass.depthStencilAttachmentSampleCount = depthStencilAttachmentInfo.TextureSlice.Texture.SampleCount;
		renderPass.depthStencilFormat = depthStencilAttachmentInfo.TextureSlice.Texture.Format;
#endif

		return renderPass;
	}

	/// <summary>
	/// 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.
	/// </summary>
	/// <param name="depthStencilAttachmentInfo">The depth stencil attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfo">The color attachment to use in the render pass.</param>
	public unsafe RenderPass BeginRenderPass(
		in DepthStencilAttachmentInfo depthStencilAttachmentInfo,
		in ColorAttachmentInfo colorAttachmentInfo
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertValidDepthAttachment(depthStencilAttachmentInfo);

		AssertTextureNotNull(colorAttachmentInfo);
		AssertColorTarget(colorAttachmentInfo);
		AssertSameSampleCount(colorAttachmentInfo.TextureSlice.Texture, depthStencilAttachmentInfo.TextureSlice.Texture);
#endif

		Refresh.ColorAttachmentInfo* refreshColorAttachmentInfos = stackalloc Refresh.ColorAttachmentInfo[1];
		refreshColorAttachmentInfos[0] = colorAttachmentInfo.ToRefresh();

		Refresh.DepthStencilAttachmentInfo refreshDepthStencilAttachmentInfo = depthStencilAttachmentInfo.ToRefresh();

		IntPtr renderPassHandle = Refresh.Refresh_BeginRenderPass(
			Handle,
			refreshColorAttachmentInfos,
			1,
			&refreshDepthStencilAttachmentInfo
		);

		RenderPass renderPass = Device.RenderPassPool.Obtain();
		renderPass.SetHandle(renderPassHandle);

#if DEBUG
		renderPassActive = true;
		renderPass.hasDepthStencilAttachment = true;
		renderPass.colorAttachmentCount = 1;
		renderPass.colorAttachmentSampleCount = colorAttachmentInfo.TextureSlice.Texture.SampleCount;
		renderPass.colorFormatOne = colorAttachmentInfo.TextureSlice.Texture.Format;
		renderPass.depthStencilAttachmentSampleCount = depthStencilAttachmentInfo.TextureSlice.Texture.SampleCount;
		renderPass.depthStencilFormat = depthStencilAttachmentInfo.TextureSlice.Texture.Format;
#endif

		return renderPass;
	}

	/// <summary>
	/// 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.
	/// </summary>
	/// <param name="depthStencilAttachmentInfo">The depth stencil attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoOne">The first color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoTwo">The second color attachment to use in the render pass.</param>
	public unsafe RenderPass BeginRenderPass(
		in DepthStencilAttachmentInfo depthStencilAttachmentInfo,
		in ColorAttachmentInfo colorAttachmentInfoOne,
		in ColorAttachmentInfo colorAttachmentInfoTwo
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertValidDepthAttachment(depthStencilAttachmentInfo);

		AssertTextureNotNull(colorAttachmentInfoOne);
		AssertColorTarget(colorAttachmentInfoOne);

		AssertTextureNotNull(colorAttachmentInfoTwo);
		AssertColorTarget(colorAttachmentInfoTwo);

		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoTwo.TextureSlice.Texture);
		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, depthStencilAttachmentInfo.TextureSlice.Texture);
#endif

		Refresh.ColorAttachmentInfo* refreshColorAttachmentInfos = stackalloc Refresh.ColorAttachmentInfo[2];
		refreshColorAttachmentInfos[0] = colorAttachmentInfoOne.ToRefresh();
		refreshColorAttachmentInfos[1] = colorAttachmentInfoTwo.ToRefresh();

		Refresh.DepthStencilAttachmentInfo refreshDepthStencilAttachmentInfo = depthStencilAttachmentInfo.ToRefresh();

		IntPtr renderPassHandle = Refresh.Refresh_BeginRenderPass(
			Handle,
			refreshColorAttachmentInfos,
			2,
			&refreshDepthStencilAttachmentInfo
		);

		RenderPass renderPass = Device.RenderPassPool.Obtain();
		renderPass.SetHandle(renderPassHandle);

#if DEBUG
		renderPassActive = true;
		renderPass.hasDepthStencilAttachment = true;
		renderPass.colorAttachmentCount = 2;
		renderPass.colorFormatOne = colorAttachmentInfoOne.TextureSlice.Texture.Format;
		renderPass.colorFormatTwo = colorAttachmentInfoTwo.TextureSlice.Texture.Format;
		renderPass.colorAttachmentSampleCount = colorAttachmentInfoOne.TextureSlice.Texture.SampleCount;
		renderPass.depthStencilAttachmentSampleCount = depthStencilAttachmentInfo.TextureSlice.Texture.SampleCount;
		renderPass.depthStencilFormat = depthStencilAttachmentInfo.TextureSlice.Texture.Format;
#endif

		return renderPass;
	}

	/// <summary>
	/// 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.
	/// </summary>
	/// <param name="depthStencilAttachmentInfo">The depth stencil attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoOne">The first color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoTwo">The second color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoThree">The third color attachment to use in the render pass.</param>
	public unsafe RenderPass BeginRenderPass(
		in DepthStencilAttachmentInfo depthStencilAttachmentInfo,
		in ColorAttachmentInfo colorAttachmentInfoOne,
		in ColorAttachmentInfo colorAttachmentInfoTwo,
		in ColorAttachmentInfo colorAttachmentInfoThree
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertValidDepthAttachment(depthStencilAttachmentInfo);

		AssertTextureNotNull(colorAttachmentInfoOne);
		AssertColorTarget(colorAttachmentInfoOne);

		AssertTextureNotNull(colorAttachmentInfoTwo);
		AssertColorTarget(colorAttachmentInfoTwo);

		AssertTextureNotNull(colorAttachmentInfoThree);
		AssertColorTarget(colorAttachmentInfoThree);

		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoTwo.TextureSlice.Texture);
		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoThree.TextureSlice.Texture);
		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, depthStencilAttachmentInfo.TextureSlice.Texture);
#endif

		Refresh.ColorAttachmentInfo* refreshColorAttachmentInfos = stackalloc Refresh.ColorAttachmentInfo[3];
		refreshColorAttachmentInfos[0] = colorAttachmentInfoOne.ToRefresh();
		refreshColorAttachmentInfos[1] = colorAttachmentInfoTwo.ToRefresh();
		refreshColorAttachmentInfos[2] = colorAttachmentInfoThree.ToRefresh();

		Refresh.DepthStencilAttachmentInfo refreshDepthStencilAttachmentInfo = depthStencilAttachmentInfo.ToRefresh();

		IntPtr renderPassHandle = Refresh.Refresh_BeginRenderPass(
			Handle,
			refreshColorAttachmentInfos,
			3,
			&refreshDepthStencilAttachmentInfo
		);

		RenderPass renderPass = Device.RenderPassPool.Obtain();
		renderPass.SetHandle(renderPassHandle);

#if DEBUG
		renderPassActive = true;
		renderPass.hasDepthStencilAttachment = true;
		renderPass.colorAttachmentCount = 3;
		renderPass.colorAttachmentSampleCount = colorAttachmentInfoOne.TextureSlice.Texture.SampleCount;
		renderPass.colorFormatOne = colorAttachmentInfoOne.TextureSlice.Texture.Format;
		renderPass.colorFormatTwo = colorAttachmentInfoTwo.TextureSlice.Texture.Format;
		renderPass.colorFormatThree = colorAttachmentInfoThree.TextureSlice.Texture.Format;
		renderPass.depthStencilAttachmentSampleCount = depthStencilAttachmentInfo.TextureSlice.Texture.SampleCount;
		renderPass.depthStencilFormat = depthStencilAttachmentInfo.TextureSlice.Texture.Format;
#endif

		return renderPass;
	}

	/// <summary>
	/// 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.
	/// </summary>
	/// <param name="depthStencilAttachmentInfo">The depth stencil attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoOne">The first color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoTwo">The second color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoThree">The third color attachment to use in the render pass.</param>
	/// <param name="colorAttachmentInfoFour">The four color attachment to use in the render pass.</param>
	public unsafe RenderPass BeginRenderPass(
		in DepthStencilAttachmentInfo depthStencilAttachmentInfo,
		in ColorAttachmentInfo colorAttachmentInfoOne,
		in ColorAttachmentInfo colorAttachmentInfoTwo,
		in ColorAttachmentInfo colorAttachmentInfoThree,
		in ColorAttachmentInfo colorAttachmentInfoFour
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertValidDepthAttachment(depthStencilAttachmentInfo);

		AssertTextureNotNull(colorAttachmentInfoOne);
		AssertColorTarget(colorAttachmentInfoOne);

		AssertTextureNotNull(colorAttachmentInfoTwo);
		AssertColorTarget(colorAttachmentInfoTwo);

		AssertTextureNotNull(colorAttachmentInfoThree);
		AssertColorTarget(colorAttachmentInfoThree);

		AssertTextureNotNull(colorAttachmentInfoFour);
		AssertColorTarget(colorAttachmentInfoFour);

		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoTwo.TextureSlice.Texture);
		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoThree.TextureSlice.Texture);
		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, colorAttachmentInfoFour.TextureSlice.Texture);
		AssertSameSampleCount(colorAttachmentInfoOne.TextureSlice.Texture, depthStencilAttachmentInfo.TextureSlice.Texture);
#endif

		Refresh.ColorAttachmentInfo* refreshColorAttachmentInfos = stackalloc Refresh.ColorAttachmentInfo[4];
		refreshColorAttachmentInfos[0] = colorAttachmentInfoOne.ToRefresh();
		refreshColorAttachmentInfos[1] = colorAttachmentInfoTwo.ToRefresh();
		refreshColorAttachmentInfos[2] = colorAttachmentInfoThree.ToRefresh();
		refreshColorAttachmentInfos[3] = colorAttachmentInfoFour.ToRefresh();

		Refresh.DepthStencilAttachmentInfo refreshDepthStencilAttachmentInfo = depthStencilAttachmentInfo.ToRefresh();

		IntPtr renderPassHandle = Refresh.Refresh_BeginRenderPass(
			Handle,
			refreshColorAttachmentInfos,
			4,
			&refreshDepthStencilAttachmentInfo
		);

		RenderPass renderPass = Device.RenderPassPool.Obtain();
		renderPass.SetHandle(renderPassHandle);

#if DEBUG
		renderPassActive = true;
		renderPass.hasDepthStencilAttachment = true;
		renderPass.colorAttachmentCount = 4;
		renderPass.colorAttachmentSampleCount = colorAttachmentInfoOne.TextureSlice.Texture.SampleCount;
		renderPass.colorFormatOne = colorAttachmentInfoOne.TextureSlice.Texture.Format;
		renderPass.colorFormatTwo = colorAttachmentInfoTwo.TextureSlice.Texture.Format;
		renderPass.colorFormatThree = colorAttachmentInfoThree.TextureSlice.Texture.Format;
		renderPass.colorFormatFour = colorAttachmentInfoFour.TextureSlice.Texture.Format;
		renderPass.depthStencilAttachmentSampleCount = depthStencilAttachmentInfo.TextureSlice.Texture.SampleCount;
		renderPass.depthStencilFormat = depthStencilAttachmentInfo.TextureSlice.Texture.Format;
#endif

		return renderPass;
	}

	/// <summary>
	/// Ends the current render pass.
	/// This must be called before beginning another render pass or submitting the command buffer.
	/// </summary>
	public void EndRenderPass(RenderPass renderPass)
	{
#if DEBUG
		AssertNotSubmitted();
		AssertRenderPassActive();

		renderPassActive = false;
#endif

		Refresh.Refresh_EndRenderPass(
			renderPass.Handle
		);

		renderPass.SetHandle(nint.Zero);
		Device.RenderPassPool.Return(renderPass);
	}

	/// <summary>
	/// Blits a texture to another texture with the specified filter.
	///
	/// This operation cannot be performed inside any pass.
	/// </summary>
	/// <param name="cycle">If true, the destination texture will cycle if bound.</param>
	public void Blit(
		in TextureRegion source,
		in TextureRegion destination,
		Filter filter,
		bool cycle
	) {
		Refresh.Refresh_Blit(
			Handle,
			source.ToRefresh(),
			destination.ToRefresh(),
			(Refresh.Filter) filter,
			Conversions.BoolToInt(cycle)
		);
	}

	public unsafe ComputePass BeginComputePass(
		in StorageTextureReadWriteBinding readWriteTextureBinding
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertNotInPass("Cannot begin compute pass while in another pass!");
		computePassActive = true;
#endif

		Refresh.StorageTextureReadWriteBinding refreshTextureBinding = readWriteTextureBinding.ToRefresh();

		IntPtr computePassHandle = Refresh.Refresh_BeginComputePass(
			Handle,
			&refreshTextureBinding,
			1,
			(Refresh.StorageBufferReadWriteBinding*) nint.Size,
			0
		);

		ComputePass computePass = Device.ComputePassPool.Obtain();
		computePass.SetHandle(computePassHandle);

#if DEBUG
		computePass.active = true;
#endif

		return computePass;
	}

	public unsafe ComputePass BeginComputePass(
		in StorageBufferReadWriteBinding readWriteBufferBinding
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertNotInPass("Cannot begin compute pass while in another pass!");
		computePassActive = true;
#endif

		Refresh.StorageBufferReadWriteBinding refreshBufferBinding = readWriteBufferBinding.ToRefresh();

		IntPtr computePassHandle = Refresh.Refresh_BeginComputePass(
			Handle,
			(Refresh.StorageTextureReadWriteBinding*) nint.Zero,
			0,
			&refreshBufferBinding,
			1
		);

		ComputePass computePass = Device.ComputePassPool.Obtain();
		computePass.SetHandle(computePassHandle);

#if DEBUG
		computePass.active = true;
#endif

		return computePass;
	}

	public unsafe ComputePass BeginComputePass(
		in StorageTextureReadWriteBinding readWriteTextureBinding,
		in StorageBufferReadWriteBinding readWriteBufferBinding
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertNotInPass("Cannot begin compute pass while in another pass!");
		computePassActive = true;
#endif

		Refresh.StorageTextureReadWriteBinding refreshTextureBinding = readWriteTextureBinding.ToRefresh();
		Refresh.StorageBufferReadWriteBinding refreshBufferBinding = readWriteBufferBinding.ToRefresh();

		IntPtr computePassHandle = Refresh.Refresh_BeginComputePass(
			Handle,
			&refreshTextureBinding,
			1,
			&refreshBufferBinding,
			1
		);

		ComputePass computePass = Device.ComputePassPool.Obtain();
		computePass.SetHandle(computePassHandle);

#if DEBUG
		computePass.active = true;
#endif

		return computePass;
	}

	public unsafe ComputePass BeginComputePass(
		Span<StorageTextureReadWriteBinding> readWriteTextureBindings,
		Span<StorageBufferReadWriteBinding> readWriteBufferBindings
	) {
#if DEBUG
		AssertNotSubmitted();
		AssertNotInPass("Cannot begin compute pass while in another pass!");
		computePassActive = true;
#endif

		void* refreshTextureBindings = NativeMemory.Alloc(
			(nuint) (readWriteTextureBindings.Length * Marshal.SizeOf<StorageTextureReadWriteBinding>())
		);

		void* refreshBufferBindings = NativeMemory.Alloc(
			(nuint) (readWriteBufferBindings.Length * Marshal.SizeOf<StorageBufferReadWriteBinding>())
		);

		IntPtr computePassHandle = Refresh.Refresh_BeginComputePass(
			Handle,
			(Refresh.StorageTextureReadWriteBinding*) refreshTextureBindings,
			(uint) readWriteTextureBindings.Length,
			(Refresh.StorageBufferReadWriteBinding*) refreshBufferBindings,
			(uint) readWriteBufferBindings.Length
		);

		ComputePass computePass = Device.ComputePassPool.Obtain();
		computePass.SetHandle(computePassHandle);

#if DEBUG
		computePass.active = true;
#endif

		NativeMemory.Free(refreshTextureBindings);
		NativeMemory.Free(refreshBufferBindings);

		return computePass;
	}

	public void EndComputePass(ComputePass computePass)
	{
#if DEBUG
		AssertNotSubmitted();
		AssertInComputePass("Cannot end compute pass while not in a compute pass!");
		computePassActive = false;
		computePass.active = false;
#endif

		Refresh.Refresh_EndComputePass(
			computePass.Handle
		);

		computePass.SetHandle(nint.Zero);
		Device.ComputePassPool.Return(computePass);
	}

	// Copy Pass

	/// <summary>
	/// Begins a copy pass.
	/// All copy commands must be made within a copy pass.
	/// It is an error to call this during any kind of pass.
	/// </summary>
	public CopyPass BeginCopyPass()
	{
#if DEBUG
		AssertNotSubmitted();
		AssertNotInPass("Cannot begin copy pass while in another pass!");
		copyPassActive = true;
#endif

		IntPtr copyPassHandle = Refresh.Refresh_BeginCopyPass(Handle);

		CopyPass copyPass = Device.CopyPassPool.Obtain();
		copyPass.SetHandle(copyPassHandle);

		return copyPass;
	}

	public void EndCopyPass(CopyPass copyPass)
	{
#if DEBUG
		AssertNotSubmitted();
		AssertInCopyPass("Cannot end copy pass while not in a copy pass!");
		copyPassActive = false;
#endif

		Refresh.Refresh_EndCopyPass(
			copyPass.Handle
		);

		copyPass.SetHandle(nint.Zero);
		Device.CopyPassPool.Return(copyPass);
	}

#if DEBUG
	private void AssertRenderPassActive(string message = "No active render pass!")
	{
		if (!renderPassActive)
		{
			throw new System.InvalidOperationException(message);
		}
	}

	private void AssertComputePipelineBound(string message = "No compute pipeline is bound!")
	{
		if (currentComputePipeline == null)
		{
			throw new System.InvalidOperationException(message);
		}
	}

	private void AssertTextureNotNull(ColorAttachmentInfo colorAttachmentInfo)
	{
		if (colorAttachmentInfo.TextureSlice.Texture == null || colorAttachmentInfo.TextureSlice.Texture.Handle == IntPtr.Zero)
		{
			throw new System.ArgumentException("Render pass color attachment Texture cannot be null!");
		}
	}

	private void AssertColorTarget(ColorAttachmentInfo colorAttachmentInfo)
	{
		if ((colorAttachmentInfo.TextureSlice.Texture.UsageFlags & TextureUsageFlags.ColorTarget) == 0)
		{
			throw new System.ArgumentException("Render pass color attachment UsageFlags must include TextureUsageFlags.ColorTarget!");
		}
	}

	private void AssertSameSampleCount(Texture a, Texture b)
	{
		if (a.SampleCount != b.SampleCount)
		{
			throw new System.ArgumentException("All attachments in a render pass must have the same SampleCount!");
		}
	}

	private void AssertValidDepthAttachment(DepthStencilAttachmentInfo depthStencilAttachmentInfo)
	{
		if (depthStencilAttachmentInfo.TextureSlice.Texture == null ||
			depthStencilAttachmentInfo.TextureSlice.Texture.Handle == IntPtr.Zero)
		{
			throw new System.ArgumentException("Render pass depth stencil attachment Texture cannot be null!");
		}

		if ((depthStencilAttachmentInfo.TextureSlice.Texture.UsageFlags & TextureUsageFlags.DepthStencil) == 0)
		{
			throw new System.ArgumentException("Render pass depth stencil attachment UsageFlags must include TextureUsageFlags.DepthStencilTarget!");
		}
	}

	private void AssertNonEmptyCopy(uint dataLengthInBytes)
	{
		if (dataLengthInBytes == 0)
		{
			throw new System.InvalidOperationException("SetBufferData must have a length greater than 0 bytes!");
		}
	}

	private void AssertBufferBoundsCheck(uint bufferLengthInBytes, uint offsetInBytes, uint copyLengthInBytes)
	{
		if (copyLengthInBytes > bufferLengthInBytes + offsetInBytes)
		{
			throw new System.InvalidOperationException($"SetBufferData overflow! buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}");
		}
	}

	private void AssertTextureBoundsCheck(uint textureSizeInBytes, uint dataLengthInBytes)
	{
		if (dataLengthInBytes > textureSizeInBytes)
		{
			throw new System.InvalidOperationException($"SetTextureData overflow! texture size {textureSizeInBytes}, data size {dataLengthInBytes}");
		}
	}

	private void AssertNotInPass(string message)
	{
		if (renderPassActive || copyPassActive || computePassActive)
		{
			throw new System.InvalidOperationException(message);
		}
	}

	private void AssertInCopyPass(string message)
	{
		if (!copyPassActive)
		{
			throw new System.InvalidOperationException(message);
		}
	}

	private void AssertInComputePass(string message)
	{
		if (!computePassActive)
		{
			throw new System.InvalidOperationException(message);
		}
	}

	private void AssertNotSubmitted()
	{
		if (Submitted)
		{
			throw new System.InvalidOperationException("Cannot add commands to a submitted command buffer!");
		}
	}
#endif
}
