[Vulkan Tutorial] 16-The End of Graphics Pipeline

목차: 01-Overview (Link)
이전 글: 15-Render Passes (Link)
다음 글: 17-TBD (Link)


Graphics Pipeline의 마지막 설명을 작성한다. 이번 글에서는 앞에서 작성한 모든 부분을 하나로 합쳐서 Graphics Pipeline을 생성한다. 앞장에서 Graphics Pipeline을 생성하기 위해서 아래의 4가지 Structure 또는 Object를 생성하였다.

  • Shader Stages: Programmable Stages (Vertex/Fragment Shaders) 코드를 생성하였다 (Vulkan Tutorial 13)
  • Fixed Function States: 모든 Fixed Function 동작을 위한 코드를 생성하였다 (Vulkan Tutorial 14)
  • Pipeline Layout: Shader에서 사용할 Uniform/Push Value에 대한 코드를 작성하였다 (Vulkan Tutorial 14)
  • Render Pass: Subpass/Render Pass에서 사용할 Attachment에 대한 코드를 작성하였다 (Vulkan Tutorial 15)

아래 코드 1은 Graphics Pipeline을 생성하는 코드이다. 해당 코드는 createGraphicsPipeline(…) 함수 안에 작성하면 된다.

코드 1: Graphics Pipeline 생성 코드

#include <iostream>
#include <stdexcept>
#include <functional>
#include <cstdlib>
#include <util_init.hpp>
#include <set>

#define APP_SHORT_NAME "mkVulkanExample"

#define MK_GET_INSTANCE_PROC_ADDR(inst, entrypoint)												\
{																								\
	fp##entrypoint = (PFN_vk##entrypoint)vkGetInstanceProcAddr(instance, "vk" #entrypoint);		\
	if (fp##entrypoint == NULL) {																\
		std::cout << "vkGetDeviceProcAddr failed to find vk" #entrypoint;						\
		exit(-1);																				\
	}																							\
}


class mkTriangle{
	public:
		void run(){

			...

		}

	private:
		void initVulkan(){

			...

		}

		void mainLoop(){
		}

		void cleanup(){

			LOGI("MK: cleanup Function");

			//MK: (코드 1-4) Graphics Pipeline 제거하는 부분
			vkDestroyPipeline(device, graphicsPipeline, NULL);
			
			vkDestroyPipelineLayout(device, pipelineLayout, NULL);
			vkDestroyRenderPass(device, renderPass, nullptr);

			for(int i = 0; i < swapChainImageViews.size(); i++){
				vkDestroyImageView(device, swapChainImageViews[i], NULL);
			}

			vkDestroySwapchainKHR(device, swapChain, NULL);
			vkDestroyDevice(device, NULL);
			vkDestroySurfaceKHR(instance, surface, NULL);
			vkDestroyInstance(instance, NULL);
		}

		void createInstance(){

			...

		}

		void createSurface(){
			
			...

		}

		void pickPhysicalDevice(){

			...

		}

		void printPhysicalDeviceInfo(VkPhysicalDevice device){

			...

		}

		void searchQueue(){

			...

		}

		void createLogicalDevice(){

			...

		}

		void createSwapChain(){

			...

		}

		void createImageViews(){

			...

		}

