Vulkan_渲染可視化調試(VK_EXT_debug_marker與RenderDoc離線圖形調試)

在使用vulkan實現各項渲染工作的時候,我們經常會因爲各種不經意的坑導致渲染失敗,所以調試工作就尤爲重要,所以今天我們就來說一下如何可視化vulkan的各項進程來調試程序。

一、簡介

首先我們來說一下vulkan驗證層:LunarG SDK附帶的Vulkan驗證層是在運行時調試應用程序所必需的,它們對於使應用程序根據規範進行驗證並確保跨不同實現的可移植性至關重要。

但是它們無法捕獲的是邏輯錯誤,因此即使啓用了所有驗證層並消除了所有錯誤,您仍然可能看不到期望的位置,並且需要逐步調試渲染的其他層。

因此,我們可以使用“ VK_EXT_debug_marker ”拓展(類似於OpenGL的GL_KHR_Debug擴展,可以增加命名和標記對象以及插入調試區域和標記的功能,以供脫機調試器顯示)和RenderDoc離線圖形調試應用程序(RenderDoc能夠捕獲應用程序的框架,包括所有API調用,對象和完整的管道狀態,並在可視化的UI中顯示所有這些信息)。
在這裏插入圖片描述
因此我們下邊就來演示其在Vulkan應用程序和圖形調試器中的用法。

二、調試標記功能封裝

話不多說,直接上代碼,通過註釋你可以看到各函數作用:

//此擴展只有在從脫機調試應用程序運行時纔會出現
namespace DebugMarker
{
	bool active = false;
	bool extensionPresent = false;

	PFN_vkDebugMarkerSetObjectTagEXT vkDebugMarkerSetObjectTag = VK_NULL_HANDLE;
	PFN_vkDebugMarkerSetObjectNameEXT vkDebugMarkerSetObjectName = VK_NULL_HANDLE;
	PFN_vkCmdDebugMarkerBeginEXT vkCmdDebugMarkerBegin = VK_NULL_HANDLE;
	PFN_vkCmdDebugMarkerEndEXT vkCmdDebugMarkerEnd = VK_NULL_HANDLE;
	PFN_vkCmdDebugMarkerInsertEXT vkCmdDebugMarkerInsert = VK_NULL_HANDLE;

	// 從設備中獲取用於調試報告擴展的函數指針
	void setup(VkDevice device, VkPhysicalDevice physicalDevice)
	{
		// 檢查是否存在調試標記擴展(從圖形調試器運行時就是這種情況)
		uint32_t extensionCount;
		vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr);
		std::vector<VkExtensionProperties> extensions(extensionCount);
		vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, extensions.data());
		for (auto extension : extensions) {
			if (strcmp(extension.extensionName, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) == 0) {
				extensionPresent = true;
				break;
			}
		}

		if (extensionPresent) {
			//調試標記擴展不是核心的一部分,因此需要手動加載函數指針
			vkDebugMarkerSetObjectTag = (PFN_vkDebugMarkerSetObjectTagEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectTagEXT");
			vkDebugMarkerSetObjectName = (PFN_vkDebugMarkerSetObjectNameEXT)vkGetDeviceProcAddr(device, "vkDebugMarkerSetObjectNameEXT");
			vkCmdDebugMarkerBegin = (PFN_vkCmdDebugMarkerBeginEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerBeginEXT");
			vkCmdDebugMarkerEnd = (PFN_vkCmdDebugMarkerEndEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerEndEXT");
			vkCmdDebugMarkerInsert = (PFN_vkCmdDebugMarkerInsertEXT)vkGetDeviceProcAddr(device, "vkCmdDebugMarkerInsertEXT");
			// 如果存在至少一個函數指針,則設置標誌
			active = (vkDebugMarkerSetObjectName != VK_NULL_HANDLE);
		}
		else {
			std::cout << "Warning: " << VK_EXT_DEBUG_MARKER_EXTENSION_NAME << " not present, debug markers are disabled.";
			std::cout << "Try running from inside a Vulkan graphics debugger (e.g. RenderDoc)" << std::endl;
		}
	}

	// 設置調試對象的名稱
	// Vulkan中的所有對象都由64位句柄表示,這些句柄被傳遞給這個函數以及對象類型
	void setObjectName(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, const char *name)
	{
		//檢查有效的函數指針(如果不在調試應用程序中運行,可能不存在)
		if (active)
		{
			VkDebugMarkerObjectNameInfoEXT nameInfo = {};
			nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
			nameInfo.objectType = objectType;
			nameInfo.object = object;
			nameInfo.pObjectName = name;
			vkDebugMarkerSetObjectName(device, &nameInfo);
		}
	}

	// 設置對象的標記
	void setObjectTag(VkDevice device, uint64_t object, VkDebugReportObjectTypeEXT objectType, uint64_t name, size_t tagSize, const void* tag)
	{
		//檢查有效的函數指針(如果不在調試應用程序中運行,可能不存在)
		if (active)
		{
			VkDebugMarkerObjectTagInfoEXT tagInfo = {};
			tagInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_TAG_INFO_EXT;
			tagInfo.objectType = objectType;
			tagInfo.object = object;
			tagInfo.tagName = name;
			tagInfo.tagSize = tagSize;
			tagInfo.pTag = tag;
			vkDebugMarkerSetObjectTag(device, &tagInfo);
		}
	}

	// 啓動一個新的調試標記區域
	void beginRegion(VkCommandBuffer cmdbuffer, const char* pMarkerName, glm::vec4 color)
	{
		//檢查有效的函數指針(如果不在調試應用程序中運行,可能不存在)
		if (active)
		{
			VkDebugMarkerMarkerInfoEXT markerInfo = {};
			markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
			memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
			markerInfo.pMarkerName = pMarkerName;
			vkCmdDebugMarkerBegin(cmdbuffer, &markerInfo);
		}
	}

	// 在命令緩衝區中插入一個新的調試標記
	void insert(VkCommandBuffer cmdbuffer, std::string markerName, glm::vec4 color)
	{
		//檢查有效的函數指針(如果不在調試應用程序中運行,可能不存在)
		if (active)
		{
			VkDebugMarkerMarkerInfoEXT markerInfo = {};
			markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
			memcpy(markerInfo.color, &color[0], sizeof(float) * 4);
			markerInfo.pMarkerName = markerName.c_str();
			vkCmdDebugMarkerInsert(cmdbuffer, &markerInfo);
		}
	}

	// 結束當前調試標記區域
	void endRegion(VkCommandBuffer cmdBuffer)
	{
		//檢查有效的函數(如果不在調試應用程序中運行,可能不存在)
		if (vkCmdDebugMarkerEnd)
		{
			vkCmdDebugMarkerEnd(cmdBuffer);
		}
	}
};

三、渲染中添加調試標記信息

接下來我們需要在渲染場景的時候添加調試標記來供離屏調試可視化使用:
首先在buildCommandBuffers中(簡化版):

void buildCommandBuffers()
{
	vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo);
	//啓動一個新的調試標記區域
	DebugMarker::beginRegion(drawCmdBuffers[i], "Render scene", glm::vec4(0.5f, 0.76f, 0.34f, 1.0f));
		
	vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);	
		
	// 啓動一個新的調試標記區域
	DebugMarker::beginRegion(drawCmdBuffers[i], "Toon shading draw", glm::vec4(0.78f, 0.74f, 0.9f, 1.0f));
	DebugMarker::endRegion(drawCmdBuffers[i]);
	// Draw scene
	...
	// 線框模式
	if (wireframe)
	{
		// 啓動一個新的調試標記區域
		DebugMarker::beginRegion(drawCmdBuffers[i], "Wireframe draw", glm::vec4(0.53f, 0.78f, 0.91f, 1.0f));
		DebugMarker::endRegion(drawCmdBuffers[i]);
		// Draw scene
		...
	}

	// 後處理
	if (glow)
	{
		// 啓動一個新的調試標記區域
		DebugMarker::beginRegion(drawCmdBuffers[i], "Apply post processing", glm::vec4(0.93f, 0.89f, 0.69f, 1.0f));
		DebugMarker::endRegion(drawCmdBuffers[i]);
		// Draw scene
		...
	}

	vkCmdEndRenderPass(drawCmdBuffers[i]);
	DebugMarker::endRegion(drawCmdBuffers[i]);
	vkEndCommandBuffer(drawCmdBuffers[i]);
}

