/* Refresh - XNA-inspired 3D Graphics Library with modern capabilities
 *
 * Copyright (c) 2020 Evan Hemsley
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from
 * the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software in a
 * product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source distribution.
 *
 * Evan "cosmonaut" Hemsley <evan@moonside.games>
 *
 */

#if REFRESH_DRIVER_D3D11

#define D3D11_NO_HELPERS
#define CINTERFACE
#define COBJMACROS
#include <d3d11.h>
#include <dxgi.h>

#include "Refresh_Driver.h"
#include "Refresh_Driver_D3D11_cdefines.h"

#include <SDL.h>
#include <SDL_syswm.h>

/* Defines */

#define D3D11_DLL "d3d11.dll"
#define DXGI_DLL "dxgi.dll"
#define WINDOW_SWAPCHAIN_DATA "Refresh_D3D11Swapchain"

#define NOT_IMPLEMENTED SDL_assert(0 && "Not implemented!");

/* Macros */

#define ERROR_CHECK(msg) \
	if (FAILED(res)) \
	{ \
		D3D11_INTERNAL_LogError(renderer->device, msg, res); \
	}

#define ERROR_CHECK_RETURN(msg, ret) \
	if (FAILED(res)) \
	{ \
		D3D11_INTERNAL_LogError(renderer->device, msg, res); \
		return ret; \
	}

#define EXPAND_ELEMENTS_IF_NEEDED(arr, initialValue, type)	\
	if (arr->count == arr->capacity)			\
	{							\
		if (arr->capacity == 0)				\
		{						\
			arr->capacity = initialValue;		\
		}						\
		else						\
		{						\
			arr->capacity *= 2;			\
		}						\
		arr->elements = (type*) SDL_realloc(		\
			arr->elements,				\
			arr->capacity * sizeof(type)		\
		);						\
	}

/* Conversions */

static DXGI_FORMAT RefreshToD3D11_SurfaceFormat[] =
{
	DXGI_FORMAT_R8G8B8A8_UNORM,	/* R8G8B8A8 */
	DXGI_FORMAT_B8G8R8A8_UNORM,	/* B8G8R8A8 */
	DXGI_FORMAT_B5G6R5_UNORM,	/* R5G6B5 */
	DXGI_FORMAT_B5G5R5A1_UNORM,	/* A1R5G5B5 */
	DXGI_FORMAT_B4G4R4A4_UNORM,	/* B4G4R4A4 */
	DXGI_FORMAT_BC1_UNORM,	/* BC1 */
	DXGI_FORMAT_BC3_UNORM,	/* BC3 */
	DXGI_FORMAT_BC5_UNORM,	/* BC5 */
	DXGI_FORMAT_R8G8_SNORM,	/* R8G8_SNORM */
	DXGI_FORMAT_R8G8B8A8_SNORM,	/* R8G8B8A8_SNORM */
	DXGI_FORMAT_R10G10B10A2_UNORM,	/* A2R10G10B10 */
	DXGI_FORMAT_R16G16_UNORM,	/* R16G16 */
	DXGI_FORMAT_R16G16B16A16_UNORM,	/* R16G16B16A16 */
	DXGI_FORMAT_R8_UNORM,	/* R8 */
	DXGI_FORMAT_R32_FLOAT,	/* R32_SFLOAT */
	DXGI_FORMAT_R32G32_FLOAT,	/* R32G32_SFLOAT */
	DXGI_FORMAT_R32G32B32A32_FLOAT,	/* R32G32B32A32_SFLOAT */
	DXGI_FORMAT_R16_FLOAT,	/* R16_SFLOAT */
	DXGI_FORMAT_R16G16_FLOAT,	/* R16G16_SFLOAT */
	DXGI_FORMAT_R16G16B16A16_FLOAT,	/* R16G16B16A16_SFLOAT */
	DXGI_FORMAT_D16_UNORM,	/* D16 */
	DXGI_FORMAT_D32_FLOAT,	/* D32 */
	DXGI_FORMAT_D24_UNORM_S8_UINT,	/* D16S8 */
	DXGI_FORMAT_D32_FLOAT_S8X24_UINT	/* D32S8 */
};

static DXGI_FORMAT RefreshToD3D11_VertexFormat[] =
{
	DXGI_FORMAT_R32_FLOAT,	/* SINGLE */
	DXGI_FORMAT_R32G32_FLOAT,	/* VECTOR2 */
	DXGI_FORMAT_R32G32B32_FLOAT,	/* VECTOR3 */
	DXGI_FORMAT_R32G32B32A32_FLOAT,	/* VECTOR4 */
	DXGI_FORMAT_R8G8B8A8_UNORM,	/* COLOR */
	DXGI_FORMAT_R8G8B8A8_UINT,	/* BYTE4 */
	DXGI_FORMAT_R16G16_SINT,	/* SHORT2 */
	DXGI_FORMAT_R16G16B16A16_SINT,	/* SHORT4 */
	DXGI_FORMAT_R16G16_SNORM,	/* NORMALIZEDSHORT2 */
	DXGI_FORMAT_R16G16B16A16_SNORM,	/* NORMALIZEDSHORT4 */
	DXGI_FORMAT_R16G16_FLOAT,	/* HALFVECTOR2 */
	DXGI_FORMAT_R16G16B16A16_FLOAT	/* HALFVECTOR4 */
};

static DXGI_FORMAT RefreshToD3D11_IndexType[] =
{
	DXGI_FORMAT_R16_UINT,	/* 16BIT */
	DXGI_FORMAT_R32_UINT	/* 32BIT */
};

