/* FNA3D - 3D Graphics Library for FNA
 *
 * Copyright (c) 2020-2022 Ethan Lee
 *
 * 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.
 *
 * Ethan "flibitijibibo" Lee <flibitijibibo@flibitijibibo.com>
 *
 */

#include "FNA3D_Driver.h"
#include "FNA3D_Tracing.h"

#include <SDL.h>

#if !SDL_VERSION_ATLEAST(2, 0, 12)
#error "SDL version older than 2.0.12"
#endif /* !SDL_VERSION_ATLEAST */

/* Drivers */

static const FNA3D_Driver *drivers[] = {
#if FNA3D_DRIVER_D3D11
	&D3D11Driver,
#endif
#if FNA3D_DRIVER_OPENGL
	&OpenGLDriver,
#endif
#if FNA3D_DRIVER_VULKAN /* TODO: Bump this to the top when Vulkan is done! */
	&VulkanDriver,
#endif
#if FNA3D_DRIVER_GNMX
	&GNMXDriver,
#endif
	NULL
};

/* Logging */

static void FNA3D_Default_LogInfo(const char *msg)
{
	SDL_LogInfo(
		SDL_LOG_CATEGORY_APPLICATION,
		"%s",
		msg
	);
}

static void FNA3D_Default_LogWarn(const char *msg)
{
	SDL_LogWarn(
		SDL_LOG_CATEGORY_APPLICATION,
		"%s",
		msg
	);
}

static void FNA3D_Default_LogError(const char *msg)
{
	SDL_LogError(
		SDL_LOG_CATEGORY_APPLICATION,
		"%s",
		msg
	);
}

static FNA3D_LogFunc FNA3D_LogInfoFunc = FNA3D_Default_LogInfo;
static FNA3D_LogFunc FNA3D_LogWarnFunc = FNA3D_Default_LogWarn;
static FNA3D_LogFunc FNA3D_LogErrorFunc = FNA3D_Default_LogError;

#define MAX_MESSAGE_SIZE 1024

void FNA3D_LogInfo(const char *fmt, ...)
{
	char msg[MAX_MESSAGE_SIZE];
	va_list ap;
	va_start(ap, fmt);
	SDL_vsnprintf(msg, sizeof(msg), fmt, ap);
	va_end(ap);
	FNA3D_LogInfoFunc(msg);
}

void FNA3D_LogWarn(const char *fmt, ...)
{
	char msg[MAX_MESSAGE_SIZE];
	va_list ap;
	va_start(ap, fmt);
	SDL_vsnprintf(msg, sizeof(msg), fmt, ap);
	va_end(ap);
	FNA3D_LogWarnFunc(msg);
}

void FNA3D_LogError(const char *fmt, ...)
{
	char msg[MAX_MESSAGE_SIZE];
	va_list ap;
	va_start(ap, fmt);
	SDL_vsnprintf(msg, sizeof(msg), fmt, ap);
	va_end(ap);
	FNA3D_LogErrorFunc(msg);
}

#undef MAX_MESSAGE_SIZE

void FNA3D_HookLogFunctions(
	FNA3D_LogFunc info,
	FNA3D_LogFunc warn,
	FNA3D_LogFunc error
) {
	FNA3D_LogInfoFunc = info;
	FNA3D_LogWarnFunc = warn;
	FNA3D_LogErrorFunc = error;
}

/* Version API */

uint32_t FNA3D_LinkedVersion(void)
{
	return FNA3D_COMPILED_VERSION;
}

/* Driver Functions */

static int32_t selectedDriver = -1;

uint32_t FNA3D_PrepareWindowAttributes(void)
{
	uint32_t result = 0;
	uint32_t i;
	const char *hint = SDL_GetHint("FNA3D_FORCE_DRIVER");
	for (i = 0; drivers[i] != NULL; i += 1)
	{
		if (hint != NULL)
		{
			if (SDL_strcmp(hint, drivers[i]->Name) != 0)
			{
				continue;
			}
		}
		if (drivers[i]->PrepareWindowAttributes(&result))
		{
			break;
		}
	}
	if (drivers[i] == NULL)
	{
		FNA3D_LogError("No supported FNA3D driver found!");
	}
	else
	{
		selectedDriver = i;
	}
	return result;
}

FNA3DAPI void FNA3D_GetDrawableSize(void* window, int32_t *w, int32_t *h)
{
	if (selectedDriver < 0)
	{
		FNA3D_LogError("Call FNA3D_PrepareWindowAttributes first!");
		return;
	}

	drivers[selectedDriver]->GetDrawableSize(window, w, h);
}

