#include "Core.h"
#if CC_GFX_BACKEND == CC_GFX_BACKEND_D3D11
#include "_GraphicsBase.h"
#include "Errors.h"
#include "Window.h"
#include "../misc/windows/D3D11Shaders.h"

/* Avoid pointless includes */
#define WIN32_LEAN_AND_MEAN
#define NOSERVICE
#define NOMCX
#define NOIME
#define COBJMACROS
#include <d3d11.h>

static const GUID guid_ID3D11Texture2D = { 0x6f15aaf2, 0xd208, 0x4e89, { 0x9a, 0xb4, 0x48, 0x95, 0x35, 0xd3, 0x4f, 0x9c } };
static const GUID guid_IXDGIDevice     = { 0x54ec77fa, 0x1377, 0x44e6, { 0x8c, 0x32, 0x88, 0xfd, 0x5f, 0x44, 0xc8, 0x4c } };
static const GUID guid_IDXGIFactory    = { 0x7b7166ec, 0x21c7, 0x44ae, { 0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3, 0x69 } };
static const GUID guid_IDXGIDevice     = { 0x54ec77fa, 0x1377, 0x44e6, { 0x8c, 0x32, 0x88, 0xfd, 0x5f, 0x44, 0xc8, 0x4c } };
static const GUID guid_IDXGIFactory2   = { 0x50c83a1c, 0xe072, 0x4c48, { 0x87, 0xb0, 0x36, 0x30, 0xfa, 0x36, 0xa6, 0xd0 } };
#ifdef CC_BUILD_UWP
#include <dxgi1_2.h>
#endif


// some generally useful debugging links
//   https://docs.microsoft.com/en-us/visualstudio/debugger/graphics/visual-studio-graphics-diagnostics
//   https://stackoverflow.com/questions/50591937/how-do-i-launch-the-hlsl-debugger-in-visual-studio-2017
//   https://docs.microsoft.com/en-us/visualstudio/debugger/graphics/graphics-object-table?view=vs-2019
//   https://github.com/gfx-rs/wgpu/issues/1171
// Some generally useful background links
//   https://gist.github.com/d7samurai/261c69490cce0620d0bfc93003cd1052

static int depthBits; // TODO implement depthBits?? for ZNear calc
static GfxResourceID white_square;

static ID3D11Device* device;
static ID3D11DeviceContext* context;
static IDXGIDevice1* dxgi_device;
static IDXGIAdapter* dxgi_adapter;
static IDXGIFactory1* dxgi_factory;
static IDXGISwapChain* swapchain;
struct ShaderDesc { const void* data; int len; };

static void IA_UpdateLayout(void);
static void VS_UpdateShader(void);
static void PS_UpdateShader(void);
static void InitPipeline(void);
static void FreePipeline(void);

#ifdef CC_BUILD_UWP
static void LoadD3D11Library(void) { }

static void CreateDevice(void) {
	// https://docs.microsoft.com/en-us/windows/uwp/gaming/simple-port-from-direct3d-9-to-11-1-part-1--initializing-direct3d
	DWORD createFlags = 0;
	D3D_FEATURE_LEVEL fl;
	HRESULT hr;
#ifdef _DEBUG
	createFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL,
			createFlags, NULL, 0, D3D11_SDK_VERSION,
			&device, &fl, &context);
	if (hr) Process_Abort2(hr, "Failed to create D3D11 device");

	Gfx.MaxTexWidth  = fl < D3D_FEATURE_LEVEL_11_0 ? 8192 : 16384;
	Gfx.MaxTexHeight = fl < D3D_FEATURE_LEVEL_11_0 ? 8192 : 16384;
}

static void CreateSwapChain(void) {
	HRESULT hr;

	DXGI_SWAP_CHAIN_DESC1 desc = { 0 };
	desc.BufferCount  = 2; // TODO 1??
	desc.Format       = DXGI_FORMAT_B8G8R8A8_UNORM;
	desc.BufferUsage  = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	desc.SampleDesc.Count   = 1;
	desc.SwapEffect         = DXGI_SWAP_EFFECT_FLIP_DISCARD;
	desc.Scaling            = DXGI_SCALING_NONE;

	IDXGIDevice* dxgi_device = NULL;
	hr = ID3D11Device_QueryInterface(device, &guid_IDXGIDevice, (void**)&dxgi_device);
	if (FAILED(hr)) Process_Abort2(hr, "Querying DXGI device");

	IDXGIAdapter* dxgi_adapter = NULL;
	hr = IDXGIDevice_GetAdapter(dxgi_device , &dxgi_adapter);
	if (FAILED(hr)) Process_Abort2(hr, "Querying DXGI adapter");

	IDXGIFactory2* dxgi_factory2 = NULL;
	hr = IDXGIAdapter_GetParent(dxgi_adapter, &guid_IDXGIFactory2, (void**)&dxgi_factory2);
	if (FAILED(hr)) Process_Abort2(hr, "Querying DXGI factory");

	void* window = Window_Main.Handle.ptr;
	hr = IDXGIFactory2_CreateSwapChainForCoreWindow(dxgi_factory2, device, window, &desc, NULL, &swapchain);
	if (FAILED(hr)) Process_Abort2(hr, "Creating swap chain");
}
#else
static PFN_D3D11_CREATE_DEVICE _D3D11CreateDevice;

static void LoadD3D11Library(void) {
	static const struct DynamicLibSym funcs[] = {
		DynamicLib_ReqSym(D3D11CreateDevice)
	};
	static const cc_string path = String_FromConst("d3d11.dll");
	void* lib;
	DynamicLib_LoadAll(&path, funcs, Array_Elems(funcs), &lib);

	if (lib) return;
	Logger_FailToStart("Failed to load d3d11.dll. You may need to install Direct3D11.\n\nNOTE: Direct3D11 requires Windows 7 or later\nYou may need to use the Direct3D9 version instead.\n");
}

static void CreateDevice(void) {
	// https://docs.microsoft.com/en-us/windows/uwp/gaming/simple-port-from-direct3d-9-to-11-1-part-1--initializing-direct3d
	DWORD createFlags = 0;
	D3D_FEATURE_LEVEL fl;
	HRESULT hr;
#ifdef _DEBUG
	createFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	hr = _D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL,
			createFlags, NULL, 0, D3D11_SDK_VERSION,
			&device, &fl, &context);
	if (hr) Process_Abort2(hr, "Failed to create D3D11 device");

	// The fog calculation requires reading Z/W of fragment position (SV_POSITION) in pixel shader,
	//  unfortunately this is unsupported in Direct3d9 (https://docs.microsoft.com/en-us/windows/uwp/gaming/glsl-to-hlsl-reference)
	// So for the sake of simplicity and since only a few old GPUs don't support feature level 10 anyways
	//   https://walbourn.github.io/direct3d-feature-levels/
	//   https://github.com/MonoGame/MonoGame/issues/5789
	//  Just don't support GPUs that do not support at least feature level 10
	if (fl < D3D_FEATURE_LEVEL_10_0)
		Logger_FailToStart("Your GPU is too old to support the Direct3D11 version.\nTry using the Direct3D9 version instead.\n");

	// https://docs.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_texture2d_desc
	// https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits
	//  see "Max Texture Dimension" row in Feature Support table
	Gfx.MaxTexWidth  = fl < D3D_FEATURE_LEVEL_11_0 ? 8192 : 16384;
	Gfx.MaxTexHeight = fl < D3D_FEATURE_LEVEL_11_0 ? 8192 : 16384;
}

