[Vulkan Tutorial] 15-Render Passes

목차: 01-Overview (Link)
이전 글: 14-Fixed Functions on Graphics Pipeline (Link)
다음 글: 16-The End of Graphics Pipeline (Link)


Graphics Pipeline을 생성하기 이전에 Rendering에 사용될 Framebuffer Attachment (여러 개의 Framebuffer을 의미하는 것 같음)를 정의해야 한다. 예를 들어서 몇 개의 Color Buffer, Depth Buffer를 사용할 예정인지, Rendering을 하는 동안 해당 Buffer의 값은 어떻게 사용될 것인지 등을 정의하는 것을 의미한다. 이러한 모든 정보를 Render Pass Object에 저장하게 된다. 아래 코드 1은 Render Pass Object를 생성하는 코드이다.

MK: Render Pass라는 개념을 이해하기 어려운 것 같다. 현재까지 이해한 바에 의하면 여러 개의 Drawcall이 합쳐져서 Framebuffer Attachment에 값을 Write 하는 과정을 의미하는 것으로 판단된다. 만약 Framebuffer Attachment가 변경되게 되면 새로운 Render Pass가 되는 것이다. Deferred Rendering의 경우 여러 개의 Render Pass를 사용해서 결과 Image를 Rendering 하게 된다. Vulkan의 경우 Render Pass를 프로그래머가 직접 설정해야 한다. 반면 OpenGL의 경우 Vulkan과 같이 프로그래머가 Render Pass를 설정하지 못한다. OpenGL의 경우 Vendor Driver에서 Render Pass를 나누게 된다. (출처2, 3)

코드 1: Render Pass Object 생성 코드

#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(){
			createInstance();
			createSurface();
			pickPhysicalDevice();
			searchQueue();
			createLogicalDevice();
			createSwapChain();
			createImageViews();

			//MK: (코드 1-2) createRenderPass 함수 호출
			createRenderPass();

			createGraphicsPipeline();
		}

		void mainLoop(){
		}

		void cleanup(){
			LOGI("MK: cleanup Function");

			vkDestroyPipelineLayout(device, pipelineLayout, NULL);
			//MK: (코드 1-8) Render Pass 제거 코드
			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(){

			...
			
		}

		//MK: (코드 1-1) createRenderPass 함수 생성
		void createRenderPass(){

			LOGI("MK: createRenderPass Function");

			//MK: (코드 1-3) Attachment Description을 설정하는 부분
			VkAttachmentDescription colorAttachment = {};
			colorAttachment.flags = 0;
			colorAttachment.format = selectedSurfaceFormat.format;
			colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
			colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
			colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
			colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
			colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
			colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
			colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

			//MK: (코드 1-4) Subpass 및 Subpass에서 참조할 Attachment에 대해서 설정하는 부분
			VkAttachmentReference colorAttachmentRef = {};
			colorAttachmentRef.attachment = 0;
			colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

			VkSubpassDescription subpass = {};
			subpass.flags = 0;
			subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
			subpass.inputAttachmentCount = 0;
			subpass.pInputAttachments = NULL;
			subpass.colorAttachmentCount = 1;
			subpass.pColorAttachments = &colorAttachmentRef;
			subpass.pResolveAttachments = NULL;
			subpass.pDepthStencilAttachment = NULL;
			subpass.preserveAttachmentCount = 0;
			subpass.pPreserveAttachments = NULL;

			//MK: (코드 1-6) VkRenderPassCreateInfo Struct를 생성하는 부분
			VkRenderPassCreateInfo renderPassInfo = {};
			renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
			renderPassInfo.pNext = NULL;
			renderPassInfo.flags = 0;
			renderPassInfo.attachmentCount = 1;
			renderPassInfo.pAttachments = &colorAttachment;
			renderPassInfo.subpassCount = 1;
			renderPassInfo.pSubpasses = &subpass;
			renderPassInfo.dependencyCount = 0;
			renderPassInfo.pDependencies = NULL;

			//MK: (코드 1-7) Render Pass Object를 생성하는 부분
			VkResult result = vkCreateRenderPass(device, &renderPassInfo, NULL, &renderPass);
			assert(result == VK_SUCCESS);
			LOGI("MK: Successfully Create Render Pass (If not, should not see this print)");
		}

		...

		//MK: (코드 1-5) VkRenderPass 변수 추가
		VkRenderPass renderPass;
};

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

}