/* Init/Quit */

FNA3D_Device* FNA3D_CreateDevice(
	FNA3D_PresentationParameters *presentationParameters,
	uint8_t debugMode
) {
	TRACE_CREATEDEVICE
	if (selectedDriver < 0)
	{
		FNA3D_LogError("Call FNA3D_PrepareWindowAttributes first!");
		return NULL;
	}

	return drivers[selectedDriver]->CreateDevice(
		presentationParameters,
		debugMode
	);
}

void FNA3D_DestroyDevice(FNA3D_Device *device)
{
	TRACE_DESTROYDEVICE
	if (device == NULL)
	{
		return;
	}

	device->DestroyDevice(device);
}

/* Presentation */

void FNA3D_SwapBuffers(
	FNA3D_Device *device,
	FNA3D_Rect *sourceRectangle,
	FNA3D_Rect *destinationRectangle,
	void* overrideWindowHandle
) {
	TRACE_SWAPBUFFERS
	if (device == NULL)
	{
		return;
	}
	device->SwapBuffers(
		device->driverData,
		sourceRectangle,
		destinationRectangle,
		overrideWindowHandle
	);
}

/* Drawing */

void FNA3D_Clear(
	FNA3D_Device *device,
	FNA3D_ClearOptions options,
	FNA3D_Vec4 *color,
	float depth,
	int32_t stencil
) {
	TRACE_CLEAR
	if (device == NULL)
	{
		return;
	}
	device->Clear(device->driverData, options, color, depth, stencil);
}

void FNA3D_DrawIndexedPrimitives(
	FNA3D_Device *device,
	FNA3D_PrimitiveType primitiveType,
	int32_t baseVertex,
	int32_t minVertexIndex,
	int32_t numVertices,
	int32_t startIndex,
	int32_t primitiveCount,
	FNA3D_Buffer *indices,
	FNA3D_IndexElementSize indexElementSize
) {
	TRACE_DRAWINDEXEDPRIMITIVES
	if (device == NULL)
	{
		return;
	}
	device->DrawIndexedPrimitives(
		device->driverData,
		primitiveType,
		baseVertex,
		minVertexIndex,
		numVertices,
		startIndex,
		primitiveCount,
		indices,
		indexElementSize
	);
}

void FNA3D_DrawInstancedPrimitives(
	FNA3D_Device *device,
	FNA3D_PrimitiveType primitiveType,
	int32_t baseVertex,
	int32_t minVertexIndex,
	int32_t numVertices,
	int32_t startIndex,
	int32_t primitiveCount,
	int32_t instanceCount,
	FNA3D_Buffer *indices,
	FNA3D_IndexElementSize indexElementSize
) {
	TRACE_DRAWINSTANCEDPRIMITIVES
	if (device == NULL)
	{
		return;
	}
	device->DrawInstancedPrimitives(
		device->driverData,
		primitiveType,
		baseVertex,
		minVertexIndex,
		numVertices,
		startIndex,
		primitiveCount,
		instanceCount,
		indices,
		indexElementSize
	);
}

void FNA3D_DrawPrimitives(
	FNA3D_Device *device,
	FNA3D_PrimitiveType primitiveType,
	int32_t vertexStart,
	int32_t primitiveCount
) {
	TRACE_DRAWPRIMITIVES
	if (device == NULL)
	{
		return;
	}
	device->DrawPrimitives(
		device->driverData,
		primitiveType,
		vertexStart,
		primitiveCount
	);
}

/* Mutable Render States */

void FNA3D_SetViewport(FNA3D_Device *device, FNA3D_Viewport *viewport)
{
	TRACE_SETVIEWPORT
	if (device == NULL)
	{
		return;
	}
	device->SetViewport(device->driverData, viewport);
}

void FNA3D_SetScissorRect(FNA3D_Device *device, FNA3D_Rect *scissor)
{
	TRACE_SETSCISSORRECT
	if (device == NULL)
	{
		return;
	}
	device->SetScissorRect(device->driverData, scissor);
}

void FNA3D_GetBlendFactor(
	FNA3D_Device *device,
	FNA3D_Color *blendFactor
) {
	/* Not traced! */
	if (device == NULL)
	{
		return;
	}
	device->GetBlendFactor(device->driverData, blendFactor);
}

