본문 바로가기
Vulkan

11. Image

by SimonLee 2024. 7. 21.

벌칸은 기본적으로 버퍼와 이미지라는 두 가지 유형의 리소스를 갖고있다.

이전 챕터에서 버퍼에 대해서 알아보았고,  

이미지 리소스 개념에 대해서 알아본다.

 

이미지리소스는 1D, 2D 또는 3D 형식으로 저장된 연속적인 바이트 배열이다.

이미지는 버퍼 리소스와는 달리 메모리에 특수한 형식으로 포매팅된 정보의 조각이다.

 

이미지 개체는 VkImage 개체로 표현을 하고 vkCreateImage API를 사용하여 생성한다.

장치 메모리를 할당하고 이미지 내용을 할당된 메모리에 저장하는 과정을 수행해야 한다.

그러기 위해 메모리와 생성된 이미지 개체와 바인딩을 해야한다.

 

셰이더 스테이지에서 생성된 이미지 개체를 활용하기 위해 이미지 뷰로 변환을 해야한다.

 

이미지를 이미지 뷰로 변환하기 전에 이미지 레이아웃을 사용하는 드라이버와 호환할수 있게 만들어 줘야한다.

이미지는 VkImageLayout을 사용하여 구현에 종속적인 레이아웃으로 변환된다.

여기서 이미지 레이아웃이란 이미지 텍스처 정보를 이미지 메모리에 그리드 좌표로 저장하는 방법으로,

용도에 따라 특화된 레이아웃을 제공한다.

 

이미지 리소스 생성하는 과정은 다음과 같습니다.

1. 이미지 개체를 생성한다.

2. 이미지 데이터 할당 및 내용 채우기

3. 이미지 샘플러 생성

4. 이미지 뷰 생성

5. 적절한 레이아웃으로 설정

6. 셰이더 수정과 업데이트 디스크립터

 

사용되는 리소스 변수이다.

VkTexture mTexture;
VkImage mImage;
VkDeviceMemory mImageMemory;
VkSampler mSampler;
VkImageView mImageView;

각 변수들의 연결 방식은

1) 생성된 VkImage와 할당된 mImageMemory는 바인딩이 되며,

2) mImageView 생성시에 VkImage 가 필요하며
3) 디스크립터 세트 업데이트 할때, mSampler와 mImageView가 사용된다.

 

각 단계별로 상세히 알아보자.

1. 이미지 개체를 생성한다.

-  vKImageCreateInfo 개체는 이미지의 크기 생성 플래그를 포함한데, 이 메타데이터를 사용하여 이미지 개체를 생성한다.

 

이미지 개체 생성 ( VkImageCreateInfo )

// Provided by VK_VERSION_1_0
VkResult vkCreateImage(
    VkDevice                                    device,
    const VkImageCreateInfo*                    pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkImage*                                    pImage);

- device : 논리장치

- pCreateInfo : VkImageCreateInfo 포인터

- pAllocator : 호스트 메모리 할당 과정 제어

- pImage : 생성된 VkImage 포인터

 

 

이미지 개체 삭제 ( vKDestroyImage )

// Provided by VK_VERSION_1_0
void vkDestroyImage(
    VkDevice                                    device,
    VkImage                                     image,
    const VkAllocationCallbacks*                pAllocator);

- device : 논리장치

- image : 삭제할 VkIamge 개체

- pAllocator : 호스트 메모리 재할당 프로세스 제어.

 

 

메타 데이터 생성 ( VkImageCreateInfo )

// Provided by VK_VERSION_1_0
typedef struct VkImageCreateInfo {
    VkStructureType          sType;
    const void*              pNext;
    VkImageCreateFlags       flags;
    VkImageType              imageType;
    VkFormat                 format;
    VkExtent3D               extent;
    uint32_t                 mipLevels;
    uint32_t                 arrayLayers;
    VkSampleCountFlagBits    samples;
    VkImageTiling            tiling;
    VkImageUsageFlags        usage;
    VkSharingMode            sharingMode;
    uint32_t                 queueFamilyIndexCount;
    const uint32_t*          pQueueFamilyIndices;
    VkImageLayout            initialLayout;
} VkImageCreateInfo;

