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

namespace Nerfed.Runtime.Graphics;

// Recreate certain types in here so we can csharp-ify them and hide the Refresh namespace

public enum PrimitiveType
{
	PointList,
	LineList,
	LineStrip,
	TriangleList,
	TriangleStrip
}

public enum LoadOp
{
	Load,
	Clear,
	DontCare
}

public enum StoreOp
{
	Store,
	DontCare
}

public enum IndexElementSize
{
	Sixteen,
	ThirtyTwo
}

public enum TextureFormat
{
	/* Unsigned Normalized Float Color Formats */
	R8G8B8A8,
	B8G8R8A8,
	R5G6B5,
	A1R5G5B5,
	B4G4R4A4,
	A2R10G10B10,
	A2B10G10R10,
	R16G16,
	R16G16B16A16,
	R8,
	A8,
	/* Compressed Unsigned Normalized Float Color Formats */
	BC1,
	BC2,
	BC3,
	BC7,
	/* Signed Normalized Float Color Formats  */
	R8G8_SNORM,
	R8G8B8A8_SNORM,
	/* Signed Float Color Formats */
	R16_SFLOAT,
	R16G16_SFLOAT,
	R16G16B16A16_SFLOAT,
	R32_SFLOAT,
	R32G32_SFLOAT,
	R32G32B32A32_SFLOAT,
	/* Unsigned Integer Color Formats */
	R8_UINT,
	R8G8_UINT,
	R8G8B8A8_UINT,
	R16_UINT,
	R16G16_UINT,
	R16G16B16A16_UINT,
	/* SRGB Color Formats */
	R8G8B8A8_SRGB,
	B8G8R8A8_SRGB,
	/* Compressed SRGB Color Formats */
	BC3_SRGB,
	BC7_SRGB,
	/* Depth Formats */
	D16_UNORM,
	D24_UNORM,
	D32_SFLOAT,
	D24_UNORM_S8_UINT,
	D32_SFLOAT_S8_UINT
}

[Flags]
public enum TextureUsageFlags
{
	Sampler = 0x1,
	ColorTarget = 0x2,
	DepthStencil = 0x4,
	GraphicsStorage = 0x8,
	ComputeStorageRead = 0x20,
	ComputeStorageWrite = 0x40
}

public enum TextureType
{
	TwoD,
	ThreeD,
	Cube
}

public enum SampleCount
{
	One,
	Two,
	Four,
	Eight
}

public enum CubeMapFace
{
	PositiveX,
	NegativeX,
	PositiveY,
	NegativeY,
	PositiveZ,
	NegativeZ
}

[Flags]
public enum BufferUsageFlags
{
	Vertex = 0x1,
	Index = 0x2,
	Indirect = 0x4,
	GraphicsStorage = 0x8,
	ComputeStorageRead = 0x20,
	ComputeStorageWrite = 0x40
}

public enum TransferBufferUsage
{
	Upload,
	Download
}

public enum ShaderStage
{
	Vertex,
	Fragment
}

public enum ShaderFormat
{
	Invalid,
	SPIRV,
	HLSL,
	DXBC,
	DXIL,
	MSL,
	METALLIB,
	SECRET
}

public enum VertexElementFormat
{
	Uint,
	Float,
	Vector2,
	Vector3,
	Vector4,
	Color,
	Byte4,
	Short2,
	Short4,
	NormalizedShort2,
	NormalizedShort4,
	HalfVector2,
	HalfVector4
}

public enum VertexInputRate
{
	Vertex,
	Instance
}

public enum FillMode
{
	Fill,
	Line
}

public enum CullMode
{
	None,
	Front,
	Back
}

public enum FrontFace
{
	CounterClockwise,
	Clockwise
}

public enum CompareOp
{
	Never,
	Less,
	Equal,
	LessOrEqual,
	Greater,
	NotEqual,
	GreaterOrEqual,
	Always
}

public enum StencilOp
{
	Keep,
	Zero,
	Replace,
	IncrementAndClamp,
	DecrementAndClamp,
	Invert,
	IncrementAndWrap,
	DecrementAndWrap
}

public enum BlendOp
{
	Add,
	Subtract,
	ReverseSubtract,
	Min,
	Max
}

public enum BlendFactor
{
	Zero,
	One,
	SourceColor,
	OneMinusSourceColor,
	DestinationColor,
	OneMinusDestinationColor,
	SourceAlpha,
	OneMinusSourceAlpha,
	DestinationAlpha,
	OneMinusDestinationAlpha,
	ConstantColor,
	OneMinusConstantColor,
	SourceAlphaSaturate
}

[Flags]
public enum ColorComponentFlags
{
	None = 0x0,
	R = 0x1,
	G = 0x2,
	B = 0x4,
	A = 0x8,
	RGB = R | G | B,
	RGBA = R | G| B | A
}

public enum Filter
{
	Nearest,
	Linear
}

public enum SamplerMipmapMode
{
	Nearest,
	Linear
}

public enum SamplerAddressMode
{
	Repeat,
	MirroredRepeat,
	ClampToEdge
}

public enum PresentMode
{
	VSync,
	Immediate,
	Mailbox
}

public enum SwapchainComposition
{
	SDR,
	SDRLinear,
	HDRExtendedLinear,
	HDR10_ST2084
}

[Flags]
public enum BackendFlags
{
	Invalid = 0x0,
	Vulkan = 0x1,
	D3D11 = 0x2,
	Metal = 0x4,
	All = Vulkan | D3D11 | Metal
}

[StructLayout(LayoutKind.Sequential)]
public struct DepthStencilValue
{
	public float Depth;
	public uint Stencil;

	public DepthStencilValue(float depth, uint stencil)
	{
		Depth = depth;
		Stencil = stencil;
	}

	// FIXME: can we do an unsafe cast somehow?
	public Refresh.DepthStencilValue ToRefresh()
	{
		return new Refresh.DepthStencilValue
		{
			Depth = Depth,
			Stencil = Stencil
		};
	}
}

