#include <d3d11.h>
#include <dxgi.h>

#include "dxgi-helpers.hpp"
#include "graphics-hook.h"
#include "../funchook.h"

struct d3d11_data {
	ID3D11Device *device;         /* do not release */
	ID3D11DeviceContext *context; /* do not release */
	uint32_t base_cx;
	uint32_t base_cy;
	uint32_t cx;
	uint32_t cy;
	DXGI_FORMAT format;
	bool using_shtex;
	bool using_scale;
	bool multisampled;

	ID3D11Texture2D *scale_tex;
	ID3D11ShaderResourceView *scale_resource;

	ID3D11VertexShader *vertex_shader;
	ID3D11InputLayout *vertex_layout;
	ID3D11PixelShader *pixel_shader;

	ID3D11SamplerState *sampler_state;
	ID3D11BlendState *blend_state;
	ID3D11DepthStencilState *zstencil_state;
	ID3D11RasterizerState *raster_state;

	ID3D11Buffer *vertex_buffer;

	union {
		/* shared texture */
		struct {
			struct shtex_data *shtex_info;
			ID3D11Texture2D *texture;
			ID3D11RenderTargetView *render_target;
			HANDLE handle;
		};
		/* shared memory */
		struct {
			ID3D11Texture2D *copy_surfaces[NUM_BUFFERS];
			ID3D11Texture2D *textures[NUM_BUFFERS];
			ID3D11RenderTargetView *render_targets[NUM_BUFFERS];
			bool texture_ready[NUM_BUFFERS];
			bool texture_mapped[NUM_BUFFERS];
			uint32_t pitch;
			struct shmem_data *shmem_info;
			int cur_tex;
			int copy_wait;
		};
	};
};

static struct d3d11_data data = {};

void d3d11_free(void)
{
	if (data.scale_tex)
		data.scale_tex->Release();
	if (data.scale_resource)
		data.scale_resource->Release();
	if (data.vertex_shader)
		data.vertex_shader->Release();
	if (data.vertex_layout)
		data.vertex_layout->Release();
	if (data.pixel_shader)
		data.pixel_shader->Release();
	if (data.sampler_state)
		data.sampler_state->Release();
	if (data.blend_state)
		data.blend_state->Release();
	if (data.zstencil_state)
		data.zstencil_state->Release();
	if (data.raster_state)
		data.raster_state->Release();
	if (data.vertex_buffer)
		data.vertex_buffer->Release();

	capture_free();

	if (data.using_shtex) {
		if (data.texture)
			data.texture->Release();
		if (data.render_target)
			data.render_target->Release();
	} else {
		for (size_t i = 0; i < NUM_BUFFERS; i++) {
			if (data.copy_surfaces[i]) {
				if (data.texture_mapped[i])
					data.context->Unmap(
						data.copy_surfaces[i], 0);
				data.copy_surfaces[i]->Release();
			}
			if (data.textures[i])
				data.textures[i]->Release();
			if (data.render_targets[i])
				data.render_targets[i]->Release();
		}
	}

	memset(&data, 0, sizeof(data));

	hlog("----------------- d3d11 capture freed ----------------");
}

static bool create_d3d11_stage_surface(ID3D11Texture2D **tex)
{
	HRESULT hr;

	D3D11_TEXTURE2D_DESC desc = {};
	desc.Width = data.cx;
	desc.Height = data.cy;
	desc.Format = data.format;
	desc.MipLevels = 1;
	desc.ArraySize = 1;
	desc.SampleDesc.Count = 1;
	desc.Usage = D3D11_USAGE_STAGING;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;

	hr = data.device->CreateTexture2D(&desc, nullptr, tex);
	if (FAILED(hr)) {
		hlog_hr("create_d3d11_stage_surface: failed to create texture",
			hr);
		return false;
	}

	return true;
}

