Files
renderdoc/util/test/demos/vk/vk_mem_bench.cpp
T
baldurk 856c838def Update copyright years to 2026 and fix copyright ranges
* In a previous update in 2021 many copyright ranges were truncated
  accidentally, and some files have been copy-pasted with wrong years. These
  dates have been fixed based on git history and original copyright messages.
2026-01-05 14:17:28 +00:00

750 lines
22 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2023-2026 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include <chrono>
#include "3rdparty/fmt/core.h"
#include "vk_test.h"
#if defined(SSE_TEST)
#include <immintrin.h>
#endif
struct Alloc
{
VkDevice device;
std::string name;
VkDeviceMemory mem;
VkMemoryPropertyFlags flags;
VkBuffer buf;
uint32_t type;
VkDeviceSize size;
byte *data = NULL;
void map() { vkMapMemory(device, mem, 0, VK_WHOLE_SIZE, 0, (void **)&data); }
void unmap() { vkUnmapMemory(device, mem); }
};
byte *refData = NULL;
size_t dummyStart, dummyEnd;
namespace FindDiffRange_shipping
{
#if 0
static __m128 zero = {0};
#endif
// assumes a and b both point to 16-byte aligned 16-byte chunks of memory.
// Returns if they're equal or different
bool Vec16NotEqual(void *a, void *b)
{
// disabled SSE version as it's acting dodgy
#if 0
__m128 avec = _mm_load_ps(aflt);
__m128 bvec = _mm_load_ps(bflt);
__m128 diff = _mm_xor_ps(avec, bvec);
__m128 eq = _mm_cmpeq_ps(diff, zero);
int mask = _mm_movemask_ps(eq);
int signMask = _mm_movemask_ps(diff);
// first check ensures that diff is floatequal to zero (ie. avec bitwise equal to bvec).
// HOWEVER -0 is floatequal to 0, so we ensure no sign bits are set on diff
if((mask^0xf) || signMask != 0)
{
return true;
}
return false;
#elif defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || \
defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) || \
(defined(__riscv) && __riscv_xlen == 64)
uint64_t *a64 = (uint64_t *)a;
uint64_t *b64 = (uint64_t *)b;
return a64[0] != b64[0] || a64[1] != b64[1];
#else
uint32_t *a32 = (uint32_t *)a;
uint32_t *b32 = (uint32_t *)b;
return a32[0] != b32[0] || a32[1] != b32[1] || a32[2] != b32[2] || a32[3] != b32[3];
#endif
}
bool FindDiffRange(void *a, void *b, size_t bufSize, size_t &diffStart, size_t &diffEnd)
{
TEST_ASSERT(uintptr_t(a) % 16 == 0, "misaligned");
TEST_ASSERT(uintptr_t(b) % 16 == 0, "misaligned");
diffStart = bufSize + 1;
diffEnd = 0;
size_t alignedSize = bufSize & (~0xf);
size_t numVecs = alignedSize / 16;
size_t offs = 0;
float *aflt = (float *)a;
float *bflt = (float *)b;
// sweep to find the start of differences
for(size_t v = 0; v < numVecs; v++)
{
if(Vec16NotEqual(aflt, bflt))
{
diffStart = offs;
break;
}
aflt += 4;
bflt += 4;
offs += 4 * sizeof(float);
}
// make sure we're byte-accurate, to comply with WRITE_NO_OVERWRITE
while(diffStart < bufSize && *((byte *)a + diffStart) == *((byte *)b + diffStart))
diffStart++;
// do we have some unaligned bytes at the end of the buffer?
if(bufSize > alignedSize)
{
size_t numBytes = bufSize - alignedSize;
// if we haven't even found a start, check in these bytes
if(diffStart > bufSize)
{
offs = alignedSize;
for(size_t by = 0; by < numBytes; by++)
{
if(*((byte *)a + alignedSize + by) != *((byte *)b + alignedSize + by))
{
diffStart = offs;
break;
}
offs++;
}
}
// sweep from the last byte to find the end
for(size_t by = 0; by < numBytes; by++)
{
if(*((byte *)a + bufSize - 1 - by) != *((byte *)b + bufSize - 1 - by))
{
diffEnd = bufSize - by;
break;
}
}
}
// if we haven't found a start, or we've found a start AND and end,
// then we're done.
if(diffStart > bufSize || diffEnd > 0)
return diffStart < bufSize;
offs = alignedSize;
// sweep from the last __m128
aflt = (float *)a + offs / sizeof(float) - 4;
bflt = (float *)b + offs / sizeof(float) - 4;
for(size_t v = 0; v < numVecs; v++)
{
if(Vec16NotEqual(aflt, bflt))
{
diffEnd = offs;
break;
}
aflt -= 4;
bflt -= 4;
offs -= 16;
}
// make sure we're byte-accurate, to comply with WRITE_NO_OVERWRITE
while(diffEnd > 0 && *((byte *)a + diffEnd - 1) == *((byte *)b + diffEnd - 1))
diffEnd--;
// if we found a start then we necessarily found an end
return diffStart < bufSize;
}
};
void stream_memcpy(void *dst, void *src, size_t len)
{
char *d = (char *)dst;
char *s = (char *)src;
#if defined(SSE_TEST)
/* If dst and src are not co-aligned, or if SSE4.1 is not present, fallback to memcpy(). */
if(((uintptr_t)d & 15) != ((uintptr_t)s & 15))
{
memcpy(d, s, len);
return;
}
/* memcpy() the misaligned header. At the end of this if block, <d> and <s>
* are aligned to a 16-byte boundary or <len> == 0.
*/
if((uintptr_t)d & 15)
{
uintptr_t bytes_before_alignment_boundary = 16 - ((uintptr_t)d & 15);
TEST_ASSERT(bytes_before_alignment_boundary < 16, "!");
memcpy(d, s, std::min(bytes_before_alignment_boundary, len));
d = (char *)AlignUp((uintptr_t)d, (uintptr_t)16ULL);
s = (char *)AlignUp((uintptr_t)s, (uintptr_t)16ULL);
len -= std::min(bytes_before_alignment_boundary, len);
}
if(len >= 64)
_mm_mfence();
while(len >= 64)
{
__m128i *dst_cacheline = (__m128i *)d;
__m128i *src_cacheline = (__m128i *)s;
__m128i temp1 = _mm_stream_load_si128(src_cacheline + 0);
__m128i temp2 = _mm_stream_load_si128(src_cacheline + 1);
__m128i temp3 = _mm_stream_load_si128(src_cacheline + 2);
__m128i temp4 = _mm_stream_load_si128(src_cacheline + 3);
_mm_store_si128(dst_cacheline + 0, temp1);
_mm_store_si128(dst_cacheline + 1, temp2);
_mm_store_si128(dst_cacheline + 2, temp3);
_mm_store_si128(dst_cacheline + 3, temp4);
d += 64;
s += 64;
len -= 64;
}
#endif
/* memcpy() the tail. */
if(len)
{
memcpy(d, s, len);
}
}
RD_TEST(VK_Mem_Bench, VulkanGraphicsTest)
{
static constexpr const char *Description = "Memory mapping benchmark";
bool bench = true;
VkDeviceSize maxMemory = 500 * 1024 * 1024;
uint32_t submits = 20;
void Prepare(int argc, char **argv)
{
for(int i = 0; i < argc; i++)
{
if(!strcmp(argv[i], "--bench"))
{
bench = true;
}
if(!strcmp(argv[i], "--maxmem") && i + 1 < argc)
{
maxMemory = atoi(argv[i + 1]) * 1024 * 1024;
}
if(!strcmp(argv[i], "--submits") && i + 1 < argc)
{
submits = atoi(argv[i + 1]);
}
}
forceComputeQueue = true;
forceTransferQueue = true;
VulkanGraphicsTest::Prepare(argc, argv);
}
int main()
{
// initialise, create window, create context, etc
if(!Init())
return 3;
VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo());
vkh::GraphicsPipelineCreateInfo pipeCreateInfo;
pipeCreateInfo.layout = layout;
pipeCreateInfo.renderPass = mainWindow->rp;
pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)};
pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = {
vkh::vertexAttr(0, 0, DefaultA2V, pos),
vkh::vertexAttr(1, 0, DefaultA2V, col),
vkh::vertexAttr(2, 0, DefaultA2V, uv),
};
pipeCreateInfo.stages = {
CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"),
CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "main"),
};
AllocatedImage img(
this,
vkh::ImageCreateInfo(mainWindow->scissor.extent.width, mainWindow->scissor.extent.height, 0,
mainWindow->format,
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY}));
VkImageView imgview = createImageView(
vkh::ImageViewCreateInfo(img.image, VK_IMAGE_VIEW_TYPE_2D, mainWindow->format));
vkh::RenderPassCreator renderPassCreateInfo;
renderPassCreateInfo.attachments.push_back(
vkh::AttachmentDescription(mainWindow->format, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_GENERAL, VK_ATTACHMENT_LOAD_OP_CLEAR));
renderPassCreateInfo.addSubpass({VkAttachmentReference({0, VK_IMAGE_LAYOUT_GENERAL})});
VkRenderPass renderPass = createRenderPass(renderPassCreateInfo);
VkFramebuffer framebuffer = createFramebuffer(
vkh::FramebufferCreateInfo(renderPass, {imgview}, mainWindow->scissor.extent));
pipeCreateInfo.renderPass = renderPass;
VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo);
AllocatedBuffer vb(
this,
vkh::BufferCreateInfo(sizeof(DefaultTri) + 128 * 1024,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT),
VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU}));
vb.upload(DefaultTri);
const VkPhysicalDeviceMemoryProperties *props = NULL;
vmaGetMemoryProperties(allocator, &props);
std::vector<Alloc> allocs;
VkDeviceSize refDataSize = 0;
uint32_t heapTypeCount[16] = {};
for(uint32_t m = 0; m < props->memoryTypeCount; m++)
{
const VkMemoryType &type = props->memoryTypes[m];
if((type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0)
{
heapTypeCount[type.heapIndex]++;
}
}
for(uint32_t m = 0; m < props->memoryTypeCount; m++)
{
const VkMemoryType &type = props->memoryTypes[m];
const VkMemoryHeap &heap = props->memoryHeaps[type.heapIndex];
if((type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0)
{
Alloc alloc;
alloc.device = device;
alloc.type = m;
alloc.flags = type.propertyFlags;
VkMemoryAllocateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
info.allocationSize =
AlignUp(std::min(maxMemory, ((heap.size * 7) / 10) / heapTypeCount[type.heapIndex]),
(VkDeviceSize)256ULL);
info.memoryTypeIndex = m;
vkAllocateMemory(device, &info, NULL, &alloc.mem);
vkCreateBuffer(
device,
vkh::BufferCreateInfo(info.allocationSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT),
NULL, &alloc.buf);
vkBindBufferMemory(device, alloc.buf, alloc.mem, 0);
alloc.size = info.allocationSize;
if(bench && alloc.size > refDataSize)
refDataSize = alloc.size;
alloc.name = fmt::format("Mem {} ({:04}MB):", m, info.allocationSize >> 20);
if((type.propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) != 0)
alloc.name += " CACHED";
if((type.propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0)
alloc.name += " COHERENT";
if((type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0)
alloc.name += " DEVICE";
allocs.push_back(alloc);
}
}
if(bench)
refData = new byte[(uint32_t)refDataSize];
AllocatedBuffer readback;
if(bench)
readback =
AllocatedBuffer(this, vkh::BufferCreateInfo(refDataSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT),
VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_UNKNOWN,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT}));
using ScanFunction = std::function<std::string(Alloc &, byte *, VkDeviceSize)>;
byte *scratch = new byte[65536];
std::vector<ScanFunction> scanners;
auto blockScan = [scratch](VkDeviceSize blockSize, bool streamMemcpy, Alloc &, byte *data,
VkDeviceSize size) {
if(blockSize == 0)
{
FindDiffRange_shipping::FindDiffRange(data, refData, (size_t)size, dummyStart, dummyEnd);
return;
}
for(VkDeviceSize i = 0; i < size; i += blockSize)
{
size_t chunkSize = (size_t)std::min(blockSize, size - i);
if(streamMemcpy)
stream_memcpy(scratch, data + i, chunkSize);
else
memcpy(scratch, data + i, chunkSize);
FindDiffRange_shipping::FindDiffRange(scratch, refData, chunkSize, dummyStart, dummyEnd);
}
};
scanners.push_back([&blockScan](Alloc &a, byte *data, VkDeviceSize size) {
blockScan(0, false, a, data, size);
return "direct";
});
scanners.push_back([&blockScan](Alloc &a, byte *data, VkDeviceSize size) {
blockScan(128, false, a, data, size);
return "block_128";
});
scanners.push_back([&blockScan](Alloc &a, byte *data, VkDeviceSize size) {
blockScan(1024, false, a, data, size);
return "block_1024";
});
scanners.push_back([&blockScan](Alloc &a, byte *data, VkDeviceSize size) {
blockScan(65536, false, a, data, size);
return "block_65536";
});
scanners.push_back([&blockScan](Alloc &a, byte *data, VkDeviceSize size) {
blockScan(128, true, a, data, size);
return "block_128_stream";
});
scanners.push_back([&blockScan](Alloc &a, byte *data, VkDeviceSize size) {
blockScan(1024, true, a, data, size);
return "block_1024_stream";
});
scanners.push_back([&blockScan](Alloc &a, byte *data, VkDeviceSize size) {
blockScan(65536, true, a, data, size);
return "block_65536_stream";
});
auto gpuReadback = [this, &readback](VkQueue q, VkCommandBuffer cmd, Alloc &a) -> byte * {
vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
VkBufferCopy region = {0, 0, a.size};
vkCmdCopyBuffer(cmd, a.buf, readback.buffer, 1, &region);
vkh::cmdPipelineBarrier(cmd, {},
{vkh::BufferMemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT,
VK_ACCESS_HOST_READ_BIT, readback.buffer)});
vkEndCommandBuffer(cmd);
std::vector<VkCommandBuffer> cmds = {cmd};
VkSubmitInfo submit = vkh::SubmitInfo(cmds);
CHECK_VKR(vkQueueSubmit(q, 1, &submit, VK_NULL_HANDLE));
vkQueueWaitIdle(q);
byte *ret = readback.map();
if((a.flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == 0)
{
VmaAllocationInfo info;
vmaGetAllocationInfo(allocator, readback.alloc, &info);
VkMappedMemoryRange range = {
VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, NULL, info.deviceMemory, info.offset, info.size,
};
vkInvalidateMappedMemoryRanges(device, 1, &range);
}
return ret;
};
struct GPUReadbackFamily
{
uint32_t family;
std::string name;
VkQueue q;
VkCommandPool pool;
VkCommandBuffer cmd;
};
GPUReadbackFamily readbackFamily[3] = {
{queueFamilyIndex, "default"},
{computeQueueFamilyIndex, "compute"},
{transferQueueFamilyIndex, "transfer"},
};
for(size_t i = 0; i < ARRAY_COUNT(readbackFamily); i++)
{
GPUReadbackFamily &f = readbackFamily[i];
if(f.family == ~0U)
continue;
vkGetDeviceQueue(device, f.family, 0, &f.q);
CHECK_VKR(vkCreateCommandPool(
device,
vkh::CommandPoolCreateInfo(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, f.family),
NULL, &f.pool));
CHECK_VKR(vkAllocateCommandBuffers(device, vkh::CommandBufferAllocateInfo(f.pool, 1), &f.cmd));
scanners.push_back(
[this, &readback, f, &blockScan, &gpuReadback](Alloc &a, byte *, VkDeviceSize size) {
vkResetCommandBuffer(f.cmd, 0);
byte *data = gpuReadback(f.q, f.cmd, a);
blockScan(0, false, a, data, size);
readback.unmap();
return "gpu_" + f.name + "_direct";
});
scanners.push_back(
[this, &readback, f, &blockScan, &gpuReadback](Alloc &a, byte *, VkDeviceSize size) {
vkResetCommandBuffer(f.cmd, 0);
byte *data = gpuReadback(f.q, f.cmd, a);
blockScan(128, false, a, data, size);
readback.unmap();
return "gpu_" + f.name + "_128";
});
scanners.push_back(
[this, &readback, f, &blockScan, &gpuReadback](Alloc &a, byte *, VkDeviceSize size) {
vkResetCommandBuffer(f.cmd, 0);
byte *data = gpuReadback(f.q, f.cmd, a);
blockScan(128, true, a, data, size);
readback.unmap();
return "gpu_" + f.name + "_128_streaming";
});
scanners.push_back(
[this, &readback, f, &blockScan, &gpuReadback](Alloc &a, byte *, VkDeviceSize size) {
vkResetCommandBuffer(f.cmd, 0);
byte *data = gpuReadback(f.q, f.cmd, a);
blockScan(1024, false, a, data, size);
readback.unmap();
return "gpu_" + f.name + "_1024";
});
scanners.push_back(
[this, &readback, f, &blockScan, &gpuReadback](Alloc &a, byte *, VkDeviceSize size) {
vkResetCommandBuffer(f.cmd, 0);
byte *data = gpuReadback(f.q, f.cmd, a);
blockScan(1024, true, a, data, size);
readback.unmap();
return "gpu_" + f.name + "_1024_streaming";
});
}
typedef std::chrono::high_resolution_clock Clock;
typedef std::chrono::time_point<Clock> Time;
uint32_t seed = 0x31F10ca8;
for(size_t i = 0; i < refDataSize; i++)
{
seed = (~seed) ^ (seed >> 5);
refData[i] = seed & 0xff;
}
while(Running())
{
for(Alloc &a : allocs)
{
a.map();
if(bench)
{
#if defined(WIN32) || defined(_WIN32)
char OSName[] = "Windows";
#else
char OSName[] = "Linux";
#endif
TEST_LOG("-------- %s on %s", physProperties.deviceName, OSName);
memcpy(a.data, refData, (size_t)a.size);
}
double bestSpeed = 0.0;
std::string bestScannerName;
for(size_t scan = 0; scan < scanners.size(); scan++)
{
Time prev = Clock::now();
std::string scannerName;
int submitsCompleted = 0;
for(uint32_t s = 0; s < submits; s++)
{
VkCommandBuffer cmd = GetCommandBuffer();
vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
VkBufferCopy region;
region.size = 128;
region.srcOffset = 0;
region.dstOffset = 128;
vkCmdCopyBuffer(cmd, a.buf, a.buf, 1, &region);
vkCmdBeginRenderPass(
cmd,
vkh::RenderPassBeginInfo(renderPass, framebuffer, mainWindow->scissor,
{vkh::ClearValue(0.2f, 0.2f, 0.2f, 1.0f)}),
VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport);
vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor);
vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0});
vkCmdDraw(cmd, 3, 1, 0, 0);
vkCmdEndRenderPass(cmd);
vkEndCommandBuffer(cmd);
if(bench)
scannerName = scanners[scan](a, a.data, a.size);
Submit(99, 99, {cmd});
submitsCompleted++;
if(bench)
{
Time cur = Clock::now();
double timeMS =
double(std::chrono::duration_cast<std::chrono::microseconds>(cur - prev).count()) /
1000.0;
if(timeMS > 10000)
break;
}
}
if(!bench)
break;
Time cur = Clock::now();
double timeMS =
double(std::chrono::duration_cast<std::chrono::microseconds>(cur - prev).count()) /
1000.0;
std::string data = a.name;
data.resize(32, ' ');
data += "scanned by ";
data += scannerName;
data.resize(70, ' ');
double speed = double((submitsCompleted * a.size) >> 20) / double(timeMS / 1000.0);
data += fmt::format("{:8.2f} MS for {} submits = {:8.2f} MB/s", timeMS, submitsCompleted,
speed);
TEST_LOG("%s", data.c_str());
if(speed > bestSpeed * 1.02)
{
bestScannerName = scannerName;
bestSpeed = speed;
}
}
if(bench)
{
TEST_LOG("--------");
TEST_LOG("%s's best scanner is %s", a.name.c_str(), bestScannerName.c_str());
TEST_LOG("--------");
}
a.unmap();
}
if(bench)
TEST_LOG("");
{
VkCommandBuffer cmd = GetCommandBuffer();
vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
VkImage swapimg =
StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);
vkh::cmdPipelineBarrier(
cmd, {
vkh::ImageMemoryBarrier(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_GENERAL, img.image),
});
blitToSwap(cmd, img.image, VK_IMAGE_LAYOUT_GENERAL, swapimg, VK_IMAGE_LAYOUT_GENERAL);
FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);
vkEndCommandBuffer(cmd);
Submit(0, 1, {cmd});
}
Present();
}
for(size_t i = 0; i < ARRAY_COUNT(readbackFamily); i++)
vkDestroyCommandPool(device, readbackFamily[i].pool, NULL);
for(Alloc &a : allocs)
{
vkDestroyBuffer(device, a.buf, NULL);
vkFreeMemory(device, a.mem, NULL);
}
return 0;
}
};
REGISTER_TEST();