- sType : VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO

- pNext : 확장판 지정 구조체

- flags : vKImageCreateFlagBits 비트 플래그 참조

- imageType : 이미지의 1D / 2D / 3D 차원을 VkImageType을 사용해 정의한다. 

  이값은 VK_IMAGE_TYPE_1D, VK_IMAGE_TYPE_2D,  VK_IMAGE_TYPE_3D 중 하나

- format : 지정된 이밎 포맷을 정의한다.  자료형은 VkFormat.

- Extent : 기초 레벨에서 각 차원에 들어있는 요소의 개수를 정의한다.

- mipLevels : 밉맵으로 압축된 샘플링 이미지 내에서 사용 가능한 Level of detail의 다른 레벨을 참조

- arrayLayers : 이미지 내 레이어 개수

- Samples : 이미지 내 엘리먼트 샘플 하위 데이터 수로, VkSampleCountFlagBits 에서 정의

- Tilings : 이미지 내의 타일링 정보를 정의한다.

이 값은 VkImageTiling에 정의된 유형으로 두 열거값 VK_IMAGE_TILING_OPTIMAL 또는 VK_IMAGE_TILING_LINEAR 하나로 지정되어야 한다.

- Usage : 이미지의 용도를 지정하는 비트 필드, VkImageUsageFlagBits로 정의된다.

- sharingMode : 이미지가 다중 큐 패밀리에 사용될때 이미지의 공유 모드를 지정한다. 이 값은 vkSharingModde 열거형에 들어있는 VK_SHARING_MODE_EXCLUSIVE 또는 VK_SHARING_MODE_CONCURRENT 둘 중 하나.

- queueFamilyIndexCount : queueFamilyIndices 배열에 들어 있는 항목의 개수를 나타낸다.

- queueFamilyIndices : 이미지의 액세스하는 큐 패밀리의 배열이다. sharingMode 필드 값이 VK_SHARING_MODE_CONCURRENT여야 한다.

- initialLayout : 이 필드는 이미지에서 모든 하위 리소스 초기의 VKImageLayout 상태를 정의한다. 이값은 VK_IMAGE_LAYOUT_UNDEFINED 또는 VK_IMAGE_LAYOUT_PREINITIALIZED 중 하나.

 

파라메터 설명

타일링 방식 ( Tilings )

위 그림방식은 리니어 타일링과 최적 타일링 2가지를 보여준다.

타일링 방식은 이미지를 저장하는 방식을 의미한다.

 

rowPitch 는 이미지의 너비를 나타낸다.

주어진 텍셀의 오프셋 위치는 Y * rowPitch + X 로 나타낼 수 있다.

이웃한 텍셀정보가 필요하지 않는 행을 따라 텍셀 정보에 접근하는 경우에는 선형방식이 잘 동작한다.

하지만 이미지의 크기가 크면 피치 크기가 증가하고, 여러개의 행의 선 레이아웃으로 펼쳐진다.

TLB(Translation Lookaside Buffer)와 캐시미스가 발생해 주소 변환이 느려지고 성능이 떨어지는 현상 발생한다.

 

대부분의 GPU에서는 이 주소변환이 느려지는 문제를 해결하기 위해 스위즐 형식으로 지정하는 방식을 사용하며,

이미지 텍셀을 저장하는 이 방법을 최적 타일링 이라한다.

연속된 메모리 덩어리 내의 여러 열과 행을 나타내는 타일 형태로 저장된다.

 

 

구조체 이미지 Usage 플래그 ( VkImageUsageFlagBits )

텍스처 사용 목적을 설정해야 하며, 셰이더에서 샘플러로 사용되어  색상을 결정한다.

그런 경우  "  VK_IMAGE_USAGE_SAMPLED_BIT " 설정한다.

