본문 바로가기
Vulkan

10 - 1. Descriptor 추가 이해

by SimonLee 2024. 8. 5.

< 그림 2 >

 

디스크립터 세트 레이아웃과 디스크립터 세트는 위의 맞물리는 물체라고 보시면 됩니다.

서로 각각 만들어 놓고 나중에 런타임에 바인딩 되죠..

코드로만 봐서는 헷갈립니다.

그래서 따로 따로 생각하시고, 절차적으로 생각하시는게 정신건강에 좋습니다.

 

 

< 그림 1 >

< 그림 1 > 그림에서는 좌측과 우측은 분리 되어있습니다.

 

좌측의 디스크립터 세트 레이아웃은 3 있구요

첫번째 디스크립터 세트 레이아웃 내부에는 3개의 디스크립터 세트 레이아웃 바인딩이 있습니다.

- 여기선 바인딩이 3개가 너무 많으니 2개로 합니다

두번째 디스크립터 세트 레이아웃 내부에는 한개의 디스크립트 세트 레이아웃 바인딩이 있습니다.

세번째 디스크립터 세트 레이아웃 내부에는 한개의 디스크립트 세트 레이아웃 바인딩이 있습니다.

4 개의 디스크립터 세트 레이아웃 바인딩이 필요합니다.

 

우측 3개의 디스크립터 세트가 있구요

첫번째 디스크립터 세트에는 디스크립터 3개가 있습니다.

두번째 디스크립터 세트, 세번째 디스크립터 세트에는 디스크립터가 각각 1개씩 있습니다.

 

< 그림 1 > 보면서 정리할게요 ( 중요한 부분임 )

디스크립터 개수 몇개죠? 원래 5개인데 4로 할게요

- 첫번째 디스크립터 세트 레이아웃의 디스크립터 바인딩 개수를 3--> 2로 수정..

우리가 필요한 리소스의 개수는 몇개죠 ? 4개요 ( 텍스처, 버퍼 ... 등 )

---> 필요한 리소스개수와 디스크립터 개수가 같음.

 

디스크립터 세트 레이아웃 개수 몇개죠 ? 3개요

디스크립터 세트 개수 몇개죠 ? 3개요 

---> 의미 적으로 디스크립터 세트 레이아웃과 디스크립터 세트 개수 같음.

 

자 여기서부터는 아래 코드와 같이 보세요

 

===============================================================

좌측의 디스크립터 세트 레이아웃을 먼저 생성해 볼게요

첫 번째 디스크립터 세트 레이아웃은 내부의 3개의 디스크립터 세트 레이아웃 바인딩이 있지요 ?

 ( 위에서 바인딩은 3개는 너무 많으니 2개로 한다고 했습니다.. )

- descriptorSetLayoutCreateInfoC 보시면 바인딩 카운트가 2개 입니다. 

- descriptorSetLayoutCreateInfoB 바인딩 카운트 1,

-  descriptorSetLayoutCreateInfoA 바인딩 카운트 1

 

descriptorSetLayoutCreateInfoC 먼저 볼게요

바인딩 카운트가 2개이니, 2개 항목이 있는 어레이로 정의 합니다.

2개의 VkDescriptorSetLayoutBinding을 정의해 줍니다.

- 첫번째 디스크립터 세트 레이아웃 바인딩의  descriptorCount 가 1입니다.

- 두번째 디스크립터 세트 레이아웃 바인딩의  descriptorCount 가 1입니다.

 

각각은 우측 디스크립터 세트 내부 디스크립터와 하나씩 맵핑이 되고,

하나의 리소스와 맵핑이 됩니다.

 

디스크립터 세트 레이아웃 바인딩의 descriptorCount 가 2가 된다면,

하나의 디스크립터 세트 레이아웃 바인딩이 두개의 디스크립터 ( 두개의 리소스) 둘다 바인딩 ? 

 

 

descriptorSetLayoutCreateInfoA , descriptorSetLayoutCreateInfoB 도 한번 보세요

 

1. 디스크립터 레이아웃 생성 >

void VkRenderer::CreateDescriptorSetLayout() {
    VkDescriptorSetLayoutBinding descriptorSetLayoutBindingA {
            .binding = 0,
            .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
            .descriptorCount = 1,
            .stageFlags = VK_SHADER_STAGE_VERTEX_BIT
    };
    VkDescriptorSetLayoutBinding descriptorSetLayoutBindingB {
            .binding = 0,
            .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
            .descriptorCount = 1,
            .stageFlags = VK_SHADER_STAGE_VERTEX_BIT
    };

    std::vector<VkDescriptorSetLayoutBinding> bindingC {
            VkDescriptorSetLayoutBinding {
                .binding = 0,
                .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
                .descriptorCount = 1,
                .stageFlags = VK_SHADER_STAGE_VERTEX_BIT
            },
            
            VkDescriptorSetLayoutBinding {
                .binding = 5,
                .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
                .descriptorCount = 1,
                .stageFlags = VK_SHADER_STAGE_VERTEX_BIT
            }
    };

    VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfoA {
            .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
            .bindingCount = 1,
            .pBindings = &descriptorSetLayoutBindingA,
    };
    VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfoB {
            .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
            .bindingCount = 1,
            .pBindings = &descriptorSetLayoutBindingB,
    };

    VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfoC {
            .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
            .bindingCount = 2,
            .pBindings = bindingC.data()
    };


    VK_CHECK_ERROR(vkCreateDescriptorSetLayout(mDevice,
                                               &descriptorSetLayoutCreateInfoA,
                                               nullptr,
                                               &mDescriptorSetLayout[0]));

    VK_CHECK_ERROR(vkCreateDescriptorSetLayout(mDevice,
                                               &descriptorSetLayoutCreateInfoB,
                                               nullptr,
                                               &mDescriptorSetLayout[1]));

    VK_CHECK_ERROR(vkCreateDescriptorSetLayout(mDevice,
                                               &descriptorSetLayoutCreateInfoC,
                                               nullptr,
                                               &mDescriptorSetLayout[2]));
}

 