static D3D11_PRIMITIVE_TOPOLOGY RefreshToD3D11_PrimitiveType[] =
{
	D3D_PRIMITIVE_TOPOLOGY_POINTLIST,	/* POINTLIST */
	D3D_PRIMITIVE_TOPOLOGY_LINELIST,	/* LINELIST */
	D3D_PRIMITIVE_TOPOLOGY_LINESTRIP,	/* LINESTRIP */
	D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST,	/* TRIANGLELIST */
	D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP	/* TRIANGLESTRIP */
};

static D3D11_FILL_MODE RefreshToD3D11_PolygonMode[] =
{
	D3D11_FILL_SOLID,	/* FILL */
	D3D11_FILL_WIREFRAME,	/* LINE */
};

static D3D11_CULL_MODE RefreshToD3D11_CullMode[] =
{
	D3D11_CULL_NONE,	/* NONE */
	D3D11_CULL_FRONT,	/* FRONT */
	D3D11_CULL_BACK	/* BACK */
};

static D3D11_BLEND RefreshToD3D11_BlendFactor[] =
{
	D3D11_BLEND_ZERO,	/* ZERO */
	D3D11_BLEND_ONE,	/* ONE */
	D3D11_BLEND_SRC_COLOR,	/* SRC_COLOR */
	D3D11_BLEND_INV_SRC_COLOR,	/* ONE_MINUS_SRC_COLOR */
	D3D11_BLEND_DEST_COLOR,	/* DST_COLOR */
	D3D11_BLEND_INV_DEST_COLOR,	/* ONE_MINUS_DST_COLOR */
	D3D11_BLEND_SRC_ALPHA,	/* SRC_ALPHA */
	D3D11_BLEND_INV_SRC_ALPHA,	/* ONE_MINUS_SRC_ALPHA */
	D3D11_BLEND_DEST_ALPHA,	/* DST_ALPHA */
	D3D11_BLEND_INV_DEST_ALPHA,	/* ONE_MINUS_DST_ALPHA */
	D3D11_BLEND_BLEND_FACTOR,	/* CONSTANT_COLOR */
	D3D11_BLEND_INV_BLEND_FACTOR,	/* ONE_MINUS_CONSTANT_COLOR */
	D3D11_BLEND_SRC_ALPHA_SAT,	/* SRC_ALPHA_SATURATE */
	D3D11_BLEND_SRC1_COLOR,	/* SRC1_COLOR */
	D3D11_BLEND_INV_SRC1_COLOR,	/* ONE_MINUS_SRC1_COLOR */
	D3D11_BLEND_SRC1_ALPHA,	/* SRC1_ALPHA */
	D3D11_BLEND_INV_SRC1_ALPHA	/* ONE_MINUS_SRC1_ALPHA */
};

static D3D11_BLEND_OP RefreshToD3D11_BlendOp[] =
{
	D3D11_BLEND_OP_ADD,	/* ADD */
	D3D11_BLEND_OP_SUBTRACT,	/* SUBTRACT */
	D3D11_BLEND_OP_REV_SUBTRACT,	/* REVERSE_SUBTRACT */
	D3D11_BLEND_OP_MIN,	/* MIN */
	D3D11_BLEND_OP_MAX	/* MAX */
};

static D3D11_COMPARISON_FUNC RefreshToD3D11_CompareOp[] =
{
	D3D11_COMPARISON_NEVER,	/* NEVER */
	D3D11_COMPARISON_LESS,	/* LESS */
	D3D11_COMPARISON_EQUAL,	/* EQUAL */
	D3D11_COMPARISON_LESS_EQUAL,	/* LESS_OR_EQUAL */
	D3D11_COMPARISON_GREATER,	/* GREATER */
	D3D11_COMPARISON_NOT_EQUAL,	/* NOT_EQUAL */
	D3D11_COMPARISON_GREATER_EQUAL,	/* GREATER_OR_EQUAL */
	D3D11_COMPARISON_ALWAYS	/* ALWAYS */
};

static D3D11_STENCIL_OP RefreshToD3D11_StencilOp[] =
{
	D3D11_STENCIL_OP_KEEP,	/* KEEP */
	D3D11_STENCIL_OP_ZERO,	/* ZERO */
	D3D11_STENCIL_OP_REPLACE,	/* REPLACE */
	D3D11_STENCIL_OP_INCR_SAT,	/* INCREMENT_AND_CLAMP */
	D3D11_STENCIL_OP_DECR_SAT,	/* DECREMENT_AND_CLAMP */
	D3D11_STENCIL_OP_INVERT,	/* INVERT */
	D3D11_STENCIL_OP_INCR,	/* INCREMENT_AND_WRAP */
	D3D11_STENCIL_OP_DECR	/* DECREMENT_AND_WRAP */
};

static int32_t RefreshToD3D11_SampleCount[] =
{
	1,	/* 1 */
	2,	/* 2 */
	4,	/* 4 */
	8,	/* 8 */
	16,	/* 16 */
	32,	/* 32 */
	64	/* 64 */
};

static D3D11_INPUT_CLASSIFICATION RefreshToD3D11_VertexInputRate[] =
{
	D3D11_INPUT_PER_VERTEX_DATA,	/* VERTEX */
	D3D11_INPUT_PER_INSTANCE_DATA	/* INSTANCE */
};

static D3D11_TEXTURE_ADDRESS_MODE RefreshToD3D11_SamplerAddressMode[] =
{
	D3D11_TEXTURE_ADDRESS_WRAP,	/* REPEAT */
	D3D11_TEXTURE_ADDRESS_MIRROR,	/* MIRRORED_REPEAT */
	D3D11_TEXTURE_ADDRESS_CLAMP,	/* CLAMP_TO_EDGE */
	D3D11_TEXTURE_ADDRESS_BORDER	/* CLAMP_TO_BORDER */
};