// Provided by VK_VERSION_1_0
typedef enum VkImageUsageFlagBits {
    VK_IMAGE_USAGE_TRANSFER_SRC_BIT = 0x00000001,
    VK_IMAGE_USAGE_TRANSFER_DST_BIT = 0x00000002,
    VK_IMAGE_USAGE_SAMPLED_BIT = 0x00000004,
    VK_IMAGE_USAGE_STORAGE_BIT = 0x00000008,
    VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT = 0x00000010,
    VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT = 0x00000020,
    VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT = 0x00000040,
    VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT = 0x00000080,
    ...
} VkImageUsageFlagBits;

- VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 전송 명령 (복사) 소스로 사용

- VK_IAMGE_USAGE_TRANSFER_DST_BIT : 전송 명령 목적지로 사용한다.

- VK_IMAGE_USAGE_SAMPLED_BIT : 이 이미지 유형은 셰이더 스테이지에서 이미지 뷰 유형을 샘플러로 사용.

  이때 연관된 디스크립터 세트 슬롯 (VkDescriptorSet) 유형은 VK_DESCRIPT0R_TYPE_SAMPLED_IMAGE 혹은 VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER 둘 중 하나.

- VK_IMAGE_USAGE_STORAGE_BIT : 이미지 메모리에 대한 로딩/저장/아토믹 연산 에 사용하며, 이미지 뷰는 VK_DESCRIPTOR_TYPE_STORAGE_IMAGE 유형의 디스크립터 슬롯에 연결한다.

- VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 이 이미지 리소스 유형으로 생성된 이미지 뷰는 깊이/스텐실 첨부 또는 프레임 버퍼 개체(VkFrameBuffer)에 연결된 첨부에 사용된다.

- VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : 이 이미지 리소스 유형으로 생성된 이미지 뷰는 색상 첨부 또는 프레임 버퍼 개체에 연결된 첨부에 사용한다. 

- VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 이 프래그를 사용한 이미지 유형은 지연 할당 한다. 이를 위한 메모리 유형은 반드시 VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT로 설정돼야 한다.

- VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT : 이 형식으로 생성된 이미지 뷰는 셰이더 스테이지와 프레임버퍼 입력 첨부로 사용한다.

 

이미지 열거형 flag 비트 ( VkImageCreateFlagBits )

- 메모리 , 포맷, 속성 등 다양한 이미지 리소스를 관리하기 위한 정보를 응용 프로그램에게 알려준다.

// Provided by VK_VERSION_1_0
typedef enum VkImageCreateFlagBits {
    VK_IMAGE_CREATE_SPARSE_BINDING_BIT = 0x00000001,
    VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT = 0x00000002,
    VK_IMAGE_CREATE_SPARSE_ALIASED_BIT = 0x00000004,
    VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT = 0x00000008,
    VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT = 0x00000010,
    ....
} VkImageCreateFlagBits;

- VK_IMAGE_CREATE_SPARSE_BINDING_BIT : 이 이미지는 희소 메모리 바인딩을 사용하여 전체가 저장된다.

- VK_IMAGE_CREATE_SPARSE_RESIDENCY_BIT : 이 이미지는 일부를 희소 메모리 바인딩을 통해 저장할 수 있음. 이 필드를 사용하려면 이미지는 반드시 VK_IMAGE_CREATE_SPARSE_BINDING_BIT 플래그 설정되어야 한다.

- VK_IMAGE_CREATE_SPARSE_ALIASED_BIT : 이 유형의 플래그에서 이미지는 희소 메모리에 저장되며, 동일한 메모리 영역에 같은 이미지의 여러 부분을 갖고 있을 수 있다. 

- VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 이 포맷은 이미지 뷰(VkImageView) 포맷이 생성된 이미지 (VkImage) 포맷과 다른 경에 유용하게 사용할 수 있다.

- VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 이 포맷은 큐브 맵핑에 사용된다. 이때 VkImageView는 반드시 VK_IMAGE_VIEW_TYPE_CUBE 또는 VK_IMAGE_VIEW_TYPE_CUBE_ARRAY 여야 한다.

 

샘플링 횟수 설정 ( Samples )

픽셀당 샘플링 할 횟수를 지정한다.

Usage >>

format과 extent의 경우는  텍스처의 포맷과 크기값을 설정해 주도록 한다.