void FNA3D_SetBlendFactor(
	FNA3D_Device *device,
	FNA3D_Color *blendFactor
) {
	TRACE_SETBLENDFACTOR
	if (device == NULL)
	{
		return;
	}
	device->SetBlendFactor(device->driverData, blendFactor);
}

int32_t FNA3D_GetMultiSampleMask(FNA3D_Device *device)
{
	/* Not traced! */
	if (device == NULL)
	{
		return 0;
	}
	return device->GetMultiSampleMask(device->driverData);
}

void FNA3D_SetMultiSampleMask(FNA3D_Device *device, int32_t mask)
{
	TRACE_SETMULTISAMPLEMASK
	if (device == NULL)
	{
		return;
	}
	device->SetMultiSampleMask(device->driverData, mask);
}

int32_t FNA3D_GetReferenceStencil(FNA3D_Device *device)
{
	/* Not traced! */
	if (device == NULL)
	{
		return 0;
	}
	return device->GetReferenceStencil(device->driverData);
}

void FNA3D_SetReferenceStencil(FNA3D_Device *device, int32_t ref)
{
	TRACE_SETREFERENCESTENCIL
	if (device == NULL)
	{
		return;
	}
	device->SetReferenceStencil(device->driverData, ref);
}

/* Immutable Render States */

void FNA3D_SetBlendState(
	FNA3D_Device *device,
	FNA3D_BlendState *blendState
) {
	TRACE_SETBLENDSTATE
	if (device == NULL)
	{
		return;
	}
	device->SetBlendState(device->driverData, blendState);
}

void FNA3D_SetDepthStencilState(
	FNA3D_Device *device,
	FNA3D_DepthStencilState *depthStencilState
) {
	TRACE_SETDEPTHSTENCILSTATE
	if (device == NULL)
	{
		return;
	}
	device->SetDepthStencilState(device->driverData, depthStencilState);
}

void FNA3D_ApplyRasterizerState(
	FNA3D_Device *device,
	FNA3D_RasterizerState *rasterizerState
) {
	TRACE_APPLYRASTERIZERSTATE
	if (device == NULL)
	{
		return;
	}
	device->ApplyRasterizerState(device->driverData, rasterizerState);
}

void FNA3D_VerifySampler(
	FNA3D_Device *device,
	int32_t index,
	FNA3D_Texture *texture,
	FNA3D_SamplerState *sampler
) {
	TRACE_VERIFYSAMPLER
	if (device == NULL)
	{
		return;
	}
	device->VerifySampler(device->driverData, index, texture, sampler);
}

void FNA3D_VerifyVertexSampler(
	FNA3D_Device *device,
	int32_t index,
	FNA3D_Texture *texture,
	FNA3D_SamplerState *sampler
) {
	TRACE_VERIFYVERTEXSAMPLER
	if (device == NULL)
	{
		return;
	}
	device->VerifyVertexSampler(device->driverData, index, texture, sampler);
}

void FNA3D_ApplyVertexBufferBindings(
	FNA3D_Device *device,
	FNA3D_VertexBufferBinding *bindings,
	int32_t numBindings,
	uint8_t bindingsUpdated,
	int32_t baseVertex
) {
	TRACE_APPLYVERTEXBUFFERBINDINGS
	if (device == NULL)
	{
		return;
	}
	device->ApplyVertexBufferBindings(
		device->driverData,
		bindings,
		numBindings,
		bindingsUpdated,
		baseVertex
	);
}

/* Render Targets */

void FNA3D_SetRenderTargets(
	FNA3D_Device *device,
	FNA3D_RenderTargetBinding *renderTargets,
	int32_t numRenderTargets,
	FNA3D_Renderbuffer *depthStencilBuffer,
	FNA3D_DepthFormat depthFormat,
	uint8_t preserveTargetContents
) {
	TRACE_SETRENDERTARGETS
	if (device == NULL)
	{
		return;
	}
	device->SetRenderTargets(
		device->driverData,
		renderTargets,
		numRenderTargets,
		depthStencilBuffer,
		depthFormat,
		preserveTargetContents
	);
}

void FNA3D_ResolveTarget(
	FNA3D_Device *device,
	FNA3D_RenderTargetBinding *target
) {
	TRACE_RESOLVETARGET
	if (device == NULL)
	{
		return;
	}
	device->ResolveTarget(device->driverData, target);
}

/* Backbuffer Functions */