디스크립터 세트 레이아웃을 생성 완료 했습니다.

우측의 디스크립터 세트를 할당 받기 전에 !!!!!!

이제 우리는 파이프라인에 우리가 생성한 디스크립터 레이아웃 생성한 걸 알려줘야 합니다.

 

파이프라인 레이아웃 메타데이터에 setLayoutCount 변수가 있는데 

setLayoutCount 의미는 파이프라인에 포함된 디스크립터 세트의 개수를 넣어야 합니다. 

<그림1> 을 보면 3개 입니다

 

VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{
        .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
        .setLayoutCount = 3,
        .pSetLayouts = &mDescriptorSetLayout[0]
};

VK_CHECK_ERROR(vkCreatePipelineLayout(mDevice,
                                      &pipelineLayoutCreateInfo,
                                      nullptr,
                                      &mPipelineLayout));
                                      
VkGraphicsPipelineCreateInfo graphicsPipelineCreateInfo{
            .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
            .....
            .layout = mPipelineLayout,            
};

VK_CHECK_ERROR(vkCreateGraphicsPipelines(mDevice,
                                         VK_NULL_HANDLE,
                                         1,
                                         &graphicsPipelineCreateInfo,
                                         nullptr,
                                         &mPipeline));

 

 

 

==========================================================================

우측의 디스크립터 세트 볼게요

디스크립터 세트는 반드시 디스크립터 풀을 사용하여 할당 받을수 있어요

그럼 풀부터 만들어야 겠죠?

 

디스크립터 풀 생성 메타데이터 ( VkDescriptorPoolCreateInfo ) 를 먼저 볼게요

여기서 헷갈리는 maxSets 와, poolSizeCount 를 볼게요

maxSets 의 정의는 " 할당할수 있는 최대 디스크립터 세트의 개수 " 입니다.

<그림 1> 에서 우측의 디스크립터 세트가 몇개 있죠? 3개있죠 ? maxSets = 3 입니다.

 

poolSizeCount의 정의는 " pPoolSizes 의 개수 " 입니다.

pPoolSizes는 구조체 VkDesciptorPoolSize 인데, 내부 변수로 type과 descriptorCount가 있습니다.

type 의 정의는 디스크립터의 타입이고,

descriptorCount의 정의는 " 할당하려는 타입의 디스크립터의 개수 " 

 

헷갈리면 안됩니다.

좌측 디스크립터 세트 레이아웃 개수 아니고,

우측 디스크립터 세트 개수 아니고, 디스크립터의 개수 입니다.

< 그림 1>에서  디스크립터는 몇개죠 ? 4개죠, 4개의 리소스를 할당 받는 겁니다

 

우리가 생성하고자 하는 4개의 리소스의 종류가 다르다고 해볼게요.

- (예제는 동일하게 되어있음 - - 다다르게 하면 귀찮음 )

descriptorPoolSize도 배열로 만들어서 리소스 마다 하나씩 넣어야 겠죠 

그러면 poolSizeCount는 4가 되고, 각 배열에 있는 descriptorCount는 1이 됩니다.

만일 생성하고자 하는 4개의 리소스가 전부 같다 ( 예제 처럼.. 유니폼 버퍼 )  그러면

 descriptorPoolSize는 1이 되고, descriptorPoolSize.descriptorCount는 4 이 됩니다.

 

2. 디스크립터 풀 생성 >

VkDescriptorPoolSize descriptorPoolSize[4];
descriptorPoolSize[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorPoolSize[0].descriptorCount = 1;
descriptorPoolSize[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorPoolSize[1].descriptorCount = 1;
descriptorPoolSize[2].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorPoolSize[2].descriptorCount = 1;
descriptorPoolSize[3].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorPoolSize[3].descriptorCount = 1;


VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
        .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
        .maxSets = 3,
        .poolSizeCount = 4,
        .pPoolSizes = &descriptorPoolSize[0]
};

VK_CHECK_ERROR(vkCreateDescriptorPool(mDevice,
                                      &descriptorPoolCreateInfo,
                                      nullptr,
                                      &mDescriptorPool));

 

디스크립터 풀은 생성되었으니, 이제 디스크립터 세트를 할당 받을 차례입니다

vkDescriptorSetAllocateInfo 구조체에서 디스크립터 세트 카운트를 또 입력받네요 ㅋㅋ

 

descriptorSetCount의 정의는 " 풀로 부터 할당받는 디스크립터 세트의 개수 "

우리는 디스크립터 세트 몇개 할당 받아야 하나요 ? 3개요 ~

VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
        .descriptorPool = mDescriptorPool,
        .descriptorSetCount = 3,
        .pSetLayouts = mDescriptorSetLayout
};