이는 플랫폼에서 제공하는 기능을 사용하여 따로 구현을 해야하며, 텍스처의 헤더를 읽는 과정이 필요하다.

 

samples의 VK_SAMPLE_COUNT_1_BIT은 주어진 픽셀에 대해서 한번의 샘플링을 한다는 의미이다.

사용 용도는 셰이더 샘플러로 사용이 되며,

타일링 방식은 최적 타일링 방식을 사용한다.

VkImageCreateInfo imageCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
        .imageType = VK_IMAGE_TYPE_2D,
        .format = textureProperties.format,
        .extent = textureProperties.extent,
        .mipLevels = 1,
        .arrayLayers = 1,
        .samples = VK_SAMPLE_COUNT_1_BIT,
        .tiling = VK_IMAGE_TILING_LINEAR,
        .usage = VK_IMAGE_USAGE_SAMPLED_BIT,
        .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
        .initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED,
};
VK_CHECK_ERROR(vkCreateImage(mDevice,
                             &imageCreateInfo,
                             nullptr,
                             &mImage));

 

2. 이미지 데이터 할당 및 내용 채우기

이미지를 복사할 메모리를 할당해보자.

 

2.1) 메모리 요구사항을 얻는다. ( vkGetImageMemoryRequirements )

메모리 할당시 VkImage 에 포맷과 크기 유형등 다양한 정보가 들어있기 때문에 다른 인자가 필요하지 않다.

// Provided by VK_VERSION_1_0
void vkGetImageMemoryRequirements(
    VkDevice                                    device,
    VkImage                                     image,
    VkMemoryRequirements*                       pMemoryRequirements);

- device : 논리장치

- image : 이미지 개체를 참고한다.

- pMemoryRequirements : VkMemoryRequirements 구조체 포인터 반환

 

전달 받은 메모리 요구사항 구조체는 다음과 같다.

// Provided by VK_VERSION_1_0
typedef struct VkMemoryRequirements {
    VkDeviceSize    size;
    VkDeviceSize    alignment;
    uint32_t        memoryTypeBits;
} VkMemoryRequirements;

- size : 요구하는 이미지 리소스의 크기

- alignment : 리소스가 요구한 할당을 위한 정렬 오프셋으로 바이트 단위로 지정

- memoryTypeBits : 이미지 리소스가 지원하는 메모리 유형을 나타내는 비트 플래그로,

  i 번째 비트가 설정돼 있으면 메모리 유형 i가 지원된다는 것을 의미한다.

  각 비트의 정의는 이미지 구조체 VkPhysicalDeviceMemoryProperties에 정의 되어있음.

 

 

2.2 메모리 유형 설정 가져오기 ( VkPhysicalDeviceMemoryProperties )

 

물리 메모리 유형 가져오기 ( vkGetPhysicalDeviceMemoryProperties )

// Provided by VK_VERSION_1_0
void vkGetPhysicalDeviceMemoryProperties(
    VkPhysicalDevice                            physicalDevice,
    VkPhysicalDeviceMemoryProperties*           pMemoryProperties);

 

전달 받은 물리 메모리 유형 ( vkPhysicalDeviceMemoryProperties )

// Provided by VK_VERSION_1_0
typedef struct VkPhysicalDeviceMemoryProperties {
    uint32_t        memoryTypeCount;
    VkMemoryType    memoryTypes[VK_MAX_MEMORY_TYPES];
    uint32_t        memoryHeapCount;
    VkMemoryHeap    memoryHeaps[VK_MAX_MEMORY_HEAPS];
} VkPhysicalDeviceMemoryProperties;

- memoryTypeCount : memoryTypes의 크기

- memoryTypes : 메모리 타입 배열

- memoryHeapCount : 힙 메모리 크기

- memoryHeaps : 힙 메모리 구조 배열

 

 

2.3 장치 메모리 할당 ( vkAllocateMemory )

 

2.1 과정에서 얻은 메모리 요구사항 구조체 

2.2 과정에서 얻은 물리 메모리 유형

 

