Skip to content

第五章 · 资源管理 (Buffers & Images)

5.1 Buffer 与 Image 概述 🔥

Buffer vs Image:
────────────────

Buffer:
  线性内存(1D 数组)
  ┌─────────────────────────────────────┐
  │ V0 │ V1 │ V2 │ V3 │ ... │ VN-1    │
  └─────────────────────────────────────┘
  用途: 顶点数据、Uniform 变量、索引

Image:
  二维/三维纹理(有维度信息)
  ┌───┬───┬───┬───┬───┐
  │ P0│ P1│ P2│ P3│ P4│
  ├───┼───┼───┼───┼───┤
  │ P5│ P6│ P7│ P8│ P9│
  ├───┼───┼───┼───┼───┤
  │ P10│P11│P12│P13│P14│
  └───┴───┴───┴───┴───┘
  用途: 纹理、帧缓冲、RTT、Compute 存储

5.2 Buffer 管理

5.2.1 Buffer 创建

python
import vkbottle

# --- 1. 创建 Buffer ---
buffer_create_info = vkbottle.BufferCreateInfo(
    size=vertex_data.nbytes,  # Buffer 大小(字节)
    usage=vkbottle.BufferUsageFlag.VERTEX_BUFFER |    # 用途
          vkbottle.BufferUsageFlag.INDEX_BUFFER,
    sharingMode=vkbottle.SharingMode.EXCLUSIVE,
)

buffer = device.create_buffer(buffer_create_info)
print(f"Buffer 已创建, 大小: {vertex_data.nbytes} bytes")

5.2.2 Buffer 内存分配 🔥

python
# --- 2. 查询内存类型 ---
mem_properties = device.get_memory_properties()

# 找到适合内存类型的索引
def find_memory_type_index(mem_prop, type_bits, properties):
    for i in range(mem_prop.memory_type_count):
        if (type_bits & (1 << i)) and (mem_prop.memory_types[i].propertyFlags & properties) == properties:
            return i
    raise RuntimeError("Failed to find suitable memory type!")

# --- 3. 分配内存 ---
mem_alloc = vkbottle.MemoryAllocateInfo(
    allocationSize=vertex_data.nbytes,
    memoryTypeIndex=find_memory_type_index(
        mem_prop,
        vkbottle.MemoryTypeFlag.HOST_VISIBLE,  # 主机可访问
        vkbottle.MemoryPropertyFlag.HOST_VISIBLE | vkbottle.MemoryPropertyFlag.HOIST_COHERENT
    ),
)

buffer_memory = device.allocate_memory(mem_alloc)

# --- 4. 绑定内存 ---
device.bind_buffer_memory(buffer, buffer_memory, 0)

# --- 5. 上传数据 ---
if buffer_memory.memory_type_index & vkbottle.MemoryPropertyFlag.HOST_VISIBLE:
    mapped_ptr = device.map_memory(buffer_memory, 0, buffer_data.nbytes)
    mapped_ptr.data = buffer_data.data  # 直接复制
    device.unmap_memory(buffer_memory)

# --- 6. 清理 ---
device.free_memory(buffer_memory)
device.destroy_buffer(buffer)

5.2.3 Buffer 用途标志

用途说明
VERTEX_BUFFER顶点数据
INDEX_BUFFER索引数据
UNIFORM_BUFFER统一变量(Shader 输入)
STORAGE_BUFFER存储缓冲区(Compute Shader)
INDEX_BUFFER索引缓冲
TRANSFER_SRC传输源
TRANSFER_DST传输目标
INDIRECT_BUFFER间接绘制参数

5.2.4 常用 Buffer 类型

python
# Uniform Buffer 用途示例
uniform_buffer_info = vkbottle.BufferCreateInfo(
    size=128,  # 128 bytes(必须为 256 的倍数)
    usage=vkbottle.BufferUsageFlag.UNIFORM_BUFFER,
    sharingMode=vkbottle.SharingMode.EXCLUSIVE,
)

uniform_buffer = device.create_buffer(uniform_buffer_info)

