์ฐ์ ํ์ํ ๋๊ธฐํ ๊ตฌ์กฐ์ฒด๋ฅผ FrameData
๊ตฌ์กฐ์ฒด์ ์ถ๊ฐํ๊ฒ ์ต๋๋ค. ๊ตฌ์กฐ์ฒด์ ์๋ก์ด ๋ฉค๋ฒ๋ฅผ ์ถ๊ฐํฉ๋๋ค.
struct FrameData {
VkSemaphore _swapchainSemaphore, _renderSemaphore;
VkFence _renderFence;
};
2๊ฐ์ ์ธ๋งํฌ์ด์ ๋ฉ์ธ ๋ ๋๋ง ํ์ค๊ฐ ํ์ํ ๊ฒ์ ๋๋ค. ์ด๋ฅผ ์์ฑํ๊ฒ ์ต๋๋ค.
_swapchainSemaphore
๋ ๋ ๋๋ง ๋ช
๋ น์ด ์ค์์ฒด์ธ ์ด๋ฏธ์ง ์์ฒญ์ ๋๊ธฐํ๋๋ก ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. _renderSemaphore
๋ ๊ทธ๋ฆฌ๊ธฐ๊ฐ ๋๋ฌ์ ๋ ์ด๋ฏธ์ง๋ฅผ OS์ ํ์ํ๋ ๊ฒ์ ์ ์ดํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค._renderFence
๋ ์ฃผ์ด์ง ํ๋ ์์ ๊ทธ๋ฆฌ๊ธฐ ๋ช
๋ น์ด ๋๋ ๋ ๊น์ง ๋๊ธฐํ ์ ์๋๋ก ํฉ๋๋ค.
์ด๋ฅผ ์ด๊ธฐํํด๋ด
์๋ค. vk_initializers.cpp
์ฝ๋์ ์๋ VkFenceCreateInfo
์ VkSemaphoreCreateInfo
๋ฅผ ์์ฑํ๋ ํจ์๋ฅผ ํ์ธํด๋ด
์๋ค.
VkFenceCreateInfo vkinit::fence_create_info(VkFenceCreateFlags flags /*= 0*/)
{
VkFenceCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
info.pNext = nullptr;
info.flags = flags;
return info;
}
VkSemaphoreCreateInfo vkinit::semaphore_create_info(VkSemaphoreCreateFlags flags /*= 0*/)
{
VkSemaphoreCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
info.pNext = nullptr;
info.flags = flags;
return info;
}
๋ ๊ตฌ์กฐ์ฒด ๋ชจ๋ ๊ฝค ๋จ์ํ๋ฉฐ, ํน๋ณํ ์ต์ ์์ด ๋ช ๊ฐ์ง ํ๋๊ทธ๋ง ์ค์ ํ๋ฉด ๋ฉ๋๋ค. ๊ตฌ์กฐ์ฒด์ ๋ํ ๋ ๋ง์ ์ ๋ณด๋ฅผ ๋ณด๋ ค๋ฉด ๋ค์ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ์ธ์. VkFenceCreateInfo, VkSemaphoreCreateInfo
์ด์ ์ค์ ๋ก ์์ฑํด๋ด ์๋ค.
void VulkanEngine::init_sync_structures()
{
//create syncronization structures
//one fence to control when the gpu has finished rendering the frame,
//and 2 semaphores to syncronize rendering with swapchain
//we want the fence to start signalled so we can wait on it on the first frame
VkFenceCreateInfo fenceCreateInfo = vkinit::fence_create_info(VK_FENCE_CREATE_SIGNALED_BIT);
VkSemaphoreCreateInfo semaphoreCreateInfo = vkinit::semaphore_create_info();
for (int i = 0; i < FRAME_OVERLAP; i++) {
VK_CHECK(vkCreateFence(_device, &fenceCreateInfo, nullptr, &_frames[i]._renderFence));
VK_CHECK(vkCreateSemaphore(_device, &semaphoreCreateInfo, nullptr, &_frames[i]._swapchainSemaphore));
VK_CHECK(vkCreateSemaphore(_device, &semaphoreCreateInfo, nullptr, &_frames[i]._renderSemaphore));
}
}
ํ์ค์์ VK_FENCE_CREATE_SIGNALED_BIT
๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ์ด๋ ๋งค์ฐ ์ค์ํ ํฉ๋๋ค. ํ์ค๊ฐ ์์ฑ๋์ ๋ง์ ๋๊ธฐํ๊ฒ ํ์ฌ ์ค๋ฅ๋ฅผ ๋ฐฉ์งํ ์ ์์ต๋๋ค. ๋ง์ฝ ์ด๋ฅผ ์ค์ ํ์ง ์๋๋ค๋ฉด, ์ฒซ ํ๋ ์์์ WaitFences๋ฅผ ํธ์ถ ํ ๋ GPU๊ฐ ์์
์ ์ํํ๊ธฐ๋ ์ ์ ํธ์ถ ๋๋ฏ๋ก ์ฐ๋ ๋๊ฐ ์ ๊น๋๋ค.
๊ฐ ํ๋ ์๋ง๋ค 3๊ฐ์ ๊ตฌ์กฐ์ฒด๋ฅผ ์์ฑํ์ต๋๋ค. ์ด๋ฅผ ์ฌ์ฉํด ๋ ๋๋ง ๋ฃจํ๋ฅผ ์์ฑํ๊ฒ ์ต๋๋ค.
๋ ๋๋ง ๋ฃจํ
ํ์ค๋ฅผ ์ฌ์ฉํ์ฌ GPU์ ์์ ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ์ผ๋ก ๋ ๋๋ง ๋ฃจํ๋ฅผ ์์ํฉ๋๋ค.
void VulkanEngine::draw()
{
// wait until the gpu has finished rendering the last frame. Timeout of 1
// second
VK_CHECK(vkWaitForFences(_device, 1, &get_current_frame()._renderFence, true, 1000000000));
VK_CHECK(vkResetFences(_device, 1, &get_current_frame()._renderFence));
}
vkWaitForFences()
๋ฅผ ์ฌ์ฉํ์ฌ GPU๊ฐ ์์
์ ๋๋ผ ๋ ๊น์ง ๋๊ธฐํ ํ, ํ์ค๋ฅผ ๋ฆฌ์
ํฉ๋๋ค. ํ์ค๋ ์ฌ์ฉ ํ ๋ฐ๋์ ๋ฆฌ์
ํด์ผ ํฉ๋๋ค. ๊ฐ์ ํ์ค๋ฅผ ๋ฆฌ์
ํ์ง ์๊ณ ์ฌ๋ฌ GPU ๋ช
๋ น์์ ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
WaitFences ํธ์ถ์ ์๊ฐ ์ ํ์ 1์ด๋ก ์ค์ ํ์ต๋๋ค. ์ด ํจ์๋ ๋๋ ธ์ด ๋จ์๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์๊ฐ ์ ํ์ 0์ผ๋ก ์ค์ ํ๋ค๋ฉด GPU๊ฐ ๋ช ๋ น์ ์คํ์ค์ธ์ง ํ์ธํ๋ ์ฉ๋๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
๋ค์์ผ๋ก๋ ์ค์์ฒด์ธ์๊ฒ ์ด๋ฏธ์ง ์ธ๋ฑ์ค๋ฅผ ์์ฒญํ๊ฒ ์ต๋๋ค.
//request image from the swapchain
uint32_t swapchainImageIndex;
VK_CHECK(vkAcquireNextImageKHR(_device, _swapchain, 1000000000, get_current_frame()._swapchainSemaphore, nullptr, &swapchainImageIndex));
vkAcquireNextImageKHR
์ ์ฌ์ฉํด ์ค์์ฒด์ธ์์ ์ด๋ฏธ์ง ์ธ๋ฑ์ค๋ฅผ ์์ฒญํฉ๋๋ค. ์ฌ์ฉ ๊ฐ๋ฅํ ์ด๋ฏธ์ง๊ฐ ์๋ค๋ฉด ์ค์ ํ ์๊ฐ ์ ํ๊น์ง ์ฐ๋ ๋๊ฐ ๋๊ธฐ ์ํ๊ฐ ๋ฉ๋๋ค. ์ฌ๊ธฐ์ ์ ํ ์๊ฐ์ 1์ด์
๋๋ค.
_swapchainSemaphore
๋ฅผ ์ด๋ป๊ฒ ์ ๋ฌํ๋์ง ํ์ธํด๋ณด์ธ์. ์ด๋ ์ค์์ฒด์ธ์์ ๋ ๋๋งํ ์ด๋ฏธ์ง๋ฅผ ์ค๋นํ๋ ์์
๊ณผ ๋ค๋ฅธ ์ฐ์ฐ๋ค์ ๋๊ธฐํํ๊ธฐ ์ํ ๊ฒ์
๋๋ค.
ํจ์์์ ๋ฐํ๋ ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํด ๊ทธ๋ฆด ์ค์์ฒด์ธ์ ์ด๋ฏธ์ง๋ฅผ ๊ฒฐ์ ํฉ๋๋ค.
์ด์ ๋ ๋๋ง ๋ช ๋ น์ ์์ฑํ๊ฒ ์ต๋๋ค. ํด๋น ํ๋ ์์ ์ปค๋งจ๋ ๋ฒํผ๋ฅผ ๋ฆฌ์ ํ๊ณ ์์(begin)ํ๊ฒ ์ต๋๋ค. ์ฌ๊ธฐ์ ์ฌ์ฉํ ๋ ๋ค๋ฅธ ์ด๊ธฐํ ํจ์๊ฐ ํ์ํฉ๋๋ค.
VkCommandBufferBeginInfo vkinit::command_buffer_begin_info(VkCommandBufferUsageFlags flags /*= 0*/)
{
VkCommandBufferBeginInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
info.pNext = nullptr;
info.pInheritanceInfo = nullptr;
info.flags = flags;
return info;
}
์ปค๋งจ๋ ๋ฒํผ๊ฐ ์์๋๋ฉด, ๋ช ๊ฐ์ง ์์ฑ์ ๋ด์ info ๊ตฌ์กฐ์ฒด๋ฅผ ์ ๋ฌํด์ผ ํฉ๋๋ค. ์ฌ๊ธฐ์๋ ์์ ์ ๋ณด๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก nullptr
๋ก ์ค์ ํ์ง๋ง, flags
์์ฑ์ ํ์ํฉ๋๋ค.
VkCommandBufferBeginInfo์์ ๋ ์์ธํ ๋ด์ฉ์ ํ์ธํ ์ ์์ต๋๋ค.
VulkanEngine::draw()
๋ก ๋์๊ฐ, ์ปค๋งจ๋ ๋ฒํผ๋ฅผ ๋ฆฌ์
ํ๊ณ ๋ค์ ์์ํ๊ฒ ์ต๋๋ค.
//naming it cmd for shorter writing
VkCommandBuffer cmd = get_current_frame()._mainCommandBuffer;
// now that we are sure that the commands finished executing, we can safely
// reset the command buffer to begin recording again.
VK_CHECK(vkResetCommandBuffer(cmd, 0));
//begin the command buffer recording. We will use this command buffer exactly once, so we want to let vulkan know that
VkCommandBufferBeginInfo cmdBeginInfo = vkinit::command_buffer_begin_info(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
//start the command buffer recording
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
FrameData
๊ตฌ์กฐ์ฒด์์ ์ปค๋งจ๋ ๋ฒํผ๋ฅผ cmd
๋ผ๋ ๋ณ์๋ก ๋ณต์ฌํ๊ฒ ์ต๋๋ค. ์ด๋ ์ดํ ์ฝ๋์์ ์ฐธ์กฐ๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ํ๊ธฐ ์ํจ์
๋๋ค. Vulkan ํธ๋ค์ 64๋นํธ ํธ๋ค ํน์ ํฌ์ธํฐ์ผ ๋ฟ์ด๋ฏ๋ก ๋ณต์ฌํด๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๋ค๋ง ์ค์ ๋ฐ์ดํฐ๋ Vulkan์ด ๊ด๋ฆฌํจ์ ๊ธฐ์ตํ์ธ์.
์ด์ vkResetCommandBuffer
๋ฅผ ํธ์ถํด ์ปค๋งจ๋ ๋ฒํผ๋ฅผ ๋ฆฌ์
ํฉ๋๋ค. ์ด ํธ์ถ์ ์ปค๋งจ๋ ๋ฒํผ์ ๋ชจ๋ ๋ช
๋ น์ ์์ ํ ์ ๊ฑฐํ๊ณ ์ฌ์ฉ๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํฉ๋๋ค. ์ด์ vkBeginCommandBuffer
๋ฅผ ์ฌ์ฉํด ์ปค๋งจ๋ ๋ฒํผ๋ฅผ ๋ค์ ์์ํ ์ ์์ต๋๋ค. cmdBeginInfo์์ VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
ํ๋๊ทธ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด๋ ์ ํ์ด์ง๋ง ๋๋ผ์ด๋ฒ์๊ฒ ํด๋น ๋ฒํผ๊ฐ ๋จ ํ ๋ฒ๋ง ์ ์ถ๋์ด ์คํ๋ ๊ฒ์์ ์ ๊ฒฝ์ฐ, ์ฝ๊ฐ์ ์ฑ๋ฅ ํฅ์์ ๊ธฐ๋ํ ์ ์์ต๋๋ค. ํ๋ ์๋ง๋ค ์ปค๋งจ๋ ๋ฒํผ๋ฅผ ๋ฆฌ์
ํด์ ํ๋ฒ๋ง ์ ์ถํ ๊ฒ์ด๋ฏ๋ก, ์ด ์ค์ ์ด ์ ์ ํฉ๋๋ค.
์ปค๋งจ๋ ๋ฒํผ ๊ธฐ๋ก์ด ์์๋์์ผ๋ฏ๋ก, ๋ช
๋ น์ ์ถ๊ฐํ๊ฒ ์ต๋๋ค. ๋จผ์ ์ค์์ฒด์ธ์ ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋ฆด ์ ์๋ ๋ ์ด์์์ผ๋ก ๋ณํํ๊ณ , VkCmdClear
๋ฅผ ์ํํ ๋ค, ์ต์ข
์ ์ผ๋ก ๋ค์ ํ์ ์ต์ ํ ๋ ์ด์์์ผ๋ก ๋ณํํ๊ฒ ์ต๋๋ค.
์ฆ, ๋ ๋ํจ์ค๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๋ ์ปค๋งจ๋ ๋ฒํผ ๋ด๋ถ์์ ์ด๋ฏธ์ง๋ฅผ ๋ณํํ ์๋จ์ด ํ์ํ๋ค๋ ๋ป์
๋๋ค. ์ด๋ฅผ ์ํํ๋ ํจ์๋ฅผ vk_images.h
์ ์ถ๊ฐํ๊ฒ ์ต๋๋ค.
#pragma once
#include <vulkan/vulkan.h>
namespace vkutil {
void transition_image(VkCommandBuffer cmd, VkImage image, VkImageLayout currentLayout, VkImageLayout newLayout);
}
์ด๋ฏธ์ง๋ฅผ ๋ณํํ๋ ๋ฐ์๋ ์ฌ๋ฌ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ๊ธฐ์กด์ ๋ ์ด์์๊ณผ ์๋ก์ด ๋ ์ด์์๋ง์ ์ฌ์ฉํ์ฌ ์ด๋ฅผ ์ํํ๋ ๊ฐ์ฅ ๋จ์ํ ๋ฐฉ๋ฒ์ ์ ํํ๊ฒ ์ต๋๋ค.
Vulkan 1.3์ ํฌํจ๋ Synchronization 2 ๊ธฐ๋ฅ ํ์ฅ์ ์ฌ์ฉํด ํ์ดํ๋ผ์ธ ๋ฐฐ๋ฆฌ์ด๋ฅผ ์ค์ ํ๊ฒ ์ต๋๋ค. ํ์ดํ๋ผ์ธ ๋ฐฐ๋ฆฌ์ด๋ ๋ช ๋ น ๊ฐ ์ฝ๊ธฐ/์ฐ๊ธฐ ์์ ์ ๋๊ธฐํํ๊ฑฐ๋, ํ ๋ช ๋ น์ด ์ด๋ฏธ์ง์ ๊ทธ๋ฆฌ๋ ๋์ ๋ค๋ฅธ ๋ช ๋ น์ด ํด๋น ์ด๋ฏธ์ง๋ฅผ ์ฝ๋๋ก ์ ์ดํ๋ ๋ฑ์ ๋ค์ํ ์์ ์ ์ํํ ์ ์์ต๋๋ค.
ํจ์๋ฅผ vk_images.cpp
์ ์์ฑํ๊ฒ ์ต๋๋ค.
#include <vk_initializers.h>
void vkutil::transition_image(VkCommandBuffer cmd, VkImage image, VkImageLayout currentLayout, VkImageLayout newLayout)
{
VkImageMemoryBarrier2 imageBarrier {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2};
imageBarrier.pNext = nullptr;
imageBarrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
imageBarrier.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT;
imageBarrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
imageBarrier.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT;
imageBarrier.oldLayout = currentLayout;
imageBarrier.newLayout = newLayout;
VkImageAspectFlags aspectMask = (newLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
imageBarrier.subresourceRange = vkinit::image_subresource_range(aspectMask);
imageBarrier.image = image;
VkDependencyInfo depInfo {};
depInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
depInfo.pNext = nullptr;
depInfo.imageMemoryBarrierCount = 1;
depInfo.pImageMemoryBarriers = &imageBarrier;
vkCmdPipelineBarrier2(cmd, &depInfo);
}
VkImageMemoryBarrier2
๋ ์ฃผ์ด์ง ์ด๋ฏธ์ง ๋ฐฐ๋ฆฌ์ด์ ์ ๋ณด๋ฅผ ๋ด๊ณ ์์ต๋๋ค. ์ฌ๊ธฐ์๋ ๋ณ๊ฒฝ ์ ๋ ์ด์์๊ณผ ๋ณ๊ฒฝ ํ ๋ ์ด์์์ ์ค์ ํฉ๋๋ค. StageMask
๋ VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT
๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด๋ GPU ํ์ดํ๋ผ์ธ์ด ์ฝ๊ฐ ๋ฉ์ถ๊ฒ ํ๋ฏ๋ก ๋นํจ์จ์ ์ผ ์ ์์ต๋๋ค. ํ์ง๋ง ์ฐ๋ฆฌ๋ ํ๋ ์๋ง๋ค ๋ช ๋ฒ๋ง ๋ณํ์ ์ํํ๋ฏ๋ก ๋ฌธ์ ๊ฐ ๋์ง ์์ต๋๋ค. ๋ง์ฝ ํ์ฒ๋ฆฌ ๊ณผ์ ์์ ํ๋ ์๋ง๋ค ์ฌ๋ฌ ๋ฒ ๋ณํ์ ์ํํ๋ค๋ฉด ์ด๋ ๊ฒ ์ค์ ํ๋ ๊ฒ์ ํผํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๋์ ์ํํ๋ ค๋ ์์
์ ๋ ์ ์ ํ StageMask
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ฐ๋์งํฉ๋๋ค.
๋ฐฐ๋ฆฌ์ด์ VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT
์ค์ ์ GPU์ ๋ชจ๋ ๋ช
๋ น์ด ๋ฐฐ๋ฆฌ์ด์์ ์์ ํ ๋ฉ์ถ๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์กฐ๊ธ ๋ ์ธ๋ฐํ StageMask
๋ฅผ ์ฌ์ฉํ๋ฉด, GPU ํ์ดํ๋ผ์ธ์ ๋ถ๋ถ์ ์ผ๋ก ๊ฒน์ณ์ ์คํํ ์ ์์ต๋๋ค. AccessMask
๋ ์ ์ฌํ๊ฒ ๋์ํฉ๋๋ค. ์ด๋ GPU์ ํน์ ์์ ์ ๊ทผ์ ์ ํํ๋ ๋ฐฉ์์ผ๋ก ๋์ํฉ๋๋ค. ์ฌ๊ธฐ์๋ VK_ACCESS_2_MEMORY_WRITE_BIT
๋ฅผ ์์ค๋ก ์ค์ ํ๊ณ , VK_ACCESS_2_MEMORY_READ_BIT
๋ฅผ ๋์์ผ๋ก ์ถ๊ฐํ๊ฒ ์ต๋๋ค. ์ด๋ ์ผ๋ฐ์ ์ผ๋ก ์ ์ ํ ์ต์
์
๋๋ค.
๋ง์ฝ ํ์ดํ๋ผ์ธ ๋ฐฐ๋ฆฌ์ด๊ฐ ์ฐ์ด๋ ์ต์ ํ ๋ฐฉ๋ฒ์ ๋ํด ๋ ์๊ณ ์ถ๋ค๋ฉด Khronos Vulkan Documentation: Syncronization examples์์ ์ฐพ์๋ณผ ์ ์์ต๋๋ค. ์ด๋ฒ ํํ ๋ฆฌ์ผ์์๋ ์ด ๋ฐฉ์์ด ์ ์๋ํ์ง๋ง, ํ์ํ๋ค๋ฉด ๋ ์ ๋ฐํ๊ณ ๊ฐ๋ฒผ์ด ๋ณํ ํจ์๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
๋ฐฐ๋ฆฌ์ด ์ค์ ์ ์ผ๋ถ๋ก VkImageSubresourceRange
๋ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ์ด๋ฅผ ํตํด ํน์ ์ด๋ฏธ์ง์ ์ผ๋ถ๋ง ๋ฐฐ๋ฆฌ์ด๋ฅผ ์ ์ฉํ ์ ์์ต๋๋ค. ์ด๋ ๋ฐฐ์ด ์ด๋ฏธ์ง๋ ๋ฐ๋งต ์ด๋ฏธ์ง์์ ํน์ ๋ ์ด์ด๋ ๋ฐ๋งต ๋จ๊ณ์๋ง ๋ฐฐ๋ฆฌ์ด๋ฅผ ์ ์ฉํ ๋ ์ ์ฉํฉ๋๋ค. ์ด๋ฒ ์์ ์์๋ ๊ธฐ๋ณธ๊ฐ์ ์ฌ์ฉํ์ฌ ๋ชจ๋ ๋ฐ๋งต ๋จ๊ณ์ ๋ชจ๋ ๋ ์ด์ด๋ฅผ ๋ณํํ๊ฒ ์ต๋๋ค.
VkImageSubresourceRange vkinit::image_subresource_range(VkImageAspectFlags aspectMask)
{
VkImageSubresourceRange subImage {};
subImage.aspectMask = aspectMask;
subImage.baseMipLevel = 0;
subImage.levelCount = VK_REMAINING_MIP_LEVELS;
subImage.baseArrayLayer = 0;
subImage.layerCount = VK_REMAINING_ARRAY_LAYERS;
return subImage;
}
์ด ๊ตฌ์กฐ์ฒด์์ ์ค์ํ ์์๋ AspectMask
์
๋๋ค. ์ด๋ ์์ ์ด๋ฏธ์ง์ ๊ฒฝ์ฐ VK_IMAGE_ASPECT_COLOR_BIT
, ๊น์ด ์ด๋ฏธ์ง์ ๊ฒฝ์ฐ VK_IMAGE_ASPECT_DEPTH_BIT
๋ก ์ค์ ๋ฉ๋๋ค. ๊น์ด ๋ฒํผ๋ฅผ ์ถ๊ฐํ ๋ ์ฌ์ฉํ VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL
์ ์ ์ธํ๊ณ ๋ชจ๋ ๊ฒฝ์ฐ์ VK_IMAGE_ASPECT_COLOR_BIT
๋ฅผ ์ฌ์ฉํ๊ฒ ์ต๋๋ค.
๋ฒ์(VkImageSubresourceRange
)์ ๋ฐฐ๋ฆฌ์ด(VkImageMemoryBarrier2
)๋ฅผ ์ค์ ํ ํ, ์ด๋ฅผ VkDependencyInfo
๊ตฌ์กฐ์ฒด์ ๋ด์ VkCmdPipelineBarrier2
๋ฅผ ํธ์ถํฉ๋๋ค. VkImageMemoryBarrier2
์ฌ๋ฌ๊ฐ๋ฅผ VkDependencyInfo
์ ์ถ๊ฐํ๋ฉด ์ฌ๋ฌ ์ด๋ฏธ์ง์ ๋ ์ด์์์ ๋์์ ๋ณํํ ์ ์์ต๋๋ค. ์ฌ๋ฌ ์ด๋ฏธ์ง๋ฅผ ๋์์ ๋ณํํ๊ฑฐ๋ ๋ฐฐ๋ฆฌ์ด๋ฅผ ์ ์ฉํ ๊ฒฝ์ฐ ์ฑ๋ฅ์ด ํฅ์๋ ์ ์์ต๋๋ค.
๋ณํ ํจ์๊ฐ ๊ตฌํ๋์์ผ๋ฏ๋ก, ์ด์ ๋ ๋๋ง์ ์ํํ ์ ์์ต๋๋ค. ์์ฑํ ํจ์๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด vk_images.h
๋ฅผ vk_engine.cpp
์๋จ์ ํฌํจํ๊ฒ ์ต๋๋ค.
//make the swapchain image into writeable mode before rendering
vkutil::transition_image(cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
//make a clear-color from frame number. This will flash with a 120 frame period.
VkClearColorValue clearValue;
float flash = std::abs(std::sin(_frameNumber / 120.f));
clearValue = { { 0.0f, 0.0f, flash, 1.0f } };
VkImageSubresourceRange clearRange = vkinit::image_subresource_range(VK_IMAGE_ASPECT_COLOR_BIT);
//clear image
vkCmdClearColorImage(cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_GENERAL, &clearValue, 1, &clearRange);
//make the swapchain image into presentable mode
vkutil::transition_image(cmd, _swapchainImages[swapchainImageIndex],VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
//finalize the command buffer (we can no longer add commands, but it can now be executed)
VK_CHECK(vkEndCommandBuffer(cmd));
๋จผ์ ์ค์์ฒด์ธ์ ์ด๋ฏธ์ง๋ฅผ ๋ณํํฉ๋๋ค. VK_IMAGE_LAYOUT_UNDEFINED
๋ ์ด๊ธฐํ๋์ง ์์ ๋ ์ด์์์
๋๋ค. ์๋ก ์์ฑ๋ ์ด๋ฏธ์ง๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํด๋น ๋ ์ด์์์ ๊ฐ์ต๋๋ค. ์ด๋ฏธ์ง์ ๊ธฐ์กด ๋ฐ์ดํฐ๊ฐ ์ค์ํ์ง ์์ผ๋ฉฐ, GPU๊ฐ ์ด๋ฅผ ๋ฎ์ด ์จ๋ ์๊ด์์ ๋ ์ฌ์ฉํฉ๋๋ค.
์ฐ๋ฆฌ๊ฐ ์ํ๋ ๋ชฉํ ๋ ์ด์์์ VK_IMAGE_LAYOUT_GENERAL
์
๋๋ค. ์ด๋ ๋ฒ์ฉ ๋ชฉ์ ์ ๋ ์ด์์์ผ๋ก, ์ด๋ฏธ์ง๋ก๋ถํฐ ์ฝ๊ธฐ์ฐ๊ธฐ๋ฅผ ํ ์ ์์ต๋๋ค. ๋ ๋๋ง์ ์ต์ ํ๋ ๋ ์ด์์์ ์๋์ง๋ง, vkCmdClearColorImage
๋ฅผ ์ฌ์ฉํ ๋๋ ์ ํฉํฉ๋๋ค. ์ปดํจํธ ์
ฐ์ด๋์์ ์ด๋ฏธ์ง๋ฅผ ์ฐ๊ธฐํ ๋ ์ฌ์ฉํ๋ ์ด๋ฏธ์ง ๋ ์ด์์์
๋๋ค. ๋ง์ฝ ์ฝ๊ธฐ ์ ์ฉ ์ด๋ฏธ์ง ํน์ ๋์คํฐํ ๋ช
๋ น์ ์ฌ์ฉ๋๋ ์ด๋ฏธ์ง๋ฅผ ์ํ๋ค๋ฉด ๋ ๋์ ์ ํ์ง๊ฐ ์์ต๋๋ค.
์ด๋ฏธ์ง ๋ ์ด์์์ ๊ดํด ์ธ๋ถ์ฌํญ์ ํ์ธํด๋ณด๊ณ ์ถ๋ค๋ฉด Vulkan Spec: image layouts์ ํ์ธํด๋ณด์ธ์.
์ด์ _frameNumber
๋ฅผ ์ฌ์ฉํ ๊ธฐ๋ณธ์ ์ธ ๊ณต์์ ํตํด ์ด๊ธฐํ ์์์ ๊ณ์ฐํฉ๋๋ค. sinํจ์๋ฅผ ์ฌ์ฉํด ํ๋์๊ณผ ๊ฒ์์ ๊ฐ ๋ณด๊ฐํ๋ฉฐ ์ฃผ๊ธฐ์ ์ผ๋ก ๋ณํํฉ๋๋ค.
vkCmdClearColorImage
๋ ์ธ๊ฐ์ง ์ฃผ์ ์ธ์๋ฅผ ๋ฐ์ต๋๋ค. ์ฒซ ๋ฒ์งธ๋ ์ด๋ฏธ์ง์ด๋ฉฐ, ์ค์์ฒด์ธ์์ ๊ฐ์ ธ์จ ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋ ๋ฒ์งธ๋ ์ด๊ธฐํ ์์์
๋๋ค. ๋ง์ง๋ง์ ์ด๋ฏธ์ง์ ์ด๋ ๋ถ๋ถ์ ์ด๊ธฐํํ ์ง๋ฅผ ๋ํ๋ด๋ SubresourceRange
๋ฅผ ์๊ตฌํฉ๋๋ค. ์ฌ๊ธฐ์๋ ๊ธฐ๋ณธ ImageSubresourceRange
๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์ด๊ธฐํ ๋ช
๋ น์ด ์คํํ ํ, ์ด๋ฏธ์ง๋ฅผ ํ๋ฉด์ ํ์ํ ์ ์๋๋ก VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
๋ ์ด์์์ผ๋ก ๋ฐ๊พธ์ด์ผ ํฉ๋๋ค. ๋ง์ง๋ง์ผ๋ก๋ vkEndCommandBuffer
๋ฅผ ํธ์ถํด ์ปค๋งจ๋ ๋ฒํผ๋ฅผ ์ข
๋ฃํฉ๋๋ค.
์ด์ GPU์ ์ ์ถํ ์ค๋น๊ฐ ๋ ์ปค๋งจ๋ ๋ฒํผ๊ฐ ์์ฑ๋์์ต๋๋ค. ํ์ง๋ง VkQueueSubmit
์ ํธ์ถํ๊ธฐ ์ ์ ๋๊ธฐํ ๊ตฌ์กฐ๋ฅผ ์ค์ ํ์ง ์์ผ๋ฉด ์ค์์ฒด์ธ๊ณผ ์ฌ๋ฐ๋ฅด๊ฒ ์ํธ์์ฉํ ์ ์์ต๋๋ค.
๋ช
๋ น์ ์ ์ถํ ๋๋ vkQueueSubmit2
๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด๋ synchronization-2์ ์ผ๋ถ๋ก, Vulkan 1.0 ๋ฒ์ ์์์ VkQueueSubmit
์ด ๊ฐ์ ๋ ๋ฒ์ ์
๋๋ค. ์ด ํจ์๋ ์ ์ถ์ ํ์ํ ์ธ๋งํฌ์ด์ ์ ๋ณด๋ฅผ ๋ด๋ VkSubmitInfo2
๊ฐ ํ์ํ๋ฉฐ, ์คํ ์๋ฃ๋ฅผ ํ์ธํ๊ธฐ ์ํด ํ์ค๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค. VkSubmitInfo2
๋ ์ฌ์ฉ๋๋ ๊ฐ๊ฐ์ ์ธ๋งํฌ์ด์ ๋ํด VkSemaphoreSubmitInfo
๊ฐ ํ์ํ๋ฉฐ, ์ ์ถ๋ ์ปค๋งจ๋ ๋ฒํผ๋ค์ ๋ํด์๋ VkCommandBufferSubmitInfo
๊ฐ ํ์ํฉ๋๋ค. ์ด๋ฌํ ๊ตฌ์กฐ์ฒด๋ค์ ์์ฑํ๋ vkinit
ํจ์๋ค์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
VkSemaphoreSubmitInfo vkinit::semaphore_submit_info(VkPipelineStageFlags2 stageMask, VkSemaphore semaphore)
{
VkSemaphoreSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
submitInfo.pNext = nullptr;
submitInfo.semaphore = semaphore;
submitInfo.stageMask = stageMask;
submitInfo.deviceIndex = 0;
submitInfo.value = 1;
return submitInfo;
}
VkCommandBufferSubmitInfo vkinit::command_buffer_submit_info(VkCommandBuffer cmd)
{
VkCommandBufferSubmitInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
info.pNext = nullptr;
info.commandBuffer = cmd;
info.deviceMask = 0;
return info;
}
VkSubmitInfo2 vkinit::submit_info(VkCommandBufferSubmitInfo* cmd, VkSemaphoreSubmitInfo* signalSemaphoreInfo,
VkSemaphoreSubmitInfo* waitSemaphoreInfo)
{
VkSubmitInfo2 info = {};
info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
info.pNext = nullptr;
info.waitSemaphoreInfoCount = waitSemaphoreInfo == nullptr ? 0 : 1;
info.pWaitSemaphoreInfos = waitSemaphoreInfo;
info.signalSemaphoreInfoCount = signalSemaphoreInfo == nullptr ? 0 : 1;
info.pSignalSemaphoreInfos = signalSemaphoreInfo;
info.commandBufferInfoCount = 1;
info.pCommandBufferInfos = cmd;
return info;
}
command_buffer_submit_info
๋ ์ปค๋งจ๋ ๋ฒํผ๋ง์ ์๊ตฌํฉ๋๋ค. ๋ค๋ฅธ ์ ๋ณด๋ ํ์ํ์ง ์์ผ๋ฉฐ, deviceMask
๋ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก 0์ผ๋ก ๋จ๊ฒจ๋ ์ ์์ต๋๋ค.
semaphore_submit_info
๋ StageMask
๋ฅผ ์๊ตฌํฉ๋๋ค. ์ด๋ transition_image
ํจ์์์ ๋ณด์๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค. ์ด์ธ์๋ ์ธ๋งํฌ์ด ํธ๋ค๋ง์ด ํ์ํฉ๋๋ค. ๋๋ฐ์ด์ค ์ธ๋ฑ์ค ์ธ์๋ ์ฌ๋ฌ GPU๊ฐ ์ธ๋งํฌ์ด๋ฅผ ์ฌ์ฉํ ๋ ํ์ํ์ง๋ง, ์ฌ๊ธฐ์๋ ์ฌ์ฉํ์ง ์์๊ฒ์
๋๋ค. ๊ทธ๋ฆฌ๊ณ value
๋ ํ์๋ผ์ธ ์ธ๋งํฌ์ด์ ์ฌ์ฉ๋๋ฉฐ, ์ด๋ ์ด์ง ์ํ ๋์ ์นด์ดํฐ ๊ฐ์ ์ฆ๊ฐ์ํค๋ฉฐ ๋์ํ๋ ํน์ํ ์ธ๋งํฌ์ด์
๋๋ค. ์ฌ๊ธฐ์๋ ์ฌ์ฉํ์ง ์์ ๊ฒ์ด๋ฏ๋ก ๊ธฐ๋ณธ์ ์ผ๋ก 1๋ก ์ค์ ํ ์ ์์ต๋๋ค.
submit_info
๋ ๋ชจ๋ ๊ฒ์ ํ๋๋ก ๋ฌถ์ต๋๋ค. ์ด๋ VkCommandSubmitInfo
์ signalSemaphoreInfo
,waitSemaphoreInfo
๋ฅผ ํ์๋ก ํฉ๋๋ค. ๋๊ธฐ์ ์ ํธ์ ๊ฐ๊ฐ 1๊ฐ์ ์ธ๋งํฌ์ด์ฉ์ ์ฌ์ฉํ ๊ฒ์ด์ง๋ง, ๋ณต์กํ ์์คํ
์์๋ ์ฌ๋ฌ ์ธ๋งํฌ์ด๋ฅผ ๋์์ ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
๊ตฌ์กฐ์ฒด์ ๋ํ ๋ ๋ง์ ์ ๋ณด๋ ๋ค์ ๋งํฌ์์ ํ์ธํ ์ ์์ต๋๋ค. VkCommandBufferSubmitInfo, VkSemaphoreSubmitInfo, VkSubmitInfo2
์ด์ ์ด๊ธฐํํ ์ค๋น๊ฐ ๋์์ผ๋ ์ ์ถ์ ์ํํด๋ด ์๋ค.
//prepare the submission to the queue.
//we want to wait on the _presentSemaphore, as that semaphore is signaled when the swapchain is ready
//we will signal the _renderSemaphore, to signal that rendering has finished
VkCommandBufferSubmitInfo cmdinfo = vkinit::command_buffer_submit_info(cmd);
VkSemaphoreSubmitInfo waitInfo = vkinit::semaphore_submit_info(VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,get_current_frame()._swapchainSemaphore);
VkSemaphoreSubmitInfo signalInfo = vkinit::semaphore_submit_info(VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT, get_current_frame()._renderSemaphore);
VkSubmitInfo2 submit = vkinit::submit_info(&cmdinfo,&signalInfo,&waitInfo);
//submit command buffer to the queue and execute it.
// _renderFence will now block until the graphic commands finish execution
VK_CHECK(vkQueueSubmit2(_graphicsQueue, 1, &submit, get_current_frame()._renderFence));
๋จผ์ ํ์ํ info ๊ตฌ์กฐ์ฒด๋ฅผ ๋ง๋ค์ด vkQueueSubmit2
๋ฅผ ํธ์ถํฉ๋๋ค.cmdInfo
์๋ ์์ฑํ ์ปค๋งจ๋ ๋ฒํผ๋ฅผ ์ ๋ฌํ ๊ฒ์
๋๋ค. waitInfo
์๋ ํ์ฌ ํ๋ ์์ ์ค์์ฒด์ธ ์ธ๋งํฌ์ด๋ฅผ ์ฌ์ฉํ ๊ฒ์
๋๋ค. vkAcquireNextImageKHR
์ ํธ์ถํ ๋, ๊ฐ์ ์ธ๋งํฌ์ด๋ฅผ ์ ํธํ๋๋ก ์ค์ ํ์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ์ค์์ฒด์ธ ์ด๋ฏธ์ง๊ฐ ์ค๋น๋ ๋ ๊น์ง ํด๋น ๋ช
๋ น์ด ์คํ๋์ง ์๊ฒ ๋ณด์ฅํฉ๋๋ค.
signalInfo
์๋ ํ์ฌ ํ๋ ์์ _renderSemaphore
๋ฅผ ์ค์ ํฉ๋๋ค. ์ด๋ ํ๋ฉด์ ์ด๋ฏธ์ง๋ฅผ ํ์ํ๋ ์์
๊ณผ ๋๊ธฐํํฉ๋๋ค.
fence
์๋ ํ์ฌ ํ๋ ์์ _renderFence
๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋ ๋๋ง ๋ฃจํ ์์์ง์ ์์ ํด๋น ํ์ค๊ฐ ์ค๋น๋๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฝ๋๋ค. ์ด๊ฒ์ด CPU์ GPU๋ฅผ ๋๊ธฐํํ๋ ๋ฐฉ๋ฒ์
๋๋ค. CPU์ ์งํ์ ๋ฉ์ถ์ด GPU๊ฐ ์์
์ ์๋ฃํ ๋ ๊น์ง ํ์ฌ ํ๋ ์์ ๋ค๋ฅธ ์์์ ์ฌ์ฉํ์ง ์๋๋ก ํฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก ํ ์ผ์ ๊ทธ๋ฆฐ ์ด๋ฏธ์ง๋ฅผ ํ๋ฉด์ ํ์ํ๋ ๊ฒ์ ๋๋ค.
//prepare present
// this will put the image we just rendered to into the visible window.
// we want to wait on the _renderSemaphore for that,
// as its necessary that drawing commands have finished before the image is displayed to the user
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = nullptr;
presentInfo.pSwapchains = &_swapchain;
presentInfo.swapchainCount = 1;
presentInfo.pWaitSemaphores = &get_current_frame()._renderSemaphore;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pImageIndices = &swapchainImageIndex;
VK_CHECK(vkQueuePresentKHR(_graphicsQueue, &presentInfo));
//increase the number of frames drawn
_frameNumber++;
vkQueuePresent
๋ info ๊ตฌ์กฐ์ฒด๋ ํ ์ ์ถ ๊ตฌ์กฐ์ฒด์ ๋งค์ฐ ์ ์ฌํฉ๋๋ค. ์ด ๊ตฌ์กฐ์ฒด์๋ ์ธ๋งํฌ์ด์ ํฌ์ธํฐ ๋ฟ๋ง ์๋๋ผ ์ด๋ฏธ์ง ์ธ๋ฑ์ค์ ์ค์์ฒด์ธ์ ์ธ๋ฑ์ค๋ฅผ ๊ฐ์ต๋๋ค. _renderSemaphore
๋ฅผ ๋๊ธฐํ๊ณ ์ด๋ฅผ ์ค์์ฒด์ธ๊ณผ ์ฐ๊ฒฐํ๊ฒ ์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ด์ ์ ์ ์ถ๋ ๋ ๋๋ง ๋ช
๋ น์ด ์๋ฃ๋ ๋ ๊น์ง ์ด๋ฏธ์ง๋ฅผ ํ๋ฉด์ ํ์ํ์ง ์์ ๊ฒ์
๋๋ค.
ํจ์์ ๋์์ ํ๋ ์ ์นด์ดํฐ๋ฅผ ์ฆ๊ฐ์์ผ์ค๋๋ค.
์ด๋ก์จ ๋ ๋๋ง ๋ฃจํ๋ฅผ ์์ฑํ์ต๋๋ค. ๋จ์ ๊ฒ์ ๋๊ธฐํ ์์์ ์ฌ๋ฐ๋ฅด๊ฒ ํด์ ํ๋ ๊ฒ์ ๋๋ค.
for (int i = 0; i < FRAME_OVERLAP; i++) {
//already written from before
vkDestroyCommandPool(_device, _frames[i]._commandPool, nullptr);
//destroy sync objects
vkDestroyFence(_device, _frames[i]._renderFence, nullptr);
vkDestroySemaphore(_device, _frames[i]._renderSemaphore, nullptr);
vkDestroySemaphore(_device ,_frames[i]._swapchainSemaphore, nullptr);
}
์ด์ ์์ง์ ์คํํด ๋ณด์ธ์. ์ ์์ ์ผ๋ก ์๋ํ๋ค๋ฉด ํ๋ ํ๋ฉด์ด ๊น๋นก์ผ ๊ฒ์ ๋๋ค. ๋๊ธฐํ ๋ฌธ์ ๋ฅผ ๊ฐ์งํ ์ ์๋๋ก ๊ฒ์ฆ ๋ ์ด์ด๋ฅผ ํ์ธํด๋ณด์ธ์.
์ด๊ฒ์ผ๋ก 1์ฅ์ด ๋๋ฌ์ต๋๋ค. ๋ค์์ ์ปดํจํธ ์ ฐ์ด๋๋ฅผ ์ฌ์ฉํด ๋จ์ํ ๊น๋นก์ด๋ ํ๋ฉด๋ณด๋ค ์กฐ๊ธ ๋ ๋ณต์กํ ๊ฒ์ ๊ทธ๋ ค๋ณด๊ฒ ์ต๋๋ค.
Next: Chapter 2: Improving the render loop