물리 메모리 유형은 해당 디바이스에서 지원하는 유형의 메모리를 전부 나열이 되어있으므로 

위 두가지를 가지고 우리가 원하는 스펙을 가진 메모리 타입 인덱스를 구해야 한다.

메모리 타입인덱스는 메타데이터 ( VkMemoryAllocateInfo의 memoryTypeIndex )에 설정이 되어야 한다.

 

우리는 CPU, GPU 접근이 둘 다 가능해야 하고, 업데이트를 자동으로 해주기 위하여 

아래 메모리 플래그가 설정된 메모리를 원한다.

- VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT

- VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,

 

2.1 메모리 요구사항 구조체 내부 memoryTypeBits 가 있으며,

2.2 물리 메모리 유형의 memoryTypes가 있다.

 

인덱스를 0 ~ 물리 메모리 유형의 내부 변수 memoryTypeCount 까지 아래 조건으로 찾는다.

 

요구사항 구조체 내부 변수 memoryTypeBits & (1 << i ) 값이 1이면,

물리 메모리 유형의 내부 변수 memoryTypes[i], i번째 메모리 타입을 지원한다는 의미이다.

 

지원되는 메모리 타입중에 우리가 원하는 플래그 2개가 지원되는지 보기 위하여

물리 메모리 유형의 내부변수 memoryTypes[i]의 내부변수 propertyFlags 검사한다.

propertyFlags 의 비트값이 위에서 설정한 Property Bit 과 일치하면

인덱스를 메모리 타입 인덱스로 설정한다.

 

자세한 내용은 Usage 함수 ( vkGetMemoryTypeIndex ) 나와있음.

// Provided by VK_VERSION_1_0
VkResult vkAllocateMemory(
    VkDevice                                    device,
    const VkMemoryAllocateInfo*                 pAllocateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkDeviceMemory*                             pMemory);

- device : 메모리를 소유한 논리장치

- pAllocateInfo : VkMemoryAllocateInfo 구조체 포인터

- pAllocator : 호스트 메모리 할당 관리

- pMemory : 할당된 메모리 핸들 ( VkDeviceMemory )

 


메모리 할당 메타데이터

// Provided by VK_VERSION_1_0
typedef struct VkMemoryAllocateInfo {
    VkStructureType    sType;
    const void*        pNext;
    VkDeviceSize       allocationSize;
    uint32_t           memoryTypeIndex;
} VkMemoryAllocateInfo;

- sType : VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO

- pNext : 확장 구조체 포인터

- allocationSize : 할당할 메모리 byte 사이즈

- memoryTypeIndex : 메모리 타입 인덱스 ( VkPhysicalDeviceMemoryProperties) 의 인덱스. 

 

2.4 할당된 메모리 바인딩 ( vkBindImageMemory )

할당된 메모리를 이미지에 바인딩 합니다.

// Provided by VK_VERSION_1_0
VkResult vkBindImageMemory(
    VkDevice                                    device,
    VkImage                                     image,
    VkDeviceMemory                              memory,
    VkDeviceSize                                memoryOffset);

- device : 메모리와 이미지를 소유한 논리장치

- image : VkImage

- memory : 이미지와 어태치할 메모리

- memoryOffset : 이미지가 바인딩된 메모리의 시작 위치로 부터 몇 바이트 떨어져 있는가

Usage >> 이미지 메모리 (vkDeviceMemory) 생성 후  이미지 (VkImage) 와 바인딩 하는 코드.

// ================================================================================
// VkImage의 VkMemoryRequirements 얻기
// ================================================================================
VkMemoryRequirements imageMemoryRequirements;
vkGetImageMemoryRequirements(mDevice, mImage, &imageMemoryRequirements);

// ================================================================================
// Image VkDeviceMemory를 할당 할 수 있는 메모리 타입 인덱스 얻기
// ================================================================================
vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &mPhysicalDeviceMemoryProperties);
uint32_t imageMemoryTypeIndex;
VK_CHECK_ERROR(vkGetMemoryTypeIndex(mPhysicalDeviceMemoryProperties,
                                    imageMemoryRequirements,
                                    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
                                    VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
                                    &imageMemoryTypeIndex));
                                    