void FNA3D_ResetBackbuffer(
	FNA3D_Device *device,
	FNA3D_PresentationParameters *presentationParameters
) {
	TRACE_RESETBACKBUFFER
	if (device == NULL)
	{
		return;
	}
	device->ResetBackbuffer(device->driverData, presentationParameters);
}

void FNA3D_ReadBackbuffer(
	FNA3D_Device *device,
	int32_t x,
	int32_t y,
	int32_t w,
	int32_t h,
	void* data,
	int32_t dataLength
) {
	TRACE_READBACKBUFFER
	if (device == NULL)
	{
		return;
	}
	device->ReadBackbuffer(
		device->driverData,
		x,
		y,
		w,
		h,
		data,
		dataLength
	);
}

void FNA3D_GetBackbufferSize(
	FNA3D_Device *device,
	int32_t *w,
	int32_t *h
) {
	/* Not traced! */
	if (device == NULL)
	{
		*w = 0;
		*h = 0;
		return;
	}
	device->GetBackbufferSize(device->driverData, w, h);
}

FNA3D_SurfaceFormat FNA3D_GetBackbufferSurfaceFormat(FNA3D_Device *device)
{
	/* Not traced! */
	if (device == NULL)
	{
		return FNA3D_SURFACEFORMAT_COLOR;
	}
	return device->GetBackbufferSurfaceFormat(device->driverData);
}

FNA3D_DepthFormat FNA3D_GetBackbufferDepthFormat(FNA3D_Device *device)
{
	/* Not traced! */
	if (device == NULL)
	{
		return FNA3D_DEPTHFORMAT_NONE;
	}
	return device->GetBackbufferDepthFormat(device->driverData);
}

int32_t FNA3D_GetBackbufferMultiSampleCount(FNA3D_Device *device)
{
	/* Not traced! */
	if (device == NULL)
	{
		return 0;
	}
	return device->GetBackbufferMultiSampleCount(device->driverData);
}

/* Textures */

FNA3D_Texture* FNA3D_CreateTexture2D(
	FNA3D_Device *device,
	FNA3D_SurfaceFormat format,
	int32_t width,
	int32_t height,
	int32_t levelCount,
	uint8_t isRenderTarget
) {
	/* We're stuck tracing _after_ the call instead of _before_, because
	 * of threading issues. This can cause timing issues!
	 */
	FNA3D_Texture *result;
	if (device == NULL)
	{
		return NULL;
	}
	result = device->CreateTexture2D(
		device->driverData,
		format,
		width,
		height,
		levelCount,
		isRenderTarget
	);
	TRACE_CREATETEXTURE2D
	return result;
}

FNA3D_Texture* FNA3D_CreateTexture3D(
	FNA3D_Device *device,
	FNA3D_SurfaceFormat format,
	int32_t width,
	int32_t height,
	int32_t depth,
	int32_t levelCount
) {
	/* We're stuck tracing _after_ the call instead of _before_, because
	 * of threading issues. This can cause timing issues!
	 */
	FNA3D_Texture *result;
	if (device == NULL)
	{
		return NULL;
	}
	result = device->CreateTexture3D(
		device->driverData,
		format,
		width,
		height,
		depth,
		levelCount
	);
	TRACE_CREATETEXTURE3D
	return result;
}

FNA3D_Texture* FNA3D_CreateTextureCube(
	FNA3D_Device *device,
	FNA3D_SurfaceFormat format,
	int32_t size,
	int32_t levelCount,
	uint8_t isRenderTarget
) {
	/* We're stuck tracing _after_ the call instead of _before_, because
	 * of threading issues. This can cause timing issues!
	 */
	FNA3D_Texture *result;
	if (device == NULL)
	{
		return NULL;
	}
	result = device->CreateTextureCube(
		device->driverData,
		format,
		size,
		levelCount,
		isRenderTarget
	);
	TRACE_CREATETEXTURECUBE
	return result;
}

void FNA3D_AddDisposeTexture(
	FNA3D_Device *device,
	FNA3D_Texture *texture
) {
	TRACE_ADDDISPOSETEXTURE
	if (device == NULL || texture == NULL)
	{
		return;
	}
	device->AddDisposeTexture(device->driverData, texture);
}

void FNA3D_SetTextureData2D(
	FNA3D_Device *device,
	FNA3D_Texture *texture,
	int32_t x,
	int32_t y,
	int32_t w,
	int32_t h,
	int32_t level,
	void* data,
	int32_t dataLength
) {
	TRACE_SETTEXTUREDATA2D
	if (device == NULL)
	{
		return;
	}
	device->SetTextureData2D(
		device->driverData,
		texture,
		x,
		y,
		w,
		h,
		level,
		data,
		dataLength
	);
}