# Vertex Buffer 用途示例
vertex_buffer_info = vkbottle.BufferCreateInfo(
    size=vertex_data.nbytes,
    usage=vkbottle.BufferUsageFlag.VERTEX_BUFFER | vkbottle.BufferUsageFlag.TRANSFER_DST,
    sharingMode=vkbottle.SharingMode.EXCLUSIVE,
)

vertex_buffer = device.create_buffer(vertex_buffer_info)

5.3 Image 管理 🔥

5.3.1 Image 创建

python
import vkbottle

# --- 1. 创建 Image ---
image_create_info = vkbottle.ImageCreateInfo(
    imageType=vkbottle.ImageType._2D,
    format=vkbottle.Format.R8G8B8A8_SRGB,  # 格式
    extent=vkbottleExtent(256, 256, 1),  # 尺寸(宽, 高, 深度)
    mipLevels=8,  # Mipmap 层级数
    arrayLayers=1,  # 层数
    samples=vkbottle.SampleCountFlagBits._1,
    tiling=vkbottle.ImageTiling.OPTIMAL,  # OPTIMAL 或 LINEAR
    usage=vkbottle.ImageUsageFlag.SAMPLED | vkbottle.ImageUsageFlag.TRANSFER_DST,
    sharingMode=vkbottle.SharingMode.EXCLUSIVE,
    initialLayout=vkbottle.ImageLayout.UNDEFINED,
)

texture_image = device.create_image(image_create_info)
print(f"Image 已创建, 尺寸: {texture_image.extent}")

5.3.2 Image 内存分配

python
# --- 2. 查询 Image 所需内存 ---
mem_requirements = texture_image.get_memory_requirements()

# 找到适合内存类型
mem_prop = device.get_memory_properties()
memory_type = find_memory_type_index(
    mem_prop,
    mem_requirements.memoryTypeBits,
    vkbottle.MemoryPropertyFlag.DEVICE_LOCAL  # GPU 本地内存
)

# --- 3. 分配内存 ---
mem_alloc = vkbottle.MemoryAllocateInfo(
    allocationSize=mem_requirements.size,
    memoryTypeIndex=memory_type,
)

image_memory = device.allocate_memory(mem_alloc)

# --- 4. 绑定内存 ---
device.bind_image_memory(texture_image, image_memory, 0)

5.3.3 Image Layout

python
# --- 5. 设置 Layout ---
def transition_image_layout(image, old_layout, new_layout, queue_family_index=vkbottle.QFO_TRANSFER_QUEUE_FAMILY_IGNORED):
    # 创建命令缓冲区
    cmd_pool = vkbottle.CommandPoolCreateInfo(
        queueFamilyIndex=queue_family_index,
        flags=vkbottle.CommandPoolCreateFlag.RESET_COMMAND_BUFFER,
    )
    cmd_pool = device.create_command_pool(cmd_pool)
    
    cmd_buffer = device.allocate_command_buffers(
        vkbottle.CommandBufferAllocateInfo(
            commandPool=cmd_pool,
            level=vkbottle.CommandBufferLevel.PRIMARY,
            commandBufferCount=1,
        )
    )
    
    # 开始记录命令
    cmd_buffer.begin(vkbottle.CommandBufferBeginInfo(flags=vkbottle.CommandBufferUsageFlag.ONE_TIME_SUBMIT))
    
    # 添加布局转换屏障
    cmd_buffer.pipeline_barrier(
        srcStageMask=vkbottle.AccessFlag.NONE,
        dstStageMask=vkbottle.AccessFlag.NONE,
        dependencyFlags=vkbottle.SubpassDependencyFlag.NONE,
        memoryBarriers=[],
        bufferMemoryBarriers=[],
        imageMemoryBarriers=[
            vkbottle.ImageMemoryBarrier(
                srcAccessMask=vkbottle.AccessFlag.NONE,
                dstAccessMask=vkbottle.AccessFlag.NONE,
                oldLayout=old_layout,
                newLayout=new_layout,
                srcQueueFamilyIndex=queue_family_index,
                dstQueueFamilyIndex=queue_family_index,
                image=image,
                subresourceRange=vkbottle.ImageSubresourceRange(
                    aspectMask=vkbottle.ImageAspectFlag.COLOR_BIT,
                    baseMipLevel=0,
                    levelCount=texture_image.mipLevels,
                    baseArrayLayer=0,
                    layerCount=texture_image.arrayLayers,
                ),
            ),
        ],
    )
    
    cmd_buffer.end()
    
    # 提交命令
    device.get_queue().submit(
        cmd_buffer,
        waitSemaphores=[],
        signalSemaphores=[],
    )
    
    device.get_queue().wait_idle()
    
    cmd_buffer.destroy()
    cmd_pool.destroy()