// ================================================================================
// Image VkDeviceMemory 할당
// ================================================================================
VkMemoryAllocateInfo imageMemoryAllocateInfo{
        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .allocationSize = imageMemoryRequirements.size,
        .memoryTypeIndex = imageMemoryTypeIndex
};
VK_CHECK_ERROR(vkAllocateMemory(mDevice, &imageMemoryAllocateInfo, nullptr, &mImageMemory));

// ================================================================================
// VkImage와 Image VkDeviceMemory 바인드
// ================================================================================
VK_CHECK_ERROR(vkBindImageMemory(mDevice, mImage, mImageMemory, 0));


inline VkResult
vkGetMemoryTypeIndex(const VkPhysicalDeviceMemoryProperties &physicalDeviceMemoryProperties,
                     const VkMemoryRequirements &memoryRequirements,
                     VkMemoryPropertyFlags memoryPropertyFlags,
                     uint32_t *memoryTypeIndex) {
    // 이들 속성의 첫 번째 인덱스를 찾기 위해 memTypes 검색
    for (auto i = 0; i != physicalDeviceMemoryProperties.memoryTypeCount; ++i) {
        if (memoryRequirements.memoryTypeBits & (1 << i)) {
            // 해당 유형이 사용 가능할 경우 사용자 속성과 일치 하는가 ?
            if ((physicalDeviceMemoryProperties.memoryTypes[i].propertyFlags &
                 memoryPropertyFlags) == memoryPropertyFlags) {
                *memoryTypeIndex = i;
                return VK_SUCCESS;
            }
        }
    }
    // 일치하는 메모리유형이 없으면 실패 반환
    *memoryTypeIndex = UINT32_MAX;
    return VK_ERROR_UNKNOWN;
}

 

2.5 할당된 메모리에 이미지 데이터 채우기

 

이미지 메모리에 복사를 하기 위해서는 리소스 레이아웃 정보를 알아야 한다.

vkGetImageSubResourceLayout() 사용하여 리소스 레이아웃 정보를 쿼리한다.

// Provided by VK_VERSION_1_0
void vkGetImageSubresourceLayout(
    VkDevice                                    device,
    VkImage                                     image,
    const VkImageSubresource*                   pSubresource,
    VkSubresourceLayout*                        pLayout);

- device : 이미지를 소유한 논리장치

- image : 레이아웃의 대해 쿼리될 이미지

- pSubresource : VkImageSubresource 구조체 포인터

- pLayout : 리턴된 리소스 레이아웃 정보 핸들

 

인자로 필요한 이미지 서브리소스

// Provided by VK_VERSION_1_0
typedef struct VkImageSubresource {
    VkImageAspectFlags    aspectMask;
    uint32_t              mipLevel;
    uint32_t              arrayLayer;
} VkImageSubresource;

- aspectMask : VkImageAspectFlags 열거 상수 값이며, 보통 VK_IMAGE_ASPECT_COLOR_BIT 사용.

- mipLevel : mip level

 

생성된 서브리소스 레이아웃

// Provided by VK_VERSION_1_0
typedef struct VkSubresourceLayout {
    VkDeviceSize    offset;
    VkDeviceSize    size;
    VkDeviceSize    rowPitch;
    VkDeviceSize    arrayPitch;
    VkDeviceSize    depthPitch;
} VkSubresourceLayout;

- offset : 이미지 서브리소스가 시작되는 시작 바이트 오프셋

- size : 이미지 서브리소스의 바이트 사이즈 ( rowPitch 기반으로 요구되는 추가 메모리도 포함됨 )

- rowPitch : 이미지 내부 가로 텍셀사이 바이트 개수

- arrayPitch : 이미지의 어레이 레이어 사이의 바이트 개수

- depthPitch : 3D 이미지의 각 단면 사이의 바이트의 개수

 

리소스 레이아웃 정보를 (VkImageSubresource ) 얻어 왔으면..

vkMapMemory() 사용하여 GPU 메모리를 CPU 호스트 메모리와 맵핑을 합니다.