[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
	public int X;
	public int Y;
	public int W;
	public int H;

	public Rect(int x, int y, int w, int h)
	{
		X = x;
		Y = y;
		W = w;
		H = h;
	}

	public Rect(int w, int h)
	{
		X = 0;
		Y = 0;
		W = w;
		H = h;
	}

	// FIXME: can we do an unsafe cast somehow?
	public Refresh.Rect ToRefresh()
	{
		return new Refresh.Rect
		{
			X = X,
			Y = Y,
			W = W,
			H = H
		};
	}
}

[StructLayout(LayoutKind.Sequential)]
public struct Viewport
{
	public float X;
	public float Y;
	public float W;
	public float H;
	public float MinDepth;
	public float MaxDepth;

	public Viewport(float w, float h)
	{
		X = 0;
		Y = 0;
		W = w;
		H = h;
		MinDepth = 0;
		MaxDepth = 1;
	}

	public Viewport(float x, float y, float w, float h)
	{
		X = x;
		Y = y;
		W = w;
		H = h;
		MinDepth = 0;
		MaxDepth = 1;
	}

	public Viewport(float x, float y, float w, float h, float minDepth, float maxDepth)
	{
		X = x;
		Y = y;
		W = w;
		H = h;
		MinDepth = minDepth;
		MaxDepth = maxDepth;
	}

	public Refresh.Viewport ToRefresh()
	{
		return new Refresh.Viewport
		{
			X = X,
			Y = Y,
			W = W,
			H = H,
			MinDepth = MinDepth,
			MaxDepth = MaxDepth
		};
	}
}

[StructLayout(LayoutKind.Sequential)]
public struct VertexBinding
{
	public uint Binding;
	public uint Stride;
	public VertexInputRate InputRate;
	public uint StepRate;

	public static VertexBinding Create<T>(
		uint binding = 0,
		VertexInputRate inputRate = VertexInputRate.Vertex,
		uint stepRate = 1
	) where T : unmanaged
	{
		return new VertexBinding
		{
			Binding = binding,
			InputRate = inputRate,
			Stride = (uint) Marshal.SizeOf<T>(),
			StepRate = stepRate
		};
	}

	public Refresh.VertexBinding ToRefresh()
	{
		return new Refresh.VertexBinding
		{
			Binding = Binding,
			Stride = Stride,
			InputRate = (Refresh.VertexInputRate) InputRate,
			StepRate = StepRate
		};
	}
}

[StructLayout(LayoutKind.Sequential)]
public struct VertexAttribute
{
	public uint Location;
	public uint Binding;
	public VertexElementFormat Format;
	public uint Offset;

	public Refresh.VertexAttribute ToRefresh()
	{
		return new Refresh.VertexAttribute
		{
			Location = Location,
			Binding = Binding,
			Format = (Refresh.VertexElementFormat) Format,
			Offset = Offset
		};
	}
}

[StructLayout(LayoutKind.Sequential)]
public struct StencilOpState
{
	public StencilOp FailOp;
	public StencilOp PassOp;
	public StencilOp DepthFailOp;
	public CompareOp CompareOp;

	public Refresh.StencilOpState ToRefresh()
	{
		return new Refresh.StencilOpState
		{
			FailOp = (Refresh.StencilOp) FailOp,
			PassOp = (Refresh.StencilOp) PassOp,
			DepthFailOp = (Refresh.StencilOp) DepthFailOp,
			CompareOp = (Refresh.CompareOp) CompareOp
		};
	}
}

/// <summary>
/// Determines how a color texture will be read/written in a render pass.
/// </summary>
public struct ColorAttachmentInfo
{
	public TextureSlice TextureSlice;

	/// <summary>
	/// If LoadOp is set to Clear, the texture slice will be cleared to this color.
	/// </summary>
	public Color ClearColor;

	/// <summary>
	/// Determines what is done with the texture slice memory
	/// at the beginning of the render pass. <br/>
	///
	///   Load:
	///     Loads the data currently in the texture slice. <br/>
	///
	///   Clear:
	///     Clears the texture slice to a single color. <br/>
	///
	///   DontCare:
	///     The driver will do whatever it wants with the texture slice data.
	///     This is a good option if you know that every single pixel will be written in the render pass.
	/// </summary>
	public LoadOp LoadOp;

	/// <summary>
	/// Determines what is done with the texture slice memory
	/// at the end of the render pass. <br/>
	///
	///   Store:
	///     Stores the results of the render pass in the texture slice memory. <br/>
	///
	///   DontCare:
	///     The driver will do whatever it wants with the texture slice memory.
	/// </summary>
	public StoreOp StoreOp;

	/// <summary>
	/// If true, cycles the texture if it is bound.
	/// </summary>
	public bool Cycle;

	public ColorAttachmentInfo(
		TextureSlice textureSlice,
		bool cycle,
		Color clearColor,
		StoreOp storeOp = StoreOp.Store
	) {
		TextureSlice = textureSlice;
		ClearColor = clearColor;
		LoadOp = LoadOp.Clear;
		StoreOp = storeOp;
		Cycle = cycle;
	}

	public ColorAttachmentInfo(
		TextureSlice textureSlice,
		bool cycle,
		LoadOp loadOp = LoadOp.DontCare,
		StoreOp storeOp = StoreOp.Store
	) {
		TextureSlice = textureSlice;
		ClearColor = Color.White;
		LoadOp = loadOp;
		StoreOp = storeOp;
		Cycle = cycle;
	}

	public Refresh.ColorAttachmentInfo ToRefresh()
	{
		return new Refresh.ColorAttachmentInfo
		{
			TextureSlice = TextureSlice.ToRefresh(),
			ClearColor = new Refresh.Color
			{
				R = ClearColor.R / 255f,
				G = ClearColor.G / 255f,
				B = ClearColor.B / 255f,
				A = ClearColor.A / 255f
			},
			LoadOp = (Refresh.LoadOp) LoadOp,
			StoreOp = (Refresh.StoreOp) StoreOp,
			Cycle = Conversions.BoolToInt(Cycle)
		};
	}
}