# 常用 Layout 转换
transition_image_layout(texture_image, vkbottle.ImageLayout.UNDEFINED, vkbottle.ImageLayout.TRANSFER_DST_OPTIMAL)
transition_image_layout(texture_image, vkbottle.ImageLayout.TRANSFER_DST_OPTIMAL, vkbottle.ImageLayout.SHADER_READ_ONLY_OPTIMAL)

5.3.4 Image View

python
# --- 6. 创建 Image View ---
image_view_create_info = vkbottle.ImageViewCreateInfo(
    image=texture_image,
    viewType=vkbottle.ImageViewType._2D,
    format=vkbottle.Format.R8G8B8A8_SRGB,
    componentMapping=vkbottle.ComponentMapping(
        vkbottle.ComponentSwizzle.IDENTITY,
        vkbottle.ComponentSwizzle.IDENTITY,
        vkbottle.ComponentSwizzle.IDENTITY,
        vkbottle.ComponentSwizzle.IDENTITY,
    ),
    subresourceRange=vkbottle.ImageSubresourceRange(
        aspectMask=vkbottle.ImageAspectFlag.COLOR_BIT,
        baseMipLevel=0,
        levelCount=texture_image.mipLevels,
        baseArrayLayer=0,
        layerCount=texture_image.arrayLayers,
    ),
)

texture_image_view = device.create_image_view(image_view_create_info)
print(f"Image View 已创建!")

5.4 纹理上传(Staging Buffer)🔥