		void createGraphicsPipeline(){

			LOGI("MK: (Begin) createGraphicsPipeline Function");

			static const char *vertShaderText=
			"#version 450\n"
			"#extension GL_ARB_separate_shader_objects : enable\n"
			"vec2 positions[3] = vec2[](vec2(0.0, -0.5), vec2(0.5, 0.5), vec2(-0.5, 0.5));\n"
			"vec3 colors[3] = vec3[](vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0));\n"
			"layout(location = 0) out vec3 fragColor;\n"
			"void main() {\n"
			"gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);\n"
			"fragColor = colors[gl_VertexIndex];}\n";

			static const char *fragShaderText=
			"#version 450\n"
			"#extension GL_ARB_separate_shader_objects : enable\n"
			"layout(location = 0) in vec3 fragColor;\n"
			"layout(location = 0) out vec4 outColor;\n"
			"void main() {\n"
			"outColor = vec4(fragColor, 1.0);}\n";

			std::vector<unsigned int> vtxSpv;
			std::vector<unsigned int> fragSpv;

			init_glslang();

			bool retVal;
			retVal = GLSLtoSPV(VK_SHADER_STAGE_VERTEX_BIT, vertShaderText, vtxSpv);
			assert(retVal == true);
			LOGI("\tMK: Vertex Code is converted to SPV");

			retVal = GLSLtoSPV(VK_SHADER_STAGE_FRAGMENT_BIT, fragShaderText, fragSpv);
			assert(retVal == true);
			LOGI("\tMK: Fragment Code is converted to SPV");

			VkShaderModule vertShaderModule;
			VkShaderModule fragShaderModule;

			VkShaderModuleCreateInfo createInfo = {};
			createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
			createInfo.pNext = NULL;
			createInfo.flags = 0;
			createInfo.codeSize = vtxSpv.size() * sizeof(unsigned int);
			createInfo.pCode = vtxSpv.data();

			VkResult result = vkCreateShaderModule(device, &createInfo, NULL, &vertShaderModule);
			assert(result == VK_SUCCESS);
			LOGI("\tMK: Vertex Module is created\n");
			
			createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
			createInfo.pNext = NULL;
			createInfo.flags = 0;
			createInfo.codeSize = fragSpv.size() * sizeof(unsigned int);
			createInfo.pCode = fragSpv.data();

			result = vkCreateShaderModule(device, &createInfo, NULL, &fragShaderModule);
			assert(result == VK_SUCCESS);
			LOGI("\tMK: Fragment Module is created\n");

			shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
			shaderStages[0].pNext = NULL;
			shaderStages[0].flags = 0;
			shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
			shaderStages[0].module = vertShaderModule;
			shaderStages[0].pName = "main";
			shaderStages[0].pSpecializationInfo = NULL;

			shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
			shaderStages[1].pNext = NULL;
			shaderStages[1].flags = 0;
			shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
			shaderStages[1].module = fragShaderModule;
			shaderStages[1].pName = "main";
			shaderStages[1].pSpecializationInfo = NULL;

			VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
			vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
			vertexInputInfo.pNext = NULL;
			vertexInputInfo.flags = 0;
			vertexInputInfo.vertexBindingDescriptionCount = 0;
			vertexInputInfo.pVertexBindingDescriptions = NULL;
			vertexInputInfo.vertexAttributeDescriptionCount = 0;
			vertexInputInfo.pVertexAttributeDescriptions = NULL;

			VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
			inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
			inputAssembly.pNext = NULL;
			inputAssembly.flags = 0;
			inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; 
			inputAssembly.primitiveRestartEnable = VK_FALSE;

			VkViewport viewport = {};
			viewport.x = 0.0f;
			viewport.y = 0.0f;
			viewport.width = (float) selectedExtent.width;
			viewport.height = (float) selectedExtent.height;
			viewport.minDepth = 0.0f;
			viewport.maxDepth = 1.0f;

			VkRect2D scissor;
			scissor.offset = {0, 0};
			scissor.extent = selectedExtent;

			VkPipelineViewportStateCreateInfo viewportState = {};
			viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
			viewportState.pNext = NULL;
			viewportState.flags = 0;
			viewportState.viewportCount = 1;
			viewportState.pViewports = &viewport;
			viewportState.scissorCount = 1;
			viewportState.pScissors = &scissor;

			VkPipelineRasterizationStateCreateInfo rasterizer = {};
			rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
			rasterizer.pNext = NULL;
			rasterizer.flags = 0;
			rasterizer.depthClampEnable = VK_FALSE;
			rasterizer.rasterizerDiscardEnable = VK_FALSE;
			rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
			rasterizer.lineWidth = 1.0f;
			rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
			rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
			rasterizer.depthBiasEnable = VK_FALSE;
			rasterizer.depthBiasConstantFactor = 0.0f;
			rasterizer.depthBiasClamp = 0.0f;
			rasterizer.depthBiasSlopeFactor = 0.0f;

			VkPipelineMultisampleStateCreateInfo multisampling = {};
			multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
			multisampling.pNext = NULL;
			multisampling.flags = 0;
			multisampling.pSampleMask = NULL;
			multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
			multisampling.sampleShadingEnable = VK_FALSE;
			multisampling.alphaToCoverageEnable = VK_FALSE;
			multisampling.alphaToOneEnable = VK_FALSE;
			multisampling.minSampleShading = 0.0f;

			VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
			colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
			colorBlendAttachment.blendEnable = VK_FALSE;
			//MK: 나머지 값은 선택임. 아무 값을 저장하지 않아도 됨
			colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; 
			colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
			colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; 
			colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
			colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; 
			colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;

			VkPipelineColorBlendStateCreateInfo colorBlending = {};
			colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
			colorBlending.logicOpEnable = VK_FALSE;
			colorBlending.attachmentCount = 1;
			colorBlending.pAttachments = &colorBlendAttachment;
			//MK: 나머지 값은 선택임. 아무 값을 저장하지 않아도 됨
			colorBlending.logicOp = VK_LOGIC_OP_COPY;			
			colorBlending.blendConstants[0] = 0.0f;
			colorBlending.blendConstants[1] = 0.0f;
			colorBlending.blendConstants[2] = 0.0f;
			colorBlending.blendConstants[3] = 0.0f;

			VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
			pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
			pipelineLayoutInfo.pNext = NULL;
			pipelineLayoutInfo.setLayoutCount = 0;
			pipelineLayoutInfo.pSetLayouts = NULL;
			pipelineLayoutInfo.pushConstantRangeCount = 0;
			pipelineLayoutInfo.pPushConstantRanges = NULL;

			result = vkCreatePipelineLayout(device, &pipelineLayoutInfo, NULL, &pipelineLayout);

			assert(result == VK_SUCCESS);
			LOGI("MK: Successfully Create Pipeline Layout (If not, should not see this print)");

			//MK: (코드 1-2) VkGraphicsPipelineCreateInfo Struct를 생성하는 코드
			VkGraphicsPipelineCreateInfo pipelineInfo = {};
			pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
			pipelineInfo.pNext = NULL;
			pipelineInfo.flags = 0;
			pipelineInfo.stageCount = 2;
			pipelineInfo.pStages = shaderStages;
			pipelineInfo.pVertexInputState = &vertexInputInfo;
			pipelineInfo.pInputAssemblyState = &inputAssembly;
			pipelineInfo.pTessellationState = NULL;
			pipelineInfo.pViewportState = &viewportState;
			pipelineInfo.pRasterizationState = &rasterizer;
			pipelineInfo.pMultisampleState = &multisampling;
			pipelineInfo.pDepthStencilState = NULL;
			pipelineInfo.pColorBlendState = &colorBlending;
			pipelineInfo.pDynamicState = NULL;
			pipelineInfo.layout = pipelineLayout;
			pipelineInfo.renderPass = renderPass;
			pipelineInfo.subpass = 0;
			pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
			pipelineInfo.basePipelineIndex =-1;

			//MK: (코드 1-3) Graphics Pipeline 생성
			result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &graphicsPipeline);