/// <summary>
/// Determines how a depth/stencil texture will be read/written in a render pass.
/// </summary>
public struct DepthStencilAttachmentInfo
{
	public TextureSlice TextureSlice;

	/// <summary>
	/// If LoadOp is set to Clear, the texture slice depth will be cleared to this depth value. <br/>
	/// If StencilLoadOp is set to Clear, the texture slice stencil value will be cleared to this stencil value.
	/// </summary>
	public DepthStencilValue DepthStencilClearValue;

	/// <summary>
	/// Determines what is done with the texture slice depth values
	/// at the beginning of the render pass. <br/>
	///
	///   Load:
	///     Loads the data currently in the texture slice. <br/>
	///
	///   Clear:
	///     Clears the texture slice to a single depth value. <br/>
	///
	///   DontCare:
	///     The driver will do whatever it wants with the texture slice data.
	///     This is a good option if you know that every single pixel will be written in the render pass.
	/// </summary>
	public LoadOp LoadOp;

	/// <summary>
	/// Determines what is done with the texture slice depth values
	/// at the end of the render pass. <br/>
	///
	///   Store:
	///     Stores the results of the render pass in the texture slice memory. <br/>
	///
	///   DontCare:
	///     The driver will do whatever it wants with the texture slice memory.
	///     This is usually a good option for depth textures that don't need to be reused.
	/// </summary>
	public StoreOp StoreOp;

	/// <summary>
	/// Determines what is done with the texture slice stencil values
	/// at the beginning of the render pass. <br/>
	///
	///   Load:
	///     Loads the data currently in the texture slice. <br/>
	///
	///   Clear:
	///     Clears the texture slice to a single stencil value. <br/>
	///
	///   DontCare:
	///     The driver will do whatever it wants with the texture slice data.
	///     This is a good option if you know that every single pixel will be written in the render pass.
	/// </summary>
	public LoadOp StencilLoadOp;

	/// <summary>
	/// Determines what is done with the texture slice stencil values
	/// at the end of the render pass. <br/>
	///
	///   Store:
	///     Stores the results of the render pass in the texture slice memory. <br/>
	///
	///   DontCare:
	///     The driver will do whatever it wants with the texture slice memory.
	///     This is usually a good option for stencil textures that don't need to be reused.
	/// </summary>
	public StoreOp StencilStoreOp;

	/// <summary>
	/// If true, cycles the texture if it is bound.
	/// </summary>
	public bool Cycle;

	public DepthStencilAttachmentInfo(
		TextureSlice textureSlice,
		bool cycle,
		DepthStencilValue clearValue,
		StoreOp depthStoreOp = StoreOp.DontCare,
		StoreOp stencilStoreOp = StoreOp.DontCare
	){
		TextureSlice = textureSlice;
		DepthStencilClearValue = clearValue;
		LoadOp = LoadOp.Clear;
		StoreOp = depthStoreOp;
		StencilLoadOp = LoadOp.Clear;
		StencilStoreOp = stencilStoreOp;
		Cycle = cycle;
	}

	public DepthStencilAttachmentInfo(
		TextureSlice textureSlice,
		bool cycle,
		LoadOp loadOp = LoadOp.DontCare,
		StoreOp storeOp = StoreOp.DontCare,
		LoadOp stencilLoadOp = LoadOp.DontCare,
		StoreOp stencilStoreOp = StoreOp.DontCare
	) {
		TextureSlice = textureSlice;
		DepthStencilClearValue = new DepthStencilValue();
		LoadOp = loadOp;
		StoreOp = storeOp;
		StencilLoadOp = stencilLoadOp;
		StencilStoreOp = stencilStoreOp;
		Cycle = cycle;
	}

	public DepthStencilAttachmentInfo(
		TextureSlice textureSlice,
		bool cycle,
		DepthStencilValue clearValue,
		LoadOp loadOp,
		StoreOp storeOp,
		LoadOp stencilLoadOp,
		StoreOp stencilStoreOp
	) {
		TextureSlice = textureSlice;
		DepthStencilClearValue = clearValue;
		LoadOp = loadOp;
		StoreOp = storeOp;
		StencilLoadOp = stencilLoadOp;
		StencilStoreOp = stencilStoreOp;
		Cycle = cycle;
	}

	public Refresh.DepthStencilAttachmentInfo ToRefresh()
	{
		return new Refresh.DepthStencilAttachmentInfo
		{
			TextureSlice = TextureSlice.ToRefresh(),
			DepthStencilClearValue = DepthStencilClearValue.ToRefresh(),
			LoadOp = (Refresh.LoadOp) LoadOp,
			StoreOp = (Refresh.StoreOp) StoreOp,
			StencilLoadOp = (Refresh.LoadOp) StencilLoadOp,
			StencilStoreOp = (Refresh.StoreOp) StencilStoreOp,
			Cycle = Conversions.BoolToInt(Cycle)
		};
	}
}

/// <summary>
/// Defines how color blending will be performed in a GraphicsPipeline.
/// </summary>
public struct ColorAttachmentBlendState
{
	/// <summary>
	/// If disabled, no blending will occur.
	/// </summary>
	public bool BlendEnable;

	/// <summary>
	/// Selects which blend operation to use with alpha values.
	/// </summary>
	public BlendOp AlphaBlendOp;
	/// <summary>
	/// Selects which blend operation to use with color values.
	/// </summary>
	public BlendOp ColorBlendOp;

	/// <summary>
	/// Specifies which of the RGBA components are enabled for writing.
	/// </summary>
	public ColorComponentFlags ColorWriteMask;

	/// <summary>
	/// Selects which blend factor is used to determine the alpha destination factor.
	/// </summary>
	public BlendFactor DestinationAlphaBlendFactor;

	/// <summary>
	/// Selects which blend factor is used to determine the color destination factor.
	/// </summary>
	public BlendFactor DestinationColorBlendFactor;

	/// <summary>
	/// Selects which blend factor is used to determine the alpha source factor.
	/// </summary>
	public BlendFactor SourceAlphaBlendFactor;