/* Structs */

typedef struct D3D11Texture
{
	ID3D11RenderTargetView *rtv;
} D3D11Texture;

typedef struct D3D11SwapchainData
{
	IDXGISwapChain* swapchain;
	D3D11Texture refreshTexture;
	void* windowHandle;
} D3D11SwapchainData;

typedef struct D3D11CommandBuffer
{
	ID3D11DeviceContext *context;
	ID3D11CommandList *commandList;
	D3D11SwapchainData *swapchainData;
	SDL_threadID threadID;
	uint8_t recording;
	uint8_t fixed;
} D3D11CommandBuffer;

typedef struct D3D11CommandBufferPool
{
	D3D11CommandBuffer **elements;
	uint32_t count;
	uint32_t capacity;
} D3D11CommandBufferPool;

typedef struct D3D11Renderer
{
	ID3D11Device *device;
	ID3D11DeviceContext *immediateContext;
	IDXGIFactory1 *factory;
	IDXGIAdapter1* adapter;
	void *d3d11_dll;
	void *dxgi_dll;
	SDL_mutex *immediateContextMutex;

	D3D11CommandBufferPool *commandBufferPool;
	SDL_mutex *commandBufferAcquisitionMutex;

	D3D11SwapchainData** swapchainDatas;
	uint32_t swapchainDataCount;
	uint32_t swapchainDataCapacity;

	Refresh_Vec4 blendFactor;

	uint8_t debugMode;
	D3D_FEATURE_LEVEL featureLevel;
} D3D11Renderer;

/* Predeclarations */

static void D3D11_Wait(Refresh_Renderer* driverData);

/* Logging */

static void D3D11_INTERNAL_LogError(
	ID3D11Device *device,
	const char *msg,
	HRESULT res
) {
	#define MAX_ERROR_LEN 1024 /* FIXME: Arbitrary! */

	/* Buffer for text, ensure space for \0 terminator after buffer */
	char wszMsgBuff[MAX_ERROR_LEN + 1];
	DWORD dwChars; /* Number of chars returned. */

	if (res == DXGI_ERROR_DEVICE_REMOVED)
	{
		res = ID3D11Device_GetDeviceRemovedReason(device);
	}

	/* Try to get the message from the system errors. */
	dwChars = FormatMessage(
		FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		res,
		0,
		wszMsgBuff,
		MAX_ERROR_LEN,
		NULL
	);

	/* No message? Screw it, just post the code. */
	if (dwChars == 0)
	{
		Refresh_LogError("%s! Error Code: 0x%08X", msg, res);
		return;
	}

	/* Ensure valid range */
	dwChars = SDL_min(dwChars, MAX_ERROR_LEN);

	/* Trim whitespace from tail of message */
	while (dwChars > 0)
	{
		if (wszMsgBuff[dwChars - 1] <= ' ')
		{
			dwChars--;
		}
		else
		{
			break;
		}
	}

	/* Ensure null-terminated string */
	wszMsgBuff[dwChars] = '\0';

	Refresh_LogError("%s! Error Code: %s (0x%08X)", msg, wszMsgBuff, res);
}

/* Swapchain Management */

static uint8_t D3D11_INTERNAL_CreateSwapchain(
	D3D11Renderer *renderer,
	void *windowHandle
) {
	IDXGIFactory1 *pParent;
	DXGI_MODE_DESC swapchainBufferDesc;
	DXGI_SWAP_CHAIN_DESC swapchainDesc;
	IDXGISwapChain *swapchain;
	D3D11SwapchainData *swapchainData;
	SDL_SysWMinfo info;
	HWND dxgiHandle;
	ID3D11Texture2D *swapchainTexture;
	D3D11_RENDER_TARGET_VIEW_DESC swapchainViewDesc;
	HRESULT res;

	SDL_VERSION(&info.version);
	SDL_GetWindowWMInfo((SDL_Window*) windowHandle, &info);
	dxgiHandle = info.info.win.window;

	/* Initialize swapchain buffer descriptor */
	swapchainBufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	swapchainBufferDesc.Width = 0;
	swapchainBufferDesc.Height = 0;
	swapchainBufferDesc.RefreshRate.Numerator = 0;
	swapchainBufferDesc.RefreshRate.Denominator = 0;
	swapchainBufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	swapchainBufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;

	/* Initialize the swapchain descriptor */
	swapchainDesc.BufferDesc = swapchainBufferDesc;
	swapchainDesc.BufferCount = 3;
	swapchainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	swapchainDesc.OutputWindow = dxgiHandle;
	swapchainDesc.Flags = 0;
	swapchainDesc.SampleDesc.Count = 1;
	swapchainDesc.SampleDesc.Quality = 0;
	swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	swapchainDesc.Windowed = 1;

	/* Create the swapchain! */
	res = IDXGIFactory1_CreateSwapChain(
		renderer->factory,
		(IUnknown*) renderer->device,
		&swapchainDesc,
		&swapchain
	);
	ERROR_CHECK_RETURN("Could not create swap chain", 0);

	/*
	 * The swapchain's parent is a separate factory from the factory that
	 * we used to create the swapchain, and only that parent can be used to
	 * set the window association. Trying to set an association on our factory
	 * will silently fail and doesn't even verify arguments or return errors.
	 * See https://gamedev.net/forums/topic/634235-dxgidisabling-altenter/4999955/
	 */
	res = IDXGISwapChain_GetParent(
		swapchain,
		&D3D_IID_IDXGIFactory1,
		(void**) &pParent
	);
	if (FAILED(res))
	{
		Refresh_LogWarn(
			"Could not get swapchain parent! Error Code: %08X",
			res
		);
	}
	else
	{
		/* Disable DXGI window crap */
		res = IDXGIFactory1_MakeWindowAssociation(
			pParent,
			dxgiHandle,
			DXGI_MWA_NO_WINDOW_CHANGES
		);
		if (FAILED(res))
		{
			Refresh_LogWarn(
				"MakeWindowAssociation failed! Error Code: %08X",
				res
			);
		}
	}

	/* Set up the swapchain data */
	swapchainData = (D3D11SwapchainData*) SDL_malloc(sizeof(D3D11SwapchainData));
	swapchainData->swapchain = swapchain;
	swapchainData->windowHandle = windowHandle;
	swapchainData->refreshTexture.rtv = NULL;
	SDL_SetWindowData((SDL_Window*) windowHandle, WINDOW_SWAPCHAIN_DATA, swapchainData);
	if (renderer->swapchainDataCount >= renderer->swapchainDataCapacity)
	{
		renderer->swapchainDataCapacity *= 2;
		renderer->swapchainDatas = SDL_realloc(
			renderer->swapchainDatas,
			renderer->swapchainDataCapacity * sizeof(D3D11SwapchainData*)
		);
	}
	renderer->swapchainDatas[renderer->swapchainDataCount] = swapchainData;
	renderer->swapchainDataCount += 1;

	/* Create the RTV for the swapchain */
	swapchainViewDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	swapchainViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
	swapchainViewDesc.Texture2D.MipSlice = 0;

	res = IDXGISwapChain_GetBuffer(
		swapchainData->swapchain,
		0,
		&D3D_IID_ID3D11Texture2D,
		(void**) &swapchainTexture
	);
	ERROR_CHECK_RETURN("Could not get buffer from swapchain", 0);

	res = ID3D11Device_CreateRenderTargetView(
		renderer->device,
		(ID3D11Resource*) swapchainTexture,
		&swapchainViewDesc,
		&swapchainData->refreshTexture.rtv
	);
	ERROR_CHECK_RETURN("Swapchain RT view creation failed", 0);

	/* Cleanup */
	ID3D11Texture2D_Release(swapchainTexture);

	return 1;
}

