[Vulkan] Vulkan Descriptor set layout
다음 내용은 OpenGL에는 존재하지 않고 Vulkan에서도 다소 복잡한 개념이다.
목차
인프런 삼각형님의 '삼각형의 실전! Vulkan 중급' 강의를 참고하였습니다.
😎 [삼각형의 실전! Vulkan 중급] 강의 들으러 가기!
Vulkan Descriptor set layout
삼각형이 움직이기 위해서는 필요한 것
삼각형을 움직이기 위해서 유니폼이 필요하다.
Vulkan에서는 유니폼 블록을 사용해서 유니폼을 정의해야 한다. 아래의 코드를 보면 OpenGL의 유니폼과 생김새가 다른 것을 알 수 있다. set, binding 같은 새로운 키워드가 등장한다.
"layout(set = 0, binding = 0) uniform Uniform { \n"
" float position[2]; \n"
"}; \n"
"void main() { \n"
" gl_Position = vec4(inPosition, 1.0); \n"
" gl_Position.x += position[0]; \n"
" gl_Position.y += position[1]; \n"
"} \n"
위의 예제 코드에서는 위치 정보를 저장하는데 데이터 타입으로 Vector2 대신 float 배열을 사용했다. (float position[2]). 이는 std140 메모리 레이아웃을 설명하기 위한 선택으로 실제 응용에서는 Vector2 사용이 더 효과적이다.
GPU는 여러 개의 하드웨어 모듈로 구성되어 있다. Graphics pipeline도 GPU를 구성하는 하나의 하드웨어 모듈인데 이 하드웨어가 유니폼 블록과 같은 리소스를 읽기 위해서는 인터페이스가 필요하다.
C++ 함수에서 파라미터를 사용하기 위해서는 어떤 파라미터를 사용할 수 있는지 알아야합니다.
int foo(int a, int b, int c) {
return a + b + c;
}
Graphics pipeline도 리소스에 접근하기 위해서 사용할 리소스가 무엇인지 알고 있어야 합니다
리소스 접근을 위한 인터페이스
Graphics pipeline이 리소스에 접근하기 위해 필요한 인터페이스
이 인터페이스는 Graphics pipeline에 어떤 리소스를 사용할 수 있는지를 정의한다.
예를 들어, 유니폼 블록에서 set과 Binding이 모두 0으로 설정되었다면, 이는 해당 유니폼 블록에 필요한 리소스가 Desciptor set layout 0 위치에 설정되어 있다는 것을 의미한다. 반면 set이 1이고 Binding이 0인 경우에는 Desciptor set layout 1 위치에 리소스가 설정되어 있어야 한다.
이러한 Descriptr set layout들은 함께 묶여야 Pipeline layout을 형성한다. 이 Pipeline layout이 Graphics pipeline이 리소스를 읽기 위한 인터페이스다.
VkDescriptorSetLayoutCreateInfo 구조체
typedef struct VkDescriptorSetLayoutCreateInfo {
VkStructureType sType;
const void* pNext;
VkDescriptorSetLayoutCreateFlags flags;
uint32_t bindingCount;
const VkDescriptorSetLayoutBinding* pBindings;
} VkDescriptorSetLayoutCreateInfo;
멤버 변수 | 설명 |
sType | 구조체의 타입 |
pNext | NULL 또는 확장 기능 구조체의 포인터 |
flags | 일단 0을 사용 |
bindingCount | VkDescriptorSetLayoutBinding의 개수 |
pBindings | VkDescriptorSetLayoutBinding 배열의 포인터 |
VkDescriptorSetLayoutBinding 구조체
typedef struct VkDescriptorSetLayoutBinding {
uint32_t binding;
VkDescriptorType descriptorType;
uint32_t descriptorCount;
VkShaderStageFlags stageFlags;
const VkSampler* pImmutableSamplers;
} VkDescriptorSetLayoutBinding;
멤버 변수 | 설명 |
binding | 바인딩 인덱스 |
descriptorType | VkDescriptorType |
descriptorCount | 일단 1을 사용 |
stageFlags | VkShaderStageFlagBits의 조합 |
pImmutableSamplers | 일단 NULL을 사용 |
VkDescriptorType 열거형
바인딩을 정의할 때도 바인딩의 타입을 명시해야 된다.
typedef enum VkDescriptorType {
VK_DESCRIPTOR_TYPE_SAMPLER = 0,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER = 1,
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE = 2,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE = 3,
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER = 4,
VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER = 5,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER = 6,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER = 7,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC = 8,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC = 9,
VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT = 10,
VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT = 1000138000,
VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV = 1000165000,
VK_DESCRIPTOR_TYPE_MAX_ENUM = 0x7FFFFFFF
} VkDescriptorType;
바인딩을 0으로 설정하고, Descriptor type을 유니폼 버퍼로 정의하고, 현재 이 유니폼 버퍼는 Vertex Shader에서만 접근하도록 설정되어 있다.
만약 이 유니폼 블록이 Fragment Shader에서도 접근이 필요하다면 VK _SHADER_STAGE_FRAGMENT_BIT을 추가해야 한다.
이렇게 어떤 Shader stage에서 리소스에접근할 수 있는지를 명확히 정의함으로써 Graphics pipeline의 성능을 최대로 이끌어낼 수 있다.
VkDescriptorSetLayoutCreateInfo 구조체
앞서 정의한 바인딩에 대한 정보(= &descriptorSetLayoutBinding)만 정의해주면 된다.
Vulkan Descriptor set layout 생성
VkResult vkCreateDescriptorSetLayout(
VkDevice device,
const VkDescriptorSetLayoutCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDescriptorSetLayout* pSetLayout);
파라미터 | 설명 |
device | VkDevice |
pCreateInfo | VkDescriptorSetLayoutCreateInfo 변수의 포인터 |
pAllocator | 일단 NULL을 사용 |
pSetLayout | VkDescriptorSetLayout 변수의 포인터 |
mDescriptorSetLayout 생성
Vulkan Descriptor set layout 파괴
void vkDestroyDescriptorSetLayout(
VkDevice device,
VkDescriptorSetLayout descriptorSetLayout,
const VkAllocationCallbacks* pAllocator);
파라미터 | 설명 |
device | VkDevice |
descriptorSetLayout | VkDescriptorSetLayout |
pAllocator | 일단 NULL을 사용 |
코드
#include ...
using namespace std;
VkRenderer::VkRenderer(ANativeWindow *window) {
// 1. VkInstance 생성
// 2. VkPhysicalDevice 선택
// 3. VkPhysicalDeviceMemoryProperties 얻기
// 4. VkDevice 생성
// 5. VkSurface 생성
// 6. VkSwapchain 생성
mSwapchainImageViews.resize(swapchainImageCount); // ImageView를 Swapchain의 개수만큼 생성
for (auto i = 0; i != swapchainImageCount; ++i) {
// 7. VkImageView 생성
}
// 8. VkCommandPool 생성
// 9. VkCommandBuffer 할당
// 10. VkFence 생성
// 11. VkSemaphore 생성
// 12. VkRenderPass 생성
mFramebuffers.resize(swapchainImageCount);
for (auto i = 0; i != swapchainImageCount; ++i) {
// 13. VkFramebuffer 생성
}
// 14. Vertex VkShaderModule 생성
// 15. Fragment VkShaderModule 생성
// ================================================================================
// 16. VkDescriptorSetLayout 생성
// ================================================================================
VkDescriptorSetLayoutBinding descriptorSetLayoutBinding{
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT
};
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1,
.pBindings = &descriptorSetLayoutBinding
};
VK_CHECK_ERROR(vkCreateDescriptorSetLayout(mDevice,
&descriptorSetLayoutCreateInfo,
nullptr,
&mDescriptorSetLayout));
// 17. VkPipelineLayout 생성
// 18. Graphics VkPipeline 생성
// 19. Vertex VkBuffer 생성
// 20. Vertex VkBuffer의 VkMemoryRequirements 얻기
// 21. Vertex VkDeviceMemory를 할당 할 수 있는 메모리 타입 인덱스 얻기
// 22. Vertex VkDeviceMemory 할당
// 23. Vertex VkBuffer와 Vertex VkDeviceMemory 바인드
// 24. Vertex 데이터 복사
}
VkRenderer::~VkRenderer() {
...
vkDestroyDescriptorSetLayout(mDevice, mDescriptorSetLayout, nullptr);
...
}
void VkRenderer::render() {
// 1. 화면에 출력할 수 있는 VkImage 얻기
// 2. VkFence 기다린 후 초기화
// 3. VkCommandBuffer 초기화
// 4. VkCommandBuffer 기록 시작
// 5. VkRenderPass 시작
// 6. Graphics VkPipeline 바인드
// ================================================================================
// 7. Vertex VkBuffer 바인드
// ================================================================================
VkDeviceSize vertexBufferOffset{0};
vkCmdBindVertexBuffers(mCommandBuffer, 0, 1, &mVertexBuffer, &vertexBufferOffset);
// 8. 삼각형 그리기
// 9. VkRenderPass 종료
// -------------9. Clear 색상 갱신--------------삭제됨.
// 10. VkCommandBuffer 기록 종료
// 11. VkCommandBuffer 제출
// 12. VkImage 화면에 출력
}
전체코드
// MIT License
//
// Copyright (c) 2024 Daemyung Jang
//
// 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 <cassert>
#include <cstddef>
#include <array>
#include <vector>
#include <iomanip>
#include "VkRenderer.h"
#include "VkUtil.h"
#include "AndroidOut.h"
using namespace std;
struct Vector3 {
union {
float x;
float r;
};
union {
float y;
float g;
};
union {
float z;
float b;
};
};
struct Vertex {
Vector3 position;
Vector3 color;
};
VkRenderer::VkRenderer(ANativeWindow *window) {
// ================================================================================
// 1. VkInstance 생성
// ================================================================================
// VkApplicationInfo 구조체 정의
VkApplicationInfo applicationInfo{
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "Practice Vulkan",
.applicationVersion = VK_MAKE_API_VERSION(0, 0, 1, 0),
.apiVersion = VK_MAKE_API_VERSION(0, 1, 3, 0)
};
// 사용할 수 있는 레이어를 얻어온다.
uint32_t instanceLayerCount;
VK_CHECK_ERROR(vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr));
vector<VkLayerProperties> instanceLayerProperties(instanceLayerCount);
VK_CHECK_ERROR(vkEnumerateInstanceLayerProperties(&instanceLayerCount,
instanceLayerProperties.data()));
// 활성화할 레이어의 이름을 배열로 만든다.
vector<const char*> instanceLayerNames;
for (const auto &layerProperty : instanceLayerProperties) {
instanceLayerNames.push_back(layerProperty.layerName);
}
uint32_t instanceExtensionCount; // 사용 가능한 InstanceExtension 개수
VK_CHECK_ERROR(vkEnumerateInstanceExtensionProperties(nullptr,
&instanceExtensionCount,
nullptr));
vector<VkExtensionProperties> instanceExtensionProperties(instanceExtensionCount);
VK_CHECK_ERROR(vkEnumerateInstanceExtensionProperties(nullptr,
&instanceExtensionCount,
instanceExtensionProperties.data()));
vector<const char *> instanceExtensionNames; // instanceExtensionName을 담는 배열
for (const auto &properties: instanceExtensionProperties) {
if (properties.extensionName == string("VK_KHR_surface") ||
properties.extensionName == string("VK_KHR_android_surface")) {
instanceExtensionNames.push_back(properties.extensionName);
}
}
assert(instanceExtensionNames.size() == 2); // 반드시 2개의 이름이 필요하기 때문에 확인
// sType: 구조체의 타입, pApplicationInfo: 어플리케이션의 이름
// enabledLayerCount, ppEnableLayerNames: 사용할 레이어의 정보를 정의
VkInstanceCreateInfo instanceCreateInfo{
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &applicationInfo,
.enabledLayerCount = static_cast<uint32_t>(instanceLayerNames.size()),
.ppEnabledLayerNames = instanceLayerNames.data(),
.enabledExtensionCount = static_cast<uint32_t>(instanceExtensionNames.size()),
.ppEnabledExtensionNames = instanceExtensionNames.data()
};
// vkCreateInstance로 인스턴스 생성. 생성된 인스턴스가 mInstance에 쓰여진다.
VK_CHECK_ERROR(vkCreateInstance(&instanceCreateInfo, nullptr, &mInstance));
// ================================================================================
// 2. VkPhysicalDevice 선택
// ================================================================================
uint32_t physicalDeviceCount;
VK_CHECK_ERROR(vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, nullptr));
vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
VK_CHECK_ERROR(vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, physicalDevices.data()));
// 간단한 예제를 위해 첫 번째 VkPhysicalDevice를 사용
mPhysicalDevice = physicalDevices[0];
VkPhysicalDeviceProperties physicalDeviceProperties; // 이 구조체 안에 GPU에 필요한 모든 정보가 있다.
vkGetPhysicalDeviceProperties(mPhysicalDevice, &physicalDeviceProperties);
aout << "Selected Physical Device Information ↓" << endl;
aout << setw(16) << left << " - Device Name: "
<< string_view(physicalDeviceProperties.deviceName) << endl;
aout << setw(16) << left << " - Device Type: "
<< vkToString(physicalDeviceProperties.deviceType) << endl;
aout << std::hex;
aout << setw(16) << left << " - Device ID: " << physicalDeviceProperties.deviceID << endl;
aout << setw(16) << left << " - Vendor ID: " << physicalDeviceProperties.vendorID << endl;
aout << std::dec;
aout << setw(16) << left << " - API Version: "
<< VK_API_VERSION_MAJOR(physicalDeviceProperties.apiVersion) << "."
<< VK_API_VERSION_MINOR(physicalDeviceProperties.apiVersion);
aout << setw(16) << left << " - Driver Version: "
<< VK_API_VERSION_MAJOR(physicalDeviceProperties.driverVersion) << "."
<< VK_API_VERSION_MINOR(physicalDeviceProperties.driverVersion);
// ================================================================================
// 3. VkPhysicalDeviceMemoryProperties 얻기
// ================================================================================
vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &mPhysicalDeviceMemoryProperties);
// ================================================================================
// 4. VkDevice 생성
// ================================================================================
uint32_t queueFamilyPropertiesCount;
//---------------------------------------------------------------------------------
//** queueFamily 속성을 조회
// 사용 가능한 queueFamily의 수(=queueFamilyPropertiesCount)를 얻어온다.
vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyPropertiesCount, nullptr);
vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyPropertiesCount);
// 해당 queueFamily들의 속성을 배열에 얻어온다.
vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueFamilyPropertiesCount, queueFamilyProperties.data());
//---------------------------------------------------------------------------------
// 특정 queueFamilyProperties가 VK_QUEUE_GRAPHICS_BIT를 지원하는지 확인.
// 지원하는 queueFamilyProperties를 찾으면 break. queueFamily에 대한 정보는 mQueueFamilyIndex에 저장.
for (mQueueFamilyIndex = 0;
mQueueFamilyIndex != queueFamilyPropertiesCount; ++mQueueFamilyIndex) {
if (queueFamilyProperties[mQueueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
break;
}
}
// 생성할 큐를 정의
const vector<float> queuePriorities{1.0};
VkDeviceQueueCreateInfo deviceQueueCreateInfo{
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = mQueueFamilyIndex, // queueFamilyIndex
.queueCount = 1, // 생성할 큐의 개수
.pQueuePriorities = queuePriorities.data() // 큐의 우선순위
};
uint32_t deviceExtensionCount; // 사용 가능한 deviceExtension 개수
VK_CHECK_ERROR(vkEnumerateDeviceExtensionProperties(mPhysicalDevice,
nullptr,
&deviceExtensionCount,
nullptr));
vector<VkExtensionProperties> deviceExtensionProperties(deviceExtensionCount);
VK_CHECK_ERROR(vkEnumerateDeviceExtensionProperties(mPhysicalDevice,
nullptr,
&deviceExtensionCount,
deviceExtensionProperties.data()));
vector<const char *> deviceExtensionNames;
for (const auto &properties: deviceExtensionProperties) {
if (properties.extensionName == string("VK_KHR_swapchain")) {
deviceExtensionNames.push_back(properties.extensionName);
}
}
assert(deviceExtensionNames.size() == 1); // VK_KHR_swapchain이 반드시 필요하기 때문에 확인
// 생성할 Device 정의
VkDeviceCreateInfo deviceCreateInfo{
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = 1, // 큐의 개수
.pQueueCreateInfos = &deviceQueueCreateInfo, // 생성할 큐의 정보
.enabledExtensionCount = static_cast<uint32_t>(deviceExtensionNames.size()),
.ppEnabledExtensionNames = deviceExtensionNames.data() // 활성화하려는 deviceExtension들을 넘겨줌
};
// vkCreateDevice를 호출하여 Device 생성(= mDevice 생성)
VK_CHECK_ERROR(vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice));
// 생성된 Device(= mDevice)로부터 큐를 vkGetDeviceQueue를 호출하여 얻어온다.
vkGetDeviceQueue(mDevice, mQueueFamilyIndex, 0, &mQueue);
// ================================================================================
// 5. VkSurface 생성
// ================================================================================
VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo{
.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
.window = window
};
// surface 생성.
VK_CHECK_ERROR(vkCreateAndroidSurfaceKHR(mInstance, &surfaceCreateInfo, nullptr, &mSurface));
VkBool32 supported; // surface 지원 여부
VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceSupportKHR(mPhysicalDevice,
mQueueFamilyIndex,
mSurface,
&supported)); // 지원 여부를 받아옴.
assert(supported);
// ================================================================================
// 6. VkSwapchain 생성
// ================================================================================
VkSurfaceCapabilitiesKHR surfaceCapabilities;
VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDevice,
mSurface,
&surfaceCapabilities));
VkCompositeAlphaFlagBitsKHR compositeAlpha = VK_COMPOSITE_ALPHA_FLAG_BITS_MAX_ENUM_KHR;
for (auto i = 0; i <= 4; ++i) {
if (auto flag = 0x1u << i; surfaceCapabilities.supportedCompositeAlpha & flag) {
compositeAlpha = static_cast<VkCompositeAlphaFlagBitsKHR>(flag);
break;
}
}
assert(compositeAlpha != VK_COMPOSITE_ALPHA_FLAG_BITS_MAX_ENUM_KHR);
VkImageUsageFlags swapchainImageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
assert(surfaceCapabilities.supportedUsageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
uint32_t surfaceFormatCount = 0;
VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice,
mSurface,
&surfaceFormatCount,
nullptr));
vector<VkSurfaceFormatKHR> surfaceFormats(surfaceFormatCount);
VK_CHECK_ERROR(vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice,
mSurface,
&surfaceFormatCount,
surfaceFormats.data()));
uint32_t surfaceFormatIndex = VK_FORMAT_MAX_ENUM;
for (auto i = 0; i != surfaceFormatCount; ++i) {
if (surfaceFormats[i].format == VK_FORMAT_R8G8B8A8_UNORM) {
surfaceFormatIndex = i;
break;
}
}
assert(surfaceFormatIndex != VK_FORMAT_MAX_ENUM);
uint32_t presentModeCount;
VK_CHECK_ERROR(vkGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDevice,
mSurface,
&presentModeCount,
nullptr));
vector<VkPresentModeKHR> presentModes(presentModeCount);
VK_CHECK_ERROR(vkGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDevice,
mSurface,
&presentModeCount,
presentModes.data()));
uint32_t presentModeIndex = VK_PRESENT_MODE_MAX_ENUM_KHR;
for (auto i = 0; i != presentModeCount; ++i) {
if (presentModes[i] == VK_PRESENT_MODE_FIFO_KHR) {
presentModeIndex = i;
break;
}
}
assert(presentModeIndex != VK_PRESENT_MODE_MAX_ENUM_KHR);
VkSwapchainCreateInfoKHR swapchainCreateInfo{
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = mSurface,
.minImageCount = surfaceCapabilities.minImageCount,
.imageFormat = surfaceFormats[surfaceFormatIndex].format,
.imageColorSpace = surfaceFormats[surfaceFormatIndex].colorSpace,
.imageExtent = mSwapchainImageExtent,
.imageArrayLayers = 1,
.imageUsage = swapchainImageUsage,
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
.preTransform = surfaceCapabilities.currentTransform,
.compositeAlpha = compositeAlpha,
.presentMode = presentModes[presentModeIndex]
};
VK_CHECK_ERROR(vkCreateSwapchainKHR(mDevice, &swapchainCreateInfo, nullptr, &mSwapchain));
uint32_t swapchainImageCount;
VK_CHECK_ERROR(vkGetSwapchainImagesKHR(mDevice, mSwapchain, &swapchainImageCount, nullptr));
mSwapchainImages.resize(swapchainImageCount);
VK_CHECK_ERROR(vkGetSwapchainImagesKHR(mDevice,
mSwapchain,
&swapchainImageCount,
mSwapchainImages.data()));
mSwapchainImageViews.resize(swapchainImageCount); // ImageView를 Swapchain의 개수만큼 생성
for (auto i = 0; i != swapchainImageCount; ++i) {
// ================================================================================
// 7. VkImageView 생성
// ================================================================================
VkImageViewCreateInfo imageViewCreateInfo{ // 생성할 ImageView를 정의
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = mSwapchainImages[i],
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = surfaceFormats[surfaceFormatIndex].format, // Swapchain 이미지 포맷과 동일한 포맷으로 설정
.components = {
.r = VK_COMPONENT_SWIZZLE_R,
.g = VK_COMPONENT_SWIZZLE_G,
.b = VK_COMPONENT_SWIZZLE_B,
.a = VK_COMPONENT_SWIZZLE_A,
},
.subresourceRange = { // 모든 이미지에 대해서 이 이미지 뷰가 접근할 수 있도록 설정
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1
}
};
VK_CHECK_ERROR(vkCreateImageView(mDevice,
&imageViewCreateInfo,
nullptr,
&mSwapchainImageViews[i])); // mSwapchainImageViews[i] 생성
}
// ================================================================================
// 8. VkCommandPool 생성
// ================================================================================
VkCommandPoolCreateInfo commandPoolCreateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | // command buffer가 자주 변경될 것임을 알려줌
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, // command buffer를 개별적으로 초기화 가능하게 설정
.queueFamilyIndex = mQueueFamilyIndex
};
VK_CHECK_ERROR(vkCreateCommandPool(mDevice, &commandPoolCreateInfo, nullptr, &mCommandPool)); // mCommandPool 생성
// ================================================================================
// 9. VkCommandBuffer 할당
// ================================================================================
VkCommandBufferAllocateInfo commandBufferAllocateInfo{ // 할당하려는 command buffer 정의
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = mCommandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1
};
VK_CHECK_ERROR(vkAllocateCommandBuffers(mDevice, &commandBufferAllocateInfo, &mCommandBuffer));
// ================================================================================
// 10. VkFence 생성
// ================================================================================
VkFenceCreateInfo fenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
}; // 생성할 Fence의 정보를 해당 구조체에서 정의
VK_CHECK_ERROR(vkCreateFence(mDevice, &fenceCreateInfo, nullptr, &mFence)); // mFence 생성. flag에 아무것도 넣어주지 않았기 때문에 생성된 Fence의 초기 상태는 Unsignal 상태다.
// ================================================================================
// 11. VkSemaphore 생성
// ================================================================================
VkSemaphoreCreateInfo semaphoreCreateInfo{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
VK_CHECK_ERROR(vkCreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr, &mSemaphore));
// ================================================================================
// 12. VkRenderPass 생성
// ================================================================================
VkAttachmentDescription attachmentDescription{
.format = surfaceFormats[surfaceFormatIndex].format,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
};
VkAttachmentReference attachmentReference{
.attachment = 0,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
};
VkSubpassDescription subpassDescription{
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = 1,
.pColorAttachments = &attachmentReference
};
VkRenderPassCreateInfo renderPassCreateInfo{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.attachmentCount = 1,
.pAttachments = &attachmentDescription,
.subpassCount = 1,
.pSubpasses = &subpassDescription
};
VK_CHECK_ERROR(vkCreateRenderPass(mDevice, &renderPassCreateInfo, nullptr, &mRenderPass)); // mRenderPass 생성.
mFramebuffers.resize(swapchainImageCount);
for (auto i = 0; i != swapchainImageCount; ++i) {
// ================================================================================
// 13. VkFramebuffer 생성
// ================================================================================
VkFramebufferCreateInfo framebufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = mRenderPass,
.attachmentCount = 1,
.pAttachments = &mSwapchainImageViews[i], // ImageView
.width = mSwapchainImageExtent.width,
.height = mSwapchainImageExtent.height,
.layers = 1
};
VK_CHECK_ERROR(vkCreateFramebuffer(mDevice, &framebufferCreateInfo, nullptr, &mFramebuffers[i]));// mFramebuffers[i] 생성
}
// ================================================================================
// 14. Vertex VkShaderModule 생성
// ================================================================================
string_view vertexShaderCode = {
"#version 310 es \n"
" \n"
"layout(location = 0) in vec3 inPosition; \n"
"layout(location = 1) in vec3 inColor; \n"
" \n"
"layout(location = 0) out vec3 outColor; \n"
" \n"
"void main() { \n"
" gl_Position = vec4(inPosition, 1.0); \n"
" outColor = inColor; \n"
"} \n"
};
std::vector<uint32_t> vertexShaderBinary;
// VKSL을 SPIR-V로 변환.
VK_CHECK_ERROR(vkCompileShader(vertexShaderCode,
VK_SHADER_TYPE_VERTEX,
&vertexShaderBinary));
VkShaderModuleCreateInfo vertexShaderModuleCreateInfo{
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = vertexShaderBinary.size() * sizeof(uint32_t), // 바이트 단위.
.pCode = vertexShaderBinary.data()
};
VK_CHECK_ERROR(vkCreateShaderModule(mDevice,
&vertexShaderModuleCreateInfo,
nullptr,
&mVertexShaderModule)); // mVertexShaderModule 생성.
// ================================================================================
// 15. Fragment VkShaderModule 생성
// ================================================================================
string_view fragmentShaderCode = {
"#version 310 es \n"
"precision mediump float; \n"
" \n"
"layout(location = 0) in vec3 inColor; \n"
" \n"
"layout(location = 0) out vec4 outColor; \n"
" \n"
"void main() { \n"
" outColor = vec4(inColor, 1.0); \n"
"} \n"
};
std::vector<uint32_t> fragmentShaderBinary;
VK_CHECK_ERROR(vkCompileShader(fragmentShaderCode,
VK_SHADER_TYPE_FRAGMENT,
&fragmentShaderBinary));
VkShaderModuleCreateInfo fragmentShaderModuleCreateInfo{
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = fragmentShaderBinary.size() * sizeof(uint32_t),
.pCode = fragmentShaderBinary.data()
};
VK_CHECK_ERROR(vkCreateShaderModule(mDevice,
&fragmentShaderModuleCreateInfo,
nullptr,
&mFragmentShaderModule));
// ================================================================================
// 16. VkDescriptorSetLayout 생성
// ================================================================================
VkDescriptorSetLayoutBinding descriptorSetLayoutBinding{
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT
};
VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1,
.pBindings = &descriptorSetLayoutBinding
};
VK_CHECK_ERROR(vkCreateDescriptorSetLayout(mDevice,
&descriptorSetLayoutCreateInfo,
nullptr,
&mDescriptorSetLayout)); // mDescriptorSetLayout 생성
// ================================================================================
// 17. VkPipelineLayout 생성
// ================================================================================
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO
};
VK_CHECK_ERROR(vkCreatePipelineLayout(mDevice,
&pipelineLayoutCreateInfo,
nullptr,
&mPipelineLayout));
// ================================================================================
// 18. Graphics VkPipeline 생성
// ================================================================================
array<VkPipelineShaderStageCreateInfo, 2> pipelineShaderStageCreateInfos{
VkPipelineShaderStageCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = mVertexShaderModule,
.pName = "main"
},
VkPipelineShaderStageCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = mFragmentShaderModule,
.pName = "main"
}
};
VkVertexInputBindingDescription vertexInputBindingDescription{
.binding = 0,
.stride = sizeof(Vertex),
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX
};
array<VkVertexInputAttributeDescription, 2> vertexInputAttributeDescriptions{
VkVertexInputAttributeDescription{
.location = 0,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, position)
},
VkVertexInputAttributeDescription{
.location = 1,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, color)
}
};
VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = &vertexInputBindingDescription,
.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributeDescriptions.size()),
.pVertexAttributeDescriptions = vertexInputAttributeDescriptions.data()
};
VkPipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology =VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
};
VkViewport viewport{
.width = static_cast<float>(mSwapchainImageExtent.width),
.height = static_cast<float>(mSwapchainImageExtent.height),
.maxDepth = 1.0f
};
VkRect2D scissor{
.extent = mSwapchainImageExtent
};
VkPipelineViewportStateCreateInfo pipelineViewportStateCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.pViewports = &viewport,
.scissorCount = 1,
.pScissors = &scissor
};
VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.polygonMode = VK_POLYGON_MODE_FILL,
.cullMode = VK_CULL_MODE_NONE,
.lineWidth = 1.0f
};
VkPipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT
};
VkPipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO
};
VkPipelineColorBlendAttachmentState pipelineColorBlendAttachmentState{
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT
};
VkPipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.attachmentCount = 1,
.pAttachments = &pipelineColorBlendAttachmentState
};
VkGraphicsPipelineCreateInfo graphicsPipelineCreateInfo{
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.stageCount = pipelineShaderStageCreateInfos.size(),
.pStages = pipelineShaderStageCreateInfos.data(),
.pVertexInputState = &pipelineVertexInputStateCreateInfo,
.pInputAssemblyState = &pipelineInputAssemblyStateCreateInfo,
.pViewportState = &pipelineViewportStateCreateInfo,
.pRasterizationState = &pipelineRasterizationStateCreateInfo,
.pMultisampleState = &pipelineMultisampleStateCreateInfo,
.pDepthStencilState = &pipelineDepthStencilStateCreateInfo,
.pColorBlendState = &pipelineColorBlendStateCreateInfo,
.layout = mPipelineLayout,
.renderPass = mRenderPass
};
VK_CHECK_ERROR(vkCreateGraphicsPipelines(mDevice,
VK_NULL_HANDLE,
1,
&graphicsPipelineCreateInfo,
nullptr,
&mPipeline));
// ================================================================================
// 19. Vertex VkBuffer 생성
// ================================================================================
constexpr array<Vertex, 3> vertices{
Vertex{
.position{0.0, -0.5, 0.0},
.color{1.0, 0.0, 0.0}
},
Vertex{
.position{0.5, 0.5, 0.0},
.color{0.0, 1.0, 0.0}
},
Vertex{
.position{-0.5, 0.5, 0.0},
.color{0.0, 0.0, 1.0}
},
};
constexpr VkDeviceSize vertexDataSize{vertices.size() * sizeof(Vertex)};
VkBufferCreateInfo vertexBufferCreateInfo{
.sType =VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = vertexDataSize,
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
};
VK_CHECK_ERROR(vkCreateBuffer(mDevice, &vertexBufferCreateInfo, nullptr, &mVertexBuffer));
// ================================================================================
// 20. Vertex VkBuffer의 VkMemoryRequirements 얻기
// ================================================================================
VkMemoryRequirements vertexMemoryRequirements;
vkGetBufferMemoryRequirements(mDevice, mVertexBuffer, &vertexMemoryRequirements);
// ================================================================================
// 21. Vertex VkDeviceMemory를 할당 할 수 있는 메모리 타입 인덱스 얻기
// ================================================================================
uint32_t vertexMemoryTypeIndex;
VK_CHECK_ERROR(vkGetMemoryTypeIndex(mPhysicalDeviceMemoryProperties,
vertexMemoryRequirements,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&vertexMemoryTypeIndex));
// ================================================================================
// 22. Vertex VkDeviceMemory 할당
// ================================================================================
VkMemoryAllocateInfo vertexMemoryAllocateInfo{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = vertexMemoryRequirements.size,
.memoryTypeIndex = vertexMemoryTypeIndex
};
VK_CHECK_ERROR(vkAllocateMemory(mDevice, &vertexMemoryAllocateInfo, nullptr, &mVertexMemory));
// ================================================================================
// 23. Vertex VkBuffer와 Vertex VkDeviceMemory 바인드
// ================================================================================
VK_CHECK_ERROR(vkBindBufferMemory(mDevice, mVertexBuffer, mVertexMemory, 0));
// ================================================================================
// 24. Vertex 데이터 복사
// ================================================================================
void* vertexData;
VK_CHECK_ERROR(vkMapMemory(mDevice, mVertexMemory, 0, vertexDataSize, 0, &vertexData));
memcpy(vertexData, vertices.data(), vertexDataSize);
vkUnmapMemory(mDevice, mVertexMemory);
}
VkRenderer::~VkRenderer() {
vkFreeMemory(mDevice, mVertexMemory, nullptr);
vkDestroyBuffer(mDevice, mVertexBuffer, nullptr);
vkDestroyDescriptorSetLayout(mDevice, mDescriptorSetLayout, nullptr);
vkDestroyPipelineLayout(mDevice, mPipelineLayout, nullptr);
vkDestroyPipeline(mDevice, mPipeline, nullptr);
vkDestroyShaderModule(mDevice, mVertexShaderModule, nullptr);
vkDestroyShaderModule(mDevice, mFragmentShaderModule, nullptr);
for (auto framebuffer : mFramebuffers) {
vkDestroyFramebuffer(mDevice, framebuffer, nullptr);
}
mFramebuffers.clear();
vkDestroyRenderPass(mDevice, mRenderPass, nullptr);
for (auto imageView : mSwapchainImageViews) {
vkDestroyImageView(mDevice, imageView, nullptr);
}
mSwapchainImageViews.clear();
vkDestroySemaphore(mDevice, mSemaphore, nullptr);
vkDestroyFence(mDevice, mFence, nullptr);
vkFreeCommandBuffers(mDevice, mCommandPool, 1, &mCommandBuffer);
vkDestroyCommandPool(mDevice, mCommandPool, nullptr);
vkDestroySwapchainKHR(mDevice, mSwapchain, nullptr);
vkDestroySurfaceKHR(mInstance, mSurface, nullptr);
vkDestroyDevice(mDevice, nullptr); // Device 파괴. queue의 경우 Device를 생성하면서 생겼기 때문에 따로 파괴하는 API가 존재하지 않는다.
vkDestroyInstance(mInstance, nullptr);
}
void VkRenderer::render() {
// ================================================================================
// 1. 화면에 출력할 수 있는 VkImage 얻기
// ================================================================================
uint32_t swapchainImageIndex;
VK_CHECK_ERROR(vkAcquireNextImageKHR(mDevice,
mSwapchain,
UINT64_MAX,
VK_NULL_HANDLE,
mFence, // Fence 설정
&swapchainImageIndex)); // 사용 가능한 이미지 변수에 담기
//auto swapchainImage = mSwapchainImages[swapchainImageIndex]; // swapchainImage에 더 이상 직접 접근하지 않으므로 이제 사용X
auto framebuffer = mFramebuffers[swapchainImageIndex];
// ================================================================================
// 2. VkFence 기다린 후 초기화
// ================================================================================
// mFence가 Signal 될 때까지 기다린다.
VK_CHECK_ERROR(vkWaitForFences(mDevice, 1, &mFence, VK_TRUE, UINT64_MAX));
// mFence가 Siganl이 되면 vkResetFences를 호출해서 Fence의 상태를 다시 초기화한다.
// 초기화하는 이유: vkAcquireNextImageKHR을 호출할 때 이 Fence의 상태는 항상 Unsignal 상태여야 하기 때문이다.
VK_CHECK_ERROR(vkResetFences(mDevice, 1, &mFence));
// ================================================================================
// 3. VkCommandBuffer 초기화
// ================================================================================
vkResetCommandBuffer(mCommandBuffer, 0);
// ================================================================================
// 4. VkCommandBuffer 기록 시작
// ================================================================================
VkCommandBufferBeginInfo commandBufferBeginInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT // 한 번만 기록되고 다시 리셋 될 것이라는 의미
};
// mCommandBuffer를 기록중인 상태로 변경.
VK_CHECK_ERROR(vkBeginCommandBuffer(mCommandBuffer, &commandBufferBeginInfo));
// ================================================================================
// 5. VkRenderPass 시작
// ================================================================================
VkRenderPassBeginInfo renderPassBeginInfo{
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = mRenderPass,
.framebuffer = framebuffer,
.renderArea{
.extent = mSwapchainImageExtent
},
.clearValueCount = 1,
.pClearValues = &mClearValue
};
vkCmdBeginRenderPass(mCommandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
// ================================================================================
// 6. Graphics VkPipeline 바인드
// ================================================================================
vkCmdBindPipeline(mCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, mPipeline);
// ================================================================================
// 7. Vertex VkBuffer 바인드
// ================================================================================
VkDeviceSize vertexBufferOffset{0};
vkCmdBindVertexBuffers(mCommandBuffer, 0, 1, &mVertexBuffer, &vertexBufferOffset);
// ================================================================================
// 8. 삼각형 그리기
// ================================================================================
vkCmdDraw(mCommandBuffer, 3, 1, 0, 0);
// ================================================================================
// 9. VkRenderPass 종료
// ================================================================================
vkCmdEndRenderPass(mCommandBuffer);
// ================================================================================
// 10. VkCommandBuffer 기록 종료
// ================================================================================
VK_CHECK_ERROR(vkEndCommandBuffer(mCommandBuffer)); // mCommandBuffer는 Executable 상태가 된다.
// ================================================================================
// 11. VkCommandBuffer 제출
// ================================================================================
VkSubmitInfo submitInfo{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &mCommandBuffer,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &mSemaphore
};
// submitInfo 구조체를 넘김으로써 commandBuffer 정보를 queue에 제출
VK_CHECK_ERROR(vkQueueSubmit(mQueue, 1, &submitInfo, VK_NULL_HANDLE));
// ================================================================================
// 12. VkImage 화면에 출력
// ================================================================================
VkPresentInfoKHR presentInfo{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &mSemaphore,
.swapchainCount = 1,
.pSwapchains = &mSwapchain,
.pImageIndices = &swapchainImageIndex
};
VK_CHECK_ERROR(vkQueuePresentKHR(mQueue, &presentInfo)); // 화면에 출력.
VK_CHECK_ERROR(vkQueueWaitIdle(mQueue));
}
'⭐ Vulkan & CMake > Vulkan' 카테고리의 다른 글
[Vulkan] Vulkan Descriptor pool (0) | 2024.09.16 |
---|---|
[Vulkan] Hello Triangle (0) | 2024.09.14 |
[Vulkan] Vulkan Memory (0) | 2024.09.12 |
[Vulkan] Vulkan Buffer (0) | 2024.09.12 |
[Vulkan] Vulkan Graphics pipeline 벌컨 그래픽스 파이프라인 (0) | 2024.09.11 |
댓글
이 글 공유하기
다른 글
-
[Vulkan] Vulkan Descriptor pool
[Vulkan] Vulkan Descriptor pool
2024.09.16 -
[Vulkan] Hello Triangle
[Vulkan] Hello Triangle
2024.09.14 -
[Vulkan] Vulkan Memory
[Vulkan] Vulkan Memory
2024.09.12 -
[Vulkan] Vulkan Buffer
[Vulkan] Vulkan Buffer
2024.09.12