	/// <summary>
	/// Selects which blend factor is used to determine the color source factor.
	/// </summary>
	public BlendFactor SourceColorBlendFactor;

	public static readonly ColorAttachmentBlendState Additive = new ColorAttachmentBlendState
	{
		BlendEnable = true,
		AlphaBlendOp = BlendOp.Add,
		ColorBlendOp = BlendOp.Add,
		ColorWriteMask = ColorComponentFlags.RGBA,
		SourceColorBlendFactor = BlendFactor.SourceAlpha,
		SourceAlphaBlendFactor = BlendFactor.SourceAlpha,
		DestinationColorBlendFactor = BlendFactor.One,
		DestinationAlphaBlendFactor = BlendFactor.One
	};

	public static readonly ColorAttachmentBlendState AlphaBlend = new ColorAttachmentBlendState
	{
		BlendEnable = true,
		AlphaBlendOp = BlendOp.Add,
		ColorBlendOp = BlendOp.Add,
		ColorWriteMask = ColorComponentFlags.RGBA,
		SourceColorBlendFactor = BlendFactor.One,
		SourceAlphaBlendFactor = BlendFactor.One,
		DestinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha,
		DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
	};

	public static readonly ColorAttachmentBlendState NonPremultiplied = new ColorAttachmentBlendState
	{
		BlendEnable = true,
		AlphaBlendOp = BlendOp.Add,
		ColorBlendOp = BlendOp.Add,
		ColorWriteMask = ColorComponentFlags.RGBA,
		SourceColorBlendFactor = BlendFactor.SourceAlpha,
		SourceAlphaBlendFactor = BlendFactor.SourceAlpha,
		DestinationColorBlendFactor = BlendFactor.OneMinusSourceAlpha,
		DestinationAlphaBlendFactor = BlendFactor.OneMinusSourceAlpha
	};

	public static readonly ColorAttachmentBlendState Opaque = new ColorAttachmentBlendState
	{
		BlendEnable = true,
		AlphaBlendOp = BlendOp.Add,
		ColorBlendOp = BlendOp.Add,
		ColorWriteMask = ColorComponentFlags.RGBA,
		SourceColorBlendFactor = BlendFactor.One,
		SourceAlphaBlendFactor = BlendFactor.One,
		DestinationColorBlendFactor = BlendFactor.Zero,
		DestinationAlphaBlendFactor = BlendFactor.Zero
	};

	public static readonly ColorAttachmentBlendState None = new ColorAttachmentBlendState
	{
		BlendEnable = false,
		ColorWriteMask = ColorComponentFlags.RGBA
	};

	public static readonly ColorAttachmentBlendState Disable = new ColorAttachmentBlendState
	{
		BlendEnable = false,
		ColorWriteMask = ColorComponentFlags.None
	};

	public Refresh.ColorAttachmentBlendState ToRefresh()
	{
		return new Refresh.ColorAttachmentBlendState
		{
			BlendEnable = Conversions.BoolToInt(BlendEnable),
			AlphaBlendOp = (Refresh.BlendOp) AlphaBlendOp,
			ColorBlendOp = (Refresh.BlendOp) ColorBlendOp,
			ColorWriteMask = (Refresh.ColorComponentFlags) ColorWriteMask,
			DestinationAlphaBlendFactor = (Refresh.BlendFactor) DestinationAlphaBlendFactor,
			DestinationColorBlendFactor = (Refresh.BlendFactor) DestinationColorBlendFactor,
			SourceAlphaBlendFactor = (Refresh.BlendFactor) SourceAlphaBlendFactor,
			SourceColorBlendFactor = (Refresh.BlendFactor) SourceColorBlendFactor
		};
	}
}

[StructLayout(LayoutKind.Sequential)]
public struct ColorAttachmentDescription
{
	public TextureFormat Format;
	public ColorAttachmentBlendState BlendState;

	public ColorAttachmentDescription(
		TextureFormat format,
		ColorAttachmentBlendState blendState
	) {
		Format = format;
		BlendState = blendState;
	}
}

[StructLayout(LayoutKind.Sequential)]
public struct IndirectDrawCommand
{
	public uint VertexCount;
	public uint InstanceCount;
	public uint FirstVertex;
	public uint FirstInstance;

	public IndirectDrawCommand(
		uint vertexCount,
		uint instanceCount,
		uint firstVertex,
		uint firstInstance
	) {
		VertexCount = vertexCount;
		InstanceCount = instanceCount;
		FirstVertex = firstVertex;
		FirstInstance = firstInstance;
	}
}

/// <summary>
/// A buffer location used when transferring data.
/// </summary>
public record struct BufferLocation(
	Buffer Buffer,
	uint Offset
) {
	public Refresh.BufferLocation ToRefresh()
	{
		return new Refresh.BufferLocation
		{
			Buffer = Buffer.Handle,
			Offset = Offset
		};
	}
}

/// <summary>
/// A buffer region used when transferring data.
/// </summary>
public record struct BufferRegion(
	Buffer Buffer,
	uint Offset,
	uint Size
) {
	public Refresh.BufferRegion ToRefresh()
	{
		return new Refresh.BufferRegion
		{
			Buffer = Buffer.Handle,
			Offset = Offset,
			Size = Size
		};
	}
}

/// <summary>
/// A buffer-offset pair to be used when binding buffers.
/// </summary>
public record struct BufferBinding(
	Buffer Buffer,
	uint Offset
) {
	public Refresh.BufferBinding ToRefresh()
	{
		return new Refresh.BufferBinding
		{
			Buffer = Buffer.Handle,
			Offset = Offset
		};
	}
}

/// <summary>
/// A texture-sampler pair to be used when binding samplers.
/// </summary>
public record struct TextureSamplerBinding(
	Texture Texture,
	Sampler Sampler
) {
	public Refresh.TextureSamplerBinding ToRefresh()
	{
		return new Refresh.TextureSamplerBinding
		{
			Texture = Texture.Handle,
			Sampler = Sampler.Handle
		};
	}
}