static uint8_t D3D11_INTERNAL_ResizeSwapchain(
	D3D11Renderer *renderer,
	D3D11SwapchainData *swapchainData
) {
	ID3D11Texture2D *swapchainTexture;
	D3D11_RENDER_TARGET_VIEW_DESC swapchainViewDesc;
	int w, h;
	HRESULT res;

	/* Release the old RTV */
	ID3D11RenderTargetView_Release(swapchainData->refreshTexture.rtv);

	/* Resize the swapchain */
	SDL_GetWindowSize((SDL_Window*) swapchainData->windowHandle, &w, &h);
	res = IDXGISwapChain_ResizeBuffers(
		swapchainData->swapchain,
		0,	/* Keep buffer count the same */
		w,
		h,
		DXGI_FORMAT_UNKNOWN,	/* Keep the old format */
		0
	);
	ERROR_CHECK_RETURN("Could not resize swapchain buffers", 0);

	/* Recreate the RTV using the new swapchain buffer */
	swapchainViewDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	swapchainViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
	swapchainViewDesc.Texture2D.MipSlice = 0;

	res = IDXGISwapChain_GetBuffer(
		swapchainData->swapchain,
		0,
		&D3D_IID_ID3D11Texture2D,
		&swapchainTexture
	);
	ERROR_CHECK_RETURN("Could not get buffer from swapchain", 0);

	res = ID3D11Device_CreateRenderTargetView(
		renderer->device,
		(ID3D11Resource*) swapchainTexture,
		&swapchainViewDesc,
		&swapchainData->refreshTexture.rtv
	);
	ERROR_CHECK_RETURN("Could not create render target view for swapchain", 0);

	/* Cleanup */
	ID3D11Texture2D_Release(swapchainTexture);

	return 1;
}

/* Quit */

static void D3D11_DestroyDevice(
	Refresh_Device* device
) {
	D3D11Renderer *renderer = (D3D11Renderer*) device->driverData;
	D3D11CommandBuffer *commandBuffer;
	uint32_t i;

	/* Free the command buffer pool */
	for (i = 0; i < renderer->commandBufferPool->count; i += 1)
	{
		commandBuffer = renderer->commandBufferPool->elements[i];
		if (commandBuffer->commandList != NULL)
		{
			ID3D11CommandList_Release(commandBuffer->commandList);
		}
		ID3D11DeviceContext_Release(commandBuffer->context);
		SDL_free(commandBuffer);
	}
	SDL_free(renderer->commandBufferPool->elements);
	SDL_free(renderer->commandBufferPool);

	/* Release swapchain */
	for (i = 0; i < renderer->swapchainDataCount; i += 1)
	{
		ID3D11RenderTargetView_Release(renderer->swapchainDatas[i]->refreshTexture.rtv);
		IDXGISwapChain_Release(renderer->swapchainDatas[i]->swapchain);
		SDL_free(renderer->swapchainDatas[i]);
	}
	SDL_free(renderer->swapchainDatas);

	/* Release persistent D3D11 objects */
	ID3D11DeviceContext_Release(renderer->immediateContext);
	ID3D11Device_Release(renderer->device);

	/* Release DXGI objects */
	IDXGIAdapter1_Release(renderer->adapter);
	IDXGIFactory1_Release(renderer->factory);

	/* Unload the DLLs */
	SDL_UnloadObject(renderer->d3d11_dll);
	SDL_UnloadObject(renderer->dxgi_dll);

	/* Free the renderer and Refresh Device */
	SDL_free(renderer);
	SDL_free(device);
}

/* Drawing */