// Provided by VK_VERSION_1_0
VkResult vkMapMemory(
    VkDevice                                    device,
    VkDeviceMemory                              memory,
    VkDeviceSize                                offset,
    VkDeviceSize                                size,
    VkMemoryMapFlags                            flags,
    void**                                      ppData);

- device : 메모리를 소유할 논리장치
- memory : 맵핑될 vkDeviceMemory

- offset : 메모리 시작할 offset byte (zero base)

- size : 메모리 맵핑될 크기

- flags : VkMemoryMapFlags 플래그 옵션 docu 참고하자.

- ppData : 맵핑된 CPU 메모리

이제 이미지 텍스처 데이터를 매핑된 버퍼에 로딩을 해야한다
선형 타일링으로 이미지 리소스 채우는 방법은 아래 코드를 참고합니다.
레이아웃 정보는 행단 위로 이미지 데이터 저장하는데 사용하는 rowPitch 정보를 활용한다.
memcpy 완료 된 후에는 vkUnMapMemory를 해줍니다.

Usage >> 할당된 메모리에 이미지 데이터 채우기

// ================================================================================
// VkImage의 VkImageSubresource 얻기
// 버퍼를 텍스처로 초기화 하기 위해 사용
// ================================================================================
VkImageSubresource imageSubresource{
        .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
        .mipLevel = 0,
        .arrayLayer = 0
};
VkSubresourceLayout subresourceLayout;
vkGetImageSubresourceLayout(mDevice,
                            mImage,
                            &imageSubresource,
                            &subresourceLayout);

// ================================================================================
// Image 데이터 초기화
// ================================================================================
void* imageData;
VK_CHECK_ERROR(vkMapMemory(mDevice, mImageMemory, 0, VK_WHOLE_SIZE, 0, &imageData));
for (auto h = 0; h < textureProperties.extent.height; ++h) {
    const auto dstOffset = h * subresourceLayout.rowPitch;
    const auto srcRowPitch = textureProperties.extent.width * 4;
    const auto srcOffset = h * srcRowPitch;
    memcpy(static_cast<uint8_t *>(imageData) + dstOffset,
           static_cast<uint8_t *>(textureProperties.pData) + srcOffset,
           srcRowPitch);
}
vkUnmapMemory(mDevice, mImageMemory);

3. 이미지 샘플러 생성

 

이미지 샘플러는

다양한 파라메터를 제어해 포맷팅된 이미지 데이터 모양을 제어하는 알고리즘 세트를 포함하는 개체입니다.

파라메터들은 이미지 변환과 축소 및 확대 필터링, 밉 매핑 Wrapping 제어하고 

이미지 최종 샘플 배열을 생성합니다.

 

// Provided by VK_VERSION_1_0
VkResult vkCreateSampler(
    VkDevice                                    device,
    const VkSamplerCreateInfo*                  pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkSampler*                                  pSampler);

- device : 샘플러를 사용할 논리장치

- pCreateInfo : vkSamplerCreateInfo 구조체 포인터

- vkAllocationCallbacks : 호스트 메모리 할당 제어

- pSampler : 생성된 샘플러 핸들

 

샘플러 생성 메타데이터

// Provided by VK_VERSION_1_0
typedef struct VkSamplerCreateInfo {
    VkStructureType         sType;
    const void*             pNext;
    VkSamplerCreateFlags    flags;
    VkFilter                magFilter;
    VkFilter                minFilter;
    VkSamplerMipmapMode     mipmapMode;
    VkSamplerAddressMode    addressModeU;
    VkSamplerAddressMode    addressModeV;
    VkSamplerAddressMode    addressModeW;
    float                   mipLodBias;
    VkBool32                anisotropyEnable;
    float                   maxAnisotropy;
    VkBool32                compareEnable;
    VkCompareOp             compareOp;
    float                   minLod;
    float                   maxLod;
    VkBorderColor           borderColor;
    VkBool32                unnormalizedCoordinates;
} VkSamplerCreateInfo;

- sType : VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO

- pNext : 확장판 지정 구조체