Attachment Description

코드 1-1과 같이 createRenderPass() 함수를 하나 생성한다. 해당 함수는 코드 1-2 initVulkan() 함수에서 호출한다. createRenderPass() 함수에 가장 먼저 Attachment Description 관련 코드를 작성한다. 앞에서 설명한 것과 같이 Attachment는 몇 개의 Framebuffer를 사용할 것이며 해당 Framebuffer의 동작 방법 등을 정의한다. 코드 1-3은 Attachment Description을 정의하는 부분이다. VkAttachmentDescription Struct을 사용해서 Attachment Description을 정의한다. VkAttachmentDescription Struct은 아래와 같이 총 9개의 변수를 가진다.

VkAttachmentDescription Struct (출처 4)

  • flags (VkAttachmentDescriptionFlags): Attachment에 추가적인 정보를 제공하기 위해서 사용
  • format (VkFormat): Attachment로 사용될 Image View Format
    • 보통 Swap Chain에서 생성한 Image Format을 사용한다.
  • samples (VkSampleCountFlagBits): Multi-Sampling을 사용할 때 필요함
    • 본 예제에서는 Multi-Sampling을 사용하지 않기 때문에 VK_SAMPLE_COUNT_1_BIT을 사용한다.
  • loadOp (VkAttachmentLoadOp): 처음 Color/Depth 값을 Loading(Subpass가 시작하는 시점)할 때 해당 값을 어떻게 처리할지 판단함
    • 본 예제에서는 Color/Depth 값을 Loading할 때 Clear 하도록 설정하기 위해서 VK_ATTACHMENT_LOAD_OP_CLEAR 값을 사용한다.
  • storeOp (VkAttachmentStoreOp): Color/Depth 값을 저장(Subpass가 끝나는 시점)할 때 해당 결과값을 어떻게 처리할지 판단함
    • 본 예제에서는 Color/Depth 값을 Buffer에 Write 해야 함으로 VK_ATTACHMENT_STORE_OP_STORE 값을 사용한다.
  • stencilLoadOp (VkAttachmentLoadOp): 처음 Stencil 값을 Loading(Subpass가 시작하는 시점)할 때 해당 값을 어떻게 처리할지 판단함
    • 본 예제에서는 Stencil을 사용하지 않기 때문에 VK_ATTACHMENT_LOAD_OP_DONT_CARE 값을 사용한다.
  • stencilStoreOp (VkAttachmentStoreOp): Stencil 값을 저장(Subpass가 끝나는 시점)할 때 해당 결과값을 어떻게 처리할지 판단함
    • 본 예제에서는 Stencil을 사용하지 않기 때문에 VK_ATTACHMENT_STORE_OP_DONT_CARE 값을 사용한다.
  • initialLayout (VkImageLayout): Render Pass가 시작할 때 Image의 Pixel 값이 저장된 순서(Layout)를 정의함 (initialLayout is the layout the attachment image subresource will be in when a render pass instance begins)
  • finalLayout (VkImageLayout): Redner Pass가 끝날 때 Image의 Pixel 값이 저장된 순서(Layout)를 정의함 (finalLayout is the layout the attachment image subresource will be transitioned to when a render pass instance ends)

