μ΄μ  μ»΄ν¨νΈ μ °μ΄λμ νμν μ½λλ₯Ό μμ±νκ² μ΅λλ€. λ¨Όμ  μ΄λ―Έμ§λ₯Ό μ λ ₯λ°μ μ°λ λ ID κΈ°λ°μΌλ‘ μμμ κ³μ°νμ¬ κ·ΈλΌλμΈνΈλ₯Ό νμ±νλ λ§€μ° λ¨μν μ °μ΄λλΆν° μμνκ² μ΅λλ€. μ΄ μ °μ΄λλ μ΄λ―Έ shaders ν΄λμ μμ±λμ΄ μμ΅λλ€. μμΌλ‘ μΆκ°λ λͺ¨λ μ °μ΄λλ ν΄λΉ ν΄λμ μΆκ°νμ¬ CMake μ€ν¬λ¦½νΈλ₯Ό ν΅ν΄ μλμΌλ‘ λΉλλλλ‘ κ΅¬μ±ν κ²μ λλ€.
gradient.comp
//GLSL version to use
#version 460
//size of a workgroup for compute
layout (local_size_x = 16, local_size_y = 16) in;
//descriptor bindings for the pipeline
layout(rgba16f,set = 0, binding = 0) uniform image2D image;
void main() 
{
    ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy);
	ivec2 size = imageSize(image);
    if(texelCoord.x < size.x && texelCoord.y < size.y)
    {
        vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
        if(gl_LocalInvocationID.x != 0 && gl_LocalInvocationID.y != 0)
        {
            color.x = float(texelCoord.x)/(size.x);
            color.y = float(texelCoord.y)/(size.y);	
        }
    
        imageStore(image, texelCoord, color);
    }
}
GLSL λ²μ μ μ§μ νλ κ²μΌλ‘ μμν©λλ€. 460μ GLSL 4.6λ²μ μ λμλ©λλ€.
λ€μμ μμ κ·Έλ£Ήμ ν¬κΈ°λ₯Ό μ μνλ layout ꡬ문μ μμ±ν©λλ€. μμ μ€λͺ νλ―μ΄, μ»΄ν¨νΈ μ °μ΄λλ μ¬λ¬ μ°λ λ(νΉμ λ μΈ)λ‘ μ΄λ£¨μ΄μ§ κ·Έλ£Ή λ¨μλ‘ μ€νλ©λλ€. μ¬κΈ°μλ x=16, y=16, z=1(κΈ°λ³Έκ°)μΌλ‘ μ§μ νκ³ μλλ°, μ΄λ κ° μμ κ·Έλ£Ήμ΄ 16 * 16 μ°λ λ λ¨μλ‘ μμ νλ€λ κ²μ μλ―Έν©λλ€.
λ€μμ μ
°μ΄λ μ
λ ₯μ μν λμ€ν¬λ¦½ν° μ
μ μ€μ ν©λλ€. μ¬κΈ°μλ set 0, binding 0μμΉμ λ¨μΌ image2Dλ₯Ό λ°μΈλ©νκ³  μμ΅λλ€. Vulkanμμλ νλμ λμ€ν¬λ¦½ν° μ
μ΄ μ¬λ¬ λ°μΈλ©μ κ°μ§ μ μμΌλ©°, μ΄λ ν΄λΉ μ
μ λ°μΈλ©ν  λ ν¨κ» λ°μΈλ©λλ μμλ€μ
λλ€. λ°λΌμ λμ€ν¬λ¦½ν° μ
 νλμ 0λ² μΈλ±μ€μ λ¨μΌ μ΄λ―Έμ§λ₯Ό 0λ² λ°μΈλ©μ ν¬ν¨μν€λ ꡬ쑰μ
