<commit_before>/******************************************************************************
 * The MIT License (MIT)
 * 
 * Copyright (c) 2015-2016 <NAME>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#include "vk_core.h"
#include "vk_debug.h"

#include "serialise/string_utils.h"

#include "maths/formatpacking.h"

#include "jpeg-compressor/jpge.h"

static bool operator <(const VkExtensionProperties &a, const VkExtensionProperties &b)
{
	int cmp = strcmp(a.extensionName, b.extensionName);
	if(cmp == 0)
		return a.specVersion < b.specVersion;

	return cmp < 0;
}

const char *VkChunkNames[] =
{
	"WrappedVulkan::Initialisation",
	"vkCreateInstance",
	"vkEnumeratePhysicalDevices",
	"vkCreateDevice",
	"vkGetDeviceQueue",
	
	"vkAllocMemory",
	"vkUnmapMemory",
	"vkFlushMappedMemoryRanges",
	"vkFreeMemory",

	"vkCreateCommandPool",
	"vkResetCommandPool",

	"vkCreateCommandBuffer",
	"vkCreateFramebuffer",
	"vkCreateRenderPass",
	"vkCreateDescriptorPool",
	"vkCreateDescriptorSetLayout",
	"vkCreateBuffer",
	"vkCreateBufferView",
	"vkCreateImage",
	"vkCreateImageView",
	"vkCreateDepthTargetView",
	"vkCreateSampler",
	"vkCreateShaderModule",
	"vkCreatePipelineLayout",
	"vkCreatePipelineCache",
	"vkCreateGraphicsPipelines",
	"vkCreateComputePipelines",
	"vkGetSwapchainImagesKHR",

	"vkCreateSemaphore",
	"vkCreateFence",
	"vkGetFenceStatus",
	"vkResetFences",
	"vkWaitForFences",
	
	"vkCreateEvent",
	"vkGetEventStatus",
	"vkSetEvent",
	"vkResetEvent",

	"vkCreateQueryPool",

	"vkAllocDescriptorSets",
	"vkUpdateDescriptorSets",

	"vkBeginCommandBuffer",
	"vkEndCommandBuffer",

	"vkQueueWaitIdle",
	"vkDeviceWaitIdle",

	"vkQueueSubmit",
	"vkBindBufferMemory",
	"vkBindImageMemory",
	
	"vkQueueBindSparse",

	"vkCmdBeginRenderPass",
	"vkCmdNextSubpass",
	"vkCmdExecuteCommands",
	"vkCmdEndRenderPass",

	"vkCmdBindPipeline",

	"vkCmdSetViewport",
	"vkCmdSetScissor",
	"vkCmdSetLineWidth",
	"vkCmdSetDepthBias",
	"vkCmdSetBlendConstants",
	"vkCmdSetDepthBounds",
	"vkCmdSetStencilCompareMask",
	"vkCmdSetStencilWriteMask",
	"vkCmdSetStencilReference",

	"vkCmdBindDescriptorSet",
	"vkCmdBindVertexBuffers",
	"vkCmdBindIndexBuffer",
	"vkCmdCopyBufferToImage",
	"vkCmdCopyImageToBuffer",
	"vkCmdCopyBuffer",
	"vkCmdCopyImage",
	"vkCmdBlitImage",
	"vkCmdResolveImage",
	"vkCmdUpdateBuffer",
	"vkCmdFillBuffer",
	"vkCmdPushConstants",

	"vkCmdClearColorImage",
	"vkCmdClearDepthStencilImage",
	"vkCmdClearAttachments",
	"vkCmdPipelineBarrier",

	"vkCmdWriteTimestamp",
	"vkCmdCopyQueryPoolResults",
	"vkCmdBeginQuery",
	"vkCmdEndQuery",
	"vkCmdResetQueryPool",

	"vkCmdSetEvent",
	"vkCmdResetEvent",
	"vkCmdWaitEvents",

	"vkCmdDraw",
	"vkCmdDrawIndirect",
	"vkCmdDrawIndexed",
	"vkCmdDrawIndexedIndirect",
	"vkCmdDispatch",
	"vkCmdDispatchIndirect",
	
	"vkCmdDbgMarkerBegin",
	"vkCmdDbgMarker", // no equivalent function at the moment
	"vkCmdDbgMarkerEnd",

	"vkDbgSetObjectName",

	"vkCreateSwapchainKHR",

	"Capture",
	"BeginCapture",
	"EndCapture",
};

VkInitParams::VkInitParams()
{
	SerialiseVersion = VK_SERIALISE_VERSION;
}

ReplayCreateStatus VkInitParams::Serialise()
{
	Serialiser *localSerialiser = GetSerialiser();

	SERIALISE_ELEMENT(uint32_t, ver, VK_SERIALISE_VERSION); SerialiseVersion = ver;

	if(ver != VK_SERIALISE_VERSION)
	{
		RDCERR("Incompatible Vulkan serialise version, expected %d got %d", VK_SERIALISE_VERSION, ver);
		return eReplayCreate_APIIncompatibleVersion;
	}

	localSerialiser->Serialise("AppName", AppName);
	localSerialiser->Serialise("EngineName", EngineName);
	localSerialiser->Serialise("AppVersion", AppVersion);
	localSerialiser->Serialise("EngineVersion", EngineVersion);
	localSerialiser->Serialise("APIVersion", APIVersion);

	localSerialiser->Serialise("Layers", Layers);
	localSerialiser->Serialise("Extensions", Extensions);

	localSerialiser->Serialise("InstanceID", InstanceID);

	return eReplayCreate_Success;
}

void VkInitParams::Set(const VkInstanceCreateInfo* pCreateInfo, ResourceId inst)
{
	RDCASSERT(pCreateInfo);

	if(pCreateInfo->pApplicationInfo)
	{
		// we don't support any extensions on appinfo structure
		RDCASSERT(pCreateInfo->pApplicationInfo->pNext == NULL);

		AppName = pCreateInfo->pApplicationInfo->pApplicationName ? pCreateInfo->pApplicationInfo->pApplicationName : "";
		EngineName = pCreateInfo->pApplicationInfo->pEngineName ? pCreateInfo->pApplicationInfo->pEngineName : "";

		AppVersion = pCreateInfo->pApplicationInfo->applicationVersion;
		EngineVersion = pCreateInfo->pApplicationInfo->engineVersion;
		APIVersion = pCreateInfo->pApplicationInfo->apiVersion;
	}
	else
	{
		AppName = "";
		EngineName = "";

		AppVersion = 0;
		EngineVersion = 0;
		APIVersion = 0;
	}

	Layers.resize(pCreateInfo->enabledLayerCount);
	Extensions.resize(pCreateInfo->enabledExtensionCount);

	for(uint32_t i=0; i < pCreateInfo->enabledLayerCount; i++)
		Layers[i] = pCreateInfo->ppEnabledLayerNames[i];

	for(uint32_t i=0; i < pCreateInfo->enabledExtensionCount; i++)
		Extensions[i] = pCreateInfo->ppEnabledExtensionNames[i];

	InstanceID = inst;
}

WrappedVulkan::WrappedVulkan(const char *logFilename)
	: m_RenderState(&m_CreationInfo)
{
#if defined(RELEASE)
	const bool debugSerialiser = false;
#else
	const bool debugSerialiser = true;
#endif

	if(RenderDoc::Inst().IsReplayApp())
	{
		m_State = READING;
		if(logFilename)
		{
			m_pSerialiser = new Serialiser(logFilename, Serialiser::READING, debugSerialiser);
		}
		else
		{
			byte dummy[4];
			m_pSerialiser = new Serialiser(4, dummy, false);
		}
	}
	else
	{
		m_State = WRITING_IDLE;
		m_pSerialiser = new Serialiser(NULL, Serialiser::WRITING, debugSerialiser);
	}

	InitSPIRVCompiler();
	RenderDoc::Inst().RegisterShutdownFunction(&ShutdownSPIRVCompiler);

	m_Replay.SetDriver(this);

	m_FrameCounter = 0;

	m_AppControlledCapture = false;

	m_FrameTimer.Restart();

	threadSerialiserTLSSlot = Threading::AllocateTLSSlot();
	tempMemoryTLSSlot = Threading::AllocateTLSSlot();

	m_TotalTime = m_AvgFrametime = m_MinFrametime = m_MaxFrametime = 0.0;
	
	m_RootEventID = 1;
	m_RootDrawcallID = 1;
	m_FirstEventID = 0;
	m_LastEventID = ~0U;

	m_DrawcallCallback = NULL;

	m_LastCmdBufferID = ResourceId();

	m_PartialReplayData.renderPassActive = false;
	m_PartialReplayData.resultPartialCmdBuffer = VK_NULL_HANDLE;
	m_PartialReplayData.outsideCmdBuffer = VK_NULL_HANDLE;
	m_PartialReplayData.partialParent = ResourceId();
	m_PartialReplayData.baseEvent = 0;

	m_DrawcallStack.push_back(&m_ParentDrawcall);

	m_ResourceManager = new VulkanResourceManager(m_State, m_pSerialiser, this);

	m_pSerialiser->SetUserData(m_ResourceManager);
	m_RenderState.m_ResourceManager = GetResourceManager();

	m_HeaderChunk = NULL;

	if(!RenderDoc::Inst().IsReplayApp())
	{
		m_FrameCaptureRecord = GetResourceManager()->AddResourceRecord(ResourceIDGen::GetNewUniqueID());
		m_FrameCaptureRecord->DataInSerialiser = false;
		m_FrameCaptureRecord->Length = 0;
		m_FrameCaptureRecord->SpecialResource = true;
	}
	else
	{
		m_FrameCaptureRecord = NULL;

		ResourceIDGen::SetReplayResourceIDs();
	}
		
#if !defined(RELEASE)
	RDCDEBUG("Debug Text enabled - for development! remove before release!");
	m_pSerialiser->SetDebugText(true);
#endif
	
	m_pSerialiser->SetChunkNameLookup(&GetChunkName);

	//////////////////////////////////////////////////////////////////////////
	// Compile time asserts

	RDCCOMPILE_ASSERT(ARRAY_COUNT(VkChunkNames) == NUM_VULKAN_CHUNKS-FIRST_CHUNK_ID, "Not right number of chunk names");
}

WrappedVulkan::~WrappedVulkan()
{
	// records must be deleted before resource manager shutdown
	if(m_FrameCaptureRecord)
	{
		RDCASSERT(m_FrameCaptureRecord->GetRefCount() == 1);
		m_FrameCaptureRecord->Delete(GetResourceManager());
		m_FrameCaptureRecord = NULL;
	}

	// in case the application leaked some objects, avoid crashing trying
	// to release them ourselves by clearing the resource manager.
	// In a well-behaved application, this should be a no-op.
	m_ResourceManager->ClearWithoutReleasing();
	SAFE_DELETE(m_ResourceManager);
		
	SAFE_DELETE(m_pSerialiser);

	for(size_t i=0; i < m_MemIdxMaps.size(); i++)
		delete[] m_MemIdxMaps[i];

	for(size_t i=0; i < m_ThreadSerialisers.size(); i++)
		delete m_ThreadSerialisers[i];

	for(size_t i=0; i < m_ThreadTempMem.size(); i++)
	{
		delete[] m_ThreadTempMem[i]->memory;
		delete m_ThreadTempMem[i];
	}
}

VkCommandBuffer WrappedVulkan::GetNextCmd()
{
	VkCommandBuffer ret;

	if(!m_InternalCmds.freecmds.empty())
	{
		ret = m_InternalCmds.freecmds.back();
		m_InternalCmds.freecmds.pop_back();

		ObjDisp(ret)->ResetCommandBuffer(Unwrap(ret), 0);
	}
	else
	{	
		VkCommandBufferAllocateInfo cmdInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, NULL, Unwrap(m_InternalCmds.cmdpool), VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 };
		VkResult vkr = ObjDisp(m_Device)->AllocateCommandBuffers(Unwrap(m_Device), &cmdInfo, &ret);

		SetDispatchTableOverMagicNumber(m_Device, ret);

		RDCASSERT(vkr == VK_SUCCESS);

		GetResourceManager()->WrapResource(Unwrap(m_Device), ret);
	}

	m_InternalCmds.pendingcmds.push_back(ret);

	return ret;
}

void WrappedVulkan::SubmitCmds()
{
	// nothing to do
	if(m_InternalCmds.pendingcmds.empty())
		return;

	vector<VkCommandBuffer> cmds = m_InternalCmds.pendingcmds;
	for(size_t i=0; i < cmds.size(); i++) cmds[i] = Unwrap(cmds[i]);

	VkSubmitInfo submitInfo = {
		VK_STRUCTURE_TYPE_SUBMIT_INFO, NULL,
		0, NULL, NULL, // wait semaphores
		(uint32_t)cmds.size(), &cmds[0], // command buffers
		0, NULL, // signal semaphores
	};

	// we might have work to do (e.g. debug manager creation command buffer) but
	// no queue, if the device is destroyed immediately. In this case we can just
	// skip the submit
	if(m_Queue != VK_NULL_HANDLE)
	{
		VkResult vkr = ObjDisp(m_Queue)->QueueSubmit(Unwrap(m_Queue), 1, &submitInfo, VK_NULL_HANDLE);
		RDCASSERT(vkr == VK_SUCCESS);
	}

	m_InternalCmds.submittedcmds.insert(m_InternalCmds.submittedcmds.end(), m_InternalCmds.pendingcmds.begin(), m_InternalCmds.pendingcmds.end());
	m_InternalCmds.pendingcmds.clear();
}

VkSemaphore WrappedVulkan::GetNextSemaphore()
{
	VkSemaphore ret;

	if(!m_InternalCmds.freesems.empty())
	{
		ret = m_InternalCmds.freesems.back();
		m_InternalCmds.freesems.pop_back();

		// assume semaphore is back to unsignaled state after being waited on
	}
	else
	{	
		VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
		VkResult vkr = ObjDisp(m_Device)->CreateSemaphore(Unwrap(m_Device), &semInfo, NULL, &ret);
		RDCASSERT(vkr == VK_SUCCESS);

		GetResourceManager()->WrapResource(Unwrap(m_Device), ret);
	}

	m_InternalCmds.pendingsems.push_back(ret);

	return ret;
}

void WrappedVulkan::SubmitSemaphores()
{
	// nothing to do
	if(m_InternalCmds.pendingsems.empty())
		return;

	// no actual submission, just mark them as 'done with' so they will be
	// recycled on next flush
	m_InternalCmds.submittedsems.insert(m_InternalCmds.submittedsems.end(), m_InternalCmds.pendingsems.begin(), m_InternalCmds.pendingsems.end());
	m_InternalCmds.pendingsems.clear();
}

void WrappedVulkan::FlushQ()
{
	// VKTODOLOW could do away with the need for this function by keeping
	// commands until N presents later, or something, or checking on fences.
	// If we do so, then check each use for FlushQ to see if it needs a
	// CPU-GPU sync or whether it is just looking to recycle command buffers
	// (Particularly the one in vkQueuePresentKHR drawing the overlay)
	

	// see comment in SubmitQ()
	if(m_Queue != VK_NULL_HANDLE)
	{
		ObjDisp(m_Queue)->QueueWaitIdle(Unwrap(m_Queue));
	}

	if(!m_InternalCmds.submittedcmds.empty())
	{
		m_InternalCmds.freecmds.insert(m_InternalCmds.freecmds.end(), m_InternalCmds.submittedcmds.begin(), m_InternalCmds.submittedcmds.end());
		m_InternalCmds.submittedcmds.clear();
	}
}

uint32_t WrappedVulkan::HandlePreDraw(VkCommandBuffer commandBuffer)
{
	if(!m_DrawcallCallback) return 0;

	// look up the EID this drawcall came from
	DrawcallUse use(m_CurChunkOffset, 0);
	auto it = std::lower_bound(m_DrawcallUses.begin(), m_DrawcallUses.end(), use);
	RDCASSERT(it != m_DrawcallUses.end());

	uint32_t eventID = it->eventID;

	RDCASSERT(eventID != 0);

	// handle all aliases of this drawcall
	++it;
	while(it != m_DrawcallUses.end() && it->fileOffset == m_CurChunkOffset)
	{
		m_DrawcallCallback->AliasEvent(eventID, it->eventID);
		++it;
	}

	m_DrawcallCallback->PreDraw(eventID, commandBuffer);

	return eventID;
}

const char * WrappedVulkan::GetChunkName(uint32_t idx)
{
	if(idx < FIRST_CHUNK_ID || idx >= NUM_VULKAN_CHUNKS)
		return "<unknown>";
	return VkChunkNames[idx-FIRST_CHUNK_ID];
}

byte *WrappedVulkan::GetTempMemory(size_t s)
{
	TempMem *mem = (TempMem *)Threading::GetTLSValue(tempMemoryTLSSlot);
	if(mem && mem->size >= s) return mem->memory;

	// alloc or grow alloc
	TempMem *newmem = mem;

	if(!newmem) newmem = new TempMem();

	// free old memory, don't need to keep contents
	if(newmem->memory) delete[] newmem->memory;

	// alloc new memory
	newmem->size = s;
	newmem->memory = new byte[s];

	Threading::SetTLSValue(tempMemoryTLSSlot, (void *)newmem);

	// if this is entirely new, save it for deletion on shutdown
	if(!mem)
	{
		SCOPED_LOCK(m_ThreadTempMemLock);
		m_ThreadTempMem.push_back(newmem);
	}

	return newmem->memory;
}

Serialiser *WrappedVulkan::GetThreadSerialiser()
{
	Serialiser *ser = (Serialiser *)Threading::GetTLSValue(threadSerialiserTLSSlot);
	if(ser) return ser;

	// slow path, but rare

#if defined(RELEASE)
	const bool debugSerialiser = false;
#else
	const bool debugSerialiser = true;
#endif

	ser = new Serialiser(NULL, Serialiser::WRITING, debugSerialiser);
	ser->SetUserData(m_ResourceManager);
	
#if !defined(RELEASE)
	RDCDEBUG("Debug Text enabled - for development! remove before release!");
	ser->SetDebugText(true);
#endif
	
	ser->SetChunkNameLookup(&GetChunkName);

	Threading::SetTLSValue(threadSerialiserTLSSlot, (void *)ser);

	{
		SCOPED_LOCK(m_ThreadSerialisersLock);
		m_ThreadSerialisers.push_back(ser);<commit_msg>Remove printing of debug text<commit_after>/******************************************************************************
 * The MIT License (MIT)
 * 
 * Copyright (c) 2015-2016 <NAME>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#include "vk_core.h"
#include "vk_debug.h"

#include "serialise/string_utils.h"

#include "maths/formatpacking.h"

#include "jpeg-compressor/jpge.h"

static bool operator <(const VkExtensionProperties &a, const VkExtensionProperties &b)
{
	int cmp = strcmp(a.extensionName, b.extensionName);
	if(cmp == 0)
		return a.specVersion < b.specVersion;

	return cmp < 0;
}

const char *VkChunkNames[] =
{
	"WrappedVulkan::Initialisation",
	"vkCreateInstance",
	"vkEnumeratePhysicalDevices",
	"vkCreateDevice",
	"vkGetDeviceQueue",
	
	"vkAllocMemory",
	"vkUnmapMemory",
	"vkFlushMappedMemoryRanges",
	"vkFreeMemory",

	"vkCreateCommandPool",
	"vkResetCommandPool",

	"vkCreateCommandBuffer",
	"vkCreateFramebuffer",
	"vkCreateRenderPass",
	"vkCreateDescriptorPool",
	"vkCreateDescriptorSetLayout",
	"vkCreateBuffer",
	"vkCreateBufferView",
	"vkCreateImage",
	"vkCreateImageView",
	"vkCreateDepthTargetView",
	"vkCreateSampler",
	"vkCreateShaderModule",
	"vkCreatePipelineLayout",
	"vkCreatePipelineCache",
	"vkCreateGraphicsPipelines",
	"vkCreateComputePipelines",
	"vkGetSwapchainImagesKHR",

	"vkCreateSemaphore",
	"vkCreateFence",
	"vkGetFenceStatus",
	"vkResetFences",
	"vkWaitForFences",
	
	"vkCreateEvent",
	"vkGetEventStatus",
	"vkSetEvent",
	"vkResetEvent",

	"vkCreateQueryPool",

	"vkAllocDescriptorSets",
	"vkUpdateDescriptorSets",

	"vkBeginCommandBuffer",
	"vkEndCommandBuffer",

	"vkQueueWaitIdle",
	"vkDeviceWaitIdle",

	"vkQueueSubmit",
	"vkBindBufferMemory",
	"vkBindImageMemory",
	
	"vkQueueBindSparse",

	"vkCmdBeginRenderPass",
	"vkCmdNextSubpass",
	"vkCmdExecuteCommands",
	"vkCmdEndRenderPass",

	"vkCmdBindPipeline",

	"vkCmdSetViewport",
	"vkCmdSetScissor",
	"vkCmdSetLineWidth",
	"vkCmdSetDepthBias",
	"vkCmdSetBlendConstants",
	"vkCmdSetDepthBounds",
	"vkCmdSetStencilCompareMask",
	"vkCmdSetStencilWriteMask",
	"vkCmdSetStencilReference",

	"vkCmdBindDescriptorSet",
	"vkCmdBindVertexBuffers",
	"vkCmdBindIndexBuffer",
	"vkCmdCopyBufferToImage",
	"vkCmdCopyImageToBuffer",
	"vkCmdCopyBuffer",
	"vkCmdCopyImage",
	"vkCmdBlitImage",
	"vkCmdResolveImage",
	"vkCmdUpdateBuffer",
	"vkCmdFillBuffer",
	"vkCmdPushConstants",

	"vkCmdClearColorImage",
	"vkCmdClearDepthStencilImage",
	"vkCmdClearAttachments",
	"vkCmdPipelineBarrier",

	"vkCmdWriteTimestamp",
	"vkCmdCopyQueryPoolResults",
	"vkCmdBeginQuery",
	"vkCmdEndQuery",
	"vkCmdResetQueryPool",

	"vkCmdSetEvent",
	"vkCmdResetEvent",
	"vkCmdWaitEvents",

	"vkCmdDraw",
	"vkCmdDrawIndirect",
	"vkCmdDrawIndexed",
	"vkCmdDrawIndexedIndirect",
	"vkCmdDispatch",
	"vkCmdDispatchIndirect",
	
	"vkCmdDbgMarkerBegin",
	"vkCmdDbgMarker", // no equivalent function at the moment
	"vkCmdDbgMarkerEnd",

	"vkDbgSetObjectName",

	"vkCreateSwapchainKHR",

	"Capture",
	"BeginCapture",
	"EndCapture",
};

VkInitParams::VkInitParams()
{
	SerialiseVersion = VK_SERIALISE_VERSION;
}

ReplayCreateStatus VkInitParams::Serialise()
{
	Serialiser *localSerialiser = GetSerialiser();

	SERIALISE_ELEMENT(uint32_t, ver, VK_SERIALISE_VERSION); SerialiseVersion = ver;

	if(ver != VK_SERIALISE_VERSION)
	{
		RDCERR("Incompatible Vulkan serialise version, expected %d got %d", VK_SERIALISE_VERSION, ver);
		return eReplayCreate_APIIncompatibleVersion;
	}

	localSerialiser->Serialise("AppName", AppName);
	localSerialiser->Serialise("EngineName", EngineName);
	localSerialiser->Serialise("AppVersion", AppVersion);
	localSerialiser->Serialise("EngineVersion", EngineVersion);
	localSerialiser->Serialise("APIVersion", APIVersion);

	localSerialiser->Serialise("Layers", Layers);
	localSerialiser->Serialise("Extensions", Extensions);

	localSerialiser->Serialise("InstanceID", InstanceID);

	return eReplayCreate_Success;
}

void VkInitParams::Set(const VkInstanceCreateInfo* pCreateInfo, ResourceId inst)
{
	RDCASSERT(pCreateInfo);

	if(pCreateInfo->pApplicationInfo)
	{
		// we don't support any extensions on appinfo structure
		RDCASSERT(pCreateInfo->pApplicationInfo->pNext == NULL);

		AppName = pCreateInfo->pApplicationInfo->pApplicationName ? pCreateInfo->pApplicationInfo->pApplicationName : "";
		EngineName = pCreateInfo->pApplicationInfo->pEngineName ? pCreateInfo->pApplicationInfo->pEngineName : "";

		AppVersion = pCreateInfo->pApplicationInfo->applicationVersion;
		EngineVersion = pCreateInfo->pApplicationInfo->engineVersion;
		APIVersion = pCreateInfo->pApplicationInfo->apiVersion;
	}
	else
	{
		AppName = "";
		EngineName = "";

		AppVersion = 0;
		EngineVersion = 0;
		APIVersion = 0;
	}

	Layers.resize(pCreateInfo->enabledLayerCount);
	Extensions.resize(pCreateInfo->enabledExtensionCount);

	for(uint32_t i=0; i < pCreateInfo->enabledLayerCount; i++)
		Layers[i] = pCreateInfo->ppEnabledLayerNames[i];

	for(uint32_t i=0; i < pCreateInfo->enabledExtensionCount; i++)
		Extensions[i] = pCreateInfo->ppEnabledExtensionNames[i];

	InstanceID = inst;
}

WrappedVulkan::WrappedVulkan(const char *logFilename)
	: m_RenderState(&m_CreationInfo)
{
#if defined(RELEASE)
	const bool debugSerialiser = false;
#else
	const bool debugSerialiser = true;
#endif

	if(RenderDoc::Inst().IsReplayApp())
	{
		m_State = READING;
		if(logFilename)
		{
			m_pSerialiser = new Serialiser(logFilename, Serialiser::READING, debugSerialiser);
		}
		else
		{
			byte dummy[4];
			m_pSerialiser = new Serialiser(4, dummy, false);
		}
	}
	else
	{
		m_State = WRITING_IDLE;
		m_pSerialiser = new Serialiser(NULL, Serialiser::WRITING, debugSerialiser);
	}

	InitSPIRVCompiler();
	RenderDoc::Inst().RegisterShutdownFunction(&ShutdownSPIRVCompiler);

	m_Replay.SetDriver(this);

	m_FrameCounter = 0;

	m_AppControlledCapture = false;

	m_FrameTimer.Restart();

	threadSerialiserTLSSlot = Threading::AllocateTLSSlot();
	tempMemoryTLSSlot = Threading::AllocateTLSSlot();

	m_TotalTime = m_AvgFrametime = m_MinFrametime = m_MaxFrametime = 0.0;
	
	m_RootEventID = 1;
	m_RootDrawcallID = 1;
	m_FirstEventID = 0;
	m_LastEventID = ~0U;

	m_DrawcallCallback = NULL;

	m_LastCmdBufferID = ResourceId();

	m_PartialReplayData.renderPassActive = false;
	m_PartialReplayData.resultPartialCmdBuffer = VK_NULL_HANDLE;
	m_PartialReplayData.outsideCmdBuffer = VK_NULL_HANDLE;
	m_PartialReplayData.partialParent = ResourceId();
	m_PartialReplayData.baseEvent = 0;

	m_DrawcallStack.push_back(&m_ParentDrawcall);

	m_ResourceManager = new VulkanResourceManager(m_State, m_pSerialiser, this);

	m_pSerialiser->SetUserData(m_ResourceManager);
	m_RenderState.m_ResourceManager = GetResourceManager();

	m_HeaderChunk = NULL;

	if(!RenderDoc::Inst().IsReplayApp())
	{
		m_FrameCaptureRecord = GetResourceManager()->AddResourceRecord(ResourceIDGen::GetNewUniqueID());
		m_FrameCaptureRecord->DataInSerialiser = false;
		m_FrameCaptureRecord->Length = 0;
		m_FrameCaptureRecord->SpecialResource = true;
	}
	else
	{
		m_FrameCaptureRecord = NULL;

		ResourceIDGen::SetReplayResourceIDs();
	}
	
	m_pSerialiser->SetChunkNameLookup(&GetChunkName);

	//////////////////////////////////////////////////////////////////////////
	// Compile time asserts

	RDCCOMPILE_ASSERT(ARRAY_COUNT(VkChunkNames) == NUM_VULKAN_CHUNKS-FIRST_CHUNK_ID, "Not right number of chunk names");
}

WrappedVulkan::~WrappedVulkan()
{
	// records must be deleted before resource manager shutdown
	if(m_FrameCaptureRecord)
	{
		RDCASSERT(m_FrameCaptureRecord->GetRefCount() == 1);
		m_FrameCaptureRecord->Delete(GetResourceManager());
		m_FrameCaptureRecord = NULL;
	}

	// in case the application leaked some objects, avoid crashing trying
	// to release them ourselves by clearing the resource manager.
	// In a well-behaved application, this should be a no-op.
	m_ResourceManager->ClearWithoutReleasing();
	SAFE_DELETE(m_ResourceManager);
		
	SAFE_DELETE(m_pSerialiser);

	for(size_t i=0; i < m_MemIdxMaps.size(); i++)
		delete[] m_MemIdxMaps[i];

	for(size_t i=0; i < m_ThreadSerialisers.size(); i++)
		delete m_ThreadSerialisers[i];

	for(size_t i=0; i < m_ThreadTempMem.size(); i++)
	{
		delete[] m_ThreadTempMem[i]->memory;
		delete m_ThreadTempMem[i];
	}
}

VkCommandBuffer WrappedVulkan::GetNextCmd()
{
	VkCommandBuffer ret;

	if(!m_InternalCmds.freecmds.empty())
	{
		ret = m_InternalCmds.freecmds.back();
		m_InternalCmds.freecmds.pop_back();

		ObjDisp(ret)->ResetCommandBuffer(Unwrap(ret), 0);
	}
	else
	{	
		VkCommandBufferAllocateInfo cmdInfo = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, NULL, Unwrap(m_InternalCmds.cmdpool), VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1 };
		VkResult vkr = ObjDisp(m_Device)->AllocateCommandBuffers(Unwrap(m_Device), &cmdInfo, &ret);

		SetDispatchTableOverMagicNumber(m_Device, ret);

		RDCASSERT(vkr == VK_SUCCESS);

		GetResourceManager()->WrapResource(Unwrap(m_Device), ret);
	}

	m_InternalCmds.pendingcmds.push_back(ret);

	return ret;
}

void WrappedVulkan::SubmitCmds()
{
	// nothing to do
	if(m_InternalCmds.pendingcmds.empty())
		return;

	vector<VkCommandBuffer> cmds = m_InternalCmds.pendingcmds;
	for(size_t i=0; i < cmds.size(); i++) cmds[i] = Unwrap(cmds[i]);

	VkSubmitInfo submitInfo = {
		VK_STRUCTURE_TYPE_SUBMIT_INFO, NULL,
		0, NULL, NULL, // wait semaphores
		(uint32_t)cmds.size(), &cmds[0], // command buffers
		0, NULL, // signal semaphores
	};

	// we might have work to do (e.g. debug manager creation command buffer) but
	// no queue, if the device is destroyed immediately. In this case we can just
	// skip the submit
	if(m_Queue != VK_NULL_HANDLE)
	{
		VkResult vkr = ObjDisp(m_Queue)->QueueSubmit(Unwrap(m_Queue), 1, &submitInfo, VK_NULL_HANDLE);
		RDCASSERT(vkr == VK_SUCCESS);
	}

	m_InternalCmds.submittedcmds.insert(m_InternalCmds.submittedcmds.end(), m_InternalCmds.pendingcmds.begin(), m_InternalCmds.pendingcmds.end());
	m_InternalCmds.pendingcmds.clear();
}

VkSemaphore WrappedVulkan::GetNextSemaphore()
{
	VkSemaphore ret;

	if(!m_InternalCmds.freesems.empty())
	{
		ret = m_InternalCmds.freesems.back();
		m_InternalCmds.freesems.pop_back();

		// assume semaphore is back to unsignaled state after being waited on
	}
	else
	{	
		VkSemaphoreCreateInfo semInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
		VkResult vkr = ObjDisp(m_Device)->CreateSemaphore(Unwrap(m_Device), &semInfo, NULL, &ret);
		RDCASSERT(vkr == VK_SUCCESS);

		GetResourceManager()->WrapResource(Unwrap(m_Device), ret);
	}

	m_InternalCmds.pendingsems.push_back(ret);

	return ret;
}

void WrappedVulkan::SubmitSemaphores()
{
	// nothing to do
	if(m_InternalCmds.pendingsems.empty())
		return;

	// no actual submission, just mark them as 'done with' so they will be
	// recycled on next flush
	m_InternalCmds.submittedsems.insert(m_InternalCmds.submittedsems.end(), m_InternalCmds.pendingsems.begin(), m_InternalCmds.pendingsems.end());
	m_InternalCmds.pendingsems.clear();
}

void WrappedVulkan::FlushQ()
{
	// VKTODOLOW could do away with the need for this function by keeping
	// commands until N presents later, or something, or checking on fences.
	// If we do so, then check each use for FlushQ to see if it needs a
	// CPU-GPU sync or whether it is just looking to recycle command buffers
	// (Particularly the one in vkQueuePresentKHR drawing the overlay)
	

	// see comment in SubmitQ()
	if(m_Queue != VK_NULL_HANDLE)
	{
		ObjDisp(m_Queue)->QueueWaitIdle(Unwrap(m_Queue));
	}

	if(!m_InternalCmds.submittedcmds.empty())
	{
		m_InternalCmds.freecmds.insert(m_InternalCmds.freecmds.end(), m_InternalCmds.submittedcmds.begin(), m_InternalCmds.submittedcmds.end());
		m_InternalCmds.submittedcmds.clear();
	}
}

uint32_t WrappedVulkan::HandlePreDraw(VkCommandBuffer commandBuffer)
{
	if(!m_DrawcallCallback) return 0;

	// look up the EID this drawcall came from
	DrawcallUse use(m_CurChunkOffset, 0);
	auto it = std::lower_bound(m_DrawcallUses.begin(), m_DrawcallUses.end(), use);
	RDCASSERT(it != m_DrawcallUses.end());

	uint32_t eventID = it->eventID;

	RDCASSERT(eventID != 0);

	// handle all aliases of this drawcall
	++it;
	while(it != m_DrawcallUses.end() && it->fileOffset == m_CurChunkOffset)
	{
		m_DrawcallCallback->AliasEvent(eventID, it->eventID);
		++it;
	}

	m_DrawcallCallback->PreDraw(eventID, commandBuffer);

	return eventID;
}

const char * WrappedVulkan::GetChunkName(uint32_t idx)
{
	if(idx < FIRST_CHUNK_ID || idx >= NUM_VULKAN_CHUNKS)
		return "<unknown>";
	return VkChunkNames[idx-FIRST_CHUNK_ID];
}

byte *WrappedVulkan::GetTempMemory(size_t s)
{
	TempMem *mem = (TempMem *)Threading::GetTLSValue(tempMemoryTLSSlot);
	if(mem && mem->size >= s) return mem->memory;

	// alloc or grow alloc
	TempMem *newmem = mem;

	if(!newmem) newmem = new TempMem();

	// free old memory, don't need to keep contents
	if(newmem->memory) delete[] newmem->memory;

	// alloc new memory
	newmem->size = s;
	newmem->memory = new byte[s];

	Threading::SetTLSValue(tempMemoryTLSSlot, (void *)newmem);

	// if this is entirely new, save it for deletion on shutdown
	if(!mem)
	{
		SCOPED_LOCK(m_ThreadTempMemLock);
		m_ThreadTempMem.push_back(newmem);
	}

	return newmem->memory;
}

Serialiser *WrappedVulkan::GetThreadSerialiser()
{
	Serialiser *ser = (Serialiser *)Threading::GetTLSValue(threadSerialiserTLSSlot);
	if(ser) return ser;

	// slow path, but rare

#if defined(RELEASE)
	const bool debugSerialiser = false;
#else
	const bool debugSerialiser = true;
#endif

	ser = new Serialiser(NULL, Serialiser::WRITING, debugSerialiser);
	ser->SetUserData(m_ResourceManager);
	
	ser->SetChunkNameLookup(&GetChunkName);

	Threading::SetTLSValue(threadSerialiserTLSSlot, (void *)ser);

	{
		SCOPED_LOCK(m_ThreadSerialisersLock);
		m_ThreadSerialisers.push_back(ser);
{'fraction_non_alphanumeric': 0.12988146805009906, 'fraction_numerical': 0.003626473254759746, 'mean_word_length': 5.859051128512206, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 139, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4805447', 'n_tokens_mistral': 10922, 'n_tokens_neox': 10240, 'n_words': 2031}