			assert(result == VK_SUCCESS);
			LOGI("MK: Successfully Create Graphics Pipeline (If not, should not see this print)");

			vkDestroyShaderModule(device, fragShaderModule, NULL);
			vkDestroyShaderModule(device, vertShaderModule, NULL);

			finalize_glslang();

			LOGI("MK: (End) createGraphicsPipeline Function");
			
		}

		void createRenderPass(){

			...

		}

		VkInstance instance;

		VkSurfaceKHR surface;
		PFN_vkCreateAndroidSurfaceKHR fpCreateAndroidSurfaceKHR;

		VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;

		uint32_t graphicQueueFamilyIndex = 0;
		uint32_t presentQueueFamilyIndex = 0;

		VkDevice device; 

		VkQueue graphicsQueue;
		VkQueue presentQueue;

		VkSurfaceCapabilitiesKHR surfaceCapabilities;
		std::vector<VkSurfaceFormatKHR> surfaceFormats;
		std::vector<VkPresentModeKHR> presentModes;

		VkSurfaceFormatKHR selectedSurfaceFormat;
		VkPresentModeKHR selectedPresentMode;
		VkExtent2D selectedExtent;
		uint32_t selectedImageCount;

		VkSwapchainKHR swapChain;

		std::vector<VkImage> swapChainImages;

		std::vector<VkImageView> swapChainImageViews;

		VkPipelineShaderStageCreateInfo shaderStages[2];

		VkPipelineLayout pipelineLayout;

		//MK: (코드 1-1) Graphics Pipeline 저장할 Object를 생성
		VkPipeline graphicsPipeline;

		VkRenderPass renderPass;
};