λλ€.
μ΄ μ °μ΄λλ global invocation IDμ μ’νλ₯Ό κΈ°λ°μΌλ‘ κ·ΈλΌλμΈνΈλ₯Ό νμ±νλ λ§€μ° λ¨μν λλ―Έ μ °μ΄λμ λλ€. λ§μ½ local Invocation IDκ° X Yμμ 0μ΄λΌλ©΄, κ²μ μμ΄ λμ¬ κ²μ λλ€. κ²°κ³Όμ μΌλ‘ μ °μ΄λ μμ κ·Έλ£Ή νΈμΆμ μκ°μ μΌλ‘ 보μ¬μ£Όλ 그리λλ₯Ό μμ±ν κ²μ λλ€.
μ °μ΄λ μ½λλ₯Ό μμ ν λλ λ°λμ ν΄λΉ μ °μ΄λλ₯Ό νκ²μΌλ‘ μΆκ°ν΄ μ»΄νμΌν΄μΌ ν©λλ€. λν μ νμΌμ μΆκ°νλ€λ©΄ CMakeλ₯Ό λ€μ ꡬμ±ν΄μΌλ§ ν©λλ€. μ΄ κ³Όμ μμ μ€λ₯ μμ΄ μ±κ³΅ν΄μΌ GPUμμ ν΄λΉ μ °μ΄λλ₯Ό μ€ννλ λ° νμν spirv νμΌλ€μ΄ μ μμ μΌλ‘ μμ±λ©λλ€. μ€ν¨ν κ²½μ°, νλ‘μ νΈμμ μ °μ΄λλ₯Ό μ€ννκΈ° μν΄ νμν spirvνμΌμ΄ λλ½λ μ μμ΅λλ€.
λμ€ν¬λ¦½ν° μ  λ μ΄μμ μ€μ νκΈ°
μ»΄ν¨νΈ νμ΄νλΌμΈμ ꡬμ±νκΈ° μν΄ νμ΄νλΌμΈ λ μ΄μμμ ꡬμ±ν΄μΌ ν©λλ€. μ΄ κ²½μ°, λ°μΈλ© 0λ²μ μ΄λ―Έμ§λ₯Ό ν¬ν¨νλ λ¨μΌ λμ€ν¬λ¦½ν° μ  λ§μ λ΄λ λ μ΄μμμ΄ μ¬μ©λ©λλ€.
λμ€ν¬λ¦½ν° μ
 λ μ΄μμμ μμ±νκΈ° μν΄, λ°μΈλ©μ μ λ³΄λ₯Ό λ°°μ΄ ννλ‘ μ μ₯ν΄μΌ ν©λλ€. μ΄λ₯Ό κ°νΈνκ² λ€λ£¨κΈ° μν΄ μ΄ κ³Όμ μ μΆμννλ ꡬ쑰체λ₯Ό μ μν΄λ³΄κ² μ΅λλ€. λμ€ν¬λ¦½ν° μΆμνλ vk_descriptors.h/cppμ μμ±ν  κ²μ
λλ€.
struct DescriptorLayoutBuilder {
    std::vector<VkDescriptorSetLayoutBinding> bindings;
    void add_binding(uint32_t binding, VkDescriptorType type);
    void clear();
    VkDescriptorSetLayout build(VkDevice device, VkShaderStageFlags shaderStages, void* pNext = nullptr, VkDescriptorSetLayoutCreateFlags flags = 0);
};
config/info κ΅¬μ‘°μ²΄μΈ VkDescriptorSetLayoutBindingλ₯Ό λ°°μ΄λ‘ λ΄μ κ²μ
λλ€. 그리고 μ΄λ₯Ό κΈ°λ°μΌλ‘ μ€μ  Vulkan κ°μ²΄ VkDescriptorSetLayoutμ λ§λλ build()ν¨μλ₯Ό μ μνκ² μ΅λλ€.
μ΄μ  Builderλ₯Ό μν ν¨μλ₯Ό μμ±νκ² μ΅λλ€.
void DescriptorLayoutBuilder::add_binding(uint32_t binding, VkDescriptorType type)
{
    VkDescriptorSetLayoutBinding newbind {};
    newbind.binding = binding;
    newbind.descriptorCount = 1;
    newbind.descriptorType = type;
    bindings.push_back(newbind);
}
void DescriptorLayoutBuilder::clear()
{
    bindings.clear();
}
λ¨Όμ , add_binding ν¨μλ₯Ό μμ±νκ² μ΅λλ€. μ΄ ν¨μλ VkDescriptorSetLayoutBinding ꡬ쑰체λ₯Ό ꡬμ±ν λ€ λ°°μ΄μ μΆκ°ν©λλ€. λ μ΄μμ λ°μΈλ©μ μμ±ν  λ μ§κΈμ λ°μΈλ© μ«μμ λμ€ν¬λ¦½ν° νμ
λ§ μκ³  μμΌλ©΄ μΆ©λΆν©λλ€. μμμ λ€λ£¬ μ»΄ν¨νΈ μ
°μ΄λ μμ μμλ λ°μΈλ© 0λ²μ νμ
μ VK_DESCRIPTOR_TYPE_STORAGE_IMAGEμ
λλ€. μ΄ νμ
μ μ°κΈ° κ°λ₯ν μ΄λ―Έμ§λ₯Ό μλ―Έν©λλ€.
μ΄μ  μ€μ  λ μ΄μμμ μμ±ν΄λ΄ μλ€.
VkDescriptorSetLayout DescriptorLayoutBuilder::build(VkDevice device, VkShaderStageFlags shaderStages, void* pNext, VkDescriptorSetLayoutCreateFlags flags)
{
    for (auto& b : bindings) {
        b.stageFlags |= shaderStages;
    }
    VkDescriptorSetLayoutCreateInfo info = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
    info.pNext = pNext;
    info.pBindings = bindings.data();
    info.bindingCount = (uint32_t)bindings.size();
    info.flags = flags;
    VkDescriptorSetLayout set;
    VK_CHECK(vkCreateDescriptorSetLayout(device, &info, nullptr, &set));
    return set;
}
λ¨Όμ  λ°μΈλ©μ μννλ©° μ °μ΄λ μ€νλ¨κ³ νλκ·Έ(stage flag)λ₯Ό μΆκ°ν©λλ€. νλμ λμ€ν¬λ¦½ν° μ  λ΄μμ κ° λμ€ν¬λ¦½ν° λ°μΈλ©μ νλκ·Έλ¨ΌνΈ μ °μ΄λλ μ μ  μ °μ΄λ λ± μλ‘ λ€λ₯Έ μ °μ΄λ λ¨κ³μμ μ¬μ©λ μ μμ΅λλ€. κ·Έλ¬λ μ΄λ² ꡬνμμλ λ°μΈλ©λ³ μ °μ΄λ λ¨κ³ νλκ·Έλ μ§μνμ§ μκ³ λμ€ν¬λ¦½ν° μ  μ 체μ λμΌν μ °μ΄λ λ¨κ³κ° μ μ©λλλ‘ κ°μ ν κ²μ λλ€.
λ€μμ VkDescriptorSetLayoutCreateInfoλ₯Ό ꡬμ±νκ² μ΅λλ€. μ€μ ν  κ²μ΄ λ§μ§λ μμλ°, λμ€ν¬λ¦½ν° λ°μΈλ©μ λ°°μ΄μ μ°κ²°νκΈ°λ§ νλ©΄ λ©λλ€. μ΄ν vkCreateDescriptorSetLayoutμ νΈμΆν΄ λμ€ν¬λ¦½ν° μ
 λ μ΄μμμ μμ±ν©λλ€.
