본문 바로가기
Vulkan

4. Pipeline Barrier And Image Layout

by SimonLee 2024. 6. 24.

파이프라인 배리어란 ?

파이프 라인인 내부 장벽을 설치하여 장벽 전의 명령을 완료하는 동기화하는 메커니즘이다.

함수 이름이 vkCmdPipelineBarrier 를 사용하여 일반적으로 파이프 라인 배리어 라고 불리고,

이미지 레이아웃을 진행할때, 메모리를 동기화 하는데 사용을 해야 하기 때문에

메모리 장벽이라고도 불린다.

 

 

메모리 장벽 (MemoryBarrier) 이란 ?

 

메모리 장벽은 데이터와 읽기와 쓰기를 동기화 하는데 도움이 된다. 

메모리 장벽은 메모리 장벽 전 후 지정한 작업이 동기화 되도록 보장한다.

즉 메모리 장벽을 삽입하면 메모리 지시를 실행하기 앞서 메모리 장벽이 실행되기 전

메모리 작동이 완료 됐는지를 확인한다.

 

메모리 장벽 종류

1) 전역 메모리 장벽 

- 이 유형의 메모리 장벽은 모든 종류의 실행 메모리 개체에 적용할 수 있으며 VkMemoryBarrier 구조체 인스턴스

2) 버퍼 메모리 장벽

- 이 유형의 메모리 장벽은 지정된 버퍼 개체의 특정 범위에 적용할 수 있으며 VkBufferMemoryBarrier 구조체 인스턴스

3) 이미지 메모리 방벽 ( ImageMemoryBarrier )

- 이미지 메모리 방벽은 VkImageMemoryBarrier 인스턴스로 표현하며, 지정된 이미지의 개체의 특정 이미지 하위 리소스 범위를 통해 다른 메모리 액세스 유형에 적용할 수 있다.

 

이미지 레이아웃이란 ?

여기서 언급된 메모리는 -> GPU 메모리.

벌칸 이미지를 효율적으로 처리하기 위해 데이터를 메모리에 배치하는 방식을 의미 한다.

큰 이미지의 사이즈를 최적화 알고리즘을 사용하여 더 작게 메모리로 유지할 수 있음.

레이아웃은 VkImageLayout 이름으로 열거되어 있음.

이미지 메모리 장벽(barrier)를 통해 벌칸 이미지 레이아웃을 변경 할 수 있다.

 

 

이미지 메모리 배리어 구조체

// Provided by VK_VERSION_1_0
typedef struct VkImageMemoryBarrier {
    VkStructureType            sType;
    const void*                pNext;
    VkAccessFlags              srcAccessMask;
    VkAccessFlags              dstAccessMask;
    VkImageLayout              oldLayout;
    VkImageLayout              newLayout;
    uint32_t                   srcQueueFamilyIndex;
    uint32_t                   dstQueueFamilyIndex;
    VkImage                    image;
    VkImageSubresourceRange    subresourceRange;
} VkImageMemoryBarrier;

- sType : VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER

- pNext : 확장 기능의 구조체 포인터

- srcAccessMask : 현재 Access mask

- dstAccessMask : 새로운 Access mask

- oldLayout : 현재의 이미지 layout

- newLayout : 새로운 이미지 layout

- srcQueueFamilyIndex : Transfer ownership 의 소스 큐 패밀리

- dstQueueFamilyIndex : Transfer ownership 의 타겟 큐 패밀리

- image : 배리어에 영향받는 이미지 핸들

- subresourceRange : 베리어에 영향받는 이미지 내부 subresource range;

이미지에 각 영역이 다른 액세스 마스크와 레이아웃이 가질수 있다.

 

VkAccessFlagBits ( VkAccessFlags ) 사용 방법

리소스의 대한 메모리 접근 유형 나타내며,

파이프라인 베리어 어떤 작업이 메모리 읽기 쓰기 등 메모리 접근순서를 정의할수 있음.

 

ex)

버퍼가 vertex attribute 용도로 사용되는 경우, VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT

클리어 작업이 TRANSFER 작업에 수행되며, clear 값이 이미지에 쓰여지기 때문에 VK_ACCESS_TRANSFER_WRITE_BIT 설정.

 

액세스 플래그 비트

리소스의 대한 메모리 접근 유형을 나타냅니다.

파이프 라인 베리어를 설정할때 전 / 후로 메모리 접근 유형을 설정한다.

예를 들어

1) 버퍼가 버텍스 버퍼로 사용될 경우, 버퍼의 내용이 버텍스 셰이더 어트리뷰로 전달되야 하기 때문에

이때는 " VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT "을 사용한다.

2) 클리어 작업은 Transfer 스테이지에서 실행되고, 클리어 값이 이미지에 쓰여지기 때문에,

" VK_ACCESS_TRANSFER_WRITE_BIT " 을 사용한다.