int sample_main(int argc, char *argv[]) {
	
	...
	
}

가장 먼저 코드 1-1과 같이 VkPipeline 변수를 하나 생성한다. 다음으로 Graphics Pipeline을 생성하기 위해서 VkGraphicsPipelineCreateInfo Struct에 필요한 값을 저장한다. VkGraphicsPipelineCreateInfo Struct는 vkDestroyShaderModule(…) 코드 전에 작성해야 한다. VkGraphicsPipelineCreateInfo Struct는 Graphics Pipeline이 어떻게 구성되어 있는지 모든 것을 정리한 부분이다. VkGraphicsPipelineCreateInfo Struct은 아래와 같이 총 19개의 변수를 가진다. 코드 1-2는 VkGraphicsPipelineCreateInfo Struct을 생성하는 부분이다.

VkGraphicsPipelineCreateInfo Struct (출처 2)

  • sType (VkStructureType): 생성하고자 하는 Struct의 Type을 나타냄
  • pNext (const void*): Extension Struct Pointer
  • flags (VkPipelineCreateFlags): VkPipelineCreateFlagBits Bitmask (Graphics Pipeline 생성 방법) (출처 3 참조)
    • 출처 1에서는 아무 값도 추가하지 않았다. 우리는 0 값을 추가하였다.
  • stageCount (uint32_t): 총 Programmable Stage 개수
  • pStages (const VkPipelineShaderStageCreateInfo*): Programmable Stage Array 변수
    • Vulkan Tutorial 13에서 생성한 값을 사용한다.
  • pVertexInputState (const VkPipelineVertexInputStateCreateInfo*): VkPipelineVertexInputStateCreateInfo Struct 포인터 변수
    • Vulkan Tutorial 14에서 생성하였다. 해당 변수는 Input 값에 대한 성명을 포함한다.
  • pInputAssemblyState (const VkPipelineInputAssemblyStateCreateInfo*): VkPipelineInputAssemblyStateCreateInfo Struct 포인터 변수
    • Vulkan Tutorial 14에서 생성하였다. 해당 변수는 Input Assembly Behavior을 정의한다.
  • pTessellationState (const VkPipelineTessellationStateCreateInfo*): VkPipelineTessellationStateCreateInfo Struct 포인터 변수
    • 우리가 작성하는 예제에서는 Tessellation이 없는 관계로 Null 값을 사용한다.
  • pViewportState (const VkPipelineViewportStateCreateInfo*): VkPipelineViewportStateCreateInfo Struct 변수
    • Vulkan Tutorial 14에서 생성하였다. 해당 변수는 Framebuffer에 표시되는 이미지의 크기를 정의한 값이다.
  • pRasterizationState (const VkPipelineRasterizationStateCreateInfo*): VkPipelineRasterizationStateCreateInfo Struct 포인터 변수
    • Vulkan Tutorial 14에서 생성하였다. Rasterization 동작을 정의한 값이다.
  • pMultisampleState (const VkPipelineMultisampleStateCreateInfo*): VkPipelineMultisampleStateCreateInfo Struct 포인터 변수
    • Vulkan Tutorial 14에서 생성하였다. AA등의 동작을 정의한 변수값이다.
  • pDepthStencilState (const VkPipelineDepthStencilStateCreateInfo*): VkPipelineDepthStencilStateCreateInfo Struct 포인터 변수
    • 우리가 작성하는 예제에서는 Stencil을 사용하지 않기 때문에 Null 값을 사용한다.
  • pColorBlendState (const VkPipelineColorBlendStateCreateInfo*): VkPipelineColorBlendStateCreateInfo Struct 포인터 변수
    • Vulkan Tutorial 14에서 생성하였다. Framebuffer에 최종 값을 쓰는 동작을 정의한다.
  • pDynamicState (const VkPipelineDynamicStateCreateInfo*): VkPipelineDynamicStateCreateInfo Struct 포인터 변수
    • 우리가 작성하는 예제에서는 Viewport 등을 변경하지 않는다. 그래서 Null 값을 사용한다.
  • layout (VkPipelineLayout): VkPipelineLayout 변수
    • Vulkan Tutorial 14에서 생성하였다. Uniform 등 Global Input값을 정의하는 부분이다.
  • renderPass (VkRenderPass): VkRenderPass 변수
    • Vulkan Tutorial 15에서 생성하였다.
  • subpass (uint32_t): 사용할 Subpass Index 값
    • Vulkan Tutorial 15에서 하나의 Subpass를 생성하였다. 그래서 해당 값을 0으로 설정한다.
  • basePipelineHandle (VkPipeline): 기존에 있던 Graphics Pipeline을 사용해서 다시 Pipeline을 생성하는 경우에 사용함
    • 우리가 작성하는 예제에서는 기존에 생성한 Pipeline이 없는 관계로 VK_NULL_HANDLE 값을 사용한다.
  • basePipelineIndex (int32_t): 역시 기존에 있던 Graphics Pipeline을 사용해서 다시 Pipeline을 생성하는 경우에 사용함
    • 우리가 작성하는 예제에서는 기존에 생성한 Pipeline이 없는 관계로 -1 값을 사용한다.