python
def upload_texture(device, staging_buffer, texture_image, texture_width, texture_height, texture_data):
    """使用 Staging Buffer 上传纹理"""
    
    # 1. 创建 Staging Buffer(主机可访问)
    staging_buffer_create_info = vkbottle.BufferCreateInfo(
        size=texture_data.nbytes,
        usage=vkbottle.BufferUsageFlag.TRANSFER_SRC,
        sharingMode=vkbottle.SharingMode.EXCLUSIVE,
    )
    staging_buffer = device.create_buffer(staging_buffer_create_info)
    
    # 2. 上传数据到 Staging Buffer
    staging_memory = device.allocate_memory(vkbottle.MemoryAllocateInfo(
        allocationSize=texture_data.nbytes,
        memoryTypeIndex=find_memory_type_index(
            device.get_memory_properties(),
            device.get_buffer_memory_requirements(staging_buffer).memoryTypeBits,
            vkbottle.MemoryPropertyFlag.HOST_VISIBLE | vkbottle.MemoryPropertyFlag.HOIST_COHERENT
        ),
    ))
    device.bind_buffer_memory(staging_buffer, staging_memory, 0)
    
    # 映射并写入数据
    mapped_ptr = device.map_memory(staging_memory, 0, texture_data.nbytes)
    mapped_ptr.data = texture_data.data
    device.unmap_memory(staging_memory)
    
    # 3. 创建 Command Pool
    cmd_pool = device.create_command_pool(vkbottle.CommandPoolCreateInfo(
        queueFamilyIndex=device.get_physical_device().get_queue_families()[0].queueFamilyIndex,
        flags=vkbottle.CommandPoolCreateFlag.RESET_COMMAND_BUFFER,
    ))
    
    cmd_buffer = device.allocate_command_buffers(vkbottle.CommandBufferAllocateInfo(
        commandPool=cmd_pool,
        level=vkbottle.CommandBufferLevel.PRIMARY,
        commandBufferCount=1,
    ))
    
    # 4. 开始记录
    cmd_buffer.begin(vkbottle.CommandBufferBeginInfo(flags=vkbottle.CommandBufferUsageFlag.ONE_TIME_SUBMIT))
    
    # 5. 布局转换
    cmd_buffer.pipeline_barrier(
        srcStageMask=vkbottle.AccessFlag.NONE,
        dstStageMask=vkbottle.AccessFlag.NONE,
        dependencyFlags=vkbottle.SubpassDependencyFlag.NONE,
        memoryBarriers=[],
        bufferMemoryBarriers=[],
        imageMemoryBarriers=[
            vkbottle.ImageMemoryBarrier(
                srcAccessMask=vkbottle.AccessFlag.NONE,
                dstAccessMask=vkbottle.AccessFlag.NONE,
                oldLayout=vkbottle.ImageLayout.UNDEFINED,
                newLayout=vkbottle.ImageLayout.TRANSFER_DST_OPTIMAL,
                srcQueueFamilyIndex=vkbottle.QFO_TRANSFER_QUEUE_FAMILY_IGNORED,
                dstQueueFamilyIndex=vkbottle.QFO_TRANSFER_QUEUE_FAMILY_IGNORED,
                image=texture_image,
                subresourceRange=vkbottle.ImageSubresourceRange(
                    aspectMask=vkbottle.ImageAspectFlag.COLOR_BIT,
                    baseMipLevel=0,
                    levelCount=texture_image.mipLevels,
                    baseArrayLayer=0,
                    layerCount=texture_image.arrayLayers,
                ),
            ),
        ],
    )
    
    # 6. Copy Buffer → Image
    cmd_buffer.copy_buffer_to_image(
        srcBuffer=staging_buffer,
        dstImage=texture_image,
        dstImageLayout=vkbottle.ImageLayout.TRANSFER_DST_OPTIMAL,
        region=vkbottle.BufferImageCopyRegion(
            bufferOffset=0,
            bufferRowLength=0,
            bufferImageHeight=0,
            imageSubresource=vkbottle.ImageSubresourceLayers(
                aspectMask=vkbottle.ImageAspectFlag.COLOR_BIT,
                mipLevel=0,
                baseArrayLayer=0,
                layerCount=1,
            ),
            imageOffset=(0, 0, 0),
            imageExtent=(texture_width, texture_height, 1),
        ),
    )
    
    # 7. 布局转换到 Shader Read Only
    cmd_buffer.pipeline_barrier(
        srcStageMask=vkbottle.AccessFlag.NONE,
        dstStageMask=vkbottle.AccessFlag.NONE,
        dependencyFlags=vkbottle.SubpassDependencyFlag.NONE,
        memoryBarriers=[],
        bufferMemoryBarriers=[],
        imageMemoryBarriers=[
            vkbottle.ImageMemoryBarrier(
                srcAccessMask=vkbottle.AccessFlag.NONE,
                dstAccessMask=vkbottle.AccessFlag.NONE,
                oldLayout=vkbottle.ImageLayout.TRANSFER_DST_OPTIMAL,
                newLayout=vkbottle.ImageLayout.SHADER_READ_ONLY_OPTIMAL,
                srcQueueFamilyIndex=vkbottle.QFO_TRANSFER_QUEUE_FAMILY_IGNORED,
                dstQueueFamilyIndex=vkbottle.QFO_TRANSFER_QUEUE_FAMILY_IGNORED,
                image=texture_image,
                subresourceRange=vkbottle.ImageSubresourceRange(
                    aspectMask=vkbottle.ImageAspectFlag.COLOR_BIT,
                    baseMipLevel=0,
                    levelCount=texture_image.mipLevels,
                    baseArrayLayer=0,
                    layerCount=texture_image.arrayLayers,
                ),
            ),
        ],
    )
    
    cmd_buffer.end()
    
    # 8. 提交
    device.get_queue().submit(cmd_buffer)
    device.get_queue().wait_idle()
    
    # 9. 清理
    cmd_buffer.destroy()
    cmd_pool.destroy()
    device.destroy_buffer(staging_buffer)
    device.free_memory(staging_memory)

5.5 Buffer vs Image 对比