static bool create_d3d11_tex(uint32_t cx, uint32_t cy, ID3D11Texture2D **tex,
			     ID3D11ShaderResourceView **resource,
			     ID3D11RenderTargetView **render_target,
			     HANDLE *handle)
{
	UINT flags = 0;
	UINT misc_flags = 0;
	HRESULT hr;

	if (!!resource)
		flags |= D3D11_BIND_SHADER_RESOURCE;
	if (!!render_target)
		flags |= D3D11_BIND_RENDER_TARGET;
	if (!!handle)
		misc_flags |= D3D11_RESOURCE_MISC_SHARED;

	D3D11_TEXTURE2D_DESC desc = {};
	desc.Width = cx;
	desc.Height = cy;
	desc.MipLevels = 1;
	desc.ArraySize = 1;
	desc.Format = data.format;
	desc.BindFlags = flags;
	desc.SampleDesc.Count = 1;
	desc.Usage = D3D11_USAGE_DEFAULT;
	desc.MiscFlags = misc_flags;

	hr = data.device->CreateTexture2D(&desc, nullptr, tex);
	if (FAILED(hr)) {
		hlog_hr("create_d3d11_tex: failed to create texture", hr);
		return false;
	}

	if (!!resource) {
		D3D11_SHADER_RESOURCE_VIEW_DESC res_desc = {};
		res_desc.Format = data.format;
		res_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
		res_desc.Texture2D.MipLevels = 1;

		hr = data.device->CreateShaderResourceView(*tex, &res_desc,
							   resource);
		if (FAILED(hr)) {
			hlog_hr("create_d3d11_tex: failed to create resource "
				"view",
				hr);
			return false;
		}
	}

	if (!!render_target) {
		hr = data.device->CreateRenderTargetView(*tex, nullptr,
							 render_target);
		if (FAILED(hr)) {
			hlog_hr("create_d3d11_tex: failed to create render "
				"target view",
				hr);
			return false;
		}
	}

	if (!!handle) {
		IDXGIResource *dxgi_res;
		hr = (*tex)->QueryInterface(__uuidof(IDXGIResource),
					    (void **)&dxgi_res);
		if (FAILED(hr)) {
			hlog_hr("create_d3d11_tex: failed to query "
				"IDXGIResource interface from texture",
				hr);
			return false;
		}

		hr = dxgi_res->GetSharedHandle(handle);
		dxgi_res->Release();
		if (FAILED(hr)) {
			hlog_hr("create_d3d11_tex: failed to get shared handle",
				hr);
			return false;
		}
	}

	return true;
}

static inline bool d3d11_init_format(IDXGISwapChain *swap, HWND &window)
{
	DXGI_SWAP_CHAIN_DESC desc;
	HRESULT hr;

	hr = swap->GetDesc(&desc);
	if (FAILED(hr)) {
		hlog_hr("d3d11_init_format: swap->GetDesc failed", hr);
		return false;
	}

	data.format = fix_dxgi_format(desc.BufferDesc.Format);
	data.multisampled = desc.SampleDesc.Count > 1;
	window = desc.OutputWindow;
	data.base_cx = desc.BufferDesc.Width;
	data.base_cy = desc.BufferDesc.Height;

	if (data.using_scale) {
		data.cx = global_hook_info->cx;
		data.cy = global_hook_info->cy;
	} else {
		data.cx = desc.BufferDesc.Width;
		data.cy = desc.BufferDesc.Height;
	}
	return true;
}