현재 작성하고 있는 코드는 Color Buffer 1개만 필요하다. initialLayout, finalLayout의 정확한 의미를 모르겠다. 추가로 알게 되면 내용을 추가할 예정이다. 출처 1에 따르면 Texture 설명을 할 때 Layout에 대해서 추가로 설명을 할 예정이라고 한다. 보통 Texture, Framebuffer는 특정 Pixel Format을 가진다 (아마 RGBA 등을 의미하는 것으로 판단됨). 하지만 해당 Pixel 값이 메모리에 저장되는 순서(Layout)는 사용 용도에 따라 변경이 될 수도 있다고 한다. initialLayout, finalLayout은 아마도 Render Pass가 시작할 때, 끝이 날 때 Pixel이 저장된 순서를 나타내기 위해서 사용되는 것으로 판단된다. 본 예제에서는 Color/Depth값은 로딩하는 과정에 Clear 하도록 설정하였다. 그래서 initialLayout은 VK_IMAGE_LAYOUT_UNDEFINED으로 설정한다. Rendering이 끝난 Image는 Swap Chain에 저장되어서 화면에 표시되도록 설정하기 위해서 finalLayout 값은 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR로 설정한다.


Subpasses and Attachment References

1개의 Render Pass는 여러 개의 Subpass로 구성할 수 있다. Subpass는 연속된 Rendering Operation을 의미한다. 각 Subpass는 이전 Subpass 결과 값을 사용해서 추가적인 연산을 수행한다. 만약 하나의 Render Pass가 여러 개의 Subpass로 구성이 되어 있으면 Vulkan API가 Memory Bandwidth를 줄이기 위해서 Subpass 순서를 변경할 수도 있다. 우리는 하나의 삼각형을 그리는 예제를 작성하고 있기 때문에 하나의 Subpass만 생성하도록 코드를 작성한다. 코드 1-4는 Subpass 및 Subpass에서 참조할 Attachment에 대해서 설정하는 부분이다.

각 Subpass 는 1개 또는 그 이상의 Attachment를 참조한다. Attachment는 우리가 앞에 설명하였다. 참조하는 Attachment를 설정하기 위해서 VkAttachmentReference Struct를 사용한다. VkAttachmentReference Struct은 아래와 같이 2개의 변수를 가진다.

VkAttachmentReference Struct (출처 5)

  • attachment (uint32_t): 참조할 Attachment Index 번호 (Integer Number)
    • 본 예제에서는 한 개의 Attachment만 사용하는 관계로 0 값으로 설정한다.
  • layout (VkImageLayout): Subpass에서 Image가 사용되는 용도를 설정함
    • 본 예제에서는 Attachment가 Color Buffer로 사용하기 때문에 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 값을 사용한다.

Subpass에서 참조할 Attachment를 설정하고 나면 Subpass를 설정해야 한다. Subpass 설정을 위해서 VkSubpassDescription Struct를 사용해야 한다. VkSubpassDescription Struct은 아래와 같이 10개의 변수를 가진다.

VkSubpassDescription Struct (출처 6)

  • flags (VkSubpassDescriptionFlags): flags is a bitmask of VkSubpassDescriptionFlagBits specifying usage of the subpass
  • pipelineBindPoint (VkPipelineBindPoint): Subpass Pipeline Type
    • 본 예제에서는 Graphic 연산으로 사용할 예정이니 VK_PIPELINE_BIND_POINT_GRAPHICS 값을 설정한다.
  • inputAttachmentCount (uint32_t): 총 Input Attachment 개수
    • 본 예제에서는 1개의 Attachment만 사용하기 때문에 1로 설정한다.
  • pInputAttachments (const VkAttachmentReference*): Subpass에서 Input으로 사용한 Attachment 포인터
    • 앞에서 생성한 VkAttachmentReference 변수로 설정한다.
  • colorAttachmentCount (uint32_t): Color Attachment 개수
    • 본 예제에서는 1개의 Color Attachment만 존재하기 때문에 1로 설정한다.
  • pColorAttachments (const VkAttachmentReference*): Color Attachment 포인터
    • 앞에서 생성한 VkAttachmentReference Struct 변수로 설정한다.
  • pResolveAttachments (const VkAttachmentReference*): Multi-Sampling에 사용될 Attachment 포인터
  • pDepthStencilAttachment(const VkAttachmentReference*): Depth/Stencil Attachment 포인터
  • preserveAttachmentCount (uint32_t): Attachment 값이 변경되면 안되는 Attachment 개수
  • pPreserveAttachments (const uint32_t*): 값을 보존해야 하는 Attachment Index Array 포인터

