Merge remote-tracking branch 'origin/master' into claude/issue-61

# Conflicts:
#	interfaces/Crafter.Graphics-Device.cppm
#	interfaces/Crafter.Graphics-VulkanBuffer.cppm
#	project.cpp
This commit is contained in:
catbot 2026-06-16 18:33:08 +00:00
commit 1f12f074b1
11 changed files with 860 additions and 9 deletions

View file

@ -169,6 +169,27 @@ export namespace Crafter {
// creation; defaults to 1 so the rounding math is well-defined before then.
inline static VkDeviceSize nonCoherentAtomSize = 1;
// Core physical-device properties, captured once at Initialize from the
// VkPhysicalDeviceProperties2 query. Kept because the pipeline-cache
// persistence path needs vendorID / deviceID / pipelineCacheUUID to
// decide whether an on-disk blob was written by this exact device.
inline static VkPhysicalDeviceProperties deviceProperties = {};
// One process-wide pipeline cache fed to every vkCreate*Pipelines call
// (compute UI/user shaders + RT pipelines). Pipeline compilation is a
// one-time cold-start cost, not a per-frame one; a single shared cache
// lets the driver reuse compiled shader binaries across the several
// pipelines built at startup and — once persisted to disk (see
// LoadPipelineCache / SavePipelineCache) — across runs. VK_NULL_HANDLE
// until LoadPipelineCache runs; passing VK_NULL_HANDLE to a create call
// is valid and simply means "no cache", so the create sites need no
// null check.
inline static VkPipelineCache pipelineCache = VK_NULL_HANDLE;
// Where the serialized cache lives. Relative to the working directory,
// matching the gpu_crash_dump-* convention. Set before Initialize to
// relocate it.
inline static std::filesystem::path pipelineCachePath = "pipeline_cache.bin";
inline static VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptorHeapProperties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT
};
@ -207,6 +228,23 @@ export namespace Crafter {
// ComputeShader read the offset off the pipeline they record.
static void CheckVkResult(VkResult result);
// ─── Pipeline cache persistence (issue #69) ─────────────────────
// LoadPipelineCache creates `pipelineCache`, seeding it from
// pipelineCachePath when the file exists *and* its header matches this
// device (PipelineCacheDataCompatible) — a stale or foreign blob is
// discarded so the driver never rejects it. Called once from
// Initialize. SavePipelineCache writes the driver's current cache blob
// back out; Initialize registers it with std::atexit so the cache is
// serialized at process shutdown without any explicit teardown call.
static void LoadPipelineCache();
static void SavePipelineCache();
// True when `data` is a VkPipelineCache blob whose header was written by
// a device with the same vendorID / deviceID / pipelineCacheUUID as
// `deviceProperties`. Pure logic over the standard 32-byte cache header,
// so it is driven directly by the PipelineCacheValidation test with no
// GPU device. A blob too short to hold the header is incompatible.
static bool PipelineCacheDataCompatible(std::span<const std::byte> data);
// Selects a memory type index from typeBits that satisfies `required`.
// When `preferred` bits are also given, a type satisfying both is
// chosen first; if none exists we fall back to required-only rather

View file

@ -82,7 +82,7 @@ export namespace Crafter {
.layout = VK_NULL_HANDLE
};
Device::CheckVkResult(Device::vkCreateRayTracingPipelinesKHR(Device::device, {}, {}, 1, &rtPipelineInfo, nullptr, &pipeline));
Device::CheckVkResult(Device::vkCreateRayTracingPipelinesKHR(Device::device, {}, Device::pipelineCache, 1, &rtPipelineInfo, nullptr, &pipeline));
std::size_t dataSize = Device::rayTracingProperties.shaderGroupHandleSize * rtPipelineInfo.groupCount;
shaderHandles.resize(dataSize);

View file

@ -65,6 +65,12 @@ namespace Crafter {
// COHERENT bit on a coherent type (and vice versa), so the flush/
// invalidate paths gate on this recorded value, never on the request.
VkMemoryPropertyFlags memoryPropertyFlagsChosen = 0;
// Byte capacity the buffer was created with, and the usage flags it was
// created with. Resize reuses the allocation in place when a new
// request still fits within `capacity` and these immutable-at-create
// properties match, avoiding a destroy+reallocate.
std::uint32_t capacity = 0;
VkBufferUsageFlags2 usageFlagsCreated = 0;
};
export template<typename T>
@ -92,6 +98,8 @@ namespace Crafter {
// available on every device — see Device::GetMemoryType.
void Create(VkBufferUsageFlags2 usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, std::uint32_t count, VkMemoryPropertyFlags preferredPropertyFlags = 0) {
size = count * sizeof(T);
capacity = size;
usageFlagsCreated = usageFlags;
// Carry usage in the maintenance5 flags2 chain so 64-bit bits
// (e.g. VK_BUFFER_USAGE_2_MEMORY_DECOMPRESSION_BIT_EXT, bit 35)
@ -148,6 +156,22 @@ namespace Crafter {
}
void Resize(VkBufferUsageFlags2 usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, std::uint32_t count, VkMemoryPropertyFlags preferredPropertyFlags = 0) {
// Reuse the existing allocation in place when the request still fits
// and the fixed-at-create properties match: usage flags are
// immutable after creation, and the memory type already chosen must
// still satisfy the required property flags. preferredPropertyFlags
// is a best-effort perf hint and does not affect correctness, so it
// is intentionally not part of the guard. The buffer handle (and its
// device address / mapped pointer) is preserved, only `size` shrinks
// to the new logical extent.
std::uint32_t requestedSize = count * sizeof(T);
if(buffer != VK_NULL_HANDLE
&& requestedSize <= capacity
&& usageFlags == usageFlagsCreated
&& (memoryPropertyFlagsChosen & memoryPropertyFlags) == memoryPropertyFlags) {
size = requestedSize;
return;
}
if(buffer != VK_NULL_HANDLE) {
Clear();
}
@ -275,6 +299,8 @@ namespace Crafter {
memory = other.memory;
size = other.size;
mappedSize = other.mappedSize;
capacity = other.capacity;
usageFlagsCreated = other.usageFlagsCreated;
memoryPropertyFlagsChosen = other.memoryPropertyFlagsChosen;
other.buffer = VK_NULL_HANDLE;
address = other.address;