public record struct StorageBufferReadWriteBinding(
	Buffer Buffer,
	bool Cycle
) {
	public Refresh.StorageBufferReadWriteBinding ToRefresh()
	{
		return new Refresh.StorageBufferReadWriteBinding
		{
			Buffer = Buffer.Handle,
			Cycle = Conversions.BoolToInt(Cycle)
		};
	}
}

public record struct StorageTextureReadWriteBinding(
	in TextureSlice TextureSlice,
	bool Cycle
) {
	public Refresh.StorageTextureReadWriteBinding ToRefresh()
	{
		return new Refresh.StorageTextureReadWriteBinding
		{
			TextureSlice = TextureSlice.ToRefresh(),
			Cycle = Conversions.BoolToInt(Cycle)
		};
	}
}

/// <summary>
/// A convenience structure for pairing a vertex binding with its associated attributes.
/// </summary>
public struct VertexBindingAndAttributes
{
	public VertexBinding VertexBinding { get; }
	public VertexAttribute[] VertexAttributes { get; }

	public VertexBindingAndAttributes(VertexBinding binding, VertexAttribute[] attributes)
	{
		VertexBinding = binding;
		VertexAttributes = attributes;
	}

	public static VertexBindingAndAttributes Create<T>(uint bindingIndex, uint locationOffset = 0, VertexInputRate inputRate = VertexInputRate.Vertex) where T : unmanaged, IVertexType
	{
		VertexBinding binding = VertexBinding.Create<T>(bindingIndex, inputRate);
		VertexAttribute[] attributes = new VertexAttribute[T.Formats.Length];

		for (uint i = 0; i < T.Formats.Length; i += 1)
		{
			VertexElementFormat format = T.Formats[i];
			uint offset = T.Offsets[i];

			attributes[i] = new VertexAttribute
			{
				Binding = bindingIndex,
				Location = locationOffset + i,
				Format = format,
				Offset = offset
			};
		}

		return new VertexBindingAndAttributes(binding, attributes);
	}
}

/// <summary>
/// Specifies how the vertex shader will interpet vertex data in a buffer.
/// </summary>
public struct VertexInputState
{
	public VertexBinding[] VertexBindings;
	public VertexAttribute[] VertexAttributes;

	public static readonly VertexInputState Empty = new VertexInputState
	{
		VertexBindings = System.Array.Empty<VertexBinding>(),
		VertexAttributes = System.Array.Empty<VertexAttribute>()
	};

	public VertexInputState(
		VertexBinding vertexBinding,
		VertexAttribute[] vertexAttributes
	) {
		VertexBindings = new VertexBinding[] { vertexBinding };
		VertexAttributes = vertexAttributes;
	}

	public VertexInputState(
		VertexBinding[] vertexBindings,
		VertexAttribute[] vertexAttributes
	) {
		VertexBindings = vertexBindings;
		VertexAttributes = vertexAttributes;
	}

	public VertexInputState(
		VertexBindingAndAttributes bindingAndAttributes
	) {
		VertexBindings = new VertexBinding[] { bindingAndAttributes.VertexBinding };
		VertexAttributes = bindingAndAttributes.VertexAttributes;
	}

	public VertexInputState(
		VertexBindingAndAttributes[] bindingAndAttributesArray
	) {
		VertexBindings = new VertexBinding[bindingAndAttributesArray.Length];
		int attributesLength = 0;

		for (int i = 0; i < bindingAndAttributesArray.Length; i += 1)
		{
			VertexBindings[i] = bindingAndAttributesArray[i].VertexBinding;
			attributesLength += bindingAndAttributesArray[i].VertexAttributes.Length;
		}

		VertexAttributes = new VertexAttribute[attributesLength];

		int attributeIndex = 0;
		for (int i = 0; i < bindingAndAttributesArray.Length; i += 1)
		{
			for (int j = 0; j < bindingAndAttributesArray[i].VertexAttributes.Length; j += 1)
			{
				VertexAttributes[attributeIndex] = bindingAndAttributesArray[i].VertexAttributes[j];
				attributeIndex += 1;
			}
		}
	}

	public static VertexInputState CreateSingleBinding<T>() where T : unmanaged, IVertexType
	{
		return new VertexInputState(VertexBindingAndAttributes.Create<T>(0));
	}
}


/// <summary>
/// Specifies how the rasterizer should be configured for a graphics pipeline.
/// </summary>
public struct RasterizerState
{
	/// <summary>
	/// Specifies whether front faces, back faces, none, or both should be culled.
	/// </summary>
	public CullMode CullMode;

	/// <summary>
	/// Specifies maximum depth bias of a fragment. Only applies if depth biasing is enabled.
	/// </summary>
	public float DepthBiasClamp;

	/// <summary>
	/// The constant depth value added to each fragment. Only applies if depth biasing is enabled.
	/// </summary>
	public float DepthBiasConstantFactor;

	/// <summary>
	/// Specifies whether depth biasing is enabled. Only applies if depth biasing is enabled.
	/// </summary>
	public bool DepthBiasEnable;

	/// <summary>
	/// Factor applied to a fragment's slope in depth bias calculations. Only applies if depth biasing is enabled.
	/// </summary>
	public float DepthBiasSlopeFactor;

	/// <summary>
	/// Specifies how triangles should be drawn.
	/// </summary>
	public FillMode FillMode;

	/// <summary>
	/// Specifies which triangle winding order is designated as front-facing.
	/// </summary>
	public FrontFace FrontFace;

	public static readonly RasterizerState CW_CullFront = new RasterizerState
	{
		CullMode = CullMode.Front,
		FrontFace = FrontFace.Clockwise,
		FillMode = FillMode.Fill,
		DepthBiasEnable = false
	};

	public static readonly RasterizerState CW_CullBack = new RasterizerState
	{
		CullMode = CullMode.Back,
		FrontFace = FrontFace.Clockwise,
		FillMode = FillMode.Fill,
		DepthBiasEnable = false
	};