static void D3D11_Clear(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_Rect* clearRect,
	Refresh_ClearOptions options,
	Refresh_Vec4* colors,
	uint32_t colorCount,
	Refresh_DepthStencilValue depthStencil
) {
	NOT_IMPLEMENTED
}

static void D3D11_DrawInstancedPrimitives(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	uint32_t baseVertex,
	uint32_t startIndex,
	uint32_t primitiveCount,
	uint32_t instanceCount,
	uint32_t vertexParamOffset,
	uint32_t fragmentParamOffset
) {
	NOT_IMPLEMENTED
}

static void D3D11_DrawIndexedPrimitives(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	uint32_t baseVertex,
	uint32_t startIndex,
	uint32_t primitiveCount,
	uint32_t vertexParamOffset,
	uint32_t fragmentParamOffset
) {
	NOT_IMPLEMENTED
}

static void D3D11_DrawPrimitives(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	uint32_t vertexStart,
	uint32_t primitiveCount,
	uint32_t vertexParamOffset,
	uint32_t fragmentParamOffset
) {
	NOT_IMPLEMENTED
}

static void D3D11_DispatchCompute(
	Refresh_Renderer* device,
	Refresh_CommandBuffer* commandBuffer,
	uint32_t groupCountX,
	uint32_t groupCountY,
	uint32_t groupCountZ,
	uint32_t computeParamOffset
) {
	NOT_IMPLEMENTED
}

/* State Creation */

static Refresh_ComputePipeline* D3D11_CreateComputePipeline(
	Refresh_Renderer* driverData,
	Refresh_ComputeShaderInfo* computeShaderInfo
) {
	NOT_IMPLEMENTED
}

static Refresh_GraphicsPipeline* D3D11_CreateGraphicsPipeline(
	Refresh_Renderer* driverData,
	Refresh_GraphicsPipelineCreateInfo* pipelineCreateInfo
) {
	NOT_IMPLEMENTED
}

static Refresh_Sampler* D3D11_CreateSampler(
	Refresh_Renderer* driverData,
	Refresh_SamplerStateCreateInfo* samplerStateCreateInfo
) {
	NOT_IMPLEMENTED
}

static Refresh_ShaderModule* D3D11_CreateShaderModule(
	Refresh_Renderer* driverData,
	Refresh_ShaderModuleCreateInfo* shaderModuleCreateInfo
) {
	NOT_IMPLEMENTED
}

static Refresh_Texture* D3D11_CreateTexture(
	Refresh_Renderer* driverData,
	Refresh_TextureCreateInfo* textureCreateInfo
) {
	NOT_IMPLEMENTED
}

static Refresh_Buffer* D3D11_CreateBuffer(
	Refresh_Renderer* driverData,
	Refresh_BufferUsageFlags usageFlags,
	uint32_t sizeInBytes
) {
	NOT_IMPLEMENTED
}

/* Setters */

static void D3D11_SetTextureData(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_TextureSlice* textureSlice,
	void* data,
	uint32_t dataLengthInBytes
) {
	NOT_IMPLEMENTED
}

static void D3D11_SetTextureDataYUV(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_Texture* y,
	Refresh_Texture* u,
	Refresh_Texture* v,
	uint32_t yWidth,
	uint32_t yHeight,
	uint32_t uvWidth,
	uint32_t uvHeight,
	void* data,
	uint32_t dataLength
) {
	NOT_IMPLEMENTED
}

static void D3D11_CopyTextureToTexture(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_TextureSlice* sourceTextureSlice,
	Refresh_TextureSlice* destinationTextureSlice,
	Refresh_Filter filter
) {
	NOT_IMPLEMENTED
}

static void D3D11_CopyTextureToBuffer(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_TextureSlice* textureSlice,
	Refresh_Buffer* buffer
) {
	NOT_IMPLEMENTED
}

static void D3D11_SetBufferData(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_Buffer* buffer,
	uint32_t offsetInBytes,
	void* data,
	uint32_t dataLength
) {
	NOT_IMPLEMENTED
}

static uint32_t D3D11_PushVertexShaderUniforms(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	void* data,
	uint32_t dataLengthInBytes
) {
	NOT_IMPLEMENTED
}

static uint32_t D3D11_PushFragmentShaderUniforms(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	void* data,
	uint32_t dataLengthInBytes
) {
	NOT_IMPLEMENTED
}

static uint32_t D3D11_PushComputeShaderUniforms(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	void* data,
	uint32_t dataLengthInBytes
) {
	NOT_IMPLEMENTED
}

static void D3D11_BindVertexSamplers(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_Texture** pTextures,
	Refresh_Sampler** pSamplers
) {
	NOT_IMPLEMENTED
}

static void D3D11_BindFragmentSamplers(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_Texture** pTextures,
	Refresh_Sampler** pSamplers
) {
	NOT_IMPLEMENTED
}

/* Getters */

static void D3D11_GetBufferData(
	Refresh_Renderer* driverData,
	Refresh_Buffer* buffer,
	void* data,
	uint32_t dataLengthInBytes
) {
	NOT_IMPLEMENTED
}

/* Disposal */

static void D3D11_QueueDestroyTexture(
	Refresh_Renderer* driverData,
	Refresh_Texture* texture
) {
	NOT_IMPLEMENTED
}

static void D3D11_QueueDestroySampler(
	Refresh_Renderer* driverData,
	Refresh_Sampler* sampler
) {
	NOT_IMPLEMENTED
}

static void D3D11_QueueDestroyBuffer(
	Refresh_Renderer* driverData,
	Refresh_Buffer* buffer
) {
	NOT_IMPLEMENTED
}

static void D3D11_QueueDestroyShaderModule(
	Refresh_Renderer* driverData,
	Refresh_ShaderModule* shaderModule
) {
	NOT_IMPLEMENTED
}