- magFilter : 확대 필터 지정한다. VK_FILTER_NEAREST, VK_FILTER_LINEAR 지정 할 수 있다.

- minFilter : 축소 필더 지정하는데 사용

- mipmapMode : 밉매핑 필터링 모드 지정하느넫 사용

- addressModeU : 이미지의 텍셀 좌표가 [0 .. 1] 범위 벗어나는 경우, U 좌표계에 따라 이미지 Wrapping 제어.

- addressModeV : 이 필드는 텍셀 좌표가 [0 .. 1] 범위 벗어나는 경우, V 좌표계에 따라 이미지 Wrapping 제어.

나머지 멤버변수는 document 참고..

Usage >>

const VkSamplerCreateInfo samplerCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
        .magFilter = VK_FILTER_NEAREST,
        .minFilter = VK_FILTER_NEAREST,
        .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
        .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
};

VK_CHECK_ERROR(vkCreateSampler(mDevice,
                               &samplerCreateInfo,
                               nullptr,
                               &mSampler));

 

4. 이미지 뷰를 만든다.

이미지 뷰 생성은 VkCreateImageView () 로 생성한다.

// Provided by VK_VERSION_1_0
VkResult vkCreateImageView(
    VkDevice                                    device,
    const VkImageViewCreateInfo*                pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkImageView*                                pView);

- device : 이미지 뷰 생성할 논리 장치 핸들

- pCreateInfo : VkCreateImageViewInfo 구조체 포인터

- pAllocator : 호스트 메모리 할당 제어

- pView : 생성된 VkImageView 개체 핸들

 

이미지 뷰 생성 메터 데이터

// Provided by VK_VERSION_1_0
typedef struct VkImageViewCreateInfo {
    VkStructureType            sType;
    const void*                pNext;
    VkImageViewCreateFlags     flags;
    VkImage                    image;
    VkImageViewType            viewType;
    VkFormat                   format;
    VkComponentMapping         components;
    VkImageSubresourceRange    subresourceRange;
} VkImageViewCreateInfo;

- sType : VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO

- pNext : 확장판 구조체 

- flags : null

- image : VkImage 핸들

- viewType : VkImageViewType의 열거형이며 주로 VK_IMAGE_VIEW_TYPE_2D 를 사용

- format : 이미지의 포맷 (VkFormat) 지정

- components : 색상 / 깊이 / 스텐실이 색상 컴포넌트로 변환 후 재 매핑에 사용된다.

- subresourceRange : 밉맵 계층의 범위 선택과 배열 계층의 선택에 사용하며 뷰를 통해서 액세사 할수 있다.

 

 

이미지 뷰 삭제는 vkDestroyImageView () 사용.

// Provided by VK_VERSION_1_0
void vkDestroyImageView(
    VkDevice                                    device,
    VkImageView                                 imageView,
    const VkAllocationCallbacks*                pAllocator);

- device : 삭제할 이미지뷰가 있는 논리장치

- imageView : 삭제할 이미지 뷰

- pAllocator : 호스트 메모리 할당 제어

 

Usage >>

VkImageViewCreateInfo imageViewCreateInfo {
    .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
    .image = mImage,
    .viewType = VK_IMAGE_VIEW_TYPE_2D,
    .format = textureProperties.format,
    .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
    }
};
vkCreateImageView(mDevice, &imageViewCreateInfo, nullptr, &mImageView);

 

Reference

https://vulkan.lunarg.com/doc/view/latest/windows/tutorial/html/10-init_render_pass.html

https://www.informit.com/articles/article.aspx?p=2756465&seqNum=2

 

Resources | Vulkan Memory and Resources | InformIT

Home > Articles 📄 Contents ␡ Host Memory Management Resources Device Memory Management Summary This chapter is from the book  Resources Vulkan operates on data. Everything else is really secondary to this. Data is stored in resources, and resources

www.informit.com

 

'Vulkan' 카테고리의 다른 글

10 - 1. Descriptor 추가 이해  (0) 2024.08.05
12. Image layout and Staging Buffer  (0) 2024.07.25
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