void FNA3D_SetTextureData3D(
	FNA3D_Device *device,
	FNA3D_Texture *texture,
	int32_t x,
	int32_t y,
	int32_t z,
	int32_t w,
	int32_t h,
	int32_t d,
	int32_t level,
	void* data,
	int32_t dataLength
) {
	TRACE_SETTEXTUREDATA3D
	if (device == NULL)
	{
		return;
	}
	device->SetTextureData3D(
		device->driverData,
		texture,
		x,
		y,
		z,
		w,
		h,
		d,
		level,
		data,
		dataLength
	);
}

void FNA3D_SetTextureDataCube(
	FNA3D_Device *device,
	FNA3D_Texture *texture,
	int32_t x,
	int32_t y,
	int32_t w,
	int32_t h,
	FNA3D_CubeMapFace cubeMapFace,
	int32_t level,
	void* data,
	int32_t dataLength
) {
	TRACE_SETTEXTUREDATACUBE
	if (device == NULL)
	{
		return;
	}
	device->SetTextureDataCube(
		device->driverData,
		texture,
		x,
		y,
		w,
		h,
		cubeMapFace,
		level,
		data,
		dataLength
	);
}

void FNA3D_SetTextureDataYUV(
	FNA3D_Device *device,
	FNA3D_Texture *y,
	FNA3D_Texture *u,
	FNA3D_Texture *v,
	int32_t yWidth,
	int32_t yHeight,
	int32_t uvWidth,
	int32_t uvHeight,
	void* data,
	int32_t dataLength
) {
	TRACE_SETTEXTUREDATAYUV
	if (device == NULL)
	{
		return;
	}
	device->SetTextureDataYUV(
		device->driverData,
		y,
		u,
		v,
		yWidth,
		yHeight,
		uvWidth,
		uvHeight,
		data,
		dataLength
	);
}

void FNA3D_GetTextureData2D(
	FNA3D_Device *device,
	FNA3D_Texture *texture,
	int32_t x,
	int32_t y,
	int32_t w,
	int32_t h,
	int32_t level,
	void* data,
	int32_t dataLength
) {
	TRACE_GETTEXTUREDATA2D
	if (device == NULL)
	{
		return;
	}
	device->GetTextureData2D(
		device->driverData,
		texture,
		x,
		y,
		w,
		h,
		level,
		data,
		dataLength
	);
}

void FNA3D_GetTextureData3D(
	FNA3D_Device *device,
	FNA3D_Texture *texture,
	int32_t x,
	int32_t y,
	int32_t z,
	int32_t w,
	int32_t h,
	int32_t d,
	int32_t level,
	void* data,
	int32_t dataLength
) {
	TRACE_SETTEXTUREDATA3D
	if (device == NULL)
	{
		return;
	}
	device->GetTextureData3D(
		device->driverData,
		texture,
		x,
		y,
		z,
		w,
		h,
		d,
		level,
		data,
		dataLength
	);
}

void FNA3D_GetTextureDataCube(
	FNA3D_Device *device,
	FNA3D_Texture *texture,
	int32_t x,
	int32_t y,
	int32_t w,
	int32_t h,
	FNA3D_CubeMapFace cubeMapFace,
	int32_t level,
	void* data,
	int32_t dataLength
) {
	TRACE_GETTEXTUREDATACUBE
	if (device == NULL)
	{
		return;
	}
	device->GetTextureDataCube(
		device->driverData,
		texture,
		x,
		y,
		w,
		h,
		cubeMapFace,
		level,
		data,
		dataLength
	);
}

/* Renderbuffers */

FNA3D_Renderbuffer* FNA3D_GenColorRenderbuffer(
	FNA3D_Device *device,
	int32_t width,
	int32_t height,
	FNA3D_SurfaceFormat format,
	int32_t multiSampleCount,
	FNA3D_Texture *texture
) {
	/* We're stuck tracing _after_ the call instead of _before_, because
	 * of threading issues. This can cause timing issues!
	 */
	FNA3D_Renderbuffer *result;
	if (device == NULL)
	{
		return NULL;
	}
	result = device->GenColorRenderbuffer(
		device->driverData,
		width,
		height,
		format,
		multiSampleCount,
		texture
	);
	TRACE_GENCOLORRENDERBUFFER
	return result;
}