static void D3D11_QueueDestroyComputePipeline(
	Refresh_Renderer* driverData,
	Refresh_ComputePipeline* computePipeline
) {
	NOT_IMPLEMENTED
}

static void D3D11_QueueDestroyGraphicsPipeline(
	Refresh_Renderer* driverData,
	Refresh_GraphicsPipeline* graphicsPipeline
) {
	NOT_IMPLEMENTED
}

/* Graphics State */

static void D3D11_BeginRenderPass(
	Refresh_Renderer *driverData,
	Refresh_CommandBuffer *commandBuffer,
	Refresh_Rect *renderArea,
	Refresh_ColorAttachmentInfo *colorAttachmentInfos,
	uint32_t colorAttachmentCount,
	Refresh_DepthStencilAttachmentInfo *depthStencilAttachmentInfo
) {
	D3D11Renderer *renderer = (D3D11Renderer*) driverData;
	D3D11CommandBuffer *cmdbuf = (D3D11CommandBuffer*) commandBuffer;
	D3D11Texture *texture;
	float clearColors[4];
	ID3D11RenderTargetView *rtViews[MAX_COLOR_TARGET_BINDINGS];
	ID3D11DepthStencilView *dsView = NULL;
	D3D11_VIEWPORT viewports[1];
	D3D11_RECT scissorRects[1];
	uint8_t i;

	/* Get the RTVs for each color attachment. */
	for (i = 0; i < colorAttachmentCount; i += 1)
	{
		rtViews[i] = ((D3D11Texture*) colorAttachmentInfos[i].texture)->rtv;
	}

	/* FIXME: Get the DSV for the depth stencil attachment, if one exists! */

	/* Set the render targets. */
	ID3D11DeviceContext_OMSetRenderTargets(
		cmdbuf->context,
		colorAttachmentCount,
		rtViews,
		NULL
	);

	/* Perform load ops on those render targets. */
	for (i = 0; i < colorAttachmentCount; i += 1)
	{
		texture = (D3D11Texture*) colorAttachmentInfos[i].texture;

		if (colorAttachmentInfos[i].loadOp == REFRESH_LOADOP_CLEAR)
		{
			clearColors[0] = colorAttachmentInfos[i].clearColor.x;
			clearColors[1] = colorAttachmentInfos[i].clearColor.y;
			clearColors[2] = colorAttachmentInfos[i].clearColor.z;
			clearColors[3] = colorAttachmentInfos[i].clearColor.w;

			ID3D11DeviceContext_ClearRenderTargetView(
				cmdbuf->context,
				texture->rtv,
				clearColors
			);
		}
	}

	/* FIXME: Set viewport and scissor state */

	/* FIXME: What should we do with render area? */
}

static void D3D11_EndRenderPass(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer
) {
	/* FIXME: What should we do here? */
}

static void D3D11_BindGraphicsPipeline(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_GraphicsPipeline* graphicsPipeline
) {
	NOT_IMPLEMENTED
}

static void D3D11_SetViewport(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_Viewport* viewport
) {
	NOT_IMPLEMENTED
}

static void D3D11_SetScissor(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_Rect* scissor
) {
	NOT_IMPLEMENTED
}

static void D3D11_BindVertexBuffers(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	uint32_t firstBinding,
	uint32_t bindingCount,
	Refresh_Buffer** pBuffers,
	uint64_t* pOffsets
) {
	NOT_IMPLEMENTED
}

static void D3D11_BindIndexBuffer(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_Buffer* buffer,
	uint64_t offset,
	Refresh_IndexElementSize indexElementSize
) {
	NOT_IMPLEMENTED
}

static void D3D11_BindComputePipeline(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_ComputePipeline* computePipeline
) {
	NOT_IMPLEMENTED
}

static void D3D11_BindComputeBuffers(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_Buffer** pBuffers
) {
	NOT_IMPLEMENTED
}

static void D3D11_BindComputeTextures(
	Refresh_Renderer* driverData,
	Refresh_CommandBuffer* commandBuffer,
	Refresh_Texture** pTextures
) {
	NOT_IMPLEMENTED
}

static Refresh_CommandBuffer* D3D11_AcquireCommandBuffer(
	Refresh_Renderer* driverData,
	uint8_t fixed
) {
	D3D11Renderer *renderer = (D3D11Renderer*) driverData;
	D3D11CommandBuffer *commandBuffer = NULL;
	uint32_t i;
	HRESULT res;

	/* Make sure multiple threads can't acquire the same command buffer. */
	SDL_LockMutex(renderer->commandBufferAcquisitionMutex);

	/* Try to use an existing command buffer, if one is available. */
	for (i = 0; i < renderer->commandBufferPool->count; i += 1)
	{
		/* Search for a command buffer in the pool that is not fixed, and is not recording. */
		if (!renderer->commandBufferPool->elements[i]->fixed && !renderer->commandBufferPool->elements[i]->recording)
		{
			commandBuffer = renderer->commandBufferPool->elements[i];
			break;
		}
	}

	/* If there are no free command buffers, make a new one. */
	if (commandBuffer == NULL)
	{
		/* Expand the capacity as needed. */
		EXPAND_ELEMENTS_IF_NEEDED(
			renderer->commandBufferPool,
			2,
			D3D11CommandBuffer*
		);

		/* Create a new command buffer */
		renderer->commandBufferPool->elements[i] = (D3D11CommandBuffer*) SDL_malloc(
			sizeof(D3D11CommandBuffer)
		);

		/* Assign it a new deferred context */
		res = ID3D11Device_CreateDeferredContext(
			renderer->device,
			0,
			&renderer->commandBufferPool->elements[i]->context
		);
		if (FAILED(res))
		{
			SDL_UnlockMutex(renderer->commandBufferAcquisitionMutex);
			ERROR_CHECK_RETURN("Could not create deferred context for command buffer", NULL);
		}

		/* Now we have a new command buffer we can use! */
		commandBuffer = renderer->commandBufferPool->elements[i];
		renderer->commandBufferPool->count += 1;
	}

	/* Set up the command buffer */
	commandBuffer->threadID = SDL_ThreadID();
	commandBuffer->recording = 1;
	commandBuffer->fixed = fixed;
	commandBuffer->swapchainData = NULL;
	commandBuffer->commandList = NULL;

	SDL_UnlockMutex(renderer->commandBufferAcquisitionMutex);

	return (Refresh_CommandBuffer*) commandBuffer;
}