	public static readonly RasterizerState CW_CullNone = new RasterizerState
	{
		CullMode = CullMode.None,
		FrontFace = FrontFace.Clockwise,
		FillMode = FillMode.Fill,
		DepthBiasEnable = false
	};

	public static readonly RasterizerState CW_Wireframe = new RasterizerState
	{
		CullMode = CullMode.None,
		FrontFace = FrontFace.Clockwise,
		FillMode = FillMode.Line,
		DepthBiasEnable = false
	};

	public static readonly RasterizerState CCW_CullFront = new RasterizerState
	{
		CullMode = CullMode.Front,
		FrontFace = FrontFace.CounterClockwise,
		FillMode = FillMode.Fill,
		DepthBiasEnable = false
	};

	public static readonly RasterizerState CCW_CullBack = new RasterizerState
	{
		CullMode = CullMode.Back,
		FrontFace = FrontFace.CounterClockwise,
		FillMode = FillMode.Fill,
		DepthBiasEnable = false
	};

	public static readonly RasterizerState CCW_CullNone = new RasterizerState
	{
		CullMode = CullMode.None,
		FrontFace = FrontFace.CounterClockwise,
		FillMode = FillMode.Fill,
		DepthBiasEnable = false
	};

	public static readonly RasterizerState CCW_Wireframe = new RasterizerState
	{
		CullMode = CullMode.None,
		FrontFace = FrontFace.CounterClockwise,
		FillMode = FillMode.Line,
		DepthBiasEnable = false
	};

	public Refresh.RasterizerState ToRefresh()
	{
		return new Refresh.RasterizerState
		{
			CullMode = (Refresh.CullMode) CullMode,
			DepthBiasClamp = DepthBiasClamp,
			DepthBiasConstantFactor = DepthBiasConstantFactor,
			DepthBiasEnable = Conversions.BoolToInt(DepthBiasEnable),
			DepthBiasSlopeFactor = DepthBiasSlopeFactor,
			FillMode = (Refresh.FillMode) FillMode,
			FrontFace = (Refresh.FrontFace) FrontFace
		};
	}
}

/// <summary>
/// Specifies how many samples should be used in rasterization.
/// </summary>
public struct MultisampleState
{
	public SampleCount MultisampleCount;
	public uint SampleMask;

	public static readonly MultisampleState None = new MultisampleState
	{
		MultisampleCount = SampleCount.One,
		SampleMask = uint.MaxValue
	};

	public MultisampleState(
		SampleCount sampleCount,
		uint sampleMask = uint.MaxValue
	) {
		MultisampleCount = sampleCount;
		SampleMask = sampleMask;
	}

	public Refresh.MultisampleState ToRefresh()
	{
		return new Refresh.MultisampleState
		{
			MultisampleCount = (Refresh.SampleCount) MultisampleCount,
			SampleMask = SampleMask
		};
	}
}

/// <summary>
/// Determines how data is written to and read from the depth/stencil buffer.
/// </summary>
public struct DepthStencilState
{
	/// <summary>
	/// If disabled, no depth culling will occur.
	/// </summary>
	public bool DepthTestEnable;

	/// <summary>
	/// Describes the back-face stencil operation.
	/// </summary>
	public StencilOpState BackStencilState;

	/// <summary>
	/// Describes the front-face stencil operation.
	/// </summary>
	public StencilOpState FrontStencilState;

	/// <summary>
	/// The compare mask for stencil ops.
	/// </summary>
	public uint CompareMask;

	/// <summary>
	/// The write mask for stencil ops.
	/// </summary>
	public uint WriteMask;

	/// <summary>
	/// The stencil reference value.
	/// </summary>
	public uint Reference;

	/// <summary>
	/// The comparison operator used in the depth test.
	/// </summary>
	public CompareOp CompareOp;

	/// <summary>
	/// Specifies whether depth values will be written to the buffer during rendering.
	/// </summary>
	public bool DepthWriteEnable;

	/// <summary>
	/// If disabled, no stencil culling will occur.
	/// </summary>
	public bool StencilTestEnable;

	public static readonly DepthStencilState DepthReadWrite = new DepthStencilState
	{
		DepthTestEnable = true,
		DepthWriteEnable = true,
		StencilTestEnable = false,
		CompareOp = CompareOp.LessOrEqual
	};

	public static readonly DepthStencilState DepthRead = new DepthStencilState
	{
		DepthTestEnable = true,
		DepthWriteEnable = false,
		StencilTestEnable = false,
		CompareOp = CompareOp.LessOrEqual
	};

	public static readonly DepthStencilState Disable = new DepthStencilState
	{
		DepthTestEnable = false,
		DepthWriteEnable = false,
		StencilTestEnable = false
	};

	public Refresh.DepthStencilState ToRefresh()
	{
		return new Refresh.DepthStencilState
		{
			DepthTestEnable = Conversions.BoolToInt(DepthTestEnable),
			BackStencilState = BackStencilState.ToRefresh(),
			FrontStencilState = FrontStencilState.ToRefresh(),
			CompareMask = CompareMask,
			WriteMask = WriteMask,
			Reference = Reference,
			CompareOp = (Refresh.CompareOp) CompareOp,
			DepthWriteEnable = Conversions.BoolToInt(DepthWriteEnable),
			StencilTestEnable = Conversions.BoolToInt(StencilTestEnable)
		};
	}
}

/// <summary>
/// Describes the kind of attachments that will be used with this pipeline.
/// </summary>
public struct GraphicsPipelineAttachmentInfo
{
	public ColorAttachmentDescription[] ColorAttachmentDescriptions;
	public bool HasDepthStencilAttachment;
	public TextureFormat DepthStencilFormat;

	public GraphicsPipelineAttachmentInfo(
		params ColorAttachmentDescription[] colorAttachmentDescriptions
	) {
		ColorAttachmentDescriptions = colorAttachmentDescriptions;
		HasDepthStencilAttachment = false;
		DepthStencilFormat = TextureFormat.D16_UNORM;
	}