typedef enum VkAccessFlagBits {
    VK_ACCESS_INDIRECT_COMMAND_READ_BIT = 0x00000001,
    VK_ACCESS_INDEX_READ_BIT = 0x00000002,
    VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT = 0x00000004,
    VK_ACCESS_UNIFORM_READ_BIT = 0x00000008,
    VK_ACCESS_INPUT_ATTACHMENT_READ_BIT = 0x00000010,
    VK_ACCESS_SHADER_READ_BIT = 0x00000020,
    VK_ACCESS_SHADER_WRITE_BIT = 0x00000040,
    VK_ACCESS_COLOR_ATTACHMENT_READ_BIT = 0x00000080,
    VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT = 0x00000100,
    VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT = 0x00000200,
    VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT = 0x00000400,
    VK_ACCESS_TRANSFER_READ_BIT = 0x00000800,
    VK_ACCESS_TRANSFER_WRITE_BIT = 0x00001000,
    VK_ACCESS_HOST_READ_BIT = 0x00002000,
    VK_ACCESS_HOST_WRITE_BIT = 0x00004000,
    VK_ACCESS_MEMORY_READ_BIT = 0x00008000,
    VK_ACCESS_MEMORY_WRITE_BIT = 0x00010000,
    ....
} VkAccessFlagBits;
typedef VkFlags VkAccessFlags;

 

 

 

이미지 레이아웃 열거형

이미지 레이아웃 사용 용도의 대한 정의

typedef enum VkImageLayout {
    VK_IMAGE_LAYOUT_UNDEFINED = 0,
    VK_IMAGE_LAYOUT_GENERAL = 1,
    VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL = 2,
    VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL = 3,
    VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL = 4,
    VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL = 5,
    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL = 6,
    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL = 7,
    VK_IMAGE_LAYOUT_PREINITIALIZED = 8,
    ...
} VkImageLayout;

 

- VK_IMAGE_LAYOUT_UNDEFINED : 이 레이아웃의 이미지 내용과 부분 영역은 상당 부분 정의되지 않은 상태이며, 생성된 바로 직후에는 이 상태라고 가정, 데이터를 유지할 필요가 없을 때 사용

- VK_IMAGE_LAYOUT_GENERAL : 이 레이아웃은 이미지나 이미지의 부분 영역에 대해 모든 연산을 할 수 있게 한다. 그 외에는 용도 플래그를 통해 연산이 지정된다. 최적화 되지 않음.

- VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : 이 레이아웃의 이미지는 프레임 버퍼 색상 첨부에만 사용한다. 프레임 버퍼의 색상 읽기와 드로잉 명령을 사용한 쓰기 접근을 할 수 있다.

- VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : 이 레이아웃의 이미지는 프레임 버퍼의 깊이/스텐실 첨부에만 사용한다.

- VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : 이 레이아웃은 이미지를 읽기 전용 셰이더 리소스로 사용한.다. 셰이더가 읽는 용도로만 접근 가능하다.

- VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : 이 레이아웃은 이미지를 데이터 전송 소스로 사용할때 사용.

- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL : 이 레이아웃은 이미지를 데이터 전송 타겟으로 사용할때 사용.

 

 

메모리 장벽을 삽입하는 코드이다. 

// Provided by VK_VERSION_1_0
void vkCmdPipelineBarrier(
    VkCommandBuffer                             commandBuffer,
    VkPipelineStageFlags                        srcStageMask,
    VkPipelineStageFlags                        dstStageMask,
    VkDependencyFlags                           dependencyFlags,
    uint32_t                                    memoryBarrierCount,
    const VkMemoryBarrier*                      pMemoryBarriers,
    uint32_t                                    bufferMemoryBarrierCount,
    const VkBufferMemoryBarrier*                pBufferMemoryBarriers,
    uint32_t                                    imageMemoryBarrierCount,
    const VkImageMemoryBarrier*                 pImageMemoryBarriers);

- commandBuffer : 메모리 장벽이 정의된 커맨드 버퍼

- srcStageMask : 이 비트 마스크 필드는 장벽 구현 전에 수행이 완료돼야 하는 파이프라인 스테이지를 정의 (VkPipelineStageFlagBits의 조합)

- dstStageMask : 이 비트 마스크 필드는 장벽 이전의 명령어가 모두 수행되기 전까지는 시작하면 안 되는 파이프라인의 스테이지를 정의 (VkPipelineStageFlagBits의 조합)

- dependencyFlags : 이 필드는 VkDependencyFlagBits 값을 참조한다. 이 값은 장벽이 스크린 공간 지역성이 있는 지를 알려준다.

- pMemoryBarriers : 이필드는 VkMemoryBarrier 배열

- memoryBarrierCount : 개수

- pBufferMemoryBarriers : 이 필드 VkBufferMemoryBarrier 개체의 배열 포인터

- bufferMemoryBarrierCount : 개수

- pImageMemoryBarriers : VkImageMemoryBarrier 개체의 배열 포인터

- imageMemoryBarrierCount : 개수

 

 

VkPipelineStageFlagBits 정의된 항목은 파이프라인 Stage를 나타낸다.

