μ»΄ν¨νΈ μ °μ΄λλ₯Ό μ€νν΄ νμν μλ¨μ΄ μμΌλ©°, μμ§μ λλ²κ·Έ UIλ₯Ό μΆκ°νλ κΈ°λ₯λ μμ΅λλ€. μ΄μ UIλ₯Ό ν΅ν΄ μ °μ΄λλ‘ λ°μ΄ν°λ₯Ό μ μ‘νκ³ μνΈμμ©ν΄λ³΄κ² μ΅λλ€.
μ °μ΄λμ λ°μ΄ν°λ₯Ό μ μ‘νλ λ° νΈμμμλ₯Ό μ¬μ©ν κ²μ λλ€. νΈμμμλ μ μ μμ λ°μ΄ν°λ₯Ό GPUλ‘ λ³΄λΌ μ μλ Vulkanμ κ³ μ κΈ°λ₯μ λλ€. μ΄ λ°μ΄ν°λ₯Ό μ κ² μ μ§νλ κ²μ΄ μ€μνλ°, λλΆλΆμ λλΌμ΄λ²κ° λ°μ΄ν°κ° νΉμ λ°μ΄νΈ μ μ΄νμΌ λ κ°μ₯ λΉ λ₯Έ κ²½λ‘λ₯Ό μ°Ύμμ£ΌκΈ° λλ¬Έμ λλ€(μμΈν ν¬κΈ°λ GPU μ μ‘°μ¬ λ¬Έμλ₯Ό μ°Έκ³ νμΈμ). μ£Ό μ©λ‘λ κ°μ²΄λ§λ€μ μΈλ±μ€ νΉμ νλ ¬μ μ λ¬νλ κ²μ λλ€. λ§μ½ 보λ΄μΌ νλ λ°μ΄ν°κ° κ½€ λ§λ€λ©΄ λ€μ μ±ν°μμ μ¬μ©ν λ€λ₯Έ μμ€ν μ μ¬μ©ν΄μΌ ν©λλ€.
νΈμ μμλ νμ΄νλΌμΈμ μμ±ν λ ꡬμ±ν©λλ€. μ½λλ₯Ό λ¨μνκ² μ μ§νκ³ λ§μ μμ μ νΌνκΈ° μν΄ νΈμμμμ κΈ°λ³Έμ μΌλ‘ 4κ°μ vec4λ₯Ό μ¬μ©νκ² μ΅λλ€. 16κ°μ floatλ μ °μ΄λμμ μΆ©λΆν©λλ€.
νλ‘μ νΈμ μ °μ΄λ ν΄λμ μ¬λ¬ μ»΄ν¨νΈ μ °μ΄λκ° μμ΅λλ€. μ΄λ₯Ό λ°κΏ μ¬μ©ν μ μμΌλ©°, νν 리μΌμμλ λ¨μν μμμλ§ μ§μ€νκ² μ§λ§ μ¬λ¬ λ°λͺ¨ μ °μ΄λλ₯Ό μ μ©ν΄ λ³Ό μ μμ΅λλ€.
λͺ¨λ μ»΄ν¨νΈ μ °μ΄λκ° κ°μ λ μ΄μμμ 곡μ νκΈ° λλ¬Έμ UIμ λλ‘λ€μ΄μ μΆκ°νλ κ²μΌλ‘ μ¬μ©ν νμ΄νλΌμΈμ μ νν μ μμ΅λλ€. μ΄λ κ² νλ©΄ λ°νμμ λ€μν μ»΄ν¨νΈ μ °μ΄λλ₯Ό λ²κ°μ μ¬μ©ν μ μμ΅λλ€.
νΈμμμλ₯Ό μ μ©ν μμ μ μ¬μ©ν μ °μ΄λλ λ€μκ³Ό κ°μ΅λλ€. μ΄λ 2κ°μ μμμ Yμ’νμ λ°λΌ νΌν©νμ¬ μμ§ κ·ΈλΌλμΈνΈλ₯Ό λ§λλλ€.
μ °μ΄λ ν΄λμ gradient_color.compμμ νμΈν μ μμ΅λλ€.
#version 460
layout (local_size_x = 16, local_size_y = 16) in;
layout(rgba16f,set = 0, binding = 0) uniform image2D image;
//push constants block
layout( push_constant ) uniform constants
{
vec4 data1;
vec4 data2;
vec4 data3;
vec4 data4;
} PushConstants;
void main()
{
ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(image);
vec4 topColor = PushConstants.data1;
vec4 bottomColor = PushConstants.data2;
if(texelCoord.x < size.x && texelCoord.y < size.y)
{
float blend = float(texelCoord.y)/(size.y);
imageStore(image, texelCoord, mix(topColor,bottomColor, blend));
}
}
λλΆλΆ μ΄μ κΈμμ μμ±νλ κ·ΈλΌλμΈνΈ μ
°μ΄λμ λμΌν©λλ€. 4κ°μ vec4
λ₯Ό λ΄λ νΈμμμ λΈλ‘μ μΆκ°νμΌλ©° μ΄λ‘λΆν° μλ¨κ³Ό νλ¨μ μμμ λΆλ¬μ΅λλ€. data3
μ data4
λ μ€μ λ‘ μ¬μ©λμ§λ μμ§λ§ μ
°μ΄λμ νΈμμμ λ²μλ³΄λ€ λλ€λ μ΄μ λ‘ κ²μ¦ λ μ΄μ΄κ° κ²½κ³ λ₯Ό λ°μνλ κ²μ λ°©μ§νκΈ° μν΄ μΆκ°νμ΅λλ€.
νΈμμμ λ²μλ₯Ό ꡬμ±νκΈ° μν΄ νμ΄νλΌμΈ λ μ΄μμμ μμ ν νμκ° μμ΅λλ€. λ¨Όμ μ΄λ¬ν νΈμ μμλ₯Ό μ§μ μ μΌλ‘ λνλ΄λ ꡬ쑰체λ₯Ό vk_engine.h
μ μΆκ°ν©λλ€.
struct ComputePushConstants {
glm::vec4 data1;
glm::vec4 data2;
glm::vec4 data3;
glm::vec4 data4;
};
νΈμμμ λ²μλ₯Ό μ€μ νκΈ° μν΄ init_pipelines
μ μμ λΆλΆμμ νμ΄νλΌμΈ λ μ΄μμμ μμ±νλ μ½λλ₯Ό μμ ν΄μΌ ν©λλ€. μλλ μμ λ λ²μ μ
λλ€.
VkPipelineLayoutCreateInfo computeLayout{};
computeLayout.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
computeLayout.pNext = nullptr;
computeLayout.pSetLayouts = &_drawImageDescriptorLayout;
computeLayout.setLayoutCount = 1;
VkPushConstantRange pushConstant{};
pushConstant.offset = 0;
pushConstant.size = sizeof(ComputePushConstants) ;
pushConstant.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
computeLayout.pPushConstantRanges = &pushConstant;
computeLayout.pushConstantRangeCount = 1;
VK_CHECK(vkCreatePipelineLayout(_device, &computeLayout, nullptr, &_gradientPipelineLayout));
VkPushConstantRange
λ₯Ό VkPipelineLayoutCreateInfo
ꡬ쑰체μ μΆκ°ν©λλ€. νΈμμμ λ²μλ μ€νμ
μ λ΄μΌλ©°(μ¬κΈ°μλ 0μΌλ‘ μ€μ ν©λλ€.), ν¬κΈ°μ μ€ν λ¨κ³ νλκ·Έλ₯Ό μ§μ ν©λλ€. ν¬κΈ°μλ μ°λ¦¬ ꡬ쑰체μ cpp λ²μ μ μ¬μ©ν κ²μ΄κ³ , μ€ν λ¨κ³ νλκ·Έλ μ°λ¦¬κ° μ€ννλ μ μΌν λ¨κ³μΈ μ»΄ν¨νΈ(compute)μΌ κ²μ
λλ€.
μ΄νμ μ °μ΄λλ₯Ό λ°κΎΈκΈ°λ§ νλ©΄ λ©λλ€.
VkShaderModule computeDrawShader;
if (!vkutil::load_shader_module("../../shaders/gradient_color.comp.spv", _device, &computeDrawShader))
{
std::cout << "Error when building the colored mesh shader" << std::endl;
}
μ΄κ²μΌλ‘ μ °μ΄λμ νΈμμμλ₯Ό μ λ¬νκΈ° μν΄ νμν λͺ¨λ μμ μ΄ μλ£λμμ΅λλ€. μ΄μ μ΄λ₯Ό λ λλ§ λ£¨νμμ μ¬μ©ν΄λ΄ μλ€.
// bind the gradient drawing compute pipeline
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, _gradientPipeline);
// bind the descriptor set containing the draw image for the compute pipeline
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, _gradientPipelineLayout, 0, 1, &_drawImageDescriptors, 0, nullptr);
ComputePushConstants pc;
pc.data1 = glm::vec4(1, 0, 0, 1);
pc.data2 = glm::vec4(0, 0, 1, 1);
vkCmdPushConstants(cmd, _gradientPipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(ComputePushConstants), &pc);
// execute the compute pipeline dispatch. We are using 16x16 workgroup size so we need to divide by it
vkCmdDispatch(cmd, std::ceil(_drawExtent.width / 16.0), std::ceil(_drawExtent.height / 16.0), 1);
νΈμ μμλ₯Ό μ
λ°μ΄νΈ νκΈ° μν΄ VkCmdPushConstants
λ₯Ό νΈμΆν©λλ€. μ΄ ν¨μλ νμ΄νλΌμΈ λ μ΄μμ, μμ±ν λ°μ΄ν°μ μ€νμ
(μ¬κΈ°μλ 0μ
λλ€.) κ·Έλ¦¬κ³ λ°μ΄ν°μ ν¬κΈ°μ 볡μ¬ν ν¬μΈν°λ₯Ό μꡬν©λλ€. λν μλ‘ λ€λ₯Έ μ
°μ΄λ λ¨κ³μ λν΄ νΈμμμλ₯Ό μ
λ°μ΄νΈν μ μκΈ° λλ¬Έμ μ
°μ΄λ λ¨κ³ νλκ·Έλ νμν©λλ€.
μ΄κ²μΌλ‘ λͺ¨λ μ€μ μ΄ λλ¬μ΅λλ€. μ΄ μμ μμ νλ‘κ·Έλ¨μ μ€νμν¨λ€λ©΄ λΉ¨κ° μκ³Ό νλ μμΌλ‘ μ΄μ΄μ§λ κ·ΈλΌλμΈνΈλ₯Ό λ³Ό μ μμ΅λλ€.
ImGui λ§€κ°λ³μ
μ§κΈμ νλμ½λ©λ μμμ μ λ¬νκ³ μμ§λ§, ImGuiμ μμ μ°½μ μΆκ°νμ¬ μμμ λ°κΏλ³Ό μ λ μμ΅λλ€.
κ·Έ κ°μΌλ‘ μ¬μ©ν ComputePushConstant
ꡬ쑰체 νλμ ν¨κ» 그릴 μ»΄ν¨νΈ νμ΄νλΌμΈμ λ°°μ΄μ λ΄μμΌ ν©λλ€. μ΄λ κ² νλ©΄ λ€μν μ»΄ν¨νΈ μ
°μ΄λλ₯Ό μ νν μ μκ² λ©λλ€.
vk_engine.h
μ ν΄λΉ ꡬ쑰체λ₯Ό μΆκ°ν©μλ€.
struct ComputeEffect {
const char* name;
VkPipeline pipeline;
VkPipelineLayout layout;
ComputePushConstants data;
};
μ΄μ VulkanEngine
ν΄λμ€μ μ
°μ΄λμ λ°°μ΄μ μΆκ°νκ³ , λ λλ§ μ μ¬μ©ν μΈλ±μ€λ₯Ό λ΄λ μ μλ ν¨κ» μΆκ°ν©λλ€.
std::vector<ComputeEffect> backgroundEffects;
int currentBackgroundEffect{0};
init_pipelines
μ μ½λλ₯Ό μμ ν΄ 2κ°μ μ΄ννΈλ₯Ό μμ±ν΄ λ΄
μλ€. νλλ κ·ΈλΌλμΈνΈ, λ€λ₯Έ νλλ λ°€νλ μ
°μ΄λμ
λλ€.
νλ μ °μ΄λλ μ¬κΈ°μ μ€λͺ νκΈ°μλ λ€μ 볡μ‘νλ―λ‘, sky.compμ μ½λλ₯Ό νμΈν΄ 보λ κ²λ μ’μ΅λλ€. shadertoyμμ κ°μ Έμ μ¬κΈ°μ μ»΄ν¨νΈ μ °μ΄λλ‘ μ€ννκΈ° μν΄ μ½κ° λ°κΎΈμμ΅λλ€. νΈμμμμ data1μ νλ μμμ x/y/zλ₯Ό λ΄μ κ²μ΄λ©°, wλ λ³μ μ«μλ₯Ό μ μ΄ν λ μ¬μ©ν κ²μ λλ€.
2κ°μ μ
°μ΄λκ° μμΌλ―λ‘ 2κ°μ VkShaderModule
μ΄ νμν©λλ€.
VkShaderModule gradientShader;
if (!vkutil::load_shader_module("../../shaders/gradient_color.comp.spv", _device, &gradientShader)) {
fmt::print("Error when building the compute shader \n");
}
VkShaderModule skyShader;
if (!vkutil::load_shader_module("../../shaders/sky.comp.spv", _device, &skyShader)) {
fmt::print("Error when building the compute shader \n");
}
VkPipelineShaderStageCreateInfo stageinfo{};
stageinfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stageinfo.pNext = nullptr;
stageinfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
stageinfo.module = gradientShader;
stageinfo.pName = "main";
VkComputePipelineCreateInfo computePipelineCreateInfo{};
computePipelineCreateInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
computePipelineCreateInfo.pNext = nullptr;
computePipelineCreateInfo.layout = _gradientPipelineLayout;
computePipelineCreateInfo.stage = stageinfo;
ComputeEffect gradient;
gradient.layout = _gradientPipelineLayout;
gradient.name = "gradient";
gradient.data = {};
//default colors
gradient.data.data1 = glm::vec4(1, 0, 0, 1);
gradient.data.data2 = glm::vec4(0, 0, 1, 1);
VK_CHECK(vkCreateComputePipelines(_device, VK_NULL_HANDLE, 1, &computePipelineCreateInfo, nullptr, &gradient.pipeline));
//change the shader module only to create the sky shader
computePipelineCreateInfo.stage.module = skyShader;
ComputeEffect sky;
sky.layout = _gradientPipelineLayout;
sky.name = "sky";
sky.data = {};
//default sky parameters
sky.data.data1 = glm::vec4(0.1, 0.2, 0.4 ,0.97);
VK_CHECK(vkCreateComputePipelines(_device, VK_NULL_HANDLE, 1, &computePipelineCreateInfo, nullptr, &sky.pipeline));
//add the 2 background effects into the array
backgroundEffects.push_back(gradient);
backgroundEffects.push_back(sky);
//destroy structures properly
vkDestroyShaderModule(_device, gradientShader, nullptr);
vkDestroyShaderModule(_device, skyShader, nullptr);
_mainDeletionQueue.push_function([=]() {
vkDestroyPipelineLayout(_device, _gradientPipelineLayout, nullptr);
vkDestroyPipeline(_device, sky.pipeline, nullptr);
vkDestroyPipeline(_device, gradient.pipeline, nullptr);
});
pipelines ν¨μλ₯Ό μμ νμ΅λλ€. κΈ°μ‘΄μ νμ΄νλΌμΈ λ μ΄μμμ κ·Έλλ‘ μ μ§νλ©΄μ, μ΄μ λ κ°μ μλ‘ λ€λ₯Έ νμ΄νλΌμΈμ μμ±νμ¬ ComputeEffect 벑ν°μ μ μ₯ν©λλ€. λν κ° μ΄ννΈμ κΈ°λ³Έκ° λ°μ΄ν°λ₯Ό μ€μ ν©λλ€.
μ΄μ μ΄ κΈ°λ₯μ μν ImGui λλ²κ·Έ μ°½μ μΆκ°ν μ μμ΅λλ€. ν΄λΉ μ½λλ run()ν¨μ λ΄λΆμ λ€μ΄κ°λ©° κΈ°μ‘΄μ λ°λͺ¨ μ΄ννΈ νΈμΆμ μλ‘μ΄ UI λ‘μ§μΌλ‘ λ체ν κ²μ λλ€.
ImGui::NewFrame();
if (ImGui::Begin("background")) {
ComputeEffect& selected = backgroundEffects[currentBackgroundEffect];
ImGui::Text("Selected effect: ", selected.name);
ImGui::SliderInt("Effect Index", ¤tBackgroundEffect,0, backgroundEffects.size() - 1);
ImGui::InputFloat4("data1",(float*)& selected.data.data1);
ImGui::InputFloat4("data2",(float*)& selected.data.data2);
ImGui::InputFloat4("data3",(float*)& selected.data.data3);
ImGui::InputFloat4("data4",(float*)& selected.data.data4);
}
ImGui::End();
ImGui::Render();
λ¨Όμ μ νλ μ»΄ν¨νΈ μ΄ννΈλ₯Ό λ°°μ΄ μΈλ±μ±μ ν΅ν΄ κ°μ Έμ΅λλ€. κ·Έ ν ImGui::Text
λ₯Ό μ¬μ©ν΄ μ΄ννΈ μ΄λ¦μ μΆλ ₯νλ©°, νΈμ§ν μ μ μ¬λΌμ΄λμ float4
μ
λ ₯μ μ 곡ν©λλ€.
λ§μ§λ§μΌλ‘ ν μΌμ λ λλ§ λ£¨νλ₯Ό μμ ν΄ μ °μ΄λλ₯Ό μ ννμ¬ κ·Έμ λμνλ λ°μ΄ν°λ₯Ό μ¬μ©νλλ‘ νλ κ²μ λλ€.
ComputeEffect& effect = backgroundEffects[currentBackgroundEffect];
// bind the background compute pipeline
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, effect.pipeline);
// bind the descriptor set containing the draw image for the compute pipeline
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, _gradientPipelineLayout, 0, 1, &_drawImageDescriptors, 0, nullptr);
vkCmdPushConstants(cmd, _gradientPipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(ComputePushConstants), &effect.data);
// execute the compute pipeline dispatch. We are using 16x16 workgroup size so we need to divide by it
vkCmdDispatch(cmd, std::ceil(_drawExtent.width / 16.0), std::ceil(_drawExtent.height / 16.0), 1);
ν¬κ² λ°λ κ²μ μμ΅λλ€. μ»΄ν¨νΈ μ΄ννΈ λ°°μ΄μ μ°κ²°νκ³ ν΄λΉ νλͺ©μΌλ‘λΆν° νΈμμμλ₯Ό μ λ‘λν΄μ£ΌκΈ°λ§ νλ©΄ λ©λλ€.
μ ν리μΌμ΄μ μ μ€ννμ¬ λλ²κ·Έ μ°½μ ν΅ν΄ μ °μ΄λλ₯Ό μ ννκ³ νλΌλ―Έν°λ₯Ό μμ ν΄λ³΄μΈμ.
Next: Chapter 3: The graphics pipeline