static D3D11SwapchainData* D3D11_INTERNAL_FetchSwapchainData(
	D3D11Renderer* renderer,
	void* windowHandle
) {
	D3D11SwapchainData* swapchainData = NULL;

	swapchainData = (D3D11SwapchainData*) SDL_GetWindowData(windowHandle, WINDOW_SWAPCHAIN_DATA);

	if (swapchainData == NULL)
	{
		if (D3D11_INTERNAL_CreateSwapchain(renderer, windowHandle))
		{
			swapchainData = (D3D11SwapchainData*) SDL_GetWindowData(windowHandle, WINDOW_SWAPCHAIN_DATA);
		}
	}

	return swapchainData;
}

Refresh_Texture* D3D11_AcquireSwapchainTexture(
	Refresh_Renderer *driverData,
	Refresh_CommandBuffer *commandBuffer,
	void *windowHandle
) {
	D3D11Renderer *renderer = (D3D11Renderer*) driverData;
	D3D11CommandBuffer *cmdbuf = (D3D11CommandBuffer*) commandBuffer;
	D3D11SwapchainData *swapchainData;
	DXGI_SWAP_CHAIN_DESC swapchainDesc;
	int w, h;
	HRESULT res;

	/* Fetch the swapchain data, creating a new swapchain if needed. */
	swapchainData = D3D11_INTERNAL_FetchSwapchainData(renderer, windowHandle);
	if (swapchainData == NULL)
	{
		return NULL;
	}

	/* Check for window size changes and resize the swapchain if needed. */
	IDXGISwapChain_GetDesc(swapchainData->swapchain, &swapchainDesc);
	SDL_GetWindowSize((SDL_Window*) windowHandle, &w, &h);

	if (w != swapchainDesc.BufferDesc.Width || h != swapchainDesc.BufferDesc.Height)
	{
		res = D3D11_INTERNAL_ResizeSwapchain(renderer, swapchainData);
		ERROR_CHECK_RETURN("Could not resize swapchain", NULL);
	}

	/* Let's try this again... */
	swapchainData = D3D11_INTERNAL_FetchSwapchainData(renderer, windowHandle);
	if (swapchainData == NULL)
	{
		return NULL;
	}

	/* Let the command buffer know it's associated with this swapchain. */
	cmdbuf->swapchainData = swapchainData;

	/* Return the swapchain texture */
	return (Refresh_Texture*) &swapchainData->refreshTexture;
}

Refresh_TextureFormat D3D11_GetSwapchainFormat(
	Refresh_Renderer* driverData,
	void* windowHandle
) {
	return DXGI_FORMAT_R8G8B8A8_UNORM;
}

static void D3D11_Submit(
	Refresh_Renderer* driverData,
	uint32_t commandBufferCount,
	Refresh_CommandBuffer** pCommandBuffers
) {
	D3D11Renderer *renderer = (D3D11Renderer*) driverData;
	ID3D11CommandList *commandList;
	uint32_t i;
	HRESULT res;

	for (i = 0; i < commandBufferCount; i += 1)
	{
		D3D11CommandBuffer *commandBuffer = (D3D11CommandBuffer*) pCommandBuffers[i];

		/* FIXME: Should add sanity check that current thread ID matches the command buffer's threadID. */

		if (commandBuffer->fixed && !commandBuffer->recording)
		{
			/* Grab the prerecorded command list. */
			commandList = commandBuffer->commandList;
		}
		else
		{
			/* Serialize the commands into a command list */
			res = ID3D11DeviceContext_FinishCommandList(
				commandBuffer->context,
				0,
				&commandList
			);
			ERROR_CHECK("Could not finish command list recording");
		}

		/* Submit the command list to the immediate context */
		SDL_LockMutex(renderer->immediateContextMutex);
		ID3D11DeviceContext_ExecuteCommandList(
			renderer->immediateContext,
			commandList,
			0
		);
		SDL_UnlockMutex(renderer->immediateContextMutex);

		/* Now that we're done, either save the command list or release it. */
		if (commandBuffer->fixed)
		{
			commandBuffer->commandList = commandList;
		}
		else
		{
			ID3D11CommandList_Release(commandList);
		}

		/* Mark the command buffer as not-recording so that it can be used to record again. */
		commandBuffer->recording = 0;

		/* Present, if applicable */
		if (commandBuffer->swapchainData)
		{
			IDXGISwapChain_Present(
				commandBuffer->swapchainData->swapchain,
				1, /* FIXME: Assumes vsync! */
				0
			);
		}
	}
}

static void D3D11_Wait(
	Refresh_Renderer* driverData
) {
	NOT_IMPLEMENTED
}