特性BufferImage
维度1D 数组2D/3D 纹理
内存分配手动(你指定)自动(driver 处理)
用途顶点、Uniform、计算纹理、帧缓冲
布局必需(不同用途不同布局)
View不需要必需(ImageView)
性能线性访问纹理优化(缓存友好)
压缩不支持支持(BC/DXT/ASTC)

5.6 资源创建速查表

资源创建函数关键参数
Bufferdevice.create_buffer()size, usage
Buffer Memorydevice.allocate_memory()allocationSize, memoryTypeIndex
Bind Bufferdevice.bind_buffer_memory()buffer, memory, offset
Imagedevice.create_image()type, format, extent, mipLevels
Image Memorydevice.allocate_memory()mem_requirements.size, memoryTypeIndex
Bind Imagedevice.bind_image_memory()image, memory, offset
Image Viewdevice.create_image_view()image, format, subresourceRange
Samplerdevice.create_sampler()mag/min/mipmap, wrapMode, anisotropy

5.7 Mipmap 生成

python
def generate_mipmaps(device, texture_image, texture_width, texture_height):
    """生成 Mipmap 链"""
    
    # 1. 检查 Image 是否支持 Transfer SRC 和 Color Transfer DST
    image_props = texture_image.get_properties()
    if not (image_props.properties & vkbottle.ImageFeatureFlag.TRANSFER_SRC):
        raise RuntimeError("Texture image does not support TRANSFER_SRC")
    if not (image_props.properties & vkbottle.ImageFeatureFlag.TRANSFER_DST):
        raise RuntimeError("Texture image does not support TRANSFER_DST")
    
    # 2. 转换 Image 到 TRANSFER_DST_OPTIMAL
    transition_layout = vkbottle.ImageLayout.TRANSFER_DST_OPTIMAL
    cmd_buffer = device.allocate_command_buffers(vkbottle.CommandBufferAllocateInfo(
        commandPool=device.create_command_pool(vkbottle.CommandPoolCreateInfo(
            queueFamilyIndex=0,
            flags=vkbottle.CommandPoolCreateFlag.RESET_COMMAND_BUFFER,
        )),
        level=vkbottle.CommandBufferLevel.PRIMARY,
        commandBufferCount=1,
    ))
    cmd_buffer.begin(vkbottle.CommandBufferBeginInfo(flags=vkbottle.CommandBufferUsageFlag.ONE_TIME_SUBMIT))
    
    # 3. 生成每个 Mipmap 层级
    current_width = texture_width
    current_height = texture_height
    
    for level in range(1, texture_image.mipLevels):
        # 从上一层级复制到当前层级
        cmd_buffer.blit_image(
            srcImage=texture_image,
            srcLevel=level - 1,
            dstImage=texture_image,
            dstLevel=level,
            regions=[
                vkbottle.ImageBlit(
                    srcSubresource=vkbottle.ImageSubresourceLayers(
                        aspectMask=vkbottle.ImageAspectFlag.COLOR_BIT,
                        mipLevel=level - 1,
                        baseArrayLayer=0,
                        layerCount=1,
                    ),
                    srcOffsets=(0, 0),  # 源图像角落(0,0)
                    srcOffsets=(current_width, current_height),  # 源图像角落(width, height)
                    dstSubresource=vkbottle.ImageSubresourceLayers(
                        aspectMask=vkbottle.ImageAspectFlag.COLOR_BIT,
                        mipLevel=level,
                        baseArrayLayer=0,
                        layerCount=1,
                    ),
                    dstOffsets=(current_width // 2, current_height // 2),  # 缩小一半
                ),
            ],
            filter=vkbottle.Filter.LINEAR,  # 双线性采样
        )
        
        current_width //= 2
        current_height //= 2
    
    # 4. 转换到 Shader Read Only
    transition_image_layout(texture_image, vkbottle.ImageLayout.TRANSFER_DST_OPTIMAL, vkbottle.ImageLayout.SHADER_READ_ONLY_OPTIMAL)
    
    cmd_buffer.end()
    device.get_queue().submit(cmd_buffer)
    device.get_queue().wait_idle()
    
    cmd_buffer.destroy()