	public GraphicsPipelineAttachmentInfo(
		TextureFormat depthStencilFormat,
		params ColorAttachmentDescription[] colorAttachmentDescriptions
	) {
		ColorAttachmentDescriptions = colorAttachmentDescriptions;
		HasDepthStencilAttachment = true;
		DepthStencilFormat = depthStencilFormat;
	}
}

public struct BlendConstants
{
	public float R;
	public float G;
	public float B;
	public float A;
}

/// <summary>
/// All of the information that is used to create a GraphicsPipeline.
/// </summary>
public struct GraphicsPipelineCreateInfo
{
	public Shader VertexShader;
	public Shader FragmentShader;
	public VertexInputState VertexInputState;
	public PrimitiveType PrimitiveType;
	public RasterizerState RasterizerState;
	public MultisampleState MultisampleState;
	public DepthStencilState DepthStencilState;
	public GraphicsPipelineAttachmentInfo AttachmentInfo;
	public BlendConstants BlendConstants;
}

public struct ComputePipelineCreateInfo
{
	public ShaderFormat ShaderFormat;
	public uint ReadOnlyStorageTextureCount;
	public uint ReadOnlyStorageBufferCount;
	public uint ReadWriteStorageTextureCount;
	public uint ReadWriteStorageBufferCount;
	public uint UniformBufferCount;
	public uint ThreadCountX;
	public uint ThreadCountY;
	public uint ThreadCountZ;
}

public struct ShaderCreateInfo
{
	public ShaderStage ShaderStage;
	public ShaderFormat ShaderFormat;
	public uint SamplerCount;
	public uint StorageTextureCount;
	public uint StorageBufferCount;
	public uint UniformBufferCount;

	public static ShaderCreateInfo None = new ShaderCreateInfo();
}

/// <summary>
/// All of the information that is used to create a texture.
/// </summary>
public struct TextureCreateInfo
{
	public uint Width;
	public uint Height;
	public uint Depth;
	public bool IsCube;
	public uint LayerCount;
	public uint LevelCount;
	public SampleCount SampleCount;
	public TextureFormat Format;
	public TextureUsageFlags UsageFlags;

	public Refresh.TextureCreateInfo ToRefresh()
	{
		return new Refresh.TextureCreateInfo
		{
			Width = Width,
			Height = Height,
			Depth = Depth,
			IsCube = Conversions.BoolToInt(IsCube),
			LayerCount = LayerCount,
			LevelCount = LevelCount,
			SampleCount = (Refresh.SampleCount) SampleCount,
			Format = (Refresh.TextureFormat) Format,
			UsageFlags = (Refresh.TextureUsageFlags) UsageFlags
		};
	}
}


/// <summary>
/// All of the information that is used to create a sampler.
/// </summary>
public struct SamplerCreateInfo
{
	/// <summary>
	/// Minification filter mode. Used when the image is downscaled.
	/// </summary>
	public Filter MinFilter;
	/// <summary>
	/// Magnification filter mode. Used when the image is upscaled.
	/// </summary>
	public Filter MagFilter;
	/// <summary>
	/// Filter mode applied to mipmap lookups.
	/// </summary>
	public SamplerMipmapMode MipmapMode;
	/// <summary>
	/// Horizontal addressing mode.
	/// </summary>
	public SamplerAddressMode AddressModeU;
	/// <summary>
	/// Vertical addressing mode.
	/// </summary>
	public SamplerAddressMode AddressModeV;
	/// <summary>
	/// Depth addressing mode.
	/// </summary>
	public SamplerAddressMode AddressModeW;
	/// <summary>
	/// Bias value added to mipmap level of detail calculation.
	/// </summary>
	public float MipLodBias;
	/// <summary>
	/// Enables anisotropic filtering.
	/// </summary>
	public bool AnisotropyEnable;
	/// <summary>
	/// Maximum anisotropy value.
	/// </summary>
	public float MaxAnisotropy;
	public bool CompareEnable;
	public CompareOp CompareOp;
	/// <summary>
	/// Clamps the LOD value to a specified minimum.
	/// </summary>
	public float MinLod;
	/// <summary>
	/// Clamps the LOD value to a specified maximum.
	/// </summary>
	public float MaxLod;

	public static readonly SamplerCreateInfo AnisotropicClamp = new SamplerCreateInfo
	{
		MinFilter = Filter.Linear,
		MagFilter = Filter.Linear,
		MipmapMode = SamplerMipmapMode.Linear,
		AddressModeU = SamplerAddressMode.ClampToEdge,
		AddressModeV = SamplerAddressMode.ClampToEdge,
		AddressModeW = SamplerAddressMode.ClampToEdge,
		CompareEnable = false,
		AnisotropyEnable = true,
		MaxAnisotropy = 4,
		MipLodBias = 0f,
		MinLod = 0,
		MaxLod = 1000 /* VK_LOD_CLAMP_NONE */
	};

	public static readonly SamplerCreateInfo AnisotropicWrap = new SamplerCreateInfo
	{
		MinFilter = Filter.Linear,
		MagFilter = Filter.Linear,
		MipmapMode = SamplerMipmapMode.Linear,
		AddressModeU = SamplerAddressMode.Repeat,
		AddressModeV = SamplerAddressMode.Repeat,
		AddressModeW = SamplerAddressMode.Repeat,
		CompareEnable = false,
		AnisotropyEnable = true,
		MaxAnisotropy = 4,
		MipLodBias = 0f,
		MinLod = 0,
		MaxLod = 1000 /* VK_LOD_CLAMP_NONE */
	};

	public static readonly SamplerCreateInfo LinearClamp = new SamplerCreateInfo
	{
		MinFilter = Filter.Linear,
		MagFilter = Filter.Linear,
		MipmapMode = SamplerMipmapMode.Linear,
		AddressModeU = SamplerAddressMode.ClampToEdge,
		AddressModeV = SamplerAddressMode.ClampToEdge,
		AddressModeW = SamplerAddressMode.ClampToEdge,
		CompareEnable = false,
		AnisotropyEnable = false,
		MipLodBias = 0f,
		MinLod = 0,
		MaxLod = 1000
	};