λμ€ν¬λ¦½ν° ν λΉκΈ°
λμ€ν¬λ¦½ν° μ  λ μ΄μμμ΄ κ΅¬νλμμΌλ λμ€ν¬λ¦½ν° μ μ ν λΉν μ μμ΅λλ€. λ§μ°¬κ°μ§λ‘ μ½λλ₯Ό κ°κ²°νκ² μμ±ν μ μλλ‘ μ΄λ₯Ό μΆμνν ν λΉμ ꡬ쑰체λ₯Ό μμ±ν΄λ³΄κ² μ΅λλ€.
struct DescriptorAllocator {
    struct PoolSizeRatio{
		VkDescriptorType type;
		float ratio;
    };
    VkDescriptorPool pool;
    void init_pool(VkDevice device, uint32_t maxSets, std::span<PoolSizeRatio> poolRatios);
    void clear_descriptors(VkDevice device);
    void destroy_pool(VkDevice device);
    VkDescriptorSet allocate(VkDevice device, VkDescriptorSetLayout layout);
};
λμ€ν¬λ¦½ν° ν λΉμ VkDescriptorPoolμ ν΅ν΄ μ΄λ£¨μ΄μ§λλ€. μ΄ κ°μ²΄λ λμ€ν¬λ¦½ν° μ
μ ν¬κΈ°μ νμ
μ 미리 μ§μ νμ¬ μ΄κΈ°νν΄μΌ ν©λλ€. νΉμ  λμ€ν¬λ¦½ν°λ₯Ό μν λ©λͺ¨λ¦¬ ν λΉμλ‘ μκ°ν  μλ μμ΅λλ€. λ§€μ° ν° 1κ°μ λμ€ν¬λ¦½ν° μ
μ΄ μ μ²΄ μμ§μ κ΄λ¦¬ν  μλ μμ§λ§, μ΄ κ²½μ° μ¬μ©ν  λͺ¨λ  λμ€ν¬λ¦½ν°λ₯Ό μ¬μ μ μμμΌ νλ€λ μ μ½μ΄ μμ΅λλ€. μ΄λ κ·λͺ¨κ° 컀μ§μλ‘ λ§€μ° μ΄λ ΅κ³  볡μ‘ν  μ μμ΅λλ€. λμ  μ‘°κΈ λ λ¨μν λ°©μμ ꡬμ±νκ² μ΅λλ€. νλ‘μ νΈμ λ€μν λΆλΆμ μ¬λ¬ λμ€ν¬λ¦½ν° νμ ν λΉνκ³  μν©μ λ§κ² λ μ νν μ€μ νλ κ²μ
λλ€.
λμ€ν¬λ¦½ν° ν κ΄λ ¨ν΄μ ν κ°μ§ λ§€μ° μ€μν μ μ νμ 리μ νλ©΄ ν΄λΉ νμμ ν λΉλ λͺ¨λ λμ€ν¬λ¦½ν° μ μ΄ νκ΄΄λλ€λ κ²μ λλ€. μ΄λ νλ μ λ¨μλ‘ μμ±λλ λμ€ν¬λ¦½ν° μ μ λ€λ£° λ λ§€μ° μ μ©ν©λλ€. ν νλ μ λμλ§ μ¬μ©λλ λμ€ν¬λ¦½ν° μ μ λμ μΌλ‘ ν λΉνκ³ , νλ μ μμ μ  νλ²μ λͺ¨λ μ κ±°ν μ μμ΅λλ€. μ΄ λ°©μμ GPU μ μ‘°μ¬μ μν΄ κ°μ₯ λΉ λ₯Έ κ²½λ‘λ‘ μ΅μ ν λμμμ΄ νμΈλμμΌλ©°, νλ μλ³ λμ€ν¬λ¦½ν° μ μ ꡬμ±ν λ μ¬μ©ν κ²μ κΆμ₯ν©λλ€.
DescriptorAllocatorμλ λμ€ν¬λ¦½ν° ν μ΄κΈ°ν, ν΄μ , 그리고 λμ€ν¬λ¦½ν° μ
μ ν λΉνλ ν¨μλ§μ μ μΈνκ² μ΅λλ€.
μ΄μ  μ½λλ₯Ό μμ±ν΄λ΄ μλ€.
void DescriptorAllocator::init_pool(VkDevice device, uint32_t maxSets, std::span<PoolSizeRatio> poolRatios)
{
    std::vector<VkDescriptorPoolSize> poolSizes;
    for (PoolSizeRatio ratio : poolRatios) {
        poolSizes.push_back(VkDescriptorPoolSize{
            .type = ratio.type,
            .descriptorCount = uint32_t(ratio.ratio * maxSets)
        });
    }
	VkDescriptorPoolCreateInfo pool_info = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
	pool_info.flags = 0;
	pool_info.maxSets = maxSets;
	pool_info.poolSizeCount = (uint32_t)poolSizes.size();
	pool_info.pPoolSizes = poolSizes.data();
	vkCreateDescriptorPool(device, &pool_info, nullptr, &pool);
}
void DescriptorAllocator::clear_descriptors(VkDevice device)
{
    vkResetDescriptorPool(device, pool, 0);
}
void DescriptorAllocator::destroy_pool(VkDevice device)
{
    vkDestroyDescriptorPool(device,pool,nullptr);
}
μμ±κ³Ό νκ΄΄ ν¨μλ₯Ό μΆκ°νμ΅λλ€. clear ν¨μλ νκ΄΄κ° μλλΌ λ¦¬μ
νλ ν¨μμ
λλ€. μ΄ ν¨μλ νλ‘λΆν° μμ±λ λͺ¨λ  λμ€ν¬λ¦½ν° μ
μ νκ΄΄νκ³  νμ μ΄κΈ° μνλ‘ λλ리μ§λ§, VkDescriptorPool κ·Έ μ체λ₯Ό νκ΄΄νμ§λ μμ΅λλ€.
λμ€ν¬λ¦½ν° νμ μ΄κΈ°ννλ €λ©΄ vkCreateDescriptorPoolμ μ¬μ©νκ³ , μ¬κΈ°μ PoolSizeRatioμ λ°°μ΄μ μ λ¬ν΄μΌ ν©λλ€. μ΄ κ΅¬μ‘°μ²΄λ λμ€ν¬λ¦½ν° νμ
(VkDescriptorType, μμ λ°μΈλ©μμ μ¬μ©ν κ²κ³Ό λμΌ)κ³Ό maxSets νλΌλ―Έν°μ κ³±ν΄μ§ λΉμ¨μ ν¨κ» ν¬ν¨νκ³  μμ΅λλ€. μ΄λ₯Ό ν΅ν΄ λμ€ν¬λ¦½ν° νμ΄ μΌλ§λ ν΄μ§λ₯Ό μ§μ  μ μ΄ν  μ μμ΅λλ€. maxSetsμ ν΄λΉ νμμ μμ±ν  μ μλ VkDescriptorSetsμ μ΅λ κ°μλ₯Ό μλ―Ένκ³ , poolSizesλ νΉμ  νμ
μ λμ€ν¬λ¦½ν° λ°μΈλ©μ΄ λͺ κ° μ‘΄μ¬ν μ§λ₯Ό λνλ
λλ€.
λ§μ§λ§μΌλ‘ DescriptorAllocator::allocate ν¨μλ₯Ό μμ±ν΄μΌ ν©λλ€. μλ μ½λλ₯Ό νμΈν΄λ³΄μΈμ.
VkDescriptorSet DescriptorAllocator::allocate(VkDevice device, VkDescriptorSetLayout layout)
{
    VkDescriptorSetAllocateInfo allocInfo = {.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
    allocInfo.pNext = nullptr;
    allocInfo.descriptorPool = pool;
    allocInfo.descriptorSetCount = 1;
    allocInfo.pSetLayouts = &layout;
    VkDescriptorSet ds;
    VK_CHECK(vkAllocateDescriptorSets(device, &allocInfo, &ds));
    return ds;
}
VkDescriptorSetAllocateInfoλ₯Ό μ±μμΌ ν©λλ€. μ΄ κ΅¬μ‘°μ²΄μλ λμ€ν¬λ¦½ν° μ
μ ν λΉλ°μ λμ€ν¬λ¦½ν° ν, ν λΉν  λμ€ν¬λ¦½ν° μ
μ κ°μ, 그리고 λμ€ν¬λ¦½ν° μ
 λ μ΄μμμ΄ νμν©λλ€.
λμ€ν¬λ¦½ν° μ κ³Ό λ μ΄μμ μ΄κΈ°ννκΈ°
VulkanEngineμ μλ‘μ΄ ν¨μμ μ¬μ©ν  λ©€λ²λ₯Ό μΆκ°ν©μλ€.
#include <vk_descriptors.h>
struct VulkanEngine{
public:
	DescriptorAllocator globalDescriptorAllocator;
	VkDescriptorSet _drawImageDescriptors;
	VkDescriptorSetLayout _drawImageDescriptorLayout;
private:
	void init_descriptors();
}
μ΄λ¬ν DescriptorAllocator μ€ νλλ₯Ό μ μ ν λΉκΈ°λ‘μ μμ§μ μ μ₯ν  κ²μ
λλ€. κ·Έ ν λ λλ§ν μ΄λ―Έμ§λ₯Ό λ°μΈλ©ν  λμ€ν¬λ¦½ν° μ
κ³Ό, ν΄λΉ νμ
μ λμ€ν¬λ¦½ν°λ₯Ό μν λμ€ν¬λ¦½ν° μ
 λ μ΄μμμ μ μ₯ν΄μΌ ν©λλ€. μ΄ λ μ΄μμμ μ΄ν νμ΄νλΌμΈ μμ± μ νμνκ² λ©λλ€.
init_descriptors() ν¨μλ₯Ό VulkanEngineμ init()ν¨μ λ΄μμ sync_structures μ΄ν μΆκ°νλ κ²μ μμ§ λ§μΈμ.
void VulkanEngine::init()
{
	//other code
	init_commands();
	init_sync_structures();
	init_descriptors();	
	//everything went fine
	_isInitialized = true;
}
μ΄μ  ν¨μλ₯Ό μμ±ν μ μμ΅λλ€.
void VulkanEngine::init_descriptors()
{
	//create a descriptor pool that will hold 10 sets with 1 image each
	std::vector<DescriptorAllocator::PoolSizeRatio> sizes =
	{
		{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 }
	};
	globalDescriptorAllocator.init_pool(_device, 10, sizes);
	//make the descriptor set layout for our compute draw
	{
		DescriptorLayoutBuilder builder;
		builder.add_binding(0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE);
		_drawImageDescriptorLayout = builder.build(_device, VK_SHADER_STAGE_COMPUTE_BIT);
	}
}
λ¨Όμ  DescriptorAllocatorλ₯Ό μ΄κΈ°ν ν  λ, μ΄10κ°μ λμ€ν¬λ¦½ν° μ
μ ν λΉ κ°λ₯νλλ‘ νκ³ , κ° λμ€ν¬λ¦½ν° μ
μλ VK_DESCRIPTOR_TYPE_STORAGE_IMAGEνμ
μ λμ€ν¬λ¦½ν°λ₯Ό 1κ°μ© κ°μ§λλ‘ μ€μ ν©λλ€. μ΄λ μ»΄ν¨νΈ μ
°μ΄λμμ μμ±ν  μ μλ μ΄λ―Έμ§μ μ¬μ©λλ νμ
μ
λλ€.
κ·Έ ν, λ μ΄μμ λΉλλ₯Ό μ¬μ©ν΄ νμν λμ€ν¬λ¦½ν° μ
 λ μ΄μμμ ꡬμ±ν  μ μμ΅λλ€. μ΄λ λ°μΈλ© 0λ²μ VK_DESCRIPTOR_TYPE_STORAGE_IMAGEλ₯Ό λνλ΄λ λ μ΄μμμ
λλ€.(μμ μ μν λμ€ν¬λ¦½ν° νκ³Ό μΌμΉν©λλ€.)
μ΄ κ³Όμ μ ν΅ν΄, μ»΄ν¨νΈ μ
°μ΄λ 그리기μ μ¬μ©ν  μ μλ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE νμ
μ λμ€ν¬λ¦½ν° μ
μ 10κ°κΉμ§ ν λΉν  μ μμ΅λλ€.
μ΄μ΄μ κ·Έ μ€ νλλ₯Ό μ€μ λ‘ ν λΉν λ€, λ λλ§ν μ΄λ―Έμ§λ₯Ό μ°Έμ‘°νλλ‘ λμ€ν¬λ¦½ν°λ₯Ό μμ±ν©λλ€.
void VulkanEngine::init_descriptors()
{
	// other code
	//allocate a descriptor set for our draw image
	_drawImageDescriptors = globalDescriptorAllocator.allocate(_device,_drawImageDescriptorLayout);	
	VkDescriptorImageInfo imgInfo{};
	imgInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
	imgInfo.imageView = _drawImage.imageView;
	
	VkWriteDescriptorSet drawImageWrite = {};
	drawImageWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
	drawImageWrite.pNext = nullptr;
	
	drawImageWrite.dstBinding = 0;
	drawImageWrite.dstSet = _drawImageDescriptors;
	drawImageWrite.descriptorCount = 1;
	drawImageWrite.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
	drawImageWrite.pImageInfo = &imgInfo;
	vkUpdateDescriptorSets(_device, 1, &drawImageWrite, 0, nullptr);
	//make sure both the descriptor allocator and the new layout get cleaned up properly
	_mainDeletionQueue.push_function([&]() {
		globalDescriptorAllocator.destroy_pool(_device);
		vkDestroyDescriptorSetLayout(_device, _drawImageDescriptorLayout, nullptr);
	});
}
λ¨Όμ  μμ μμ±ν DescriptorAllocator, _drawImageDescriptorLayoutμ ν΅ν΄ λμ€ν¬λ¦½ν° μ
 κ°μ²΄λ₯Ό ν λΉν©λλ€. κ·Έ ν λμ€ν¬λ¦½ν° μ
μ μ°λ¦¬κ° μ¬μ©ν  μ΄λ―Έμ§μ μ°κ²°ν΄μ£Όμ΄μΌ ν©λλ€. μ΄λ₯Ό μν΄ vkUpdateDescriptorSets ν¨μλ₯Ό μ¬μ©ν©λλ€. μ΄ ν¨μλ κ°λ³ μ
λ°μ΄νΈ μ λ³΄λ₯Ό λ΄λ VkWriteDescriptorSets λ°°μ΄μ μΈμλ‘ λ°μ΅λλ€. ν λΉν λμ€ν¬λ¦½ν° μ
μ λ°μΈλ© 0λ²μ κ°λ¦¬ν€λ λ¨μΌ writeλ₯Ό μμ±νμ¬ μ¬λ°λ₯Έ λμ€ν¬λ¦½ν° νμ
μ λͺ
μν©λλ€. μ΄λ λν λ°μΈλ©ν  μ€μ  μ΄λ―Έμ§ λ°μ΄ν°λ₯Ό λ΄λ VkDescriptorImageInfoλ₯Ό κ°λ¦¬ν΅λλ€. VkDescriptorImageInfoλ 그릴 μ΄λ―Έμ§μ μ΄λ―Έμ§ λ·°λ₯Ό ν¬ν¨ν©λλ€.
μ΄ κ³Όμ μ λ§μΉλ©΄, 그릴 μ΄λ―Έμ§μ λ°μΈλ©ν λμ€ν¬λ¦½ν° μ κ³Ό νμν λ μ΄μμμ΄ λ§λ€μ΄μ‘μ΅λλ€. λ§μΉ¨λ΄ μ»΄ν¨νΈ νμ΄νλΌμΈμ μμ±ν μ μμ΅λλ€.
μ»΄ν¨νΈ νμ΄νλΌμΈ
λμ€ν¬λ¦½ν° μ
 λ μ΄μμμ΄ μ€λΉλμμΌλ―λ‘ νμ΄νλΌμΈ λ μ΄μμμ μμ±ν  κΈ°λ°μ΄ λ§λ ¨λμμ΅λλ€. νμ΄νλΌμΈμ μμ±νκΈ° μ μ λ§μ§λ§μΌλ‘ ν΄μΌ ν  μμ
μ΄ λ¨μμμ΅λλ€. μ
°μ΄λ μ½λλ₯Ό λΆλ¬μ λλΌμ΄λ²μ μ°κ²°νλ κ²μ
λλ€. Vulkan νμ΄νλΌμΈμμλ μ
°μ΄λλ₯Ό μ€μ νκΈ° μν΄ VkShaderModuleμ μμ±ν΄μΌ ν©λλ€. μ΄λ₯Ό λΆλ¬μ€λ ν¨μλ₯Ό vk_pipelines.h/cppμ μΆκ°ν©μλ€.
λ€μμ vk_pipelines.cppμ μΆκ°ν©λλ€.
#include <vk_pipelines.h>
#include <fstream>
#include <vk_initializers.h>
μ΄ ν¨μλ₯Ό μΆκ°νκ³ ν€λμλ μ μΈλΆλ₯Ό μΆκ°ν΄μ€λλ€.
bool vkutil::load_shader_module(const char* filePath,
    VkDevice device,
    VkShaderModule* outShaderModule)
{
    // open the file. With cursor at the end
    std::ifstream file(filePath, std::ios::ate | std::ios::binary);
    if (!file.is_open()) {
        return false;
    }
    // find what the size of the file is by looking up the location of the cursor
    // because the cursor is at the end, it gives the size directly in bytes
    size_t fileSize = (size_t)file.tellg();
    // spirv expects the buffer to be on uint32, so make sure to reserve a int
    // vector big enough for the entire file
    std::vector<uint32_t> buffer(fileSize / sizeof(uint32_t));
    // put file cursor at beginning
    file.seekg(0);
    // load the entire file into the buffer
    file.read((char*)buffer.data(), fileSize);
    // now that the file is loaded into the buffer, we can close it
    file.close();
    // create a new shader module, using the buffer we loaded
    VkShaderModuleCreateInfo createInfo = {};
    createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
    createInfo.pNext = nullptr;
    // codeSize has to be in bytes, so multply the ints in the buffer by size of
    // int to know the real size of the buffer
    createInfo.codeSize = buffer.size() * sizeof(uint32_t);
    createInfo.pCode = buffer.data();
    // check that the creation goes well.
    VkShaderModule shaderModule;
    if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
        return false;
    }
    *outShaderModule = shaderModule;
    return true;
}
μ΄ ν¨μλ λ¨Όμ  νμΌμ std::vector<uint32_t>νμμΌλ‘ λΆλ¬μ΅λλ€. μ΄λ μ»΄νμΌλ μ
°μ΄λ λ°μ΄ν°λ₯Ό λ΄μ κ²μ΄λ©°, vkCreateShaderModuleμ νΈμΆν  λ μ¬μ©λ©λλ€. μ
°μ΄λ λͺ¨λμ createInfoλ μ
°μ΄λ λ°μ΄ν°λ₯Ό λ΄λ λ°°μ΄ μΈμλ νΉλ³ν νμν μ λ³΄λ μμ΅λλ€. μ
°μ΄λ λͺ¨λμ νμ΄νλΌμΈμ μμ±ν  λμλ§ νμνλ©°, νμ΄νλΌμΈμ΄ μμ±λ μ΄νμλ μμ νκ² νκ΄΄ν΄λ λλ―λ‘ μ΄λ₯Ό VulkanEngineμ λ΄μ νμλ μμ΅λλ€.
μ΄μ  VulkanEngineμΌλ‘ λμμ νμν μλ‘μ΄ λ©€λ²λ₯Ό μΆκ°νκ³  init_pipelines()ν¨μμ init_background_pipelines() ν¨μλ₯Ό μΆκ°ν©μλ€. init_pipelines()λ μΆν νν λ¦¬μΌμ΄ μ§νλλ©° μΆκ°ν  λ€λ₯Έ νμ΄νλΌμΈ μ΄κΈ°ν ν¨μλ νΈμΆν  κ²μ
λλ€.
class VulkanEngine{
public:
	VkPipeline _gradientPipeline;
	VkPipelineLayout _gradientPipelineLayout;
private:
	void init_pipelines();
	void init_background_pipelines();
}
μ΄λ₯Ό initν¨μμ μΆκ°νκ³  vk_pipelines.hλ₯Ό νμΌ μλ¨μ ν¬ν¨μν΅λλ€. init_pipelines() ν¨μλ init_background_pipelines()λ₯Ό νΈμΆν  κ²μ
λλ€.
#include <vk_pipelines.h>
void VulkanEngine::init()
{
	//other code
	init_commands();
	init_sync_structures();
	init_descriptors();	
	init_pipelines();
	//everything went fine
	_isInitialized = true;
}
void VulkanEngine::init_pipelines()
{
	init_background_pipelines();
}
μ΄μ  νμ΄νλΌμΈμ μμ±νκ² μ΅λλ€. λ¨Όμ  νμ΄νλΌμΈ λ μ΄μμμ λ§λ€μ΄μΌ ν©λλ€.
void VulkanEngine::init_background_pipelines()
{
	VkPipelineLayoutCreateInfo computeLayout{};
	computeLayout.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
	computeLayout.pNext = nullptr;
	computeLayout.pSetLayouts = &_drawImageDescriptorLayout;
	computeLayout.setLayoutCount = 1;
	VK_CHECK(vkCreatePipelineLayout(_device, &computeLayout, nullptr, &_gradientPipelineLayout));
}
νμ΄νλΌμΈμ μμ±νλ €λ©΄ μ¬μ©ν λμ€ν¬λ¦½ν° μ  λ μ΄μμ λ°°μ΄κ³Ό νΈμμμμ κ°μ μΆκ° ꡬμ±μ΄ νμν©λλ€. μ΄ μ °μ΄λμμλ μ΄λ¬ν ꡬμ±μ΄ νμ μκΈ° λλ¬Έμ, λμ€ν¬λ¦½ν° μ  λ μ΄μμλ§ μ¬μ©νλ©΄ λ©λλ€.
μ΄μ  μ
°μ΄λ λͺ¨λμ λΆλ¬μ λ€λ₯Έ μ΅μ
λ€μ VkComputePipelineCreateInfoμ μΆκ°ν¨μΌλ‘μ νμ΄νλΌμΈ κ°μ²΄ μ체λ₯Ό μμ±ν©λλ€.
void VulkanEngine::init_background_pipelines()
{
	//layout code
	VkShaderModule computeDrawShader;
	if (!vkutil::load_shader_module("../../shaders/gradient.comp.spv", _device, &computeDrawShader))
	{
		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 = computeDrawShader;
	stageinfo.pName = "main";
	VkComputePipelineCreateInfo computePipelineCreateInfo{};
	computePipelineCreateInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
	computePipelineCreateInfo.pNext = nullptr;
	computePipelineCreateInfo.layout = _gradientPipelineLayout;
	computePipelineCreateInfo.stage = stageinfo;
	
	VK_CHECK(vkCreateComputePipelines(_device,VK_NULL_HANDLE,1,&computePipelineCreateInfo, nullptr, &_gradientPipeline));
}
λ¨Όμ  μμ λ§λ  ν¨μλ₯Ό μ¬μ©ν΄ VkShaderModuleμ λΆλ¬μ νμΌμ μ€λ₯κ° μλμ§ νμΈν©λλ€. μ¬κΈ°μ κ²½λ‘λ κΈ°λ³Έ Windows + msvc λΉλ ν΄λλ₯Ό κΈ°μ€μΌλ‘ μ€μ λμ΄ μλ€λ μ μ μ μνμΈμ. λ§μ½ λ€λ₯Έ μ΅μ
μ μ¬μ©νλ€λ©΄ νμΌ κ²½λ‘κ° μ λλ‘ λμλμ§ νμΈνμΈμ. κ΅¬μ± νμΌμ μ€μ λ ν΄λλ₯Ό κΈ°μ€μΌλ‘ κ²½λ‘λ₯Ό μΆμννλ κ²λ κ³ λ €ν΄λ³΄μΈμ
κ·Έ λ€μμΌλ‘ μ
°μ΄λλ₯Ό VkPipelineShaderStageCreateInfoλ‘ μ°κ²°ν©λλ€. μ¬κΈ°μ μ μν  μ μ μ¬μ©ν  μ
°μ΄λμ ν¨μμ μ΄λ¦μ μ λ¬νλ€λ μ μ
λλ€. μ¬κΈ°μλ main()μ
λλ€. μ΄λ λμΌν μ
°μ΄λ νμΌ μμ λ€λ₯Έ μ§μ
μ μ μ€μ ν¨μΌλ‘μ¨ μ¬λ¬ μ»΄ν¨νΈ μ
°μ΄λλ₯Ό λ΄μ μ μλ€λ μλ―Έμ
λλ€.
λ§μ§λ§μΌλ‘ VkComputePipelineCreateInfoλ₯Ό μ€μ ν©λλ€. μ΄λ μ»΄ν¨νΈ μ
°μ΄λμ λ¨κ³ μ λ³΄μ λ μ΄μμμ΄ νμν©λλ€. κ·Έ ν vkCreateComputePipelinesλ₯Ό νΈμΆν  μ μμ΅λλ€.
ν¨μμ λμμ μμ  νμ λ±λ‘νμ¬ νλ‘κ·Έλ¨ μ’ λ£ μ ꡬ쑰체λ€μ΄ μ 리λλλ‘ ν©λλ€.
	vkDestroyShaderModule(_device, computeDrawShader, nullptr);
	_mainDeletionQueue.push_function([&]() {
		vkDestroyPipelineLayout(_device, _gradientPipelineLayout, nullptr);
		vkDestroyPipeline(_device, _gradientPipeline, nullptr);
		});
μ °μ΄λ λͺ¨λμ ν΄λΉ ν¨μλ΄μμ λ°λ‘ νκ΄΄ν μ μμ΅λλ€. νμ΄νλΌμΈμ΄ μ΄λ―Έ μμ±λμκΈ° λλ¬Έμ λ μ΄μ νμνμ§ μμ΅λλ€. λ°λ©΄, νμ΄νλΌμΈκ³Ό νμ΄νλΌμΈ λ μ΄μμμ κ³μ νμνλ―λ‘ νλ‘κ·Έλ¨μ΄ μ’ λ£ μ νκ΄΄ν©λλ€.
μ΄μ  그릴 μ€λΉκ° λλ¬μ΅λλ€.
μ»΄ν¨νΈ μ °μ΄λ 그리기
draw_background()ν¨μλ‘ λμμ vkCmdClearλ₯Ό μ»΄ν¨νΈ μ
°μ΄λ νΈμΆλ‘ λ체ν©λλ€.
	// 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);
	// 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);
λ¨Όμ  vkCmdBindPipelineμ μ¬μ©νμ¬ νμ΄νλΌμΈμ λ°μΈλ©ν©λλ€. μ»΄ν¨νΈ μ
°μ΄λλ₯Ό μ¬μ©νκΈ° λλ¬Έμ VK_PIPELINE_BIND_POINT_COMPUTEλ₯Ό μ¬μ©ν©λλ€. κ·Έ ν, 그릴 μ΄λ―Έμ§λ₯Ό λ΄κ³ μλ λμ€ν¬λ¦½ν° μ
μ λ°μΈλ©νμ¬ μ
°μ΄λκ° μ κ·Όν  μ μλλ‘ ν©λλ€. λ§μ§λ§μΌλ‘ vkCmdDispatchλ₯Ό μ¬μ©νμ¬ μ»΄ν¨νΈ μ
°μ΄λλ₯Ό μ€νν©λλ€. μΌλ§λ μ
°μ΄λκ° λͺ λ² μ€ν λ μ§λ, μμ
 κ·Έλ£ΉλΉ 16 * 16 μ°λ λκ° μ€νλλ€λ μ μ κ³ λ €νμ¬, 그릴 μ΄λ―Έμ§μ ν΄μλλ₯Ό 16μΌλ‘ λλκ³  λ°μ¬λ¦Όν¨μΌλ‘μ¨ κ²°μ ν©λλ€.
μ΄ μμ μμ νλ‘κ·Έλ¨μ μ€ννλ©΄ μ΄λ―Έμ§κ° νμλ κ²μ λλ€. λ§μ½ μ °μ΄λλ₯Ό λΆλ¬μ€λ κ³Όμ μμ μ€λ₯κ° λ°μνλ€λ©΄ CMakeλ₯Ό μ¬μ€ννκ³ μ °μ΄λ νκ²μ μ¬μ€μ νμ¬ μ °μ΄λλ₯Ό λΆλ¬μ€μΈμ. μ΄λ μμ§μ ꡬμ±ν λ μλμΌλ‘ ꡬμ±λμ§ μκΈ° λλ¬Έμ μ °μ΄λκ° λ°λ λ λ§λ€ μλμΌλ‘ μ¬λΉλν΄μΌν©λλ€.

Next: Setting up IMGUI