view semicongine/vulkan/buffer.nim @ 1052:b8d20f75ecef

add: some sync (not finished), simplify renderpass af
author sam <sam@basx.dev>
date Sat, 30 Mar 2024 20:29:33 +0700
parents c66503386e8b
children b65068f5f246
line wrap: on
line source

import std/strformat
import std/typetraits
import std/sequtils
import std/tables
import std/logging

import ../core
import ./device
import ./memory
import ./physicaldevice
import ./commandbuffer

type
  Buffer* = object
    device*: Device
    vk*: VkBuffer
    size*: int
    usage*: seq[VkBufferUsageFlagBits]
    case memoryAllocated*: bool
      of false: discard
      of true:
        memory*: DeviceMemory


proc `==`*(a, b: Buffer): bool =
  a.vk == b.vk

func `$`*(buffer: Buffer): string =
  &"Buffer(vk: {buffer.vk}, size: {buffer.size}, usage: {buffer.usage})"

proc requirements(buffer: Buffer): MemoryRequirements =
  assert buffer.vk.valid
  assert buffer.device.vk.valid
  var req: VkMemoryRequirements
  buffer.device.vk.vkGetBufferMemoryRequirements(buffer.vk, addr req)
  result.size = req.size
  result.alignment = req.alignment
  let memorytypes = buffer.device.physicaldevice.vk.getMemoryProperties().types
  for i in 0 ..< sizeof(req.memoryTypeBits) * 8:
    if ((req.memoryTypeBits shr i) and 1) == 1:
      result.memoryTypes.add memorytypes[i]

proc allocateMemory(buffer: var Buffer, requireMappable: bool, preferVRAM: bool, preferAutoFlush: bool) =
  assert buffer.device.vk.valid
  assert buffer.memoryAllocated == false

  let requirements = buffer.requirements()
  let memoryType = requirements.memoryTypes.selectBestMemoryType(
    requireMappable = requireMappable,
    preferVRAM = preferVRAM,
    preferAutoFlush = preferAutoFlush
  )

  debug "Allocating memory for buffer: ", buffer.size, " bytes of type ", memoryType
  # need to replace the whole buffer object, due to case statement
  buffer = Buffer(
    device: buffer.device,
    vk: buffer.vk,
    size: buffer.size,
    usage: buffer.usage,
    memoryAllocated: true,
    memory: buffer.device.allocate(requirements.size, memoryType)
  )
  checkVkResult buffer.device.vk.vkBindBufferMemory(buffer.vk, buffer.memory.vk, VkDeviceSize(0))

# currently no support for extended structure and concurrent/shared use
# (shardingMode = VK_SHARING_MODE_CONCURRENT not supported)
proc createBuffer*(
  device: Device,
  size: int,
  usage: openArray[VkBufferUsageFlagBits],
  requireMappable: bool,
  preferVRAM: bool,
  preferAutoFlush = true,
): Buffer =
  assert device.vk.valid
  assert size > 0

  result.device = device
  result.size = size
  result.usage = usage.toSeq
  if not requireMappable:
    result.usage.add VK_BUFFER_USAGE_TRANSFER_DST_BIT
  var createInfo = VkBufferCreateInfo(
    sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
    flags: VkBufferCreateFlags(0),
    size: uint64(size),
    usage: toBits(result.usage),
    sharingMode: VK_SHARING_MODE_EXCLUSIVE,
  )

  checkVkResult vkCreateBuffer(
    device = device.vk,
    pCreateInfo = addr createInfo,
    pAllocator = nil,
    pBuffer = addr result.vk
  )
  result.allocateMemory(requireMappable = requireMappable, preferVRAM = preferVRAM, preferAutoFlush = preferAutoFlush)


proc copy*(src, dst: Buffer, queue: Queue, dstOffset = 0) =
  assert src.device.vk.valid
  assert dst.device.vk.valid
  assert src.device == dst.device
  assert src.size <= dst.size - dstOffset
  assert VK_BUFFER_USAGE_TRANSFER_SRC_BIT in src.usage
  assert VK_BUFFER_USAGE_TRANSFER_DST_BIT in dst.usage

  var copyRegion = VkBufferCopy(size: VkDeviceSize(src.size), dstOffset: VkDeviceSize(dstOffset))
  withSingleUseCommandBuffer(src.device, queue, true, commandBuffer):
    let barrier = VkMemoryBarrier2(
      sType: VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
      srcStageMask: [VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT].toBits,
      srcAccessMask: [VK_ACCESS_2_MEMORY_WRITE_BIT].toBits,
      dstStageMask: [VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT].toBits,
      dstAccessMask: [VK_ACCESS_2_MEMORY_READ_BIT].toBits,
    )
    commandBuffer.pipelineBarrier(memoryBarriers = [barrier])
    commandBuffer.vkCmdCopyBuffer(src.vk, dst.vk, 1, addr(copyRegion))

proc destroy*(buffer: var Buffer) =
  assert buffer.device.vk.valid
  assert buffer.vk.valid
  buffer.device.vk.vkDestroyBuffer(buffer.vk, nil)
  if buffer.memoryAllocated:
    assert buffer.memory.vk.valid
    buffer.memory.free
    buffer = Buffer(
      device: buffer.device,
      vk: buffer.vk,
      size: buffer.size,
      usage: buffer.usage,
      memoryAllocated: false,
    )
  buffer.vk.reset

proc setData*(dst: Buffer, queue: Queue, src: pointer, size: int, bufferOffset = 0) =
  assert bufferOffset + size <= dst.size
  if dst.memory.canMap:
    copyMem(cast[pointer](cast[int](dst.memory.data) + bufferOffset), src, size)
    if dst.memory.needsFlushing:
      dst.memory.flush()
  else: # use staging buffer, slower but required if memory is not host visible
    var stagingBuffer = dst.device.createBuffer(size, [VK_BUFFER_USAGE_TRANSFER_SRC_BIT], requireMappable = true, preferVRAM = false, preferAutoFlush = true)
    setData(stagingBuffer, queue, src, size, 0)
    stagingBuffer.copy(dst, queue, bufferOffset)
    stagingBuffer.destroy()

proc setData*[T: seq](dst: Buffer, queue: Queue, src: ptr T, offset = 0'u64) =
  dst.setData(queue, src, sizeof(get(genericParams(T), 0)) * src[].len, offset = offset)

proc setData*[T](dst: Buffer, queue: Queue, src: ptr T, offset = 0'u64) =
  dst.setData(queue, src, sizeof(T), offset = offset)