static inline bool d3d11_init_vertex_shader(void)
{
	D3D11_INPUT_ELEMENT_DESC desc[2];
	uint8_t *vs_data;
	size_t size;
	HRESULT hr;

	vs_data = get_d3d1x_vertex_shader(&size);

	hr = data.device->CreateVertexShader(vs_data, size, nullptr,
					     &data.vertex_shader);
	if (FAILED(hr)) {
		hlog_hr("d3d11_init_vertex_shader: failed to create shader",
			hr);
		return false;
	}

	desc[0].SemanticName = "SV_Position";
	desc[0].SemanticIndex = 0;
	desc[0].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
	desc[0].InputSlot = 0;
	desc[0].AlignedByteOffset = 0;
	desc[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	desc[0].InstanceDataStepRate = 0;

	desc[1].SemanticName = "TEXCOORD";
	desc[1].SemanticIndex = 0;
	desc[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	desc[1].InputSlot = 0;
	desc[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	desc[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	desc[1].InstanceDataStepRate = 0;

	hr = data.device->CreateInputLayout(desc, 2, vs_data, size,
					    &data.vertex_layout);
	if (FAILED(hr)) {
		hlog_hr("d3d11_init_vertex_shader: failed to create layout",
			hr);
		return false;
	}

	return true;
}

static inline bool d3d11_init_pixel_shader(void)
{
	uint8_t *ps_data;
	size_t size;
	HRESULT hr;

	ps_data = get_d3d1x_pixel_shader(&size);

	hr = data.device->CreatePixelShader(ps_data, size, nullptr,
					    &data.pixel_shader);
	if (FAILED(hr)) {
		hlog_hr("d3d11_init_pixel_shader: failed to create shader", hr);
		return false;
	}

	return true;
}

static inline bool d3d11_init_sampler_state(void)
{
	HRESULT hr;

	D3D11_SAMPLER_DESC desc = {};
	desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
	desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
	desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;

	hr = data.device->CreateSamplerState(&desc, &data.sampler_state);
	if (FAILED(hr)) {
		hlog_hr("d3d11_init_sampler_state: failed to create sampler "
			"state",
			hr);
		return false;
	}

	return true;
}

static inline bool d3d11_init_blend_state(void)
{
	D3D11_BLEND_DESC desc = {};
	HRESULT hr;

	for (size_t i = 0; i < 8; i++)
		desc.RenderTarget[i].RenderTargetWriteMask =
			D3D11_COLOR_WRITE_ENABLE_ALL;

	hr = data.device->CreateBlendState(&desc, &data.blend_state);
	if (FAILED(hr)) {
		hlog_hr("d3d11_init_blend_state: failed to create blend state",
			hr);
		return false;
	}

	return true;
}

static inline bool d3d11_init_zstencil_state(void)
{
	D3D11_DEPTH_STENCIL_DESC desc = {}; /* defaults all to off */
	HRESULT hr;

	hr = data.device->CreateDepthStencilState(&desc, &data.zstencil_state);
	if (FAILED(hr)) {
		hlog_hr("d3d11_init_zstencil_state: failed to create "
			"zstencil state",
			hr);
		return false;
	}

	return true;
}

static inline bool d3d11_init_raster_state(void)
{
	D3D11_RASTERIZER_DESC desc = {};
	HRESULT hr;

	desc.FillMode = D3D11_FILL_SOLID;
	desc.CullMode = D3D11_CULL_NONE;

	hr = data.device->CreateRasterizerState(&desc, &data.raster_state);
	if (FAILED(hr)) {
		hlog_hr("d3d11_init_raster_state: failed to create raster "
			"state",
			hr);
		return false;
	}

	return true;
}

#define NUM_VERTS 4

static inline bool d3d11_init_vertex_buffer(void)
{
	HRESULT hr;
	const vertex verts[NUM_VERTS] = {
		{{-1.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}},
		{{-1.0f, -1.0f, 0.0f, 1.0f}, {0.0f, 1.0f}},
		{{1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
		{{1.0f, -1.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}};

	D3D11_BUFFER_DESC desc;
	desc.ByteWidth = sizeof(vertex) * NUM_VERTS;
	desc.Usage = D3D11_USAGE_DEFAULT;
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	desc.CPUAccessFlags = 0;
	desc.MiscFlags = 0;

	D3D11_SUBRESOURCE_DATA srd = {};
	srd.pSysMem = (const void *)verts;

	hr = data.device->CreateBuffer(&desc, &srd, &data.vertex_buffer);
	if (FAILED(hr)) {
		hlog_hr("d3d11_init_vertex_buffer: failed to create vertex "
			"buffer",
			hr);
		return false;
	}

	return true;
}

static bool d3d11_init_scaling(void)
{
	bool success;

	success = create_d3d11_tex(data.base_cx, data.base_cy, &data.scale_tex,
				   &data.scale_resource, nullptr, nullptr);
	if (!success) {
		hlog("d3d11_init_scaling: failed to create scale texture");
		return false;
	}

	if (!d3d11_init_vertex_shader()) {
		return false;
	}
	if (!d3d11_init_pixel_shader()) {
		return false;
	}
	if (!d3d11_init_sampler_state()) {
		return false;
	}
	if (!d3d11_init_blend_state()) {
		return false;
	}
	if (!d3d11_init_zstencil_state()) {
		return false;
	}
	if (!d3d11_init_raster_state()) {
		return false;
	}
	if (!d3d11_init_vertex_buffer()) {
		return false;
	}

	return true;
}

static bool d3d11_shmem_init_buffers(size_t idx)
{
	bool success;

	success = create_d3d11_stage_surface(&data.copy_surfaces[idx]);
	if (!success) {
		hlog("d3d11_shmem_init_buffers: failed to create copy surface");
		return false;
	}

	if (idx == 0) {
		D3D11_MAPPED_SUBRESOURCE map = {};
		HRESULT hr;

		hr = data.context->Map(data.copy_surfaces[idx], 0,
				       D3D11_MAP_READ, 0, &map);
		if (FAILED(hr)) {
			hlog_hr("d3d11_shmem_init_buffers: failed to get "
				"pitch",
				hr);
			return false;
		}

		data.pitch = map.RowPitch;
		data.context->Unmap(data.copy_surfaces[idx], 0);
	}

	success = create_d3d11_tex(data.cx, data.cy, &data.textures[idx],
				   nullptr, &data.render_targets[idx], nullptr);
	if (!success) {
		hlog("d3d11_shmem_init_buffers: failed to create texture");
		return false;
	}

	return true;
}

static bool d3d11_shmem_init(HWND window)
{
	data.using_shtex = false;

	for (size_t i = 0; i < NUM_BUFFERS; i++) {
		if (!d3d11_shmem_init_buffers(i)) {
			return false;
		}
	}
	if (!capture_init_shmem(&data.shmem_info, window, data.base_cx,
				data.base_cy, data.cx, data.cy, data.pitch,
				data.format, false)) {
		return false;
	}

	hlog("d3d11 memory capture successful");
	return true;
}

static bool d3d11_shtex_init(HWND window)
{
	ID3D11ShaderResourceView *resource = nullptr;
	bool success;

	data.using_shtex = true;

	success = create_d3d11_tex(data.cx, data.cy, &data.texture, &resource,
				   &data.render_target, &data.handle);
	if (resource)
		resource->Release();

	if (!success) {
		hlog("d3d11_shtex_init: failed to create texture");
		return false;
	}
	if (!capture_init_shtex(&data.shtex_info, window, data.base_cx,
				data.base_cy, data.cx, data.cy, data.format,
				false, (uintptr_t)data.handle)) {
		return false;
	}

	hlog("d3d11 shared texture capture successful");
	return true;
}

static void d3d11_init(IDXGISwapChain *swap)
{
	bool success = true;
	HWND window;
	HRESULT hr;

	data.using_scale = global_hook_info->use_scale;

	hr = swap->GetDevice(__uuidof(ID3D11Device), (void **)&data.device);
	if (FAILED(hr)) {
		hlog_hr("d3d11_init: failed to get device from swap", hr);
		return;
	}

	data.device->Release();

	data.device->GetImmediateContext(&data.context);
	data.context->Release();

	if (!d3d11_init_format(swap, window)) {
		return;
	}
	if (data.using_scale && !d3d11_init_scaling()) {
		hlog("d3d11_init: failed to initialize scaling");
		success = false;
	}
	if (success) {
		if (global_hook_info->force_shmem) {
			success = d3d11_shmem_init(window);
		} else {
			success = d3d11_shtex_init(window);
		}
	}

	if (!success)
		d3d11_free();
}

#define MAX_RENDER_TARGETS D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT
#define MAX_SO_TARGETS 4
#define MAX_CLASS_INSTS 256

struct d3d11_state {
	ID3D11GeometryShader *geom_shader;
	ID3D11InputLayout *vertex_layout;
	D3D11_PRIMITIVE_TOPOLOGY topology;
	ID3D11Buffer *vertex_buffer;
	UINT vb_stride;
	UINT vb_offset;
	ID3D11BlendState *blend_state;
	float blend_factor[4];
	UINT sample_mask;
	ID3D11DepthStencilState *zstencil_state;
	UINT zstencil_ref;
	ID3D11RenderTargetView *render_targets[MAX_RENDER_TARGETS];
	ID3D11DepthStencilView *zstencil_view;
	ID3D11SamplerState *sampler_state;
	ID3D11PixelShader *pixel_shader;
	ID3D11ShaderResourceView *resource;
	ID3D11RasterizerState *raster_state;
	UINT num_viewports;
	D3D11_VIEWPORT *viewports;
	ID3D11Buffer *stream_output_targets[MAX_SO_TARGETS];
	ID3D11VertexShader *vertex_shader;
	ID3D11ClassInstance *gs_class_instances[MAX_CLASS_INSTS];
	ID3D11ClassInstance *ps_class_instances[MAX_CLASS_INSTS];
	ID3D11ClassInstance *vs_class_instances[MAX_CLASS_INSTS];
	UINT gs_class_inst_count;
	UINT ps_class_inst_count;
	UINT vs_class_inst_count;
};

static inline void d3d11_save_state(struct d3d11_state *state)
{
	state->gs_class_inst_count = MAX_CLASS_INSTS;
	state->ps_class_inst_count = MAX_CLASS_INSTS;
	state->vs_class_inst_count = MAX_CLASS_INSTS;

	data.context->GSGetShader(&state->geom_shader,
				  state->gs_class_instances,
				  &state->gs_class_inst_count);
	data.context->IAGetInputLayout(&state->vertex_layout);
	data.context->IAGetPrimitiveTopology(&state->topology);
	data.context->IAGetVertexBuffers(0, 1, &state->vertex_buffer,
					 &state->vb_stride, &state->vb_offset);
	data.context->OMGetBlendState(&state->blend_state, state->blend_factor,
				      &state->sample_mask);
	data.context->OMGetDepthStencilState(&state->zstencil_state,
					     &state->zstencil_ref);
	data.context->OMGetRenderTargets(MAX_RENDER_TARGETS,
					 state->render_targets,
					 &state->zstencil_view);
	data.context->PSGetSamplers(0, 1, &state->sampler_state);
	data.context->PSGetShader(&state->pixel_shader,
				  state->ps_class_instances,
				  &state->ps_class_inst_count);
	data.context->PSGetShaderResources(0, 1, &state->resource);
	data.context->RSGetState(&state->raster_state);
	data.context->RSGetViewports(&state->num_viewports, nullptr);
	if (state->num_viewports) {
		state->viewports = (D3D11_VIEWPORT *)malloc(
			sizeof(D3D11_VIEWPORT) * state->num_viewports);
		data.context->RSGetViewports(&state->num_viewports,
					     state->viewports);
	}
	data.context->SOGetTargets(MAX_SO_TARGETS,
				   state->stream_output_targets);
	data.context->VSGetShader(&state->vertex_shader,
				  state->vs_class_instances,
				  &state->vs_class_inst_count);
}

static inline void safe_release(IUnknown *p)
{
	if (p)
		p->Release();
}

#define SO_APPEND ((UINT)-1)

static inline void d3d11_restore_state(struct d3d11_state *state)
{
	UINT so_offsets[MAX_SO_TARGETS] = {SO_APPEND, SO_APPEND, SO_APPEND,
					   SO_APPEND};

	data.context->GSSetShader(state->geom_shader, state->gs_class_instances,
				  state->gs_class_inst_count);
	data.context->IASetInputLayout(state->vertex_layout);
	data.context->IASetPrimitiveTopology(state->topology);
	data.context->IASetVertexBuffers(0, 1, &state->vertex_buffer,
					 &state->vb_stride, &state->vb_offset);
	data.context->OMSetBlendState(state->blend_state, state->blend_factor,
				      state->sample_mask);
	data.context->OMSetDepthStencilState(state->zstencil_state,
					     state->zstencil_ref);
	data.context->OMSetRenderTargets(MAX_RENDER_TARGETS,
					 state->render_targets,
					 state->zstencil_view);
	data.context->PSSetSamplers(0, 1, &state->sampler_state);
	data.context->PSSetShader(state->pixel_shader,
				  state->ps_class_instances,
				  state->ps_class_inst_count);
	data.context->PSSetShaderResources(0, 1, &state->resource);
	data.context->RSSetState(state->raster_state);
	data.context->RSSetViewports(state->num_viewports, state->viewports);
	data.context->SOSetTargets(MAX_SO_TARGETS, state->stream_output_targets,
				   so_offsets);
	data.context->VSSetShader(state->vertex_shader,
				  state->vs_class_instances,
				  state->vs_class_inst_count);
	safe_release(state->geom_shader);
	safe_release(state->vertex_layout);
	safe_release(state->vertex_buffer);
	safe_release(state->blend_state);
	safe_release(state->zstencil_state);
	for (size_t i = 0; i < MAX_RENDER_TARGETS; i++)
		safe_release(state->render_targets[i]);
	safe_release(state->zstencil_view);
	safe_release(state->sampler_state);
	safe_release(state->pixel_shader);
	safe_release(state->resource);
	safe_release(state->raster_state);
	for (size_t i = 0; i < MAX_SO_TARGETS; i++)
		safe_release(state->stream_output_targets[i]);
	safe_release(state->vertex_shader);
	for (size_t i = 0; i < state->gs_class_inst_count; i++)
		state->gs_class_instances[i]->Release();
	for (size_t i = 0; i < state->ps_class_inst_count; i++)
		state->ps_class_instances[i]->Release();
	for (size_t i = 0; i < state->vs_class_inst_count; i++)
		state->vs_class_instances[i]->Release();
	free(state->viewports);
	memset(state, 0, sizeof(*state));
}

static inline void d3d11_setup_pipeline(ID3D11RenderTargetView *target,
					ID3D11ShaderResourceView *resource)
{
	const float factor[4] = {1.0f, 1.0f, 1.0f, 1.0f};
	D3D11_VIEWPORT viewport = {0};
	UINT stride = sizeof(vertex);
	void *emptyptr = nullptr;
	UINT zero = 0;

	viewport.Width = (float)data.cx;
	viewport.Height = (float)data.cy;
	viewport.MaxDepth = 1.0f;

	data.context->GSSetShader(nullptr, nullptr, 0);
	data.context->IASetInputLayout(data.vertex_layout);
	data.context->IASetPrimitiveTopology(
		D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
	data.context->IASetVertexBuffers(0, 1, &data.vertex_buffer, &stride,
					 &zero);
	data.context->OMSetBlendState(data.blend_state, factor, 0xFFFFFFFF);
	data.context->OMSetDepthStencilState(data.zstencil_state, 0);
	data.context->OMSetRenderTargets(1, &target, nullptr);
	data.context->PSSetSamplers(0, 1, &data.sampler_state);
	data.context->PSSetShader(data.pixel_shader, nullptr, 0);
	data.context->PSSetShaderResources(0, 1, &resource);
	data.context->RSSetState(data.raster_state);
	data.context->RSSetViewports(1, &viewport);
	data.context->SOSetTargets(1, (ID3D11Buffer **)&emptyptr, &zero);
	data.context->VSSetShader(data.vertex_shader, nullptr, 0);
}

static inline void d3d11_scale_texture(ID3D11RenderTargetView *target,
				       ID3D11ShaderResourceView *resource)
{
	static struct d3d11_state old_state = {0};

	d3d11_save_state(&old_state);
	d3d11_setup_pipeline(target, resource);
	data.context->Draw(4, 0);
	d3d11_restore_state(&old_state);
}

static inline void d3d11_copy_texture(ID3D11Resource *dst, ID3D11Resource *src)
{
	if (data.multisampled) {
		data.context->ResolveSubresource(dst, 0, src, 0, data.format);
	} else {
		data.context->CopyResource(dst, src);
	}
}

static inline void d3d11_shtex_capture(ID3D11Resource *backbuffer)
{
	if (data.using_scale) {
		d3d11_copy_texture(data.scale_tex, backbuffer);
		d3d11_scale_texture(data.render_target, data.scale_resource);
	} else {
		d3d11_copy_texture(data.texture, backbuffer);
	}
}

static inline void d3d11_shmem_queue_copy()
{
	for (size_t i = 0; i < NUM_BUFFERS; i++) {
		D3D11_MAPPED_SUBRESOURCE map;
		HRESULT hr;

		if (data.texture_ready[i]) {
			data.texture_ready[i] = false;

			hr = data.context->Map(data.copy_surfaces[i], 0,
					       D3D11_MAP_READ, 0, &map);
			if (SUCCEEDED(hr)) {
				data.texture_mapped[i] = true;
				shmem_copy_data(i, map.pData);
			}
			break;
		}
	}
}

static inline void d3d11_shmem_capture(ID3D11Resource *backbuffer)
{
	int next_tex;

	d3d11_shmem_queue_copy();

	next_tex = (data.cur_tex == NUM_BUFFERS - 1) ? 0 : data.cur_tex + 1;

	if (data.using_scale) {
		d3d11_copy_texture(data.scale_tex, backbuffer);
		d3d11_scale_texture(data.render_targets[data.cur_tex],
				    data.scale_resource);
	} else {
		d3d11_copy_texture(data.textures[data.cur_tex], backbuffer);
	}

	if (data.copy_wait < NUM_BUFFERS - 1) {
		data.copy_wait++;
	} else {
		ID3D11Texture2D *src = data.textures[next_tex];
		ID3D11Texture2D *dst = data.copy_surfaces[next_tex];

		if (shmem_texture_data_lock(next_tex)) {
			data.context->Unmap(dst, 0);
			data.texture_mapped[next_tex] = false;
			shmem_texture_data_unlock(next_tex);
		}

		d3d11_copy_texture(dst, src);
		data.texture_ready[next_tex] = true;
	}

	data.cur_tex = next_tex;
}

void d3d11_capture(void *swap_ptr, void *backbuffer_ptr, bool)
{
	IDXGIResource *dxgi_backbuffer = (IDXGIResource *)backbuffer_ptr;
	IDXGISwapChain *swap = (IDXGISwapChain *)swap_ptr;

	HRESULT hr;
	if (capture_should_stop()) {
		d3d11_free();
	}
	if (capture_should_init()) {
		d3d11_init(swap);
	}
	if (capture_ready()) {
		ID3D11Resource *backbuffer;

		hr = dxgi_backbuffer->QueryInterface(__uuidof(ID3D11Resource),
						     (void **)&backbuffer);
		if (FAILED(hr)) {
			hlog_hr("d3d11_shtex_capture: failed to get "
				"backbuffer",
				hr);
			return;
		}

		if (data.using_shtex)
			d3d11_shtex_capture(backbuffer);
		else
			d3d11_shmem_capture(backbuffer);

		backbuffer->Release();
	}
}
