Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 14 additions & 15 deletions src/vulkan/vulkan-backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -565,11 +565,18 @@ namespace nvrhi::vulkan
VulkanAllocator& m_Allocator;
};

struct StagingTextureRegion
struct PlacedSubresourceFootprint
{
// offset, size in bytes
off_t offset;
size_t size;
size_t offset;
size_t totalBytes;
uint32_t rowSizeInBytes;
uint32_t numRows;
Format format;
uint32_t width;
uint32_t height;
uint32_t depth;
uint32_t rowPitch;
};

class StagingTexture : public RefCounter<IStagingTexture>
Expand All @@ -580,19 +587,11 @@ namespace nvrhi::vulkan
RefCountPtr<Buffer> buffer;
// per-mip, per-slice regions
// offset = mipLevel * numDepthSlices + depthSlice
std::vector<StagingTextureRegion> sliceRegions;
std::vector<PlacedSubresourceFootprint> placedFootprints;

size_t computeSliceSize(uint32_t mipLevel);
const StagingTextureRegion& getSliceRegion(uint32_t mipLevel, uint32_t arraySlice, uint32_t z);
void populateSliceRegions();

size_t getBufferSize()
{
assert(sliceRegions.size());
size_t size = sliceRegions.back().offset + sliceRegions.back().size;
assert(size > 0);
return size;
}
size_t computeCopyableFootprints();
const PlacedSubresourceFootprint& getCopyableFootprint(uint32_t mipLevel,
uint32_t arraySlice);

const TextureDesc& getDesc() const override { return desc; }
};
Expand Down
220 changes: 111 additions & 109 deletions src/vulkan/vulkan-staging-texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,85 +28,79 @@ namespace nvrhi::vulkan

extern vk::ImageAspectFlags guessImageAspectFlags(vk::Format format);

// we follow DX conventions when mapping slices and mip levels:
// for a 3D or array texture, array layers / 3d depth slices for a given mip slice
// are consecutive in memory, with padding in between for alignment
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn705766(v=vs.85).aspx

// compute the size of a mip level slice
// this is the size of a single slice of a 3D texture / array for the given mip level
size_t StagingTexture::computeSliceSize(uint32_t mipLevel)
static size_t alignBufferOffset(size_t off)
{
const FormatInfo& formatInfo = getFormatInfo(desc.format);
static constexpr size_t bufferAlignmentBytes = 4;
return ((off + (bufferAlignmentBytes - 1)) / bufferAlignmentBytes) * bufferAlignmentBytes;
}

uint32_t wInBlocks = std::max(((desc.width >> mipLevel) + formatInfo.blockSize - 1) / formatInfo.blockSize, 1u);
uint32_t hInBlocks = std::max(((desc.height >> mipLevel) + formatInfo.blockSize - 1) / formatInfo.blockSize, 1u);
static size_t computePlacedBufferOffset(
const PlacedSubresourceFootprint& footprint, uint32_t x, uint32_t y, uint32_t z) {
auto formatInfo = getFormatInfo(footprint.format);

size_t blockPitchBytes = wInBlocks * formatInfo.bytesPerBlock;
return blockPitchBytes * hInBlocks;
}
uint32_t blockX = x / formatInfo.blockSize;
uint32_t blockY = y / formatInfo.blockSize;
uint32_t blockZ = z;

static off_t alignBufferOffset(off_t off)
{
static constexpr off_t bufferAlignmentBytes = 4;
return ((off + (bufferAlignmentBytes - 1)) / bufferAlignmentBytes) * bufferAlignmentBytes;
return footprint.offset +
(blockX + (blockY + blockZ * footprint.numRows) * footprint.rowPitch);
}