FNA3D_Renderbuffer* FNA3D_GenDepthStencilRenderbuffer(
	FNA3D_Device *device,
	int32_t width,
	int32_t height,
	FNA3D_DepthFormat format,
	int32_t multiSampleCount
) {
	/* We're stuck tracing _after_ the call instead of _before_, because
	 * of threading issues. This can cause timing issues!
	 */
	FNA3D_Renderbuffer *result;
	if (device == NULL)
	{
		return NULL;
	}
	result = device->GenDepthStencilRenderbuffer(
		device->driverData,
		width,
		height,
		format,
		multiSampleCount
	);
	TRACE_GENDEPTHSTENCILRENDERBUFFER
	return result;
}

void FNA3D_AddDisposeRenderbuffer(
	FNA3D_Device *device,
	FNA3D_Renderbuffer *renderbuffer
) {
	TRACE_ADDDISPOSERENDERBUFFER
	if (device == NULL || renderbuffer == NULL)
	{
		return;
	}
	device->AddDisposeRenderbuffer(
		device->driverData,
		renderbuffer
	);
}

/* Vertex Buffers */

FNA3D_Buffer* FNA3D_GenVertexBuffer(
	FNA3D_Device *device,
	uint8_t dynamic,
	FNA3D_BufferUsage usage,
	int32_t sizeInBytes
) {
	/* We're stuck tracing _after_ the call instead of _before_, because
	 * of threading issues. This can cause timing issues!
	 */
	FNA3D_Buffer *result;
	if (device == NULL)
	{
		return NULL;
	}
	result = device->GenVertexBuffer(
		device->driverData,
		dynamic,
		usage,
		sizeInBytes
	);
	TRACE_GENVERTEXBUFFER
	return result;
}

void FNA3D_AddDisposeVertexBuffer(
	FNA3D_Device *device,
	FNA3D_Buffer *buffer
) {
	TRACE_ADDDISPOSEVERTEXBUFFER
	if (device == NULL || buffer == NULL)
	{
		return;
	}
	device->AddDisposeVertexBuffer(device->driverData, buffer);
}

void FNA3D_SetVertexBufferData(
	FNA3D_Device *device,
	FNA3D_Buffer *buffer,
	int32_t offsetInBytes,
	void* data,
	int32_t elementCount,
	int32_t elementSizeInBytes,
	int32_t vertexStride,
	FNA3D_SetDataOptions options
) {
	TRACE_SETVERTEXBUFFERDATA
	if (device == NULL)
	{
		return;
	}
	device->SetVertexBufferData(
		device->driverData,
		buffer,
		offsetInBytes,
		data,
		elementCount,
		elementSizeInBytes,
		vertexStride,
		options
	);
}

void FNA3D_GetVertexBufferData(
	FNA3D_Device *device,
	FNA3D_Buffer *buffer,
	int32_t offsetInBytes,
	void* data,
	int32_t elementCount,
	int32_t elementSizeInBytes,
	int32_t vertexStride
) {
	TRACE_GETVERTEXBUFFERDATA
	if (device == NULL)
	{
		return;
	}
	device->GetVertexBufferData(
		device->driverData,
		buffer,
		offsetInBytes,
		data,
		elementCount,
		elementSizeInBytes,
		vertexStride
	);
}

/* Index Buffers */

FNA3D_Buffer* FNA3D_GenIndexBuffer(
	FNA3D_Device *device,
	uint8_t dynamic,
	FNA3D_BufferUsage usage,
	int32_t sizeInBytes
) {
	/* We're stuck tracing _after_ the call instead of _before_, because
	 * of threading issues. This can cause timing issues!
	 */
	FNA3D_Buffer *result;
	if (device == NULL)
	{
		return NULL;
	}
	result = device->GenIndexBuffer(
		device->driverData,
		dynamic,
		usage,
		sizeInBytes
	);
	TRACE_GENINDEXBUFFER
	return result;
}

void FNA3D_AddDisposeIndexBuffer(
	FNA3D_Device *device,
	FNA3D_Buffer *buffer
) {
	TRACE_ADDDISPOSEINDEXBUFFER
	if (device == NULL || buffer == NULL)
	{
		return;
	}
	device->AddDisposeIndexBuffer(device->driverData, buffer);
}

