[Vulkan] Vulkan Graphics pipeline 벌컨 그래픽스 파이프라인
글의 요약 설명 부분. 150자를 적어주세요. 글의 요약 설명 부분. 150자를 적어주세요. 글의 요약 설명 부분. 150자를 적어주세요. 글의 요약 설명 부분. 150자를 적어주세요. 글의 요약 설명 부분. 150자를 적어주세요. 글의 요약 설명 부분. 150자입니다
목차
인프런 삼각형님의 '삼각형의 실전! Vulkan 중급' 강의를 참고하였습니다.
😎 [삼각형의 실전! Vulkan 중급] 강의 들으러 가기!
Vulkan Graphics pipeline
Vulkan Graphics pipeline이란?
Vulkan Graphics pipeline은 OpenGL과 동일하다.
Vulkan은 OpenGL와 달리 Graphics pipeline의 모든 단계를 개발자가 직접 명시적으로 설정해야 한다.
이러한 특징은 개발자에게 Graphics pipeline에 더 높은 수준의 제어를 가능하게 해준다.
VkGraphicsPipelineCreateInfo 구조체
typedef struct VkGraphicsPipelineCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineCreateFlags flags;
uint32_t stageCount;
const VkPipelineShaderStageCreateInfo* pStages;
const VkPipelineVertexInputStateCreateInfo* pVertexInputState;
const VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState;
const VkPipelineTessellationStateCreateInfo* pTessellationState;
const VkPipelineViewportStateCreateInfo* pViewportState;
const VkPipelineRasterizationStateCreateInfo* pRasterizationState;
const VkPipelineMultisampleStateCreateInfo* pMultisampleState;
const VkPipelineDepthStencilStateCreateInfo* pDepthStencilState;
const VkPipelineColorBlendStateCreateInfo* pColorBlendState;
const VkPipelineDynamicStateCreateInfo* pDynamicState;
VkPipelineLayout layout;
VkRenderPass renderPass;
uint32_t subpass;
VkPipeline basePipelineHandle;
uint32_t basePipelineIndex;
} VkGraphicsPipelineCreateInfo;
멤버 변수 | 설명 |
sType | 구조체 타입 |
pNext | NULL 또는 확장 기능 구조체의 포인터 |
flags | 일단 0을 사용 |
stageCoun | VkPipelineShaderStageCreateInfo의 개수 |
pStages | VkPipelineShaderStageCreateInfo 배열의 포인터 |
pVertexInputState | VkPipelineVertexInputStateCreateInfo 변수의 포인터 |
pInputAssemblyState | VkPipelineInputAssemblyStateCreateInfo 변수의 포인터 |
pTessellationState | 일단 NULL을 사용 |
pViewportState | VkPipelineViewportStateCreateInfo 변수의 포인터 |
pRasterizationState | VkPipelineRasterizationStateCreateInfo 변수의 포인터 |
pMultisampleState | 일단 NULL을 사용 |
pDepthStencilState | VkPipelineDepthStencilStateCreateInfo 변수의 포인터 |
pColorBlendState | VkPipelineColorBlendStateCreateInfo 변수의 포인터 |
pDynamicState | VkPipelineDynamicStateCreateInfo 변수의 포인터 |
layout | VkPipelineLayout |
renderPass | VkRenderPass |
subpass | 일단 0을 사용 |
basePipelineHandle | VK_NULL_HANDLE을 사용 |
basePipelineIndex | 일단 0을 사용 |
주목할 Vulkan Graphics pipeline 단계
삼각형에 그리기에 꼭 필요한 단계들
Input Assembly 단계에서 정점을 삼각형으로 구성한다. 사실 이러한 처리는 Vertex Shader 다음 단계인 Primitive Assembly 단계에서 진행되는데 Vulkan에서는 이 단계를 따로 두지 않고 Input Assembly의 정보를 활용한다.
Vertex Shader 단계에서는 삼각형의 각 정점의 위치를 정의한다.
Viewport 단계에서는 렌더링 결과가 Framebuffer의 어느 부분에 그려질지를 결정한다.
Rasterization 단계에서는 3D를 2D로 변환한다.
Fragment Shader는 삼각형의 최종 색상을 결정한다. 즉, 삼각형의 내부가 어떤 색깔로 채워질지를 결정한다.
Color Blend 단계에서 RGBA가 Framebuffer에 어떻게 쓰일지 결정한다. 이렇게 설정된 Graphics Pipeline을 가지고 렌더링을 하면 화면에 삼각형을 볼 수 있다.
VkPipelineShaderStageCreateInfo 구조체
typedef struct VkPipelineShaderStageCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineShaderStageCreateFlags flags;
VkShaderStageFlagBits stage;
VkShaderModule module;
const char* pName;
const VkSpecializationInfo* pSpecializationInfo;
} VkPipelineShaderStageCreateInfo;
멤버 변수 | 설명 |
sType | 구조체 타입 |
pNext | NULL 또는 확장 기능 구조체의 포인터 |
flags | 일단 0을 사용 |
stage | VkShaderStageFlagBits |
module | VkShaderModule |
pName | Entry point 이름 |
pSpecializationInfo | 일단 NULL 을 사용 |
VkShaderStageFlagBits 열거형
typedef enum VkShaderStageFlagBits {
VK_SHADER_STAGE_VERTEX_BIT = 0x00000001,
VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT = 0x00000002,
VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT = 0x00000004,
VK_SHADER_STAGE_GEOMETRY_BIT = 0x00000008,
VK_SHADER_STAGE_FRAGMENT_BIT = 0x00000010,
VK_SHADER_STAGE_COMPUTE_BIT = 0x00000020,
VK_SHADER_STAGE_ALL_GRAPHICS = 0x0000001F,
VK_SHADER_STAGE_ALL = 0x7FFFFFFF,
} VkShaderStageFlagBits;
VkPipelineInputAssemblyStateCreateInfo 구조체
typedef struct VkPipelineInputAssemblyStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineInputAssemblyStateCreateFlags flags;
VkPrimitiveTopology topology;
VkBool32 primitiveRestartEnable;
} VkPipelineInputAssemblyStateCreateInfo;
멤버 변수 | 설명 |
sType | 구조체 타입 |
pNext | NULL 또는 확장 기능 구조체의 포인터 |
flags | 일단 0을 사용 |
topology | VkPrimitiveTopology |
primitiveRestartEnable | 기본 도형 조합을 다시 시작할지 결정한다. 인덱스가 0xFFFFFFFF인 경우에 다시 시작된다. |
VkPrimitiveTopology 열거형
VkPrimitiveTopology 열거형은 정점들로부터 구성할 수 있는 다양한 기본 도형을 정의한다.
typedef enum VkPrimitiveTopology {
VK_PRIMITIVE_TOPOLOGY_POINT_LIST = 0,
VK_PRIMITIVE_TOPOLOGY_LINE_LIST = 1,
VK_PRIMITIVE_TOPOLOGY_LINE_STRIP = 2,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST = 3,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP = 4,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN = 5,
VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY = 6,
VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY = 7,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY = 8,
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY = 9,
} VkPrimitiveTopology;
PrimitiveTopology 종류
아래 예제에서는 삼각형을 그리기 위해 Triangle List 사용.
Primitive Restart란?
Primitive Restart는 인덱스가 특정 값을 가질 때 Primitive의 구성을 새로 시작하는 기능이다.
8번 인덱스의 값이 0xFFFFFFFF입니다.
8번 인덱스의 값이 특수한 값으로 설정되었다. 8번에서 기본 도형의 구성이 끝나고 9번부터 다시 시작하는 것을 볼 수 있다.
VkPipelineInputAssemblyStateCreateInfo 구조체
VkPipelineInputAssemblyStateCreateInfo 구조체를 통해서 렌더링의 결과가 Framebuffer의 어느 부분에 그려질지를 정의하고 출력이 표시될 특정 영역을 지정한다.
typedef struct VkPipelineViewportStageCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineViewportStageCreateFlags flags;
uint32_t viewportCount;
const VkViewport* pViewports;
uint32_t scissorCount;
const VkRect2D* pScissors;
} VkPipelineViewportStageCreateInfo;
멤버 변수 | 설명 |
sType | 구조체 타입 |
pNext | NULL 또는 확장 기능 구조체의 포인터 |
flags | 일단 0을 사용 |
viewportCount | VkViewport의 개수 |
pViewports | VkViewport 배열의 포인터 |
scissorCount | VkRect2D의 개수 |
pScissors | VkRect2D 배열의 포인터 |
VkPipelineViewportStateCreateInfo 구조체
Viewport는 렌더링의 결과가 Framebuffer의 어느 부분에 그려질지를 정리하며,
Scissor 테스트는 축력이 표시될 특정 영역을 지정한다.
Viewport와 Scissor를 통해 렌더링의 범위를 효과적으로 제어할 수 있다.
위의 그림은 Viewport와 Scissor 테스트에 대해 보여준다.
첫번째 예시에서는 Viewport가 Framebuffer의 절반으로 설정되어 있어 Framebuffer의 상단 부분에만 렌더링이 이루어진다. Scissor 영역은 이 Viewport보다 크기 때문에 렌더링 결과가 완전히 출력된다.
두번째 예시는 Viewport의 크기는 Framebuffer와 동일하고, 화면 전체의 렌더링의 결과가 모두 표시된다. 하지만 Scissor 영역이 Framebuffer의 절반이기 때문에 결과적으로 상단 부분만 출력이 되고 하단은 Scissor 테스트에 실패해서 출력되지 않는다. 이러한 설정은 렌더링 결과의 특정 부분만을 화면에 보여주고자 할 때 유용하다.
VkViewport 구조체
typedef struct VkViewport {
float x;
float y;
float width;
float height;
float minDepth;
float maxDepth;
} VkViewport;
멤버 변수 | 설명 |
x | 좌측 상단의 x 좌표 |
y | 좌측 상단의 y 좌표 |
width | 너비 |
height | 높이 |
minDepth | 최소 깊이 값. 0에서 1사이의 값을 가진다. |
maxDepth | 최대 깊이 값. 0에서 1사이의 값을 가진다. |
위의 코드에서는 Viewport가 Swapchain 이미지의 크기와 동일하게 설정함. 이 설정으로 인해 렌더링의 결과가 Framebuffer 전체에 그려지게 된다.
VkPipelineViewportStateCreateInfo 구조체
VkPipelineRasterizationStateCreateInfo 구조체
VkPipelineRasterizationStateCreateInfo 구조체를 통해 rasterization이 어떻게 수행될지 정의할 수 있다.
typedef struct VkPipelineRasterizationStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineRasterizationStateCreateFlags flags;
VkBool32 depthClampEnable;
VkBool32 rasterizerDiscardEnable;
VkPolygonMode polygonMode;
VkCullModeFlags cullMode;
VkFrontFace frontFace;
VkBool32 depthBiasEnable;
float depthBiasConstantFactor;
float depthBiasClamp;
float depthBiasSlopeFactor;
float lineWidth;
} VkPipelineRasterizationStateCreateInfo;
멤버 변수 | 설명 |
sType | 구조체 타입 |
pNext | NULL 또는 확장 기능 구조체의 포인터 |
flags | 일단 0을 사용 |
depthClampEnable | 일단 VK_FALSE을 사용 |
rasterizerDiscardEnable | 일단 VK_FALSE을 사용 |
polygoneMode | VkPolygonMode |
cullMode | VkCullMode |
frontFace | VkFrontFace |
depthBiasEnable | 일단 VK_FALSE을 사용 |
depthBiasConstantFactor | 일단 0을 사용 |
depthBiasClamp | 일단 0을 사용 |
depthBiasSlopeFactor | 일단 0을 사용 |
lineWidth | 선의 너비. 일단 1을 사용 |
Rasterization은 3D 객체를 2D 모니터에 표시하기 위해 필요한 과정이다. 이 단계가 필요한 이유는 Vertex Shader 상에서 계산된 정점들은 3D 공간에 존재하기 때문이다. 3D 도형을 모니터와 같은 2D 디스플레이 장치로 표현하는 것은 불가능하므로 이를 2D로 변환해야 한다.
모니터는 오른쪽 그림처럼 수많은 픽셀들로 구성되어 있다. Rasterization 과정에서 이러한 픽셀에 맞게 3D 정보를 2D로 변환하는 작업이 수행된다.
VkPolygonMode 열거형
typedef enum VkPolygonMode {
VK_POLYGON_MODE_FILL = 0,
VK_POLYGON_MODE_LINE = 1,
VK_POLYGON_MODE_POINT = 2,
VK_POLYGON_MODE_FILL_RECTANGLE_NV = 1000153000,
VK_POLYGON_MODE_MAX_ENUM = 0x7FFFFFFF
} VkPolygonMode;
Fill은 도형 내부를 완전히 채운다. 가장 많이 사용.
Line은 윤곽선만 그린다. 디버깅 시 자주 사용.
Point는 도형의 정점만 그린다. 디버깅 시 자주 사용. Particle 구현 시 사용.
VkCullModeFlagBits 열거형
Cull Mode는 렌더링 과정에서 도형의 앞면 또는 뒷면이 화면에 보여질지 말지를 결정한다.
typedef enum VkCullModeFlagBits {
VK_CULL_MODE_NONE = 0, // 컬링 적용X. 모든 면이 렌더링 됨.
VK_CULL_MODE_FRONT_BIT = 0x00000001, // 도형의 전면을 컬링. 눈 앞에 보이는 삼각형이 렌더링X.
VK_CULL_MODE_BACK_BIT = 0x00000002, // 도형의 후면을 컬링. 정육면체의 뒷면에 표시되는 삼각형이 렌더링X.
VK_CULL_MODE_FRONT_AND_BACK = 0x00000003,
VK_CULL_MODE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkCullModeFlagBits;
VkFrontFace 열거형
typedef enum VkFrontFace {
VK_FRONT_FACE_COUNTER_CLOCK_WISE = 0,
VK_FRONT_FACE_CLOCK_WISE = 1,
} VkFrontFace;
Counter Clock Wise는 시계 반대 방향을 의미. 반시계 방향으로 삼각형 그려졌을 때 앞면으로 인식이 된다.
VkPipelineRasterizationStateCreateInfo 구조체
VkPipelineColorBlendStateCreateInfo 구조체
typedef struct VkPipelineColorBlendStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineColorBlendStateCreateFlags flags;
VkBool32 logicOpEnable;
VkLogicOp logicOp;
uint32_t attachmentCount;
const VkPipelineColorBlendAttachmentState* pAttachments;
float blendConstants[4];
} VkPipelineColorBlendStateCreateInfo;
멤버 변수 | 설명 |
sType | 구조체 타입 |
pNext | NULL 또는 확장 기능 구조체의 포인터 |
flags | 일단 0을 사용 |
logicOpEnable | 일단 VK_FALSE 을 사용 |
logicOp | 일단 스킵 |
attachmentCount | VkPipelineColorBlendAttachmentState의 개수 |
pAttachments | VkPipelineColorBlendAttachmentState 배열의 포인터 |
blendConstants | 컬러 블렌딩에 사용되는 상수 값 |
VkPipelineColorBlendAttachmentState 구조체
각 Color Attachment가 어떻게 블렌딩 될지는 VkPipelineColorBlendAttachmentState 구조체를 통해 정의할 수 있다.
이 구조체를 올바르게 설정하지 않으면 렌더링 결과가 Framebuffer에 기록되지 않는다.
typedef struct VkPipelineColorBlendAttachmentState {
VkBool32 blendEnable;
VkBlendFactor srcColorBlendFactor;
VkBlendFactor dstColorBlendFactor;
VkBlendOp colorBlendOp;
VkBlendFactor srcAlphaBlendFactor;
VkBlendFactor dstAlphaBlendFactor;
VkBlendOp alphaBlendOp;
VkColorComponentFlags colorWriteMask;
} VkPipelineColorBlendAttachmentState;
멤버 변수 | 설명 |
blendEnable | 컬러 블렌딩을 활성화 또는 비활성화한다. |
srcColorBlendFactor | |
dstColorBlendFactor | |
colorBlendOp | |
srcAlphaBlendFactor | |
dstAlphaBlendFactor | |
alphaBlendOp | |
colorWriteMask | VkColorComponentFlagBits의 조합. 각 채널의 쓰기를 활성화한다. |
VkColorComponentFlagBits 열거형
ColorWriteMask는 VkColorComponentFlagBits의 조합으로 정의할 수 있다.
렌더링 결과를 Framebuffer에 온전히 기록하기 위해서는 R, G, B, A를 모두 활성화해야 한다.
typedef enum VkColorComponentFlagBits {
VK_COLOR_COMPONENT_R_BIT = 0x00000001,
VK_COLOR_COMPONENT_G_BIT = 0x00000002,
VK_COLOR_COMPONENT_B_BIT = 0x00000004,
VK_COLOR_COMPONENT_A_BIT = 0x00000007,
} VkColorComponentFlagBits;
VkPipelineColorBlendAttachmentState 구조체
모든 채널을 활성화시키기 위해서 R, G, B, A를 OR 연산을 통해서 정의한다.
VkPipelineColorBlendStateCreateInfo 구조체
VkGraphicsPipelineCreateInfo 구조체
이제 Graphics Pipeline을 생성하기 위한 모든 준비를 마쳤다. Graphics Pipeline을 생성하기 위해 VkGraphicsPipelineCreateInfo 구조체를 정의한다. 대부분 정의한 구조체 변수에 포인터를 설정한다.
Vulkan Graphics pipeline 생성
VkResult vkCreateGraphicsPipelines(
VkDevice device,
VkPipelineCache pipelineCache,
uint32_t createInfoCount,
const VkGraphicsPipelineCreateInfo* pCreateInfos,
const VkAllocationCallbacks* pAllocator,
VkPipeline* pPipelines);
멤버 변수 | 설명 |
device | VkDevice |
pipelineCache | VK_NULL_HANDLE을 사용 |
createInfoCount | VkGraphicsPipelineCreateInfo의 개수 |
pCreateInfos | VkGraphicsPipelineCreateInfo 배열의 포인터 |
pAllocator | 일단 NULL을 사용 |
pPipelines | VkPipeline 변수의 포인터 |
Vulkan Graphics pipeline 생성
Vulkan Graphics pipeline 파괴
void vkDestroyPipeline(
VkDevice device,
VkPipeline pipeline,
const VkAllocationCallbacks* pAllocator);
멤버 변수 | 설명 |
device | VkDevice |
pipeline | VkPipeline |
pAllocator | 일단 NULL을 사용 |
Vulkan Graphics pipeline 바인드하기
생성한 Graphics pipeline을 사용하기 위해 바인드한다. 이 함수는 Command Buffer의 Command를 기록하는 방식으로 작동한다.
void vkCmdBindPipeline(
VkCommandBuffer commandBuffer,
VkPipelineBindPoint pipelineBindPoint,
VkPipeline pipeline);
멤버 변수 | 설명 |
commandBuffer | VkCommandBuffer |
pipelineBindPoint | 일단 VK_PIPELINE_BIND_POINT_GRAPHICS을 사용 |
pipeline | VkPipeline |
mPipeline을 바인딩한다.
삼각형 그리기
void vkCmdDraw(
VkCommandBuffer commandBuffer,
uint32_t vertexCount,
uint32_t instanceCount,
uint32_t firstVertex,
uint32_t firstInstance);
파라미터 | 설명 |
commandBuffer | VkCommandBuffer |
vertexCount | 그릴 정점의 개수 |
instanceCount | 일단 1을 사용 |
firstVertex | 일단 0을 사용 |
firstInstance | 일단 0을 사용 |
코드
#include ...
using namespace std;
VkRenderer::VkRenderer(ANativeWindow *window) {
// 1. VkInstance 생성
// 2. VkPhysicalDevice 선택
// 3. VkDevice 생성
// 4. VkSurface 생성
// 5. VkSwapchain 생성
mSwapchainImageViews.resize(swapchainImageCount); // ImageView를 Swapchain의 개수만큼 생성
for (auto i = 0; i != swapchainImageCount; ++i) {
// 6. VkImageView 생성
}
// 7. VkCommandPool 생성
// 8. VkCommandBuffer 할당
// 9. VkFence 생성
// 10. VkSemaphore 생성
// 11. VkRenderPass 생성
mFramebuffers.resize(swapchainImageCount);
for (auto i = 0; i != swapchainImageCount; ++i) {
// 12. VkFramebuffer 생성
}
// 13. Vertex VkShaderModule 생성
// 14. Fragment VkShaderModule 생성
// ================================================================================
// 15. VkPipelineLayout 생성
// ================================================================================
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO
};
VK_CHECK_ERROR(vkCreatePipelineLayout(mDevice,
&pipelineLayoutCreateInfo,
nullptr,
&mPipelineLayout));
// ================================================================================
// 16. 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"
}
};
VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
};
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));
}
VkRenderer::~VkRenderer() {
vkDestroyPipelineLayout(mDevice, mPipelineLayout, nullptr);
vkDestroyPipeline(mDevice, mPipeline, nullptr);
...
}
void VkRenderer::render() {
// 1. 화면에 출력할 수 있는 VkImage 얻기
// 2. VkFence 기다린 후 초기화
// 3. VkCommandBuffer 초기화
// 4. VkCommandBuffer 기록 시작
// 5. VkRenderPass 시작
// ================================================================================
// 6. Graphics VkPipeline 바인드
// ================================================================================
vkCmdBindPipeline(mCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, mPipeline);
// ================================================================================
// 7. 삼각형 그리기
// ================================================================================
vkCmdDraw(mCommandBuffer, 3, 1, 0, 0);
// ================================================================================
// 8. VkRenderPass 종료
// ================================================================================
vkCmdEndRenderPass(mCommandBuffer);
// 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 <array>
#include <vector>
#include <iomanip>
#include "VkRenderer.h"
#include "VkUtil.h"
#include "AndroidOut.h"
using namespace std;
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. 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);
// ================================================================================
// 4. 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);
// ================================================================================
// 5. 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 = surfaceCapabilities.currentExtent,
.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) {
// ================================================================================
// 6. 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] 생성
}
// ================================================================================
// 7. 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 생성
// ================================================================================
// 8. 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));
// ================================================================================
// 9. VkFence 생성
// ================================================================================
VkFenceCreateInfo fenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
}; // 생성할 Fence의 정보를 해당 구조체에서 정의
VK_CHECK_ERROR(vkCreateFence(mDevice, &fenceCreateInfo, nullptr, &mFence)); // mFence 생성. flag에 아무것도 넣어주지 않았기 때문에 생성된 Fence의 초기 상태는 Unsignal 상태다.
// ================================================================================
// 10. VkSemaphore 생성
// ================================================================================
VkSemaphoreCreateInfo semaphoreCreateInfo{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
VK_CHECK_ERROR(vkCreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr, &mSemaphore));
// ================================================================================
// 11. 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) {
// ================================================================================
// 12. 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] 생성
}
// ================================================================================
// 13. Vertex VkShaderModule 생성
// ================================================================================
string_view vertexShaderCode = {
"#version 310 es \n"
" \n"
"void main() { \n"
" vec2 pos[3] = vec2[3](vec2(-0.5, 0.5), \n"
" vec2( 0.5, 0.5), \n"
" vec2( 0.0, -0.5)); \n"
" \n"
" gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0); \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 생성.
// ================================================================================
// 14. Fragment VkShaderModule 생성
// ================================================================================
string_view fragmentShaderCode = {
"#version 310 es \n"
"precision mediump float; \n"
" \n"
"layout(location = 0) out vec4 fragmentColor; \n"
" \n"
"void main() { \n"
" fragmentColor = vec4(1.0, 0.0, 0.0, 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));
// ================================================================================
// 15. VkPipelineLayout 생성
// ================================================================================
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO
};
VK_CHECK_ERROR(vkCreatePipelineLayout(mDevice,
&pipelineLayoutCreateInfo,
nullptr,
&mPipelineLayout));
// ================================================================================
// 16. 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"
}
};
VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
};
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));
}
VkRenderer::~VkRenderer() {
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. 삼각형 그리기
// ================================================================================
vkCmdDraw(mCommandBuffer, 3, 1, 0, 0);
// ================================================================================
// 8. VkRenderPass 종료
// ================================================================================
vkCmdEndRenderPass(mCommandBuffer);
// ================================================================================
// 9. Clear 색상 갱신
// ================================================================================
for (auto i = 0; i != 4; ++i) {
mClearValue.color.float32[i] = fmodf(mClearValue.color.float32[i] + 0.01, 1.0);
}
// ================================================================================
// 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 Memory (0) | 2024.09.12 |
---|---|
[Vulkan] Vulkan Buffer (0) | 2024.09.12 |
[Vulkan] Vulkan Shader module (0) | 2024.09.10 |
[Vulkan] VKSL 주의사항 (0) | 2024.09.10 |
[Vulkan] SPIR-V (0) | 2024.09.10 |
댓글
이 글 공유하기
다른 글
-
[Vulkan] Vulkan Memory
[Vulkan] Vulkan Memory
2024.09.12 -
[Vulkan] Vulkan Buffer
[Vulkan] Vulkan Buffer
2024.09.12 -
[Vulkan] Vulkan Shader module
[Vulkan] Vulkan Shader module
2024.09.10 -
[Vulkan] VKSL 주의사항
[Vulkan] VKSL 주의사항
2024.09.10