정확한 시점을 GPU에 알려주어 효율적인 처리가 가능하도록 한다.

typedef enum VkPipelineStageFlagBits {
    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT = 0x00000001,
    VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT = 0x00000002,
    VK_PIPELINE_STAGE_VERTEX_INPUT_BIT = 0x00000004,
    VK_PIPELINE_STAGE_VERTEX_SHADER_BIT = 0x00000008,
    VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT = 0x00000010,
    VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT = 0x00000020,
    VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT = 0x00000040,
    VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT = 0x00000080,
    VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT = 0x00000100,
    VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT = 0x00000200,
    VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT = 0x00000400,
    VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT = 0x00000800,
    VK_PIPELINE_STAGE_TRANSFER_BIT = 0x00001000,
    VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT = 0x00002000,
    VK_PIPELINE_STAGE_HOST_BIT = 0x00004000,
    VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT = 0x00008000,
    VK_PIPELINE_STAGE_ALL_COMMANDS_BIT = 0x00010000,
    VK_PIPELINE_STAGE_NONE = 0,
    ....
} VkPipelineStageFlagBits;

 

TOP_OF_PIPE_BIT : 파이프라인 시작 단계

BOTTOM_OF_PIPE_BIT : 파이프라인 끝난 단계

FRAGMENT_SHADER_BIT : Fragment Shader Staging

COMPUTE_SHADER_BIT : Compute Sahder Staging

ALL_GRAPHICS_BIT : 좌측 파이프라인 Staging

ALL_COMMANDS_BIT : 모든 파이프라인 Staging

...

 

파이프라인 스테이트

 

1) 클리어 컬러 작업을 위한 이미지 레이아웃 변경

액세스 마스크  " VK_ACCESS_NONE " --> " VK_ACCESS_TRASNFER_WRITE_BIT " 

레이아웃은 " VK_IMAGE_LAYOUT_UNDEFINED " --> " VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL "

 

2) 클리어 컬러 작업 수행

 

3) 출력을 위해 이미지 레이아웃 재변경

액세스 마스크 : " VK_ACCESS_TRANSFER_WRITE_BIT " --> 0

레이아웃 : " VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL " --> " VK_IMAGE_LAYOUT_PRESENT_SRC_KHR "

 

Usage >>

// 클리어 컬러를 위한 이미지 레이아웃 변경
VkImageMemoryBarrier imageMemoryBarrierForClearColorImage
{
    .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
    .srcAccessMask = VK_ACCESS_NONE,
    .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
    .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
    .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
    .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
    .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
    .image = swapchainImage.image,
    .subresourceRange = {
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
            .baseMipLevel = 0,
            .levelCount = 1,
            .baseArrayLayer = 0,
            .layerCount = 1
    }
};

vkCmdPipelineBarrier(
	 mCommandBuffer,
     VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
     VK_PIPELINE_STAGE_TRANSFER_BIT,
     0,
     0,
     nullptr,
     0,
     nullptr,
     1,
     &imageMemoryBarrierForClearColorImage);


// 클리어 컬러
VkClearColorValue clearColorValue {
    .float32 = {0.64, 0.7, 0.2, 1.0}
};
VkImageSubresourceRange imageSubresourceRange {
    .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
    .baseMipLevel = 0,
    .levelCount = 1,
    .baseArrayLayer = 0,
    .layerCount = 1
};
vkCmdClearColorImage(mCommandBuffer,
                     swapchainImage.image,
                     VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                     &clearColorValue,
                     1,
                     &imageSubresourceRange);

// 화면 출력을 위한 이미지 레이아웃 변경
VkImageMemoryBarrier imageMemoryBarrierForPresentSwapchainImage{
    .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
    .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
    .dstAccessMask = 0,
    .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
    .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
    .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
    .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
    .image = swapchainImage.image,
    .subresourceRange = {
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
            .baseMipLevel = 0,
            .levelCount = 1,
            .baseArrayLayer = 0,
            .layerCount = 1
    }
};

vkCmdPipelineBarrier(
     mCommandBuffer,
     VK_PIPELINE_STAGE_TRANSFER_BIT,
     VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
     0,
     0,
     nullptr,
     0,
     nullptr,
     1,
     &imageMemoryBarrierForPresentSwapchainImage);
}
 

Reference

https://docs.vulkan.org/spec/latest/chapters/synchronization.html#VkPipelineStageFlagBits

 

Synchronization and Cache Control :: Vulkan Documentation Project

The special queue family index VK_QUEUE_FAMILY_FOREIGN_EXT represents any queue external to the resource’s current Vulkan instance, regardless of the queue’s underlying physical device or driver version. This includes, for example, queues for fixed-fun

docs.vulkan.org

 

'Vulkan' 카테고리의 다른 글

6. RenderPass  (0) 2024.07.01
5. Presentation And Synchronize ( Fence, Semaphore, Event )  (0) 2024.06.27
3. Command buffer  (0) 2024.06.21
2. Surface And SwapChain  (1) 2024.06.11
1. Vulkan Instance And device  (0) 2024.06.09