對場景模型的每個部分我們也分別在draw函數中定義各調試標記:

	std::vector<std::string> modelPartNames{ "hill", "crystals", "rocks", "cave", "tree", "mushroom stems", "blue mushroom caps", "red mushroom caps", "grass blades", "chest box", "chest fittings" };

	void draw(VkCommandBuffer cmdBuffer)
	{
		VkDeviceSize offsets[1] = { 0 };
		vkCmdBindVertexBuffers(cmdBuffer, 0, 1, &model.vertices.buffer, offsets);
		vkCmdBindIndexBuffer(cmdBuffer, model.indices.buffer, 0, VK_INDEX_TYPE_UINT32);
		for (auto i = 0; i < model.parts.size(); i++)
		{
			// 添加調試網格名稱標記
			DebugMarker::insert(cmdBuffer, "Draw \"" + modelPartNames[i] + "\"", glm::vec4(0.0f));
			vkCmdDrawIndexed(cmdBuffer, model.parts[i].indexCount, 1, model.parts[i].indexBase, 0, 0);
		}
	}

簡單的設置這些調試標識後,我們運行程序生產exe文件。

四、可視化(RenderDoc使用)

4.1 加載程序

RenderDoc是一個獨立的圖形調試器,已與LunarG Vulkan SDK一起發佈,因此也是最早支持Vulkan的圖形調試器之一。
從文章開始鏈接處下載並安裝,啓動RenderDoc並選擇示例應用程序的二進制文件進行捕獲。您可能還想在“操作”下將捕獲排隊,以便RenderDoc自動捕獲第二幀(如果沒有捕獲,則隨時使用F12捕獲):
在這裏插入圖片描述

4.2 運行

在這裏插入圖片描述
運行後,我們可以看到默認截取兩幀的圖片,並在Event Browser中可以看到對應幀畫面調用的所有API:
在這裏插入圖片描述
此時你可以看到左側我們在程序中插入的一些調試標識,點開對應的對應的渲染子項,我們可以看到我們在draw函數中設置的細分調試標識:
在這裏插入圖片描述
點擊對應的地方,我們可以看到不同繪製(vkCmdDrawIndexed)的顯示效果:
在這裏插入圖片描述

小結

使用VK_EXT_debug_marker擴展和RenderDoc等脫機調試工具,在Vulkan中調試實際的渲染代碼可以更加直觀。而驗證層將可以幫助您根據規範驗證代碼並避免錯誤,而離線調試使您可以詳細檢查渲染組成和資源使用情況,並可以定義自己的調試區域和標記並命名其他Vulkan對象,甚至調試複雜的應用程序現在都應該變得容易得多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章