static Refresh_Device* D3D11_CreateDevice(
	Refresh_PresentationParameters* presentationParameters,
	uint8_t debugMode
) {
	D3D11Renderer *renderer;
	PFN_CREATE_DXGI_FACTORY CreateDXGIFactoryFunc;
	PFN_D3D11_CREATE_DEVICE D3D11CreateDeviceFunc;
	D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_0 };
	void* factory6;
	uint32_t flags;
	DXGI_ADAPTER_DESC1 adapterDesc;
	HRESULT res;
	Refresh_Device* result;

	/* Allocate and zero out the renderer */
	renderer = (D3D11Renderer*) SDL_malloc(sizeof(D3D11Renderer));
	SDL_memset(renderer, 0, sizeof(D3D11Renderer));

	/* Load the DXGI library */
	renderer->dxgi_dll = SDL_LoadObject(DXGI_DLL);
	if (renderer->dxgi_dll == NULL)
	{
		Refresh_LogError("Could not find " DXGI_DLL);
		return NULL;
	}

	/* Load the CreateDXGIFactory function */
	CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY) SDL_LoadFunction(
		renderer->dxgi_dll,
		"CreateDXGIFactory1"
	);
	if (CreateDXGIFactoryFunc == NULL)
	{
		Refresh_LogError("Could not load CreateDXGIFactory1 function!");
		return NULL;
	}

	/* Create the DXGI factory */
	res = CreateDXGIFactoryFunc(
		&D3D_IID_IDXGIFactory1,
		&renderer->factory
	);
	ERROR_CHECK_RETURN("Could not create DXGIFactory", NULL);

	/* Get the default adapter */
	res = IDXGIAdapter1_QueryInterface(
		renderer->factory,
		&D3D_IID_IDXGIFactory6,
		(void**) &factory6
	);
	if (SUCCEEDED(res))
	{
		IDXGIFactory6_EnumAdapterByGpuPreference(
			(IDXGIFactory6*) factory6,
			0,
			DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
			&D3D_IID_IDXGIAdapter1,
			&renderer->adapter
		);
	}
	else
	{
		IDXGIFactory1_EnumAdapters1(
			renderer->factory,
			0,
			&renderer->adapter
		);
	}

	/* Get information about the selected adapter. Used for logging info. */
	IDXGIAdapter1_GetDesc1(renderer->adapter, &adapterDesc);

	/* Load the D3D library */
	renderer->d3d11_dll = SDL_LoadObject(D3D11_DLL);
	if (renderer->d3d11_dll == NULL)
	{
		Refresh_LogError("Could not find " D3D11_DLL);
		return NULL;
	}

	/* Load the CreateDevice function */
	D3D11CreateDeviceFunc = (PFN_D3D11_CREATE_DEVICE) SDL_LoadFunction(
		renderer->d3d11_dll,
		"D3D11CreateDevice"
	);
	if (D3D11CreateDeviceFunc == NULL)
	{
		Refresh_LogError("Could not load D3D11CreateDevice function!");
		return NULL;
	}

	/* Set up device flags */
	flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
	if (debugMode)
	{
		flags |= D3D11_CREATE_DEVICE_DEBUG;
	}

	/* Create the device */
tryCreateDevice:
	res = D3D11CreateDeviceFunc(
		(IDXGIAdapter*) renderer->adapter,
		D3D_DRIVER_TYPE_UNKNOWN, /* Must be UNKNOWN if adapter is non-null according to spec */
		NULL,
		flags,
		levels,
		SDL_arraysize(levels),
		D3D11_SDK_VERSION,
		&renderer->device,
		&renderer->featureLevel,
		&renderer->immediateContext
	);
	if (FAILED(res) && debugMode)
	{
		/* If device creation failed, and we're in debug mode, remove the debug flag and try again. */
		Refresh_LogWarn("Creating device in debug mode failed with error %08X. Trying non-debug.", res);
		flags &= ~D3D11_CREATE_DEVICE_DEBUG;
		debugMode = 0;
		goto tryCreateDevice;
	}

	ERROR_CHECK_RETURN("Could not create D3D11 device", NULL);

	/* Print driver info */
	Refresh_LogInfo("Refresh Driver: D3D11");
	Refresh_LogInfo("D3D11 Adapter: %S", adapterDesc.Description);

	/* Create the command buffer pool */
	renderer->commandBufferPool = (D3D11CommandBufferPool*) SDL_malloc(
		sizeof(D3D11CommandBufferPool)
	);
	SDL_memset(renderer->commandBufferPool, 0, sizeof(D3D11CommandBufferPool));

	/* Create mutexes */
	renderer->immediateContextMutex = SDL_CreateMutex();
	renderer->commandBufferAcquisitionMutex = SDL_CreateMutex();

	/* Initialize miscellaneous renderer members */
	renderer->debugMode = (flags & D3D11_CREATE_DEVICE_DEBUG) != 0;
	renderer->blendFactor.x = 1.0f;
	renderer->blendFactor.y = 1.0f;
	renderer->blendFactor.z = 1.0f;
	renderer->blendFactor.w = 1.0f;

	/* Create the Refresh Device */
	result = (Refresh_Device*) SDL_malloc(sizeof(Refresh_Device));
	ASSIGN_DRIVER(D3D11)

	result->driverData = (Refresh_Renderer*) renderer;

	/* Create the initial swapchain */
	renderer->swapchainDataCapacity = 1;
	renderer->swapchainDataCount = 0;
	renderer->swapchainDatas = SDL_malloc(
		renderer->swapchainDataCapacity * sizeof(D3D11SwapchainData*)
	);

	if (!D3D11_INTERNAL_CreateSwapchain(renderer, presentationParameters->deviceWindowHandle))
	{
		return NULL;
	}

	return result;
}

Refresh_Driver D3D11Driver = {
	"D3D11",
	D3D11_CreateDevice
};

#endif //REFRESH_DRIVER_D3D11