/* Crafter®.Graphics Copyright (C) 2026 Catcrafts® catcrafts.net This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3.0 as published by the Free Software Foundation; This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // Regression test for the ranged FlushDevice (UI descriptor registration): // WriteBufferDescriptor / WriteSampledImageDescriptor / RegisterSampler used to // FlushDevice() the whole multi-KB descriptor heap after writing one few-byte // descriptor. They now flush only the written byte range via // VulkanBuffer::FlushDevice(offset, bytes), which rounds the range outward to // VkPhysicalDeviceLimits::nonCoherentAtomSize (vkFlushMappedMemoryRanges rejects // an unaligned sub-buffer offset/size). // // The rounding lives in AlignMappedFlushRange — pure math with no Vulkan // dependency — so this drives it directly with no GPU device, and also confirms // the ranged FlushDevice keeps the issue-#60 coherent-memory gate (a real flush // would fault with Device::device == VK_NULL_HANDLE). #include #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 HOST_VISIBLE = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; constexpr auto HOST_COHERENT = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; constexpr auto DEVICE_LOCAL = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; } // namespace int main() { Check(Device::device == VK_NULL_HANDLE, "no Vulkan device created — a real flush would fault"); // ── AlignMappedFlushRange: the spec demands offset and size be multiples // of nonCoherentAtomSize (atom). Start rounds down, end rounds up. ── { // A descriptor wholly inside one atom expands to cover that atom. auto r = AlignMappedFlushRange(/*offset*/ 80, /*bytes*/ 16, /*atom*/ 64, /*mappingSize*/ 4096); Check(r.offset == 64 && r.size == 64, "sub-atom range rounds out to the enclosing atom [64,128)"); Check(r.offset % 64 == 0 && r.size % 64 == 0, "rounded offset and size are atom multiples"); } { // A range straddling an atom boundary expands both ways. auto r = AlignMappedFlushRange(/*offset*/ 60, /*bytes*/ 8, /*atom*/ 64, /*mappingSize*/ 4096); Check(r.offset == 0 && r.size == 128, "straddling range rounds out to [0,128)"); } { // Already-aligned range is left exactly as is. auto r = AlignMappedFlushRange(/*offset*/ 128, /*bytes*/ 64, /*atom*/ 64, /*mappingSize*/ 4096); Check(r.offset == 128 && r.size == 64, "already atom-aligned range is unchanged"); } { // atom == 1 (no coherence granularity): range passes through verbatim. auto r = AlignMappedFlushRange(/*offset*/ 80, /*bytes*/ 16, /*atom*/ 1, /*mappingSize*/ 4096); Check(r.offset == 80 && r.size == 16, "atom==1 leaves the range exact"); } { // Near the end of a non-atom-aligned mapping: the rounded-up end is // clamped to mappingSize so offset+size == mapping size (the spec's // exception that permits a non-atom-multiple size). auto r = AlignMappedFlushRange(/*offset*/ 4030, /*bytes*/ 8, /*atom*/ 64, /*mappingSize*/ 4040); Check(r.offset == 3968 && r.offset + r.size == 4040, "tail range clamps its end to the mapping size"); Check(r.offset % 64 == 0, "clamped range still has an atom-aligned offset"); } // ── Ranged FlushDevice keeps the coherent-memory gate (issue #60). ── { VulkanBuffer buf; buf.memoryPropertyFlagsChosen = HOST_VISIBLE | HOST_COHERENT; buf.mappedSize = 4096; buf.FlushDevice(/*offset*/ 80, /*bytes*/ 16); // must early-return Check(true, "ranged FlushDevice skips the Vulkan call on coherent memory"); } { VulkanBuffer buf; buf.memoryPropertyFlagsChosen = HOST_VISIBLE | DEVICE_LOCAL; Check(!(buf.memoryPropertyFlagsChosen & HOST_COHERENT), "non-coherent chosen type is not gated as coherent (ranged flush still required)"); } if (failures != 0) { std::println("{} check(s) failed", failures); return EXIT_FAILURE; } std::println("all checks passed"); return EXIT_SUCCESS; }