필요한 값을 모두 저장하고 나면 Graphics Pipeline을 생성할 차례이다. 코드 1-3은 Graphics Pipeline을 생성하는 부분이다. Graphics Pipeline 생성을 위해서 vkCreateGraphicsPipelines(…) 함수를 사용한다. vkCreateGraphicsPipelines(…) 함수는 아래와 같이 6개의 인자를 가진다.

vkCreateGraphicsPipelines(…) (출처 4)

  • device (VkDevice): Logical Device 변수
    • Vulkan Tutorial 9에서 생성한 변수를 사용한다.
  • pipelineCache (VkPipelineCache): Pipeline Cache Object 변수
    • 우리가 사용하는 예제에서는 Pipeline Cache를 사용하지 않기 때문에 VK_NULL_HANDLE 값을 사용한다.
  • createInfoCount (uint32_t): VkGraphicsPipelineCreateInfo (pCreateInfo) Array 개수
    • 예제에서는 1개의 VkGraphicsPipelineCreateInfo Struct를 생성하기 때문에 1값을 사용한다.
  • pCreateInfos(const VkGraphicsPipelineCreateInfo*): VkGraphicsPipelineCreateInfo Struct Array
    • 앞에서 생성한 pCreateInfo 변수를 사용한다.
  • pAllocator(const VkAllocationCallbacks*): pAllocator controls host memory allocation as described in the Memory Allocation chapter
    • 우리가 작성하는 예제에서는 NULL 값을 사용한다.
  • pPipelines(VkPipeline*): VkPipeline Object를 저장한 변수
    • 코드 1-1에서 생성한 변수를 사용한다.

vkCreateGraphicsPipelines(…) 함수를 호출하면 Graphics Pipeline Object가 생성된다. 만약 생성하는 과정에서 문제가 생기면 코드가 멈추도록 작성하였다. 이렇게 생성한 Object는 vkDestroyPipeline(…) 함수를 호출해서 제거해야 한다. 코드 1-4는 Graphics Pipeline을 제거하는 코드이다. 코드를 컴파일해서 실행하면 여전히 검정색 화면이 출력된다.

그림 1: Logcat 결과 화면

그림 1은 Logcat에서 출력되는 결과값이다. 지난 결과와 큰 차이는 없고 Graphics Pipeline이 정상적으로 생성되었다는 로그만 추가로 출력된다. 코드는 출처 5에서 다운로드 가능하다.


출처

  1. https://vulkan-tutorial.com/
  2. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkGraphicsPipelineCreateInfo.html
  3. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkPipelineCreateFlagBits.html
  4. https://www.khronos.org/registry/vulkan/specs/1.0/man/html/vkCreateGraphicsPipelines.html
  5. https://github.com/mkblog-cokr/androidVulkanTutorial

Leave a Comment