const StagingTextureRegion& StagingTexture::getSliceRegion(uint32_t mipLevel, uint32_t arraySlice, uint32_t z)
{
if (desc.depth != 1)
{
// Hard case, since each mip level has half the slices as the previous one.
assert(arraySlice == 0);
assert(z < desc.depth);

uint32_t mipDepth = desc.depth;
uint32_t index = 0;
while (mipLevel-- > 0)
{
index += mipDepth;
mipDepth = std::max(mipDepth, uint32_t(1));
}
return sliceRegions[index + z];
}
else if (desc.arraySize != 1)
{
// Easy case, since each mip level has a consistent number of slices.
assert(z == 0);
assert(arraySlice < desc.arraySize);
assert(sliceRegions.size() == desc.mipLevels * desc.arraySize);
return sliceRegions[mipLevel * desc.arraySize + arraySlice];
size_t StagingTexture::computeCopyableFootprints() {
uint32_t width = desc.width;
uint32_t height = desc.height;
uint32_t depth = desc.depth;
uint32_t numMips = desc.mipLevels;
uint32_t arraySize = desc.arraySize;

if (desc.dimension == TextureDimension::Texture3D) {
assert(desc.arraySize == 1);
arraySize = 1;
} else {
assert(desc.depth == 1);
depth = 1;
}
else
{
assert(arraySlice == 0);
assert(z == 0);
assert(sliceRegions.size() == desc.mipLevels);
return sliceRegions[mipLevel];
}
}

void StagingTexture::populateSliceRegions()
{
off_t curOffset = 0;
const FormatInfo& formatInfo = getFormatInfo(desc.format);

sliceRegions.clear();
size_t offset = 0;

for (uint32_t mipSlice = 0; mipSlice < numMips; ++mipSlice) {
uint32_t wInBlocks =
std::max((width + formatInfo.blockSize - 1) / formatInfo.blockSize, 1u);
uint32_t hInBlocks =
std::max((height + formatInfo.blockSize - 1) / formatInfo.blockSize, 1u);

PlacedSubresourceFootprint layout;
layout.rowSizeInBytes = wInBlocks * formatInfo.bytesPerBlock;
layout.numRows = hInBlocks;
layout.totalBytes = size_t(depth) * hInBlocks * layout.rowSizeInBytes;
layout.format = desc.format;
layout.width = width;
layout.height = height;
layout.depth = depth;
layout.rowPitch = layout.rowSizeInBytes;

for (uint32_t arraySlice = 0; arraySlice < arraySize; ++arraySlice) {
layout.offset = alignBufferOffset(offset);
offset += layout.totalBytes;
placedFootprints.push_back(layout);
}

for(uint32_t mip = 0; mip < desc.mipLevels; mip++)
{
auto sliceSize = computeSliceSize(mip);
width = std::max(width >> 1u, 1u);
height = std::max(height >> 1u, 1u);
depth = std::max(depth >> 1u, 1u);
}

uint32_t depth = std::max(desc.depth >> mip, uint32_t(1));
uint32_t numSlices = desc.arraySize * depth;
return offset; // total size in bytes of upload buffer
}

for (uint32_t slice = 0; slice < numSlices; slice++)
{
sliceRegions.push_back({ curOffset, sliceSize });
const PlacedSubresourceFootprint& StagingTexture::getCopyableFootprint(
uint32_t mipLevel, uint32_t arraySlice) {
uint32_t arraySize = desc.arraySize;

// update offset for the next region
curOffset = alignBufferOffset(off_t(curOffset + sliceSize));
}
}
uint32_t subresourceIndex = mipLevel * arraySize + arraySlice;
return placedFootprints[subresourceIndex];
}

StagingTextureHandle Device::createStagingTexture(const TextureDesc& desc, CpuAccessMode cpuAccess)
Expand All @@ -115,10 +109,11 @@ namespace nvrhi::vulkan

StagingTexture *tex = new StagingTexture();
tex->desc = desc;
tex->populateSliceRegions();

size_t totalSizeInBytes = tex->computeCopyableFootprints();

BufferDesc bufDesc;
bufDesc.byteSize = tex->getBufferSize();
bufDesc.byteSize = totalSizeInBytes;
assert(bufDesc.byteSize > 0);
bufDesc.debugName = desc.debugName;
bufDesc.cpuAccess = cpuAccess;
Expand All @@ -145,19 +140,14 @@ namespace nvrhi::vulkan

auto resolvedSlice = slice.resolve(tex->desc);

auto region = tex->getSliceRegion(resolvedSlice.mipLevel, resolvedSlice.arraySlice, resolvedSlice.z);

assert((region.offset & 0x3) == 0); // per vulkan spec
assert(region.size > 0);
auto layout =
tex->getCopyableFootprint(resolvedSlice.mipLevel, resolvedSlice.arraySlice);
assert((layout.offset & 0x3) == 0); // per vulkan spec
assert(layout.totalBytes > 0);

const FormatInfo& formatInfo = getFormatInfo(tex->desc.format);
assert(outRowPitch);
*outRowPitch = layout.rowPitch;

auto wInBlocks = resolvedSlice.width / formatInfo.blockSize;

*outRowPitch = wInBlocks * formatInfo.bytesPerBlock;

return mapBuffer(tex->buffer, cpuAccess, region.offset, region.size);
return mapBuffer(tex->buffer, cpuAccess, layout.offset, layout.totalBytes);
}

void Device::unmapStagingTexture(IStagingTexture* _tex)
Expand All @@ -172,30 +162,37 @@ namespace nvrhi::vulkan
Texture* src = checked_cast<Texture*>(_src);
StagingTexture* dst = checked_cast<StagingTexture*>(_dst);

auto resolvedDstSlice = dstSlice.resolve(dst->desc);
auto &resolvedDstSlice = dstSlice;
auto resolvedSrcSlice = srcSlice.resolve(src->desc);

assert(resolvedDstSlice.depth == 1);

auto dstRegion = dst->getSliceRegion(resolvedDstSlice.mipLevel, resolvedDstSlice.arraySlice, resolvedDstSlice.z);
assert((dstRegion.offset & 0x3) == 0); // per Vulkan spec
auto dstFootprint = dst->getCopyableFootprint(resolvedDstSlice.mipLevel, resolvedDstSlice.arraySlice);
size_t dstBufferOffset =
computePlacedBufferOffset(dstFootprint, dstSlice.x, dstSlice.y, dstSlice.z);
assert((dstBufferOffset & 0x3) == 0); // per Vulkan spec

TextureSubresourceSet srcSubresource = TextureSubresourceSet(
resolvedSrcSlice.mipLevel, 1,
resolvedSrcSlice.arraySlice, 1
);

auto imageCopy = vk::BufferImageCopy()
.setBufferOffset(dstRegion.offset)
.setBufferRowLength(resolvedDstSlice.width)
.setBufferImageHeight(resolvedDstSlice.height)
.setImageSubresource(vk::ImageSubresourceLayers()
.setAspectMask(guessImageAspectFlags(src->imageInfo.format))
.setMipLevel(resolvedSrcSlice.mipLevel)
.setBaseArrayLayer(resolvedSrcSlice.arraySlice)
.setLayerCount(1))
.setImageOffset(vk::Offset3D(resolvedSrcSlice.x, resolvedSrcSlice.y, resolvedSrcSlice.z))
.setImageExtent(vk::Extent3D(resolvedSrcSlice.width, resolvedSrcSlice.height, resolvedSrcSlice.depth));
auto imageCopy =
vk::BufferImageCopy()
.setBufferOffset(dstBufferOffset)
.setBufferRowLength(dstFootprint.width)
.setBufferImageHeight(dstFootprint.height)
.setImageSubresource(
vk::ImageSubresourceLayers()
.setAspectMask(guessImageAspectFlags(src->imageInfo.format))
.setMipLevel(resolvedSrcSlice.mipLevel)
.setBaseArrayLayer(resolvedSrcSlice.arraySlice)
.setLayerCount(1))
.setImageOffset(vk::Offset3D(resolvedSrcSlice.x, resolvedSrcSlice.y,
resolvedSrcSlice.z))
.setImageExtent(vk::Extent3D(resolvedSrcSlice.width,
resolvedSrcSlice.height,
resolvedSrcSlice.depth));

assert(m_CurrentCmdBuf);

Expand All @@ -220,13 +217,14 @@ namespace nvrhi::vulkan
StagingTexture* src = checked_cast<StagingTexture*>(_src);
Texture* dst = checked_cast<Texture*>(_dst);

auto resolvedDstSlice = dstSlice.resolve(dst->desc);
auto &resolvedDstSlice = dstSlice;
auto resolvedSrcSlice = srcSlice.resolve(src->desc);

auto srcRegion = src->getSliceRegion(resolvedSrcSlice.mipLevel, resolvedSrcSlice.arraySlice, resolvedSrcSlice.z);

assert((srcRegion.offset & 0x3) == 0); // per vulkan spec
assert(srcRegion.size > 0);
auto srcFootprint = src->getCopyableFootprint(resolvedSrcSlice.mipLevel,
resolvedSrcSlice.arraySlice);
size_t srcBufferOffset =
computePlacedBufferOffset(srcFootprint, srcSlice.x, srcSlice.y, srcSlice.z);
assert((srcBufferOffset & 0x3) == 0); // per vulkan spec

TextureSubresourceSet dstSubresource = TextureSubresourceSet(
resolvedDstSlice.mipLevel, 1,
Expand All @@ -235,17 +233,21 @@ namespace nvrhi::vulkan

vk::Offset3D dstOffset(resolvedDstSlice.x, resolvedDstSlice.y, resolvedDstSlice.z);

auto imageCopy = vk::BufferImageCopy()
.setBufferOffset(srcRegion.offset)
.setBufferRowLength(resolvedSrcSlice.width)
.setBufferImageHeight(resolvedSrcSlice.height)
.setImageSubresource(vk::ImageSubresourceLayers()
.setAspectMask(guessImageAspectFlags(dst->imageInfo.format))
.setMipLevel(resolvedDstSlice.mipLevel)
.setBaseArrayLayer(resolvedDstSlice.arraySlice)
.setLayerCount(1))
.setImageOffset(dstOffset)
.setImageExtent(vk::Extent3D(resolvedDstSlice.width, resolvedDstSlice.height, resolvedDstSlice.depth));
auto imageCopy =
vk::BufferImageCopy()
.setBufferOffset(srcBufferOffset)
.setBufferRowLength(srcFootprint.width)
.setBufferImageHeight(srcFootprint.height)
.setImageSubresource(
vk::ImageSubresourceLayers()
.setAspectMask(guessImageAspectFlags(dst->imageInfo.format))
.setMipLevel(resolvedDstSlice.mipLevel)
.setBaseArrayLayer(resolvedDstSlice.arraySlice)
.setLayerCount(1))
.setImageOffset(dstOffset)
.setImageExtent(vk::Extent3D(resolvedSrcSlice.width,
resolvedSrcSlice.height,
resolvedSrcSlice.depth));

assert(m_CurrentCmdBuf);

Expand Down