2026-07-22 18:09:06 +02:00
|
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
2026-06-16 17:04:26 +00:00
|
|
|
|
|
|
|
|
// Regression test for issue #60: VulkanBuffer::FlushDevice / FlushHost used to
|
|
|
|
|
// call vkFlushMappedMemoryRanges / vkInvalidateMappedMemoryRanges
|
|
|
|
|
// unconditionally. They now record the *chosen* memory type's propertyFlags at
|
|
|
|
|
// Create time and skip the flush/invalidate when the memory is HOST_COHERENT
|
|
|
|
|
// (coherent memory makes host/device writes mutually visible with no explicit
|
|
|
|
|
// flush).
|
|
|
|
|
//
|
|
|
|
|
// The gate is pure logic over the recorded flags, so this test drives it
|
|
|
|
|
// directly with no GPU device: it stamps memoryPropertyFlagsChosen and verifies
|
|
|
|
|
// the coherent path returns without issuing any Vulkan call. With no device
|
|
|
|
|
// created, Device::device is VK_NULL_HANDLE — so if the gate were removed, the
|
|
|
|
|
// flush call would dereference a null dispatch handle and crash. Reaching the
|
|
|
|
|
// line after the calls is the assertion that the gate held.
|
|
|
|
|
|
|
|
|
|
#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;
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
// No device is created, so Device::device stays VK_NULL_HANDLE; any real
|
|
|
|
|
// flush/invalidate call below would fault. The coherent gate must prevent
|
|
|
|
|
// it.
|
|
|
|
|
Check(Device::device == VK_NULL_HANDLE,
|
|
|
|
|
"no Vulkan device created — a real flush would fault");
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
VulkanBuffer<float, true> buf;
|
|
|
|
|
// Coherent type chosen (even though no COHERENT bit was requested):
|
|
|
|
|
// both flush paths must early-return without touching Vulkan.
|
|
|
|
|
buf.memoryPropertyFlagsChosen = HOST_VISIBLE | HOST_COHERENT;
|
|
|
|
|
buf.FlushDevice();
|
|
|
|
|
buf.FlushHost();
|
|
|
|
|
Check(true, "FlushDevice/FlushHost skip the Vulkan call on coherent memory");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
// A non-coherent type would require the flush — verify the recorded
|
|
|
|
|
// flags reflect the gate's decision input rather than the request. The
|
|
|
|
|
// bit test is the exact condition FlushDevice/FlushHost branch on.
|
|
|
|
|
VulkanBuffer<float, true> buf;
|
|
|
|
|
buf.memoryPropertyFlagsChosen = HOST_VISIBLE | DEVICE_LOCAL;
|
|
|
|
|
Check(!(buf.memoryPropertyFlagsChosen & HOST_COHERENT),
|
|
|
|
|
"non-coherent chosen type is not gated as coherent (flush still required)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (failures != 0) {
|
|
|
|
|
std::println("{} check(s) failed", failures);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
std::println("all checks passed");
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
}
|