static void CreateSwapChain(void) {
	HRESULT hr;

	DXGI_SWAP_CHAIN_DESC desc = { 0 };
	desc.BufferCount = 1;
	desc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	// RefreshRate intentionally left at 0 so display's refresh rate is used
	desc.BufferUsage  = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	desc.OutputWindow = Window_Main.Handle.ptr;
	desc.SampleDesc.Count   = 1;
	desc.SampleDesc.Quality = 0;
	desc.Windowed           = TRUE;
	desc.SwapEffect         = DXGI_SWAP_EFFECT_DISCARD;

	IDXGIDevice* dxgi_device = NULL;
	hr = ID3D11Device_QueryInterface(device, &guid_IDXGIDevice, (void**)&dxgi_device);
	if (FAILED(hr)) Process_Abort2(hr, "Querying DXGI device");

	IDXGIAdapter* dxgi_adapter = NULL;
	hr = IDXGIDevice_GetAdapter(dxgi_device , &dxgi_adapter);
	if (FAILED(hr)) Process_Abort2(hr, "Querying DXGI adapter");

	IDXGIFactory* dxgi_factory = NULL;
	hr = IDXGIAdapter_GetParent(dxgi_adapter, &guid_IDXGIFactory, (void**)&dxgi_factory);
	if (FAILED(hr)) Process_Abort2(hr, "Querying DXGI factory");

	void* window = Window_Main.Handle.ptr;
	hr = IDXGIFactory_CreateSwapChain(dxgi_factory, (IUnknown*)device, &desc, &swapchain);
	if (FAILED(hr)) Process_Abort2(hr, "Creating swap chain");
}
#endif

void Gfx_Create(void) {
	LoadD3D11Library();
	CreateDevice();
	CreateSwapChain();

	Gfx.Created         = true;
	Gfx.BackendType     = CC_GFX_BACKEND_D3D11;
	customMipmapsLevels = true;
	Gfx_RestoreState();
}

void Gfx_Free(void) {
	Gfx_FreeState();
	IDXGISwapChain_Release(swapchain);
	ID3D11DeviceContext_Release(context);

#ifndef _DEBUG
	ID3D11Device_Release(device);
#else
	ULONG refCount = ID3D11Device_Release(device);
	if (refCount == 0) return; // device destroyed with no issues

	ID3D11Debug *d3dDebug;
	static const GUID guid_d3dDebug = { 0x79cf2233, 0x7536, 0x4948,{ 0x9d, 0x36, 0x1e, 0x46, 0x92, 0xdc, 0x57, 0x60 } };
	HRESULT hr = ID3D11Device_QueryInterface(device, &guid_d3dDebug, (void**)&d3dDebug);
	if (SUCCEEDED(hr))
	{
		hr = ID3D11Debug_ReportLiveDeviceObjects(d3dDebug, D3D11_RLDO_DETAIL);
	}
#endif
}

static cc_bool inited;
cc_bool Gfx_TryRestoreContext(void) {
	// https://docs.microsoft.com/en-us/windows/uwp/gaming/handling-device-lost-scenarios
	Gfx_Free();
	Gfx_Create();
	return true;
}

static void Gfx_FreeState(void) {
	if (!inited) return;
	inited = false;

	FreeDefaultResources();
	FreePipeline();
	Gfx_DeleteTexture(&white_square);
}

static void Gfx_RestoreState(void) {
	if (inited) return;
	inited = true;

	InitDefaultResources();
	gfx_format = -1;
	InitPipeline();

	/* 1x1 dummy white texture */
	struct Bitmap bmp;
	BitmapCol pixels[1] = { BITMAPCOLOR_WHITE };
	Bitmap_Init(bmp, 1, 1, pixels);
	Gfx_RecreateTexture(&white_square, &bmp, 0, false);
}


/*########################################################################################################################*
*---------------------------------------------------------Textures--------------------------------------------------------*
*#########################################################################################################################*/
static void D3D11_DoMipmaps(ID3D11Resource* texture, int x, int y, struct Bitmap* bmp, int rowWidth) {
	BitmapCol* prev = bmp->scan0;
	BitmapCol* cur;

	int lvls = CalcMipmapsLevels(bmp->width, bmp->height);
	int lvl, width = bmp->width, height = bmp->height;

	for (lvl = 1; lvl <= lvls; lvl++) 
	{
		x /= 2; y /= 2;
		if (width > 1)  width  /= 2;
		if (height > 1) height /= 2;

		cur = (BitmapCol*)Mem_Alloc(width * height, BITMAPCOLOR_SIZE, "mipmaps");
		GenMipmaps(width, height, cur, prev, rowWidth);

		D3D11_BOX box;
		box.front  = 0;
		box.back   = 1;
		box.left   = x;
		box.right  = x + width;
		box.top    = y;
		box.bottom = y + height;

		// https://eatplayhate.me/2013/09/29/d3d11-texture-update-costs/
		// Might not be ideal, but seems to work well enough
		int stride = width * 4;
		ID3D11DeviceContext_UpdateSubresource(context, (ID3D11Resource*)texture, lvl, 
												&box, cur, stride, stride * height);

		if (prev != bmp->scan0) Mem_Free(prev);
		prev     = cur;
		rowWidth = width;
	}
	if (prev != bmp->scan0) Mem_Free(prev);
}

