118 lines
4.4 KiB
C++
118 lines
4.4 KiB
C++
//SPDX-License-Identifier: LGPL-3.0-only
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
// Regression test for issue #63: VulkanBuffer::Resize used to unconditionally
|
|
// destroy + reallocate. It now reuses the existing allocation in place when the
|
|
// new request still fits within the created capacity AND the immutable-at-create
|
|
// properties match (usage flags are fixed at create; the chosen memory type must
|
|
// still satisfy the required property flags). The reuse path only shrinks `size`
|
|
// and issues no Vulkan call.
|
|
//
|
|
// The guard is pure logic over recorded fields, so this test drives it directly
|
|
// with no GPU device: it stamps a fake non-null buffer handle plus capacity /
|
|
// usage / chosen-flags, then calls Resize. With Device::device == VK_NULL_HANDLE
|
|
// any real destroy/reallocate would dereference a null dispatch handle and
|
|
// crash — so reaching the line after a reuse Resize is the assertion that the
|
|
// in-place path was taken. The realloc path (flag/size mismatch) is intentionally
|
|
// not exercised here because it would issue real Vulkan calls.
|
|
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include "vulkan/vulkan.h"
|
|
|
|
import Crafter.Graphics;
|
|
import std;
|
|
using namespace Crafter;
|
|
|
|
namespace {
|
|
|
|
int failures = 0;
|
|
|
|
void Check(bool ok, std::string_view what) {
|
|
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
|
if (!ok) ++failures;
|
|
}
|
|
|
|
constexpr auto DEVICE_LOCAL = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
|
constexpr auto HOST_VISIBLE = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
|
|
constexpr auto HOST_COHERENT = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
|
|
constexpr VkBufferUsageFlags2 USAGE =
|
|
VK_BUFFER_USAGE_2_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT;
|
|
|
|
// A fake non-null, non-dereferenced handle: Resize's reuse path never touches it.
|
|
VkBuffer FakeHandle() {
|
|
return reinterpret_cast<VkBuffer>(static_cast<std::uintptr_t>(0x1));
|
|
}
|
|
|
|
// Stamp a buffer into the "already created with capacity C" state without
|
|
// touching the GPU, then neutralise it so the destructor's Clear() is skipped.
|
|
template <typename Buf>
|
|
void NeutraliseHandle(Buf& buf) {
|
|
buf.buffer = VK_NULL_HANDLE;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main() {
|
|
Check(Device::device == VK_NULL_HANDLE,
|
|
"no Vulkan device created — a real reallocate would fault");
|
|
|
|
{
|
|
// Shrink within capacity, flags match → reuse in place, only size shrinks.
|
|
VulkanBuffer<float, false> buf;
|
|
buf.buffer = FakeHandle();
|
|
buf.capacity = 16 * sizeof(float);
|
|
buf.size = 16 * sizeof(float);
|
|
buf.usageFlagsCreated = USAGE;
|
|
buf.memoryPropertyFlagsChosen = HOST_VISIBLE | HOST_COHERENT;
|
|
|
|
buf.Resize(USAGE, HOST_VISIBLE, 4); // 4 floats <= 16-float capacity
|
|
Check(buf.buffer == FakeHandle(),
|
|
"fitting Resize reuses the existing handle (no realloc)");
|
|
Check(buf.size == 4 * sizeof(float),
|
|
"reuse updates size to the new logical extent");
|
|
Check(buf.capacity == 16 * sizeof(float),
|
|
"reuse leaves capacity at the original allocation size");
|
|
NeutraliseHandle(buf);
|
|
}
|
|
|
|
{
|
|
// Exact-fit request (size == capacity) is still a reuse.
|
|
VulkanBuffer<float, false> buf;
|
|
buf.buffer = FakeHandle();
|
|
buf.capacity = 8 * sizeof(float);
|
|
buf.size = 2 * sizeof(float);
|
|
buf.usageFlagsCreated = USAGE;
|
|
buf.memoryPropertyFlagsChosen = HOST_VISIBLE;
|
|
|
|
buf.Resize(USAGE, HOST_VISIBLE, 8);
|
|
Check(buf.buffer == FakeHandle() && buf.size == 8 * sizeof(float),
|
|
"exact-capacity Resize reuses the allocation");
|
|
NeutraliseHandle(buf);
|
|
}
|
|
|
|
{
|
|
// Reuse requires the chosen memory type to still satisfy the required
|
|
// flags. Chosen type carries DEVICE_LOCAL, so a request for it is
|
|
// satisfied and reuse is allowed.
|
|
VulkanBuffer<float, false> buf;
|
|
buf.buffer = FakeHandle();
|
|
buf.capacity = 8 * sizeof(float);
|
|
buf.size = 8 * sizeof(float);
|
|
buf.usageFlagsCreated = USAGE;
|
|
buf.memoryPropertyFlagsChosen = HOST_VISIBLE | DEVICE_LOCAL;
|
|
|
|
buf.Resize(USAGE, DEVICE_LOCAL, 4);
|
|
Check(buf.buffer == FakeHandle() && buf.size == 4 * sizeof(float),
|
|
"reuse allowed when chosen memory type satisfies required flags");
|
|
NeutraliseHandle(buf);
|
|
}
|
|
|
|
if (failures != 0) {
|
|
std::println("{} check(s) failed", failures);
|
|
return EXIT_FAILURE;
|
|
}
|
|
std::println("all checks passed");
|
|
return EXIT_SUCCESS;
|
|
}
|