void FNA3D_SetIndexBufferData(
	FNA3D_Device *device,
	FNA3D_Buffer *buffer,
	int32_t offsetInBytes,
	void* data,
	int32_t dataLength,
	FNA3D_SetDataOptions options
) {
	TRACE_SETINDEXBUFFERDATA
	if (device == NULL)
	{
		return;
	}
	device->SetIndexBufferData(
		device->driverData,
		buffer,
		offsetInBytes,
		data,
		dataLength,
		options
	);
}

void FNA3D_GetIndexBufferData(
	FNA3D_Device *device,
	FNA3D_Buffer *buffer,
	int32_t offsetInBytes,
	void* data,
	int32_t dataLength
) {
	TRACE_GETINDEXBUFFERDATA
	if (device == NULL)
	{
		return;
	}
	device->GetIndexBufferData(
		device->driverData,
		buffer,
		offsetInBytes,
		data,
		dataLength
	);
}

/* Effects */

void FNA3D_CreateEffect(
	FNA3D_Device *device,
	uint8_t *effectCode,
	uint32_t effectCodeLength,
	FNA3D_Effect **effect,
	MOJOSHADER_effect **effectData
) {
	/* We're stuck tracing _after_ the call instead of _before_, because
	 * of threading issues. This can cause timing issues!
	 */
	if (device == NULL)
	{
		*effect = NULL;
		*effectData = NULL;
		return;
	}
	device->CreateEffect(
		device->driverData,
		effectCode,
		effectCodeLength,
		effect,
		effectData
	);
	TRACE_CREATEEFFECT
}

void FNA3D_CloneEffect(
	FNA3D_Device *device,
	FNA3D_Effect *cloneSource,
	FNA3D_Effect **effect,
	MOJOSHADER_effect **effectData
) {
	/* We're stuck tracing _after_ the call instead of _before_, because
	 * of threading issues. This can cause timing issues!
	 */
	if (device == NULL)
	{
		*effect = NULL;
		*effectData = NULL;
		return;
	}
	device->CloneEffect(
		device->driverData,
		cloneSource,
		effect,
		effectData
	);
	TRACE_CLONEEFFECT
}

void FNA3D_AddDisposeEffect(
	FNA3D_Device *device,
	FNA3D_Effect *effect
) {
	TRACE_ADDDISPOSEEFFECT
	if (device == NULL || effect == NULL)
	{
		return;
	}
	device->AddDisposeEffect(device->driverData, effect);
}

void FNA3D_SetEffectTechnique(
	FNA3D_Device *device,
	FNA3D_Effect *effect,
	MOJOSHADER_effectTechnique *technique
) {
	TRACE_SETEFFECTTECHNIQUE
	if (device == NULL)
	{
		return;
	}
	device->SetEffectTechnique(device->driverData, effect, technique);
}

void FNA3D_ApplyEffect(
	FNA3D_Device *device,
	FNA3D_Effect *effect,
	uint32_t pass,
	MOJOSHADER_effectStateChanges *stateChanges
) {
	TRACE_APPLYEFFECT
	if (device == NULL)
	{
		return;
	}
	device->ApplyEffect(
		device->driverData,
		effect,
		pass,
		stateChanges
	);
}

void FNA3D_BeginPassRestore(
	FNA3D_Device *device,
	FNA3D_Effect *effect,
	MOJOSHADER_effectStateChanges *stateChanges
) {
	TRACE_BEGINPASSRESTORE
	if (device == NULL)
	{
		return;
	}
	device->BeginPassRestore(
		device->driverData,
		effect,
		stateChanges
	);
}

void FNA3D_EndPassRestore(
	FNA3D_Device *device,
	FNA3D_Effect *effect
) {
	TRACE_ENDPASSRESTORE
	if (device == NULL)
	{
		return;
	}
	device->EndPassRestore(device->driverData, effect);
}

/* Queries */

FNA3D_Query* FNA3D_CreateQuery(FNA3D_Device *device)
{
	/* We're stuck tracing _after_ the call instead of _before_, because
	 * of threading issues. This can cause timing issues!
	 */
	FNA3D_Query *result;
	if (device == NULL)
	{
		return NULL;
	}
	result = device->CreateQuery(device->driverData);
	TRACE_CREATEQUERY
	return result;
}

void FNA3D_AddDisposeQuery(FNA3D_Device *device, FNA3D_Query *query)
{
	TRACE_ADDDISPOSEQUERY
	if (device == NULL || query == NULL)
	{
		return;
	}
	device->AddDisposeQuery(device->driverData, query);
}