GfxResourceID Gfx_AllocTexture(struct Bitmap* bmp, int rowWidth, cc_uint8 flags, cc_bool mipmaps) {
	ID3D11Texture2D* tex = NULL;
	ID3D11ShaderResourceView* view = NULL;
	HRESULT hr;

	D3D11_TEXTURE2D_DESC desc = { 0 };
	desc.Width     = bmp->width;
	desc.Height    = bmp->height;
	desc.MipLevels = 1;
	desc.ArraySize = 1;
	desc.Format    = DXGI_FORMAT_B8G8R8A8_UNORM;
	desc.Usage     = D3D11_USAGE_DEFAULT; // TODO D3D11_USAGE_IMMUTABLE when not dynamic
	desc.SampleDesc.Count = 1;
	desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;

	D3D11_SUBRESOURCE_DATA data;
	data.pSysMem          = bmp->scan0;
	data.SysMemPitch      = rowWidth * 4;
	data.SysMemSlicePitch = 0;
	D3D11_SUBRESOURCE_DATA* src = &data;

	// Direct3D11 specifies pInitialData as an array of D3D11_SUBRESOURCE_DATA of length desc->MipsLevels
	// Rather than writing such code just to support the mipmaps case, I went with the simpler approach of
	//  leaving pInitialData as NULL and specfiying the texture data later using Gfx_UpdateTexturePart
	if (mipmaps) {
		desc.MipLevels += CalcMipmapsLevels(bmp->width, bmp->height);
		src = NULL;
	}

	while ((hr = ID3D11Device_CreateTexture2D(device, &desc, src, &tex)))
	{
		if (hr == E_OUTOFMEMORY) {
			// insufficient VRAM or RAM left to allocate texture, try to reduce the memory in use first
			//  if can't reduce, return 'empty' texture so that at least the game will continue running
			if (!Game_ReduceVRAM()) return 0;
		} else {
			// unknown issue, so don't even try to handle the error
			Process_Abort2(hr, "Failed to create texture");
		}
	}

	hr = ID3D11Device_CreateShaderResourceView(device, (ID3D11Resource*)tex, 
												NULL, &view);
	if (hr) Process_Abort2(hr, "Failed to create view");

	if (mipmaps) Gfx_UpdateTexture(view, 0, 0, bmp, rowWidth, mipmaps);
	return view;
}

void Gfx_UpdateTexture(GfxResourceID texId, int x, int y, struct Bitmap* part, int rowWidth, cc_bool mipmaps) {
	ID3D11ShaderResourceView* view = (ID3D11ShaderResourceView*)texId;
	ID3D11Resource* res = NULL;
	D3D11_BOX box;
	box.front  = 0;
	box.back   = 1;
	box.left   = x;
	box.right  = x + part->width;
	box.top    = y;
	box.bottom = y + part->height;

	// https://eatplayhate.me/2013/09/29/d3d11-texture-update-costs/
	// Might not be ideal, but seems to work well enough
	int stride = rowWidth * 4;
	ID3D11ShaderResourceView_GetResource(view, &res);
	ID3D11DeviceContext_UpdateSubresource(context, (ID3D11Resource*)res, 0, 
											&box, part->scan0, stride, stride * part->height);

	if (mipmaps) D3D11_DoMipmaps(res, x, y, part, rowWidth);
	ID3D11Resource_Release(res);
}

void Gfx_DeleteTexture(GfxResourceID* texId) {
	ID3D11ShaderResourceView* view = (ID3D11ShaderResourceView*)(*texId);
	ID3D11Resource* res = NULL;

	if (view) {
		ID3D11ShaderResourceView_GetResource(view, &res);
		ID3D11Resource_Release(res);
		ID3D11ShaderResourceView_Release(view);

		// note that ID3D11ShaderResourceView_GetResource increments refcount, so need to Release twice
		//  https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11view-getresource
		ID3D11Resource_Release(res);
	}
	*texId = NULL;
}


/*########################################################################################################################*
*-------------------------------------------------------Index buffers-----------------------------------------------------*
*#########################################################################################################################*/
GfxResourceID Gfx_CreateIb2(int count, Gfx_FillIBFunc fillFunc, void* obj) {
	ID3D11Buffer* buffer = NULL;
	cc_uint16 indices[GFX_MAX_INDICES];
	fillFunc(indices, count, obj);
	
	D3D11_BUFFER_DESC desc = { 0 };
	desc.Usage     = D3D11_USAGE_DEFAULT;
	desc.ByteWidth = count * sizeof(cc_uint16);
	desc.BindFlags = D3D11_BIND_INDEX_BUFFER;

	D3D11_SUBRESOURCE_DATA data;
	data.pSysMem          = indices;
	data.SysMemPitch      = 0;
	data.SysMemSlicePitch = 0;

	HRESULT hr = ID3D11Device_CreateBuffer(device, &desc, &data, &buffer);
	if (hr) Process_Abort2(hr, "Failed to create index buffer");
	return buffer;
}

void Gfx_DeleteIb(GfxResourceID* ib) {
	ID3D11Buffer* buffer = (ID3D11Buffer*)(*ib);
	if (buffer) ID3D11Buffer_Release(buffer);
	*ib = NULL;
}


/*########################################################################################################################*
*------------------------------------------------------Vertex buffers-----------------------------------------------------*
*#########################################################################################################################*/
static ID3D11Buffer* CreateVertexBuffer(VertexFormat fmt, int count, cc_bool dynamic) {
	ID3D11Buffer* buffer = NULL;

	D3D11_BUFFER_DESC desc = { 0 };
	desc.Usage          = dynamic ? D3D11_USAGE_DYNAMIC : D3D11_USAGE_DEFAULT;
	desc.CPUAccessFlags = dynamic ? D3D11_CPU_ACCESS_WRITE : 0;
	desc.ByteWidth = count * strideSizes[fmt];
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	/* TODO set data initially */

	HRESULT hr = ID3D11Device_CreateBuffer(device, &desc, NULL, &buffer);
	if (hr) Process_Abort2(hr, "Failed to create vertex buffer");
	return buffer;
}

static GfxResourceID Gfx_AllocStaticVb(VertexFormat fmt, int count) {
	/* TODO immutable? */
	return CreateVertexBuffer(fmt, count, false);
}

void Gfx_DeleteVb(GfxResourceID* vb) { 
	ID3D11Buffer* buffer = (ID3D11Buffer*)(*vb);
	if (buffer) ID3D11Buffer_Release(buffer);
	*vb = NULL;
}

static void* tmp;
void* Gfx_LockVb(GfxResourceID vb, VertexFormat fmt, int count) {
	tmp = Mem_TryAlloc(count, strideSizes[fmt]);
	return tmp;
}

void Gfx_UnlockVb(GfxResourceID vb) {
	ID3D11DeviceContext_UpdateSubresource(context, (ID3D11Resource*)vb, 0, 
											NULL, tmp, 0, 0);
	Mem_Free(tmp);
	tmp = NULL;
}


/*########################################################################################################################*
*--------------------------------------------------Dynamic vertex buffers-------------------------------------------------*
*#########################################################################################################################*/
static GfxResourceID Gfx_AllocDynamicVb(VertexFormat fmt, int maxVertices) {
	return CreateVertexBuffer(fmt, maxVertices, true);
}

void Gfx_DeleteDynamicVb(GfxResourceID* vb) { 
	ID3D11Buffer* buffer = (ID3D11Buffer*)(*vb);
	if (buffer) ID3D11Buffer_Release(buffer);
	*vb = NULL;
}

static D3D11_MAPPED_SUBRESOURCE mapDesc;
void* Gfx_LockDynamicVb(GfxResourceID vb, VertexFormat fmt, int count) {
	ID3D11Buffer* buffer = (ID3D11Buffer*)vb;
	mapDesc.pData = NULL;

	HRESULT hr = ID3D11DeviceContext_Map(context, (ID3D11Resource*)buffer, 0, 
											D3D11_MAP_WRITE_DISCARD, 0, &mapDesc);
	if (hr) Process_Abort2(hr, "Failed to lock dynamic VB");
	return mapDesc.pData;
}