vkAllocateDescriptorSets(mDevice, &descriptorSetAllocateInfo, &mDescriptorSet[0]);

 

==========================================================================

이제 좌측 디스크립터 세트 레이아웃도 만들었고, 우측 디스크립터 세트도 할당 받았습니다.

 

아직까지 디스크립터 세트는 빈껍데기라 버퍼랑 바인딩 해줘야 하는데,

버퍼 생성 -> 메모리 할당 -> 버퍼와 메모리 바인딩은 

아래 Reference에 자세히 나오니 참고 하시고, 

 

디스크립터 세트와 버퍼와 연결하는 부분만 보겠습니다.

 

vkUpdateDescriptorSets를 하면되는데, 

메타데이터에서 또 dscriptorCount가 있습니다.

descriptorCount의 정의는 업데이트할 데스크립터의 개수 입니다.

 

descriptorBufferInfo를 보면 버퍼 하나의 대해서 정의했죠 ?? 즉 리소스 하나죠 ?

아까 < 그림 1 >에서 리소스와 디스크립터는 매핑 되니 descriptorCount는 1이겠죠...

 

descriptorBufferInfo가 배열이고 여러개의 버퍼를 디스크립터 세트에 연결하려고 하면 ??

디스크립터 세트의 내부 디스크립터가 여러개면 되긴하는데

pBufferInfo가 여러개의 버퍼를 설정 못하니, 하나의 버퍼를 내부 디스크립터가 공유 ?? 

VkDescriptorBufferInfo descriptorBufferInfo{
        .buffer = mUniformBuffer,
        .range = VK_WHOLE_SIZE
};

VkWriteDescriptorSet writeDescriptorSet{
        .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
        .dstSet = mDescriptorSet[2],
        .dstBinding = 5,
        .descriptorCount = 1,
        .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
        .pBufferInfo = &descriptorBufferInfo
};
vkUpdateDescriptorSets(mDevice, 1, &writeDescriptorSet, 0, nullptr);

 

지금까지 디스크립터 초기화 과정이 였습니다 - -

 

아 도대체 <그림 2>

좌측 디스크립터 세트 레이아웃과 우측 디스크립터 세트는  서로 언제 연결되는거야 ????

응? 렌더 함수에서 연결해 ....

 

디스크립터 세트는 아래 mDescriptorSet 고,  

디스크립터 세트 레이아웃은 어딨나 ??? mPipelineLayout  내부에 있다. 

위에서 디스크립터 세트 레이아웃 생성하는 부분에 있음.

mPipelineLayout 메타데이터 생성시 mDescriptorSetLayout 을 넣어줬었다.

// VkDescriptorSet 바인드
vkCmdBindDescriptorSets(mCommandBuffer,
                        VK_PIPELINE_BIND_POINT_GRAPHICS,
                        mPipelineLayout,
                        0,
                        3,
                        &mDescriptorSet,
                        0,
                        nullptr);
                        
vkCmdDraw(mCommandBuffer, 3, 1, 0, 0);
vkCmdEndRenderPass(mCommandBuffer);

 

 

연결되는 부분이 <그림2> 같이 되어 있어, 이해하기 어렵다.

제일 좋은 방법은 내가 한번 만들고 싶은 디스크립터 세트 레이아웃 구조를 손으로 그려보고

그대로 구현해보는 것이다.

 

정리하면 다음과 같다.

초기화 부분

- 디스크립터 세트 레이아웃 생성 --> 파이프라인 레이아웃에 등록

- 디스크립터 풀 생성 --> 디스크립터 할당 --> 버퍼생성 --> 메모리 할당 --> 업데이트 디스크립터(바인딩)  

런타임 부분

- 디스크립터 세트와 파이프라인 레이아웃을 바인딩을 통해 디스크립터 세트 레이아웃과 디스크립터 세트를 연결함...

 

Reference

https://graphicsimon.tistory.com/65

 

10. Descriptor set and Descriptor set layout

이전 챕터에서는 버텍스 버퍼를 생성하고 메모리를 생성하고 둘을 바인딩했었다.이때 메모리 생성시 " VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT " 을 사용해서 CPU GPU 공유가 가능했다.  ( R/W 빈번하게 일어남

graphicsimon.tistory.com

 

 

 

 

 

 

'Vulkan' 카테고리의 다른 글

12. Image layout and Staging Buffer  (0) 2024.07.25
11. Image  (0) 2024.07.21
10. Descriptor set and Descriptor set layout  (0) 2024.07.12
9. Pipeline and Pipeline State  (0) 2024.07.08
8. Vertex Buffer and Memory  (0) 2024.07.05