예제 코드 작성에 pipelineBindPoint, colorAttachmentCount, pColorAttachments 만 설정한다. 나머지는 0 또는 Null로 설정을 하였다. VkSubpassDescription Struct까지 코드를 작성하면 모든 Subpass 설정이 끝난다.


Render Pass

Attachment와 Subpass에 대해서 정의가 완료되면 드디어 Render Pass를 생성할 차례이다. 먼저 코드 1-5와 같이 VkRenderPass 변수를 하나 추가한다. 다음으로 VkRenderPassCreateInfo Struct에 필요한 값을 설정한다. VkRenderPassCreateInfo Struct는 아래와 같이 9개의 변수를 가진다. 코드 1-6은 VkRenderPassCreateInfo Struct를 설정하는 부분이다.

VkRenderPassCreateInfo Struct (출처 7)

  • sType (VkStructureType): 생성하고자 하는 Struct의 Type을 나타냄
  • pNext (const void*): Extension Struct Pointer
  • flags (VkRenderPassCreateFlags): flags is a bitmask of VkRenderPassCreateFlagBits
    • 본 예제에서의 0으로 설정한다.
  • attachmentCount (uint32_t): Render Pass에서 사용하는 Attachment 개수
    • 본 예제에서는 1개의 Attachment만 사용하기 때문에 1로 설정한다.
  • pAttachments (const VkAttachmentDescription*): VkAttachmentDescription Struct Array 포인터
    • 앞에서 생성한 VkAttachmentDescription Struct 포인터를 설정한다.
  • subpassCount (uint32_t): Render Pass를 구성하고 있는 Subpass 개수
    • 본 예제에서는 Render Pass가 1개의 Subpass로 구성되어 있기 때문에 1을 설정한다.
  • pSubpasses (const VkSubpassDescription*): VkSubpassDescription Struct Array 포인터
    • 앞에서 생성한 VkSubpassDescription Struct 포인터를 설정한다.
  • dependencyCount (uint32_t): Subpass간의 Memory Dependency 개수
    • 본 예제에서는 한개의 Dependency도 없기 때문에 0으로 설정한다.
  • pDependencies (const VkSubpassDependency*): VkSubpassDependency Struct Array 포인터
    • Memory Dependency가 없기 때문에 Null값을 사용한다.

VkRenderPassCreateInfo Struct 설정이 완료되면 vkCreateRenderPass(…) 함수를 사용해서 Render Pass를 생성한다 (출처 8). 코드 1-7은 vkCreateRenderPass(…) 함수를 사용해서 Render Pass Object를 생성하는 부분이다. 생성한 RenderPass를 vkDestroyRenderPass(…) 함수를 사용해서 제거해야 한다. 코드 1-8은 Render Pass를 제거하는 부분이다.


Result

그림 1: Logcat 결과 화면

위 코드 1을 컴파일 해서 실행하면 여전히 검은색 화면만 나타난다. 그림 1은 코드를 컴파일해서 실행하면 Logcat에 출력되는 결과 화면이다. Render Pass가 정상적으로 생성되었다는 메시지만 추가하였기 때문에 이전 글에서 보여준 결과와 큰 차이가 없다. 코드 1의 전체 코드는 출처 9에서 다운로드 가능하다.


출처

  1. https://vulkan-tutorial.com/
  2. https://stackoverflow.com/questions/34382340/what-is-a-renderpass
  3. https://developer.arm.com/solutions/graphics-and-gaming/developer-guides/learn-the-basics/understanding-render-passes
  4. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkAttachmentDescription.html
  5. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkAttachmentReference.html
  6. https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkSubpassDescription.html
  7. https://www.khronos.org/registry/vulkan/specs/1.0/man/html/VkRenderPassCreateInfo.html
  8. https://vulkan.lunarg.com/doc/view/1.0.33.0/linux/vkspec.chunked/ch07s01.html
  9. https://github.com/mkblog-cokr/androidVulkanTutorial

Leave a Comment