void Gfx_UnlockDynamicVb(GfxResourceID vb) {
	ID3D11DeviceContext_Unmap(context, (ID3D11Resource*)vb, 0);
	Gfx_BindDynamicVb(vb);
}


/*########################################################################################################################*
*-----------------------------------------------------Vertex rendering----------------------------------------------------*
*#########################################################################################################################*/
void Gfx_SetVertexFormat(VertexFormat fmt) {
	if (fmt == gfx_format) return;
	gfx_format = fmt;
	gfx_stride = strideSizes[fmt];

	IA_UpdateLayout();
	VS_UpdateShader();
	PS_UpdateShader();
}

void Gfx_DrawVb_Lines(int verticesCount) {
	ID3D11DeviceContext_IASetPrimitiveTopology(context, D3D11_PRIMITIVE_TOPOLOGY_LINELIST);
	ID3D11DeviceContext_Draw(context, verticesCount, 0);
	ID3D11DeviceContext_IASetPrimitiveTopology(context, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
}

void Gfx_DrawVb_IndexedTris(int verticesCount) {
	ID3D11DeviceContext_DrawIndexed(context, ICOUNT(verticesCount), 0, 0);
}

void Gfx_DrawVb_IndexedTris_Range(int verticesCount, int startVertex, DrawHints hints) {
	ID3D11DeviceContext_DrawIndexed(context, ICOUNT(verticesCount), 0, startVertex);
}

void Gfx_DrawIndexedTris_T2fC4b(int verticesCount, int startVertex) {
	ID3D11DeviceContext_DrawIndexed(context, ICOUNT(verticesCount), 0, startVertex);
}


/*########################################################################################################################*
*---------------------------------------------------------Matrices--------------------------------------------------------*
*#########################################################################################################################*/
void Gfx_CalcOrthoMatrix(struct Matrix* matrix, float width, float height, float zNear, float zFar) {
	// Source https://learn.microsoft.com/en-us/windows/win32/direct3d9/d3dxmatrixorthooffcenterrh
	//   The simplified calculation below uses: L = 0, R = width, T = 0, B = height
	// NOTE: This calculation is shared with Direct3D 9 backend
	*matrix = Matrix_Identity;

	matrix->row1.x =  2.0f / width;
	matrix->row2.y = -2.0f / height;
	matrix->row3.z =  1.0f / (zNear - zFar);

	matrix->row4.x = -1.0f;
	matrix->row4.y =  1.0f;
	matrix->row4.z = zNear / (zNear - zFar);
}

static float Cotangent(float x) { return Math_CosF(x) / Math_SinF(x); }
void Gfx_CalcPerspectiveMatrix(struct Matrix* matrix, float fov, float aspect, float zFar) {
	// Deliberately swap zNear/zFar in projection matrix calculation to produce
	//  a projection matrix that results in a reversed depth buffer
	// https://developer.nvidia.com/content/depth-precision-visualized
	float zNear_ = zFar;
	float zFar_  = Reversed_CalcZNear(fov, 24); // TODO don't always hardcode to 24 bits

	// Source https://learn.microsoft.com/en-us/windows/win32/direct3d9/d3dxmatrixperspectivefovrh
	// NOTE: This calculation is shared with Direct3D 9 backend
	float c = Cotangent(0.5f * fov);
	*matrix = Matrix_Identity;

	matrix->row1.x =  c / aspect;
	matrix->row2.y =  c;
	matrix->row3.z = zFar_ / (zNear_ - zFar_);
	matrix->row3.w = -1.0f;
	matrix->row4.z = (zNear_ * zFar_) / (zNear_ - zFar_);
	matrix->row4.w =  0.0f;
}

//#####################z###################################################################################################
//-------------------------------------------------------Input Assembler--------------------------------------------------
//########################################################################################################################
// https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-input-assembler-stage
static ID3D11InputLayout* input_textured;

static void IA_CreateLayouts(void) {
	ID3D11InputLayout* input = NULL;
	// https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-input-assembler-stage-getting-started
	// https://docs.microsoft.com/en-us/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-legacy-formats
	// https://stackoverflow.com/questions/23398711/d3d11-input-element-desc-element-types-ordering-packing
	// D3D11_APPEND_ALIGNED_ELEMENT
	static D3D11_INPUT_ELEMENT_DESC T_layout[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,  0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "COLOR"   , 0, DXGI_FORMAT_R8G8B8A8_UNORM,  0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, 16, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	};
	HRESULT hr = ID3D11Device_CreateInputLayout(device, T_layout, Array_Elems(T_layout), 
												vs_textured, sizeof(vs_textured), &input);
	input_textured = input;
}

static void IA_UpdateLayout(void) {
	ID3D11DeviceContext_IASetInputLayout(context, input_textured);
}

static void IA_Init(void) {
	IA_CreateLayouts();
	ID3D11DeviceContext_IASetPrimitiveTopology(context, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
}

static void IA_Free(void) {
	ID3D11InputLayout_Release(input_textured);
}

void Gfx_BindIb(GfxResourceID ib) {
	ID3D11Buffer* buffer = (ID3D11Buffer*)ib;
	ID3D11DeviceContext_IASetIndexBuffer(context, buffer, DXGI_FORMAT_R16_UINT, 0);
}

void Gfx_BindVb(GfxResourceID vb) {
	ID3D11Buffer* buffer   = (ID3D11Buffer*)vb;
	static UINT32 offset[] = { 0 };
	ID3D11DeviceContext_IASetVertexBuffers(context, 0, 1, &buffer, &gfx_stride, offset);
}

void Gfx_BindDynamicVb(GfxResourceID vb) {
	ID3D11Buffer* buffer   = (ID3D11Buffer*)vb;
	static UINT32 offset[] = { 0 };
	ID3D11DeviceContext_IASetVertexBuffers(context, 0, 1, &buffer, &gfx_stride, offset);
}


//########################################################################################################################
//--------------------------------------------------------Vertex shader---------------------------------------------------
//########################################################################################################################
// https://docs.microsoft.com/en-us/windows/win32/direct3d11/vertex-shader-stage
static ID3D11VertexShader* vs_shaders[3];
static ID3D11Buffer* vs_cBuffer;

static struct CC_ALIGNED(64) VSConstants {
	struct Matrix mvp;
	float texX, texY;
} vs_constants;
static const struct ShaderDesc vs_descs[] = {
	{ vs_colored,         sizeof(vs_colored) },
	{ vs_textured,        sizeof(vs_textured) },
	{ vs_textured_offset, sizeof(vs_textured_offset) },
};

static void VS_CreateShaders(void) {
	for (int i = 0; i < Array_Elems(vs_shaders); i++)
	{
		HRESULT hr = ID3D11Device_CreateVertexShader(device, vs_descs[i].data, vs_descs[i].len, NULL, &vs_shaders[i]);
		if (hr) Process_Abort2(hr, "Failed to compile vertex shader");
	}
}

static void VS_CreateConstants(void) {
	// https://developer.nvidia.com/content/constant-buffers-without-constant-pain-0
	// https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-buffers-constant-how-to
	// https://gamedev.stackexchange.com/questions/18026/directx11-how-do-i-manage-and-update-multiple-shader-constant-buffers
	D3D11_BUFFER_DESC desc = { 0 };
	desc.ByteWidth      = sizeof(vs_constants);
	//desc.Usage          = D3D11_USAGE_DYNAMIC;
	//desc.BindFlags      = D3D11_BIND_CONSTANT_BUFFER;
	//desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	desc.Usage     = D3D11_USAGE_DEFAULT;
	desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;

	D3D11_SUBRESOURCE_DATA data;
	data.pSysMem          = &vs_constants;
	data.SysMemPitch      = 0;
	data.SysMemSlicePitch = 0;

	HRESULT hr = ID3D11Device_CreateBuffer(device, &desc, &data, &vs_cBuffer);
	ID3D11DeviceContext_VSSetConstantBuffers(context, 0, 1, &vs_cBuffer);
}

static int VS_CalcShaderIndex(void) {
	if (gfx_format == VERTEX_FORMAT_COLOURED) return 0;

	cc_bool has_offset = vs_constants.texX != 0 || vs_constants.texY != 0;
	return has_offset ? 2 : 1;
}

static void VS_UpdateShader(void) {
	int idx = VS_CalcShaderIndex();
	ID3D11DeviceContext_VSSetShader(context, vs_shaders[idx], NULL, 0);
}

static void VS_FreeShaders(void) {
	for (int i = 0; i < Array_Elems(vs_shaders); i++) 
	{
		ID3D11VertexShader_Release(vs_shaders[i]);
	}
}

static void VS_UpdateConstants(void) {
	ID3D11DeviceContext_UpdateSubresource(context, (ID3D11Resource*)vs_cBuffer, 0, 
											NULL, &vs_constants, 0, 0);
}

static void VS_FreeConstants(void) {
	ID3D11Buffer_Release(vs_cBuffer);
}

static void VS_Init(void) {
	VS_CreateShaders();
	VS_CreateConstants();
	VS_UpdateShader();
}

static void VS_Free(void) {
	VS_FreeShaders();
	VS_FreeConstants();
}

static struct Matrix _view, _proj;
void Gfx_LoadMatrix(MatrixType type, const struct Matrix* matrix) {
	if (type == MATRIX_VIEW) _view = *matrix;
	if (type == MATRIX_PROJ) _proj = *matrix;

	Matrix_Mul(&vs_constants.mvp, &_view, &_proj);
	VS_UpdateConstants();
}

void Gfx_LoadMVP(const struct Matrix* view, const struct Matrix* proj, struct Matrix* mvp) {
	Gfx_LoadMatrix(MATRIX_VIEW, view);
	Gfx_LoadMatrix(MATRIX_PROJ, proj);
	Matrix_Mul(mvp, view, proj);
}

void Gfx_EnableTextureOffset(float x, float y) {
	vs_constants.texX = x;
	vs_constants.texY = y;
	VS_UpdateShader();
	VS_UpdateConstants();
}

void Gfx_DisableTextureOffset(void) {
	vs_constants.texX = 0;
	vs_constants.texY = 0;
	VS_UpdateShader();
}


//########################################################################################################################
//---------------------------------------------------------Rasteriser-----------------------------------------------------
//########################################################################################################################
// https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-rasterizer-stage
static ID3D11RasterizerState* rs_states[4];
static cc_bool rs_culling, rs_scissor;

static void RS_CreateRasterState(void) {
	// https://docs.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_rasterizer_desc
	D3D11_RASTERIZER_DESC desc = { 0 };
	for (int i = 0; i < Array_Elems(rs_states); i++)
	{
		desc.CullMode              = (i & 1) ? D3D11_CULL_BACK : D3D11_CULL_NONE;
		desc.ScissorEnable         = (i & 2) != 0;
		desc.FillMode              = D3D11_FILL_SOLID;
		desc.FrontCounterClockwise = true;
		desc.DepthClipEnable       = true; // otherwise vertices/pixels beyond far plane are still wrongly rendered
		ID3D11Device_CreateRasterizerState(device, &desc, &rs_states[i]);
	}
}

static void RS_UpdateRasterState(void) {
	ID3D11DeviceContext_RSSetState(context, rs_states[rs_culling | (rs_scissor << 1)]);
}

static void RS_FreeRasterStates(void) {
	for (int i = 0; i < Array_Elems(rs_states); i++)
	{
		ID3D11RasterizerState_Release(rs_states[i]);
	}
}


void Gfx_SetViewport(int x, int y, int w, int h) {
	D3D11_VIEWPORT viewport;
	viewport.TopLeftX = x;
	viewport.TopLeftY = y;
	viewport.Width    = w;
	viewport.Height   = h;
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
	ID3D11DeviceContext_RSSetViewports(context, 1, &viewport);
}

void Gfx_SetScissor(int x, int y, int w, int h) {
	rs_scissor = x != 0 || y != 0 || w != Game.Width || h != Game.Height;

	D3D11_RECT rect;
	rect.left   = x;
	rect.top    = y;
	rect.right  = x + w;
	rect.bottom = y + h;
	ID3D11DeviceContext_RSSetScissorRects(context, 1, &rect);
	RS_UpdateRasterState();
}

static void RS_Init(void) {
	RS_CreateRasterState();
	Gfx_SetViewport(0, 0, Game.Width, Game.Height);
	RS_UpdateRasterState();
}

static void RS_Free(void) {
	RS_FreeRasterStates();
}

void Gfx_SetFaceCulling(cc_bool enabled) {
	rs_culling = enabled;
	RS_UpdateRasterState();
}


//########################################################################################################################
//--------------------------------------------------------Pixel shader----------------------------------------------------
//########################################################################################################################
// https://docs.microsoft.com/en-us/windows/win32/direct3d11/pixel-shader-stage
static ID3D11SamplerState* ps_samplers[2];
static ID3D11PixelShader* ps_shaders[12];
static ID3D11Buffer* ps_cBuffer;
static cc_bool ps_mipmaps;
static float ps_fogEnd, ps_fogDensity;
static PackedCol ps_fogColor;
static int ps_fogMode;

static struct CC_ALIGNED(64) PSConstants {
	float fogValue;
	float fogR, fogG, fogB;
} ps_constants;
static const struct ShaderDesc ps_descs[] = {
	{ ps_colored,       sizeof(ps_colored) },
	{ ps_textured,      sizeof(ps_textured) },
	{ ps_colored_test,  sizeof(ps_colored_test) },
	{ ps_textured_test, sizeof(ps_textured_test) },
	{ ps_colored_linear,       sizeof(ps_colored_linear) },
	{ ps_textured_linear,      sizeof(ps_textured_linear) },
	{ ps_colored_test_linear,  sizeof(ps_colored_test_linear) },
	{ ps_textured_test_linear, sizeof(ps_textured_test_linear) },
	{ ps_colored_density,       sizeof(ps_colored_density) },
	{ ps_textured_density,      sizeof(ps_textured_density) },
	{ ps_colored_test_density,  sizeof(ps_colored_test_density) },
	{ ps_textured_test_density, sizeof(ps_textured_test_density) },
};

static void PS_CreateShaders(void) {
	for (int i = 0; i < Array_Elems(ps_shaders); i++) 
	{
		HRESULT hr = ID3D11Device_CreatePixelShader(device, ps_descs[i].data, ps_descs[i].len, NULL, &ps_shaders[i]);
		if (hr) Process_Abort2(hr, "Failed to compile pixel shader");
	}
}

static int PS_CalcShaderIndex(void) {
	int idx = gfx_format == VERTEX_FORMAT_COLOURED ? 0 : 1;
	if (gfx_alphaTest) idx += 2;

	if (gfx_fogEnabled) {
		// uncomment when it works
		if (ps_fogMode == FOG_LINEAR) idx += 4;
		if (ps_fogMode == FOG_EXP)    idx += 8;
	}
	return idx;
}

static void PS_UpdateShader(void) {
	int idx = PS_CalcShaderIndex();
	ID3D11DeviceContext_PSSetShader(context, ps_shaders[idx], NULL, 0);
}

static void PS_FreeShaders(void) {
	for (int i = 0; i < Array_Elems(ps_shaders); i++) 
	{
		ID3D11PixelShader_Release(ps_shaders[i]);
	}
}

static void PS_CreateSamplers(void) {
	// https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11device-createsamplerstate
	// https://gamedev.stackexchange.com/questions/18026/directx11-how-do-i-manage-and-update-multiple-shader-constant-buffers
	// https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-buffers-constant-how-to
	D3D11_SAMPLER_DESC desc = { 0 };

	desc.Filter   = D3D11_FILTER_MIN_MAG_MIP_POINT;
	desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	desc.MaxAnisotropy  = 1;
	desc.MaxLOD         = D3D11_FLOAT32_MAX;
	desc.ComparisonFunc = D3D11_COMPARISON_NEVER;

	desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
	HRESULT hr1 = ID3D11Device_CreateSamplerState(device, &desc, &ps_samplers[0]);

	desc.Filter = D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR;
	HRESULT hr2 = ID3D11Device_CreateSamplerState(device, &desc, &ps_samplers[1]);
}

static void PS_UpdateSampler(void) {
	ID3D11DeviceContext_PSSetSamplers(context, 0, 1, &ps_samplers[ps_mipmaps]);
}

static void PS_FreeSamplers(void) {
	for (int i = 0; i < Array_Elems(ps_samplers); i++) 
	{
		ID3D11SamplerState_Release(ps_samplers[i]);
	}
}

static void PS_CreateConstants(void) {
	D3D11_BUFFER_DESC desc = { 0 }; // TODO see notes in VS_CreateConstants
	desc.ByteWidth      = sizeof(ps_constants);
	desc.Usage     = D3D11_USAGE_DEFAULT;
	desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;

	D3D11_SUBRESOURCE_DATA data;
	data.pSysMem          = &ps_constants;
	data.SysMemPitch      = 0;
	data.SysMemSlicePitch = 0;

	HRESULT hr = ID3D11Device_CreateBuffer(device, &desc, &data, &ps_cBuffer);
	ID3D11DeviceContext_PSSetConstantBuffers(context, 0, 1, &ps_cBuffer);
}

static void PS_UpdateConstants(void) {
	ps_constants.fogR = PackedCol_R(ps_fogColor) / 255.0f;
	ps_constants.fogG = PackedCol_G(ps_fogColor) / 255.0f;
	ps_constants.fogB = PackedCol_B(ps_fogColor) / 255.0f;

	// avoid doing - in pixel shader for density fog
	ps_constants.fogValue = ps_fogMode == FOG_LINEAR ? ps_fogEnd : -ps_fogDensity;
	ID3D11DeviceContext_UpdateSubresource(context, (ID3D11Resource*)ps_cBuffer, 0, 
											NULL, &ps_constants, 0, 0);
}

static void PS_FreeConstants(void) {
	ID3D11Buffer_Release(ps_cBuffer);
}

static void PS_Init(void) {
	PS_CreateShaders();
	PS_CreateSamplers();
	PS_CreateConstants();
	PS_UpdateSampler();
	PS_UpdateShader();
}

static void PS_Free(void) {
	PS_FreeShaders();
	PS_FreeSamplers();
	PS_FreeConstants();
}

static void SetAlphaTest(cc_bool enabled) {
	PS_UpdateShader();
}
// unnecessary? check if any performance is gained, probably irrelevant
void Gfx_SetAlphaArgBlend(cc_bool enabled) { }

void Gfx_BindTexture(GfxResourceID texId) {
	/* defasult texture is otherwise transparent black */
	if (!texId) texId = white_square;

	ID3D11ShaderResourceView* view = (ID3D11ShaderResourceView*)texId;
	ID3D11DeviceContext_PSSetShaderResources(context, 0, 1, &view);
}

void Gfx_SetFog(cc_bool enabled) {
	if (gfx_fogEnabled == enabled) return;
	gfx_fogEnabled = enabled;
	PS_UpdateShader();
}

void Gfx_SetFogCol(PackedCol color) {
	if (color == ps_fogColor) return;
	ps_fogColor = color;
	PS_UpdateConstants();
}

void Gfx_SetFogDensity(float value) {
	if (value == ps_fogDensity) return;
	ps_fogDensity = value;
	PS_UpdateConstants();
}

void Gfx_SetFogEnd(float value) {
	if (value == ps_fogEnd) return;
	ps_fogEnd = value;
	PS_UpdateConstants();
}

void Gfx_SetFogMode(FogFunc func) {
	if (ps_fogMode == func) return;
	ps_fogMode = func;
	PS_UpdateShader();
}

void Gfx_EnableMipmaps(void) {
	if (!Gfx.Mipmaps) return;
	ps_mipmaps = true;
	PS_UpdateSampler();
}

void Gfx_DisableMipmaps(void) {
	if (!Gfx.Mipmaps) return;
	ps_mipmaps = false;
	PS_UpdateSampler();
}


//########################################################################################################################
//-------------------------------------------------------Output merger----------------------------------------------------
//########################################################################################################################
// https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-output-merger-stage
static ID3D11RenderTargetView* backbuffer;
static ID3D11Texture2D* depthbuffer;
static ID3D11DepthStencilView* depthbufferView;
static ID3D11BlendState* om_blendStates[16 * 2];
static ID3D11DepthStencilState* om_depthStates[4];
static float gfx_clearColor[4];
static cc_bool gfx_channels[4] = { true, true, true, true };
static cc_bool gfx_depthTest, gfx_depthWrite;

static void OM_Clear(GfxBuffers buffers) {
	if (buffers & GFX_BUFFER_COLOR) {
		ID3D11DeviceContext_ClearRenderTargetView(context, backbuffer, gfx_clearColor);
	}
	if (buffers & GFX_BUFFER_DEPTH) {
		ID3D11DeviceContext_ClearDepthStencilView(context, depthbufferView, D3D11_CLEAR_DEPTH, 0.0f, 0);
	}
}

static void OM_UpdateTarget(void) {
	ID3D11DeviceContext_OMSetRenderTargets(context, 1, &backbuffer, depthbufferView);
}

static void OM_InitTargets(void) {
	// https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-depth-stencil
	ID3D11Texture2D* pBackBuffer = NULL;
	D3D11_TEXTURE2D_DESC desc;
	HRESULT hr;

	hr = IDXGISwapChain_GetBuffer(swapchain, 0, &guid_ID3D11Texture2D, (void**)&pBackBuffer);
	if (hr) Process_Abort2(hr, "Failed to get swapchain backbuffer");

	hr = ID3D11Device_CreateRenderTargetView(device, (ID3D11Resource*)pBackBuffer, 
												NULL, &backbuffer);
	if (hr) Process_Abort2(hr, "Failed to create render target");

	ID3D11Texture2D_GetDesc(pBackBuffer, &desc);
    desc.Format    = DXGI_FORMAT_D24_UNORM_S8_UINT;
    desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;

    hr = ID3D11Device_CreateTexture2D(device, &desc, NULL, &depthbuffer);
	if (hr) Process_Abort2(hr, "Failed to create depthbuffer texture");

	hr = ID3D11Device_CreateDepthStencilView(device, (ID3D11Resource*)depthbuffer, 
												NULL, &depthbufferView);
	if (hr) Process_Abort2(hr, "Failed to create depthbuffer view");

	ID3D11Texture2D_Release(pBackBuffer);
	OM_UpdateTarget();
}

static void OM_CreateDepthStates(void) {
	D3D11_DEPTH_STENCIL_DESC desc = { 0 };
	HRESULT hr;
	desc.DepthFunc = D3D11_COMPARISON_GREATER_EQUAL;

	for (int i = 0; i < Array_Elems(om_depthStates); i++) 
	{
		desc.DepthEnable    = (i & 1) != 0;
		desc.DepthWriteMask = (i & 2) != 0;

		hr = ID3D11Device_CreateDepthStencilState(device, &desc, &om_depthStates[i]);
		if (hr) Process_Abort2(hr, "Failed to create depth state");
	}
}

static void OM_UpdateDepthState(void) {
	ID3D11DepthStencilState* depthState = om_depthStates[gfx_depthTest | (gfx_depthWrite << 1)];
	ID3D11DeviceContext_OMSetDepthStencilState(context, depthState, 0);
}

static void OM_FreeDepthStates(void) {
	for (int i = 0; i < Array_Elems(om_depthStates); i++) 
	{
		ID3D11DepthStencilState_Release(om_depthStates[i]);
	}
}

static void OM_CreateBlendStates(void) {
	// https://docs.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-blend-state
	D3D11_BLEND_DESC desc = { 0 };
	HRESULT hr;
	desc.RenderTarget[0].BlendOp        = D3D11_BLEND_OP_ADD;
	desc.RenderTarget[0].BlendOpAlpha   = D3D11_BLEND_OP_ADD;
	desc.RenderTarget[0].SrcBlend       = D3D11_BLEND_SRC_ALPHA;
	desc.RenderTarget[0].SrcBlendAlpha  = D3D11_BLEND_SRC_ALPHA;
	desc.RenderTarget[0].DestBlend      = D3D11_BLEND_INV_SRC_ALPHA;
	desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;

	for (int i = 0; i < Array_Elems(om_blendStates); i++) 
	{
		int mask = 0;
		if (i & 0x01) mask |= D3D11_COLOR_WRITE_ENABLE_RED;
		if (i & 0x02) mask |= D3D11_COLOR_WRITE_ENABLE_GREEN;
		if (i & 0x04) mask |= D3D11_COLOR_WRITE_ENABLE_BLUE;
		if (i & 0x08) mask |= D3D11_COLOR_WRITE_ENABLE_ALPHA;
		
		desc.RenderTarget[0].RenderTargetWriteMask = mask;
		desc.RenderTarget[0].BlendEnable           = (i & 0x10) != 0;

		hr = ID3D11Device_CreateBlendState(device, &desc, &om_blendStates[i]);
		if (hr) Process_Abort2(hr, "Failed to create blend state");
	}
}

static void OM_UpdateBlendState(void) {
	int idx = (gfx_channels[0]) | (gfx_channels[1] << 1) | (gfx_channels[2] << 2) | (gfx_channels[3] << 3) | (gfx_alphaBlend << 4);
	ID3D11BlendState* blendState = om_blendStates[idx];
	ID3D11DeviceContext_OMSetBlendState(context, blendState, NULL, 0xffffffff);
}

static void OM_FreeBlendStates(void) {
	for (int i = 0; i < Array_Elems(om_blendStates); i++) 
	{
		ID3D11BlendState_Release(om_blendStates[i]);
	}
}

static void OM_Init(void) {
	OM_InitTargets();
	OM_CreateDepthStates();
	OM_UpdateDepthState();
	OM_CreateBlendStates();
	OM_UpdateBlendState();
}

static void OM_FreeTargets(void) {
	ID3D11DeviceContext_OMSetRenderTargets(context, 0, NULL, NULL);
	ID3D11RenderTargetView_Release(backbuffer);
	ID3D11DepthStencilView_Release(depthbufferView);
	ID3D11Texture2D_Release(depthbuffer);
}

static void OM_Free(void) {
	OM_FreeTargets();
	OM_FreeDepthStates();
	OM_FreeBlendStates();
}

void Gfx_ClearColor(PackedCol color) {
	gfx_clearColor[0] = PackedCol_R(color) / 255.0f;
	gfx_clearColor[1] = PackedCol_G(color) / 255.0f;
	gfx_clearColor[2] = PackedCol_B(color) / 255.0f;
	gfx_clearColor[3] = PackedCol_A(color) / 255.0f;
}

void Gfx_SetDepthTest(cc_bool enabled) {
	gfx_depthTest = enabled;
	OM_UpdateDepthState();
}

void Gfx_SetDepthWrite(cc_bool enabled) {
	gfx_depthWrite = enabled;
	OM_UpdateDepthState();
}

static void SetAlphaBlend(cc_bool enabled) {
	OM_UpdateBlendState();
}

static void SetColorWrite(cc_bool r, cc_bool g, cc_bool b, cc_bool a) {
	gfx_channels[0] = r;
	gfx_channels[1] = g;
	gfx_channels[2] = b;
	gfx_channels[3] = a;
	OM_UpdateBlendState();
}

void Gfx_DepthOnlyRendering(cc_bool depthOnly) {
	cc_bool enabled = !depthOnly;
	SetColorWrite(enabled & gfx_colorMask[0], enabled & gfx_colorMask[1], 
				  enabled & gfx_colorMask[2], enabled & gfx_colorMask[3]);
}


/*########################################################################################################################*
*-----------------------------------------------------------Misc----------------------------------------------------------*
*#########################################################################################################################*/
static BitmapCol* D3D11_GetRow(struct Bitmap* bmp, int y, void* ctx) {
	D3D11_MAPPED_SUBRESOURCE* buffer = (D3D11_MAPPED_SUBRESOURCE*)ctx;
	char* row = (char*)buffer->pData + y * buffer->RowPitch;
	return (BitmapCol*)row;
}

cc_result Gfx_TakeScreenshot(struct Stream* output) {
	ID3D11Texture2D* tmp = NULL;
	struct Bitmap bmp;
	HRESULT hr;

	ID3D11Resource* backbuffer_res;
	D3D11_RENDER_TARGET_VIEW_DESC backbuffer_desc;
	D3D11_MAPPED_SUBRESOURCE buffer;
	ID3D11RenderTargetView_GetResource(backbuffer, &backbuffer_res);
	ID3D11RenderTargetView_GetDesc(backbuffer,     &backbuffer_desc);

	D3D11_TEXTURE2D_DESC desc = { 0 };
	desc.Width     = Window_Main.Width;
	desc.Height    = Window_Main.Height;
	desc.MipLevels = 1;
	desc.ArraySize = 1;
	desc.Format    = DXGI_FORMAT_B8G8R8A8_UNORM;
	desc.SampleDesc.Count = 1;
	desc.Usage     = D3D11_USAGE_STAGING;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;

	hr  = ID3D11Device_CreateTexture2D(device, &desc, NULL, &tmp);
	if (hr) goto finished;
	ID3D11DeviceContext_CopyResource(context, (ID3D11Resource*)tmp, backbuffer_res);

	hr = ID3D11DeviceContext_Map(context, (ID3D11Resource*)tmp, 0, 
									D3D11_MAP_READ, 0, &buffer);
	if (hr) goto finished;
	{
		Bitmap_Init(bmp, desc.Width, desc.Height, NULL);
		hr = Png_Encode(&bmp, output, D3D11_GetRow, false, &buffer);
	}
	ID3D11DeviceContext_Unmap(context, (ID3D11Resource*)tmp, 0);

finished:
	if (tmp) { ID3D11Texture2D_Release(tmp); }
	ID3D11Resource_Release(backbuffer_res);
	return hr;
}

void Gfx_SetVSync(cc_bool vsync) {
	gfx_vsync = vsync;
}
void Gfx_BeginFrame(void) { OM_UpdateTarget(); }

void Gfx_ClearBuffers(GfxBuffers buffers) {
	OM_Clear(buffers); 
}

void Gfx_EndFrame(void) {
	// https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-present
	// gfx_vsync happens to match SyncInterval parameter
	HRESULT hr = IDXGISwapChain_Present(swapchain, gfx_vsync, 0);

	// run at reduced FPS when minimised
	if (hr == DXGI_STATUS_OCCLUDED) {
		TickReducedPerformance(); return;
	}
	EndReducedPerformance();

	if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
		Gfx_LoseContext(" (Direct3D11 device lost)");
	} else if (hr) {
		Process_Abort2(hr, "Failed to swap buffers");
	}
}

cc_bool Gfx_WarnIfNecessary(void) { return false; }
cc_bool Gfx_GetUIOptions(struct MenuOptionsScreen* s) { return false; }

void Gfx_GetApiInfo(cc_string* info) {
	int pointerSize = sizeof(void*) * 8;
	HRESULT hr;
	String_Format1(info, "-- Using Direct3D11 (%i bit) --\n", &pointerSize);

	// TODO this overlaps with global declarations, switch to them at some point.. ?
	// apparently using D3D11CreateDeviceAndSwapChain is bad, need to investigate
	IDXGIDevice* dxgi_device = NULL;
	hr = ID3D11Device_QueryInterface(device, &guid_IXDGIDevice, (void**)&dxgi_device);
	if (hr || !dxgi_device) return;

	IDXGIAdapter* dxgi_adapter;
	hr = IDXGIDevice_GetAdapter(dxgi_device, &dxgi_adapter);
	if (hr || !dxgi_adapter) goto release_device;

	DXGI_ADAPTER_DESC desc = { 0 };
	hr = IDXGIAdapter_GetDesc(dxgi_adapter, &desc);
	if (hr) goto release_adapter;

	// desc.Description is a WCHAR, convert to char
	char adapter[128] = { 0 };
	for (int i = 0; i < 128; i++) { adapter[i] = desc.Description[i]; }

	SIZE_T vram = desc.DedicatedVideoMemory;
	SIZE_T dram = desc.DedicatedSystemMemory;
	SIZE_T sram = desc.SharedSystemMemory;
	float tram_ = (vram + dram + sram)  / (1024.0 * 1024.0);
	float vram_ = vram                  / (1024.0 * 1024.0);

	String_Format1(info, "Adapter: %c\n", adapter);
	String_Format2(info, "Graphics memory: %f2 MB total (%f2 MB VRAM)\n", &tram_, &vram_);
	PrintMaxTextureInfo(info);

release_adapter:
	IDXGIAdapter_Release(dxgi_adapter);
release_device:
	IDXGIDevice_Release(dxgi_device);
}

void Gfx_OnWindowResize(void) {
	// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/d3d10-graphics-programming-guide-dxgi#handling-window-resizing
	OM_FreeTargets();
	HRESULT hr = IDXGISwapChain_ResizeBuffers(swapchain, 0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
	if (hr) Process_Abort2(hr, "Failed to resize swapchain");

	OM_InitTargets();
	Gfx_SetViewport(0, 0, Game.Width, Game.Height);
}

static void InitPipeline(void) {
	// https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline
	IA_Init();
	VS_Init();
	RS_Init();
	PS_Init();
	OM_Init();
}

static void FreePipeline(void) {
	IA_Free();
	VS_Free();
	RS_Free();
	PS_Free();
	OM_Free();

	ID3D11DeviceContext_ClearState(context);
	// Direct3D11 uses deferred resource destruction, so Flush to force destruction
	//  https://stackoverflow.com/questions/44155133/directx11-com-object-with-0-references-not-released
	//  https://stackoverflow.com/questions/20032816/can-someone-explain-why-i-still-have-live-objects-after-releasing-the-pointers-t
	//  https://www.gamedev.net/forums/topic/659651-dxgi-leak-warnings/5172345/
	ID3D11DeviceContext_Flush(context);
}
#endif