void FNA3D_QueryBegin(FNA3D_Device *device, FNA3D_Query *query)
{
	TRACE_QUERYBEGIN
	if (device == NULL)
	{
		return;
	}
	device->QueryBegin(device->driverData, query);
}

void FNA3D_QueryEnd(FNA3D_Device *device, FNA3D_Query *query)
{
	TRACE_QUERYEND
	if (device == NULL)
	{
		return;
	}
	device->QueryEnd(device->driverData, query);
}

uint8_t FNA3D_QueryComplete(FNA3D_Device *device, FNA3D_Query *query)
{
	/* Not traced! */
	if (device == NULL)
	{
		return 1;
	}
	return device->QueryComplete(device->driverData, query);
}

int32_t FNA3D_QueryPixelCount(
	FNA3D_Device *device,
	FNA3D_Query *query
) {
	TRACE_QUERYPIXELCOUNT
	if (device == NULL)
	{
		return 0;
	}
	return device->QueryPixelCount(device->driverData, query);
}

/* Feature Queries */

uint8_t FNA3D_SupportsDXT1(FNA3D_Device *device)
{
	/* Not traced! */
	if (device == NULL)
	{
		return 0;
	}
	return device->SupportsDXT1(device->driverData);
}

uint8_t FNA3D_SupportsS3TC(FNA3D_Device *device)
{
	/* Not traced! */
	if (device == NULL)
	{
		return 0;
	}
	return device->SupportsS3TC(device->driverData);
}

uint8_t FNA3D_SupportsBC7(FNA3D_Device *device)
{
	/* Not traced! */
	if (device == NULL)
	{
		return 0;
	}
	return device->SupportsBC7(device->driverData);
}

uint8_t FNA3D_SupportsHardwareInstancing(FNA3D_Device *device)
{
	/* Not traced! */
	if (device == NULL)
	{
		return 0;
	}
	return device->SupportsHardwareInstancing(device->driverData);
}

uint8_t FNA3D_SupportsNoOverwrite(FNA3D_Device *device)
{
	/* Not traced! */
	if (device == NULL)
	{
		return 0;
	}
	return device->SupportsNoOverwrite(device->driverData);
}


uint8_t FNA3D_SupportsSRGBRenderTargets(FNA3D_Device *device)
{
	/* Not traced! */
	if (device == NULL)
	{
		return 0;
	}
	return device->SupportsSRGBRenderTargets(device->driverData);
}

void FNA3D_GetMaxTextureSlots(
	FNA3D_Device *device,
	int32_t *textures,
	int32_t *vertexTextures
) {
	/* Not traced! */
	if (device == NULL)
	{
		return;
	}
	device->GetMaxTextureSlots(
		device->driverData,
		textures,
		vertexTextures
	);
}

int32_t FNA3D_GetMaxMultiSampleCount(
	FNA3D_Device *device,
	FNA3D_SurfaceFormat format,
	int32_t multiSampleCount
) {
	/* Not traced! */
	if (device == NULL)
	{
		return 0;
	}
	return device->GetMaxMultiSampleCount(
		device->driverData,
		format,
		multiSampleCount
	);
}

/* Debugging */

void FNA3D_SetStringMarker(FNA3D_Device *device, const char *text)
{
	TRACE_SETSTRINGMARKER
	if (device == NULL)
	{
		return;
	}
	device->SetStringMarker(device->driverData, text);
}

/* External Interop */

void FNA3D_GetSysRendererEXT(
	FNA3D_Device *device,
	FNA3D_SysRendererEXT *sysrenderer
) {
#ifdef FNA3D_TRACING
	SDL_assert(0 && "Tracing does not support SysRendererEXT!");
#endif
	if (	device == NULL ||
		sysrenderer == NULL ||
		sysrenderer->version != FNA3D_SYSRENDERER_VERSION_EXT	)
	{
		return;
	}
	device->GetSysRenderer(
		device->driverData,
		sysrenderer
	);
}

FNA3D_Texture* FNA3D_CreateSysTextureEXT(
	FNA3D_Device *device,
	FNA3D_SysTextureEXT *systexture
) {
#ifdef FNA3D_TRACING
	SDL_assert(0 && "Tracing does not support SysTextureEXT!");
#endif
	if (	device == NULL ||
		systexture == NULL ||
		systexture->version != FNA3D_SYSRENDERER_VERSION_EXT	)
	{
		return NULL;
	}
	return device->CreateSysTexture(
		device->driverData,
		systexture
	);
}

/* vim: set noexpandtab shiftwidth=8 tabstop=8: */