	public static readonly SamplerCreateInfo LinearWrap = new SamplerCreateInfo
	{
		MinFilter = Filter.Linear,
		MagFilter = Filter.Linear,
		MipmapMode = SamplerMipmapMode.Linear,
		AddressModeU = SamplerAddressMode.Repeat,
		AddressModeV = SamplerAddressMode.Repeat,
		AddressModeW = SamplerAddressMode.Repeat,
		CompareEnable = false,
		AnisotropyEnable = false,
		MipLodBias = 0f,
		MinLod = 0,
		MaxLod = 1000
	};

	public static readonly SamplerCreateInfo PointClamp = new SamplerCreateInfo
	{
		MinFilter = Filter.Nearest,
		MagFilter = Filter.Nearest,
		MipmapMode = SamplerMipmapMode.Nearest,
		AddressModeU = SamplerAddressMode.ClampToEdge,
		AddressModeV = SamplerAddressMode.ClampToEdge,
		AddressModeW = SamplerAddressMode.ClampToEdge,
		CompareEnable = false,
		AnisotropyEnable = false,
		MipLodBias = 0f,
		MinLod = 0,
		MaxLod = 1000
	};

	public static readonly SamplerCreateInfo PointWrap = new SamplerCreateInfo
	{
		MinFilter = Filter.Nearest,
		MagFilter = Filter.Nearest,
		MipmapMode = SamplerMipmapMode.Nearest,
		AddressModeU = SamplerAddressMode.Repeat,
		AddressModeV = SamplerAddressMode.Repeat,
		AddressModeW = SamplerAddressMode.Repeat,
		CompareEnable = false,
		AnisotropyEnable = false,
		MipLodBias = 0f,
		MinLod = 0,
		MaxLod = 1000
	};

	public Refresh.SamplerCreateInfo ToRefresh()
	{
		return new Refresh.SamplerCreateInfo
		{
			MinFilter = (Refresh.Filter) MinFilter,
			MagFilter = (Refresh.Filter) MagFilter,
			MipmapMode = (Refresh.SamplerMipmapMode) MipmapMode,
			AddressModeU = (Refresh.SamplerAddressMode) AddressModeU,
			AddressModeV = (Refresh.SamplerAddressMode) AddressModeV,
			AddressModeW = (Refresh.SamplerAddressMode) AddressModeW,
			MipLodBias = MipLodBias,
			AnisotropyEnable = Conversions.BoolToInt(AnisotropyEnable),
			MaxAnisotropy = MaxAnisotropy,
			CompareEnable = Conversions.BoolToInt(CompareEnable),
			CompareOp = (Refresh.CompareOp) CompareOp,
			MinLod = MinLod,
			MaxLod = MaxLod
		};
	}
}

/// <summary>
/// Contains data pertaining to a texture upload or download.
/// </summary>
public record struct TextureTransferInfo(
	TransferBuffer TransferBuffer,
	uint Offset = 0,
	uint ImagePitch = 0,
	uint ImageHeight = 0
) {
	public Refresh.TextureTransferInfo ToRefresh()
	{
		return new Refresh.TextureTransferInfo
		{
			TransferBuffer = TransferBuffer.Handle,
			Offset = Offset,
			ImagePitch = ImagePitch,
			ImageHeight = ImageHeight
		};
	}
}

public record struct TransferBufferLocation(
	TransferBuffer TransferBuffer,
	uint Offset = 0
) {
	public Refresh.TransferBufferLocation ToRefresh()
	{
		return new Refresh.TransferBufferLocation
		{
			TransferBuffer = TransferBuffer.Handle,
			Offset = Offset
		};
	}
}

public record struct TransferBufferRegion(
	TransferBuffer TransferBuffer,
	uint Offset,
	uint Size
) {
	public Refresh.TransferBufferRegion ToRefresh()
	{
		return new Refresh.TransferBufferRegion
		{
			TransferBuffer = TransferBuffer.Handle,
			Offset = Offset,
			Size = Size
		};
	}
}

/// <summary>
/// A texture slice specifies a subresource of a texture.
/// </summary>
public record struct TextureSlice(
	Texture Texture,
	uint MipLevel = 0,
	uint Layer = 0
) {
	public uint Size => (Texture.Width * Texture.Height * Texture.Depth * Texture.BytesPerPixel(Texture.Format) / Texture.BlockSizeSquared(Texture.Format)) >> (int) MipLevel;

	public Refresh.TextureSlice ToRefresh()
	{
		return new Refresh.TextureSlice
		{
			Texture = Texture.Handle,
			MipLevel = MipLevel,
			Layer = Layer
		};
	}
}

public record struct TextureLocation
(
	TextureSlice TextureSlice,
	uint X = 0,
	uint Y = 0,
	uint Z = 0
) {
	public Refresh.TextureLocation ToRefresh()
	{
		return new Refresh.TextureLocation
		{
			TextureSlice = TextureSlice.ToRefresh(),
			X = X,
			Y = Y,
			Z = Z
		};
	}
}

/// <summary>
/// A texture region specifies a subregion of a texture.
/// These are used by copy commands.
/// </summary>
public record struct TextureRegion(
	TextureSlice TextureSlice,
	uint X,
	uint Y,
	uint Z,
	uint Width,
	uint Height,
	uint Depth
) {
	public uint Size => (Width * Height * Depth * Texture.BytesPerPixel(TextureSlice.Texture.Format) / Texture.BlockSizeSquared(TextureSlice.Texture.Format)) >> (int) TextureSlice.MipLevel;

	public TextureRegion(Texture texture) : this(texture, 0, 0, 0, texture.Width, texture.Height, texture.Depth) { }

	public Refresh.TextureRegion ToRefresh()
	{
		return new Refresh.TextureRegion
		{
			TextureSlice = TextureSlice.ToRefresh(),
			X = X,
			Y = Y,
			Z = Z,
			W = Width,
			H = Height,
			D = Depth
		};
	}
}
