feat: windows on their own clock, deferred deletion for every handle owner, staged re-uploads, and RTPass push data
Window - updateInterval runs Update on the window's own clock while the compositor sends no frame callbacks (hidden, minimized): the Wayland loop is a poll() loop, and the Win32 loop sleeps until the next update. Both read the interval each pass. - SetPresentMode (FIFO, Mailbox, Immediate). A mode the surface lacks, or whose swapchain has other than numFrames images (Mesa's Mailbox has four), falls back to FIFO until the mode is set again. - AddPollFd / RemovePollFd wake the Wayland loop on the app's descriptors. - The swapchain has TRANSFER_DST where the surface allows it. - CaptureNextFrame replaces SaveFrame: it records the copy in the frame, before the present, where SaveFrame read an image already presented. A frame unwound past before the write still frees the readback buffer. - Inter-pass barriers on the swapchain image carry the access masks of the stages the passes use. Deferred deletion (issue #101) - VulkanBuffer, Mesh, VulkanShader, ComputeShader and PipelineRTVulkan hand their handles to the deletion queue from their destructors and moves, as a frame in flight may still use them; a static owner goes after the driver has torn down. VulkanShader and PipelineRTVulkan move and can't be copied; re-Load and re-Init queue the old pipeline. - The queue and its mutex are never destroyed, and the queue takes handles from any thread; Device::frameCounter is atomic. Both the queue and the staging rings count frames as one window renders them (documented). - DrainDeletions also lets every buffer's staging ring start over: without a Window nothing else reclaims either, so an app without one calls it after waiting for its work. Meshes and buffers - A Mesh rebuild queues the old acceleration structure and builds into the same storage after a barrier, so the BLAS address stays put when it fits. The refit and rebuild barriers cover compute ray queries too. - RTBuildOptions::noDuplicateAnyHit. Mesh::geometryFlags replaces opaque, and the refit and rebuild fallbacks keep the flags. - A re-upload into a buffer a frame in flight may read (UploadDeviceLocal of the same size, UploadDeviceLocalRange) copies on the GPU, after those reads, through a per-frame staging ring; on ReBAR it used to write the buffer in place. Empty uploads record nothing. - A compressed Mesh::Build that keeps its buffers decompresses into them after the reads of frames in flight, and the hit shaders and ray queries wait for the decompress as well as the BLAS build. Its vertex buffer takes a ranged refit's copies. - Mesh move-assigns (queueing the BLAS it replaces), so it can be replaced in place or erased from a vector. - VulkanBuffer::Create asks for a device address only for buffers with SHADER_DEVICE_ADDRESS usage, as vkGetBufferDeviceAddress requires; the others have address 0. UIRenderer::RegisterBuffer refuses those. - Build inputs are synchronized as shader reads. Other - RTPass pushes the app's own push data and traces at an explicit size. - DescriptorHeapVulkan hands freed slot ranges out again (they leaked), and keeps its free lists sorted. - ImageVulkan's mip chain clamps each level to 1 texel: a non-square image's shorter side went to 0. - VulkanShader throws on a missing file instead of failing in bad_alloc. - CRAFTER_GRAPHICS_DEVICE picks the GPU by its number among the distinct GPUs Vulkan lists; anything but such a number is an error. - The debug messenger prints info messages only with CRAFTER_GRAPHICS_VALIDATION set. - Windows: the WinRT gamepad code moved to a C file (mingw-w64's WinRT headers don't compile as C++), -lcombase dropped, a reference leak fixed. - project.cpp honours --local for Crafter.Event, Math and Asset, as Asset does for Math, and builds every test through one helper, projectArgs included. - Tests: DebugMessengerSeverity, DeletionQueueThreads, DeviceSelection, HeapSlotReuse, MeshLifetime, MipChainNonSquare, PipelineLifetime, RTPassPushData, SwapchainTransferDst, UploadInFlight, VulkanBufferAddress, VulkanShaderLifetime, WindowUpdateClock; others extended. Breaking: SaveFrame is CaptureNextFrame; Mesh::opaque is geometryFlags; a buffer without SHADER_DEVICE_ADDRESS usage has no address; frameCounter is std::atomic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
449d5da553
commit
079b6b0e1f
47 changed files with 3572 additions and 1430 deletions
|
|
@ -29,6 +29,20 @@ namespace {
|
|||
constexpr VkBufferUsageFlags2 kIndexUsageBase =
|
||||
kVertexUsageBase | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
|
||||
// Where the geometry buffers are read: the BLAS build, and hit shaders or
|
||||
// compute ray queries fetching vertices by address. An upload is ordered
|
||||
// before these reads, and a re-upload after the earlier ones, which frames
|
||||
// still in flight may be doing. A build reads its inputs as a shader read.
|
||||
constexpr VkPipelineStageFlags kGeometryReaders =
|
||||
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR
|
||||
| VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR
|
||||
| VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
||||
// The same, for synchronization2's barriers.
|
||||
constexpr VkPipelineStageFlags2 kGeometryReaders2 =
|
||||
VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR
|
||||
| VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT_KHR
|
||||
| VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT;
|
||||
|
||||
// Translate the portable RTBuildOptions to Vulkan build flags. The two
|
||||
// preferences are mutually exclusive; ALLOW_UPDATE is layered on top so
|
||||
// a later in-place refit (UPDATE mode) is permitted.
|
||||
|
|
@ -42,6 +56,15 @@ namespace {
|
|||
return flags;
|
||||
}
|
||||
|
||||
// The geometry's flags for a build: opaque or not, and whether any-hit
|
||||
// may run more than once per primitive.
|
||||
VkGeometryFlagsKHR GeometryFlags(bool opaque, const RTBuildOptions& options) {
|
||||
VkGeometryFlagsKHR flags = opaque ? static_cast<VkGeometryFlagsKHR>(VK_GEOMETRY_OPAQUE_BIT_KHR) : VkGeometryFlagsKHR{};
|
||||
if (options.noDuplicateAnyHit)
|
||||
flags |= VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR;
|
||||
return flags;
|
||||
}
|
||||
|
||||
// Shared BLAS sizing / creation / build-record tail — geometry-type
|
||||
// agnostic; both the triangle and the AABB (procedural) paths feed
|
||||
// their single geometry through here. `update` selects an in-place
|
||||
|
|
@ -61,11 +84,38 @@ namespace {
|
|||
.pGeometries = &blasGeometry,
|
||||
};
|
||||
|
||||
// A refit writes the BLAS in place, and a rebuild writes the same
|
||||
// storage and scratch whenever the new BLAS fits. Earlier work, in
|
||||
// this command buffer or in a frame still in flight, may read the BLAS
|
||||
// (a TLAS build, a trace, a compute ray query) or write the scratch (a
|
||||
// build), so order that before this build. A first build touches
|
||||
// nothing earlier.
|
||||
if (self.accelerationStructure != VK_NULL_HANDLE) {
|
||||
VkMemoryBarrier asBarrier {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,
|
||||
.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR
|
||||
};
|
||||
vkCmdPipelineBarrier(cmd,
|
||||
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
||||
0, 1, &asBarrier, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
if (!update) {
|
||||
// Fresh build: query sizes, (re)allocate scratch + AS storage,
|
||||
// create a new AS handle. The scratch buffer is sized for the
|
||||
// build (always >= the update scratch requirement), so it also
|
||||
// covers later in-place refits without resizing.
|
||||
//
|
||||
// On a rebuild the old handle may still be referenced by this
|
||||
// command buffer and by frames in flight, so it goes to the
|
||||
// deletion queue instead of being destroyed here. It is queued
|
||||
// before the Resizes below, which queue the old storage when the
|
||||
// new BLAS doesn't fit, so it is freed before the buffer it lives in.
|
||||
Device::EnqueueDeletion(self.accelerationStructure);
|
||||
self.accelerationStructure = VK_NULL_HANDLE;
|
||||
|
||||
VkAccelerationStructureBuildSizesInfoKHR blasBuildSizes = {
|
||||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR
|
||||
};
|
||||
|
|
@ -89,13 +139,6 @@ namespace {
|
|||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
blasBuildSizes.accelerationStructureSize);
|
||||
|
||||
// Re-Build of an existing Mesh: destroy the stale handle first
|
||||
// so the previous AS storage isn't leaked.
|
||||
if (self.accelerationStructure != VK_NULL_HANDLE) {
|
||||
Device::vkDestroyAccelerationStructureKHR(Device::device, self.accelerationStructure, nullptr);
|
||||
self.accelerationStructure = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkAccelerationStructureCreateInfoKHR blasCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR,
|
||||
.buffer = self.blasBuffer.buffer,
|
||||
|
|
@ -110,19 +153,6 @@ namespace {
|
|||
.accelerationStructure = self.accelerationStructure
|
||||
};
|
||||
self.blasAddr = Device::vkGetAccelerationStructureDeviceAddressKHR(Device::device, &addrInfo);
|
||||
} else {
|
||||
// In-place refit: the AS may have been read by a prior TLAS
|
||||
// build / trace, so order that read before the build overwrites
|
||||
// it. Scratch reuse needs the same write-after-read guard.
|
||||
VkMemoryBarrier asBarrier {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR,
|
||||
.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR
|
||||
};
|
||||
vkCmdPipelineBarrier(cmd,
|
||||
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR,
|
||||
VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
||||
0, 1, &asBarrier, 0, nullptr, 0, nullptr);
|
||||
}
|
||||
|
||||
blasBuildGeometryInfo.scratchData.deviceAddress = self.scratchBuffer.address;
|
||||
|
|
@ -183,13 +213,56 @@ namespace {
|
|||
.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR,
|
||||
.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR,
|
||||
.geometry = geometryData,
|
||||
.flags = VK_GEOMETRY_OPAQUE_BIT_KHR
|
||||
.flags = self.geometryFlags
|
||||
};
|
||||
|
||||
RecordBLASBuildFromGeometry(self, blasGeometry, indexCount / 3, flags, update, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
Mesh::Mesh(Mesh&& other)
|
||||
: scratchBuffer(std::move(other.scratchBuffer))
|
||||
, blasBuffer(std::move(other.blasBuffer))
|
||||
, vertexBuffer(std::move(other.vertexBuffer))
|
||||
, indexBuffer(std::move(other.indexBuffer))
|
||||
, aabbBuffer(std::move(other.aabbBuffer))
|
||||
, compressedStaging(std::move(other.compressedStaging))
|
||||
, accelerationStructure(std::exchange(other.accelerationStructure, VK_NULL_HANDLE))
|
||||
, blasAddr(std::exchange(other.blasAddr, 0))
|
||||
, geometryFlags(other.geometryFlags)
|
||||
, buildFlags(other.buildFlags)
|
||||
, builtPrimitiveCount(other.builtPrimitiveCount)
|
||||
, builtInputCount(other.builtInputCount)
|
||||
, allowUpdate(other.allowUpdate) {
|
||||
}
|
||||
|
||||
Mesh& Mesh::operator=(Mesh&& other) {
|
||||
if (this == &other) return *this;
|
||||
// Ahead of blasBuffer, which it lives in and which queues its own as it
|
||||
// takes other's over.
|
||||
Device::EnqueueDeletion(accelerationStructure);
|
||||
scratchBuffer = std::move(other.scratchBuffer);
|
||||
blasBuffer = std::move(other.blasBuffer);
|
||||
vertexBuffer = std::move(other.vertexBuffer);
|
||||
indexBuffer = std::move(other.indexBuffer);
|
||||
aabbBuffer = std::move(other.aabbBuffer);
|
||||
compressedStaging = std::move(other.compressedStaging);
|
||||
accelerationStructure = std::exchange(other.accelerationStructure, VK_NULL_HANDLE);
|
||||
blasAddr = std::exchange(other.blasAddr, 0);
|
||||
geometryFlags = other.geometryFlags;
|
||||
buildFlags = other.buildFlags;
|
||||
builtPrimitiveCount = other.builtPrimitiveCount;
|
||||
builtInputCount = other.builtInputCount;
|
||||
allowUpdate = other.allowUpdate;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Mesh::~Mesh() {
|
||||
// The body runs before the members' destructors, so the handle is queued
|
||||
// ahead of blasBuffer, which it lives in.
|
||||
Device::EnqueueDeletion(accelerationStructure);
|
||||
}
|
||||
|
||||
void Mesh::Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd, RTBuildOptions options) {
|
||||
// Every Build* records an acceleration-structure build; without ray
|
||||
// tracing those entry points are not loaded. Fail with the reason.
|
||||
|
|
@ -199,10 +272,11 @@ void Mesh::Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32
|
|||
// upload before the BLAS build reads them. Replaces the previous
|
||||
// HOST_VISIBLE allocation that the build (and any hit-shader fetch) would
|
||||
// read over PCIe every frame (#73).
|
||||
vertexBuffer.UploadDeviceLocal(kVertexUsageBase, verticies.data(), static_cast<std::uint32_t>(verticies.size()), cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
||||
indexBuffer.UploadDeviceLocal(kIndexUsageBase, indicies.data(), static_cast<std::uint32_t>(indicies.size()), cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
||||
vertexBuffer.UploadDeviceLocal(kVertexUsageBase, verticies.data(), static_cast<std::uint32_t>(verticies.size()), cmd, VK_ACCESS_SHADER_READ_BIT, kGeometryReaders);
|
||||
indexBuffer.UploadDeviceLocal(kIndexUsageBase, indicies.data(), static_cast<std::uint32_t>(indicies.size()), cmd, VK_ACCESS_SHADER_READ_BIT, kGeometryReaders);
|
||||
|
||||
allowUpdate = options.allowUpdate;
|
||||
geometryFlags = GeometryFlags(/*opaque*/ true, options);
|
||||
builtInputCount = static_cast<std::uint32_t>(verticies.size());
|
||||
RecordBLASBuild(*this, static_cast<std::uint32_t>(verticies.size()), static_cast<std::uint32_t>(indicies.size()), BlasFlags(options), /*update*/ false, cmd);
|
||||
}
|
||||
|
|
@ -231,9 +305,12 @@ void Mesh::Build(const CompressedMeshAsset& asset, VkCommandBuffer cmd, RTBuildO
|
|||
// The GPU decompressor writes vertex/index directly (the build input is
|
||||
// never host-written on this path), so they want pure DEVICE_LOCAL — no
|
||||
// host visibility, no map. This is where the BLAS build and any hit-shader
|
||||
// fetch then read them from (#73).
|
||||
// fetch then read them from (#73). A ranged Refit copies the moved
|
||||
// vertices in, hence TRANSFER_DST.
|
||||
const VkBuffer previousVertices = vertexBuffer.buffer;
|
||||
const VkBuffer previousIndices = indexBuffer.buffer;
|
||||
vertexBuffer.Resize(
|
||||
kVertexUsageBase | VK_BUFFER_USAGE_2_MEMORY_DECOMPRESSION_BIT_EXT,
|
||||
kVertexUsageBase | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_2_MEMORY_DECOMPRESSION_BIT_EXT,
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
|
||||
asset.vertexCount);
|
||||
indexBuffer.Resize(
|
||||
|
|
@ -269,11 +346,33 @@ void Mesh::Build(const CompressedMeshAsset& asset, VkCommandBuffer cmd, RTBuildO
|
|||
regions);
|
||||
}
|
||||
|
||||
// A rebuild that keeps its buffers (Resize keeps an allocation that fits)
|
||||
// decompresses into what frames in flight may still be reading: the
|
||||
// decompress waits for those reads. Fresh buffers nothing reads yet.
|
||||
if ((vertexBuffer.buffer != VK_NULL_HANDLE && vertexBuffer.buffer == previousVertices)
|
||||
|| (indexBuffer.buffer != VK_NULL_HANDLE && indexBuffer.buffer == previousIndices)) {
|
||||
const VkMemoryBarrier2 readsDone {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
|
||||
.srcStageMask = kGeometryReaders2,
|
||||
.srcAccessMask = 0,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_MEMORY_DECOMPRESSION_BIT_EXT,
|
||||
.dstAccessMask = VK_ACCESS_2_MEMORY_DECOMPRESSION_WRITE_BIT_EXT,
|
||||
};
|
||||
const VkDependencyInfo beforeDecompress {
|
||||
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
||||
.memoryBarrierCount = 1,
|
||||
.pMemoryBarriers = &readsDone,
|
||||
};
|
||||
vkCmdPipelineBarrier2(cmd, &beforeDecompress);
|
||||
}
|
||||
|
||||
// Then the BLAS build reads the decompressed geometry as a shader read,
|
||||
// as do the hit shaders and ray queries that fetch it by address.
|
||||
Decompress::DecompressOnGPU(
|
||||
cmd,
|
||||
regions,
|
||||
VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR,
|
||||
VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR);
|
||||
kGeometryReaders2,
|
||||
VK_ACCESS_2_SHADER_READ_BIT);
|
||||
|
||||
// The compressed staging is only read by the decompress recorded above; the
|
||||
// subsequent BLAS build reads the decompressed vertex/index buffers, never
|
||||
|
|
@ -286,6 +385,7 @@ void Mesh::Build(const CompressedMeshAsset& asset, VkCommandBuffer cmd, RTBuildO
|
|||
compressedStaging.DeferredClear();
|
||||
|
||||
allowUpdate = options.allowUpdate;
|
||||
geometryFlags = GeometryFlags(/*opaque*/ true, options);
|
||||
builtInputCount = asset.vertexCount;
|
||||
RecordBLASBuild(*this, asset.vertexCount, asset.indexCount, BlasFlags(options), /*update*/ false, cmd);
|
||||
}
|
||||
|
|
@ -296,7 +396,7 @@ namespace {
|
|||
// Geometry-source agnostic: the host-upload path (RecordProceduralBuild)
|
||||
// points this at self.aabbBuffer, while the device-buffer overloads
|
||||
// point it straight at the producer's buffer — no copy involved either
|
||||
// way. The geometry's opaque bit is read from self.opaque so an UPDATE
|
||||
// way. The geometry's flags are read from self.geometryFlags so an UPDATE
|
||||
// keeps the exact geometry description of the original build.
|
||||
void RecordProceduralBuildFromAddress(Mesh& self, VkDeviceAddress aabbAddress, std::uint32_t count, std::uint32_t stride, VkBuildAccelerationStructureFlagsKHR flags, bool update, VkCommandBuffer cmd) {
|
||||
VkAccelerationStructureGeometryAabbsDataKHR aabbsData {
|
||||
|
|
@ -312,7 +412,7 @@ namespace {
|
|||
.geometry = geometryData,
|
||||
// Non-opaque by default (mirrors the WebGPU path) so any-hit
|
||||
// shaders run for procedural geometry unless the caller opts out.
|
||||
.flags = self.opaque ? static_cast<VkGeometryFlagsKHR>(VK_GEOMETRY_OPAQUE_BIT_KHR) : VkGeometryFlagsKHR{}
|
||||
.flags = self.geometryFlags
|
||||
};
|
||||
|
||||
RecordBLASBuildFromGeometry(self, blasGeometry, count, flags, update, cmd);
|
||||
|
|
@ -330,7 +430,7 @@ namespace {
|
|||
// re-uploads into the same same-sized buffer: Resize reuses the existing
|
||||
// allocation (count unchanged), so the device address — and the geometry
|
||||
// it feeds — stays stable for an in-place UPDATE.
|
||||
self.aabbBuffer.UploadDeviceLocal(kVertexUsageBase, aabbs.data(), static_cast<std::uint32_t>(aabbs.size()), cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
||||
self.aabbBuffer.UploadDeviceLocal(kVertexUsageBase, aabbs.data(), static_cast<std::uint32_t>(aabbs.size()), cmd, VK_ACCESS_SHADER_READ_BIT, kGeometryReaders);
|
||||
|
||||
RecordProceduralBuildFromAddress(self, self.aabbBuffer.address, static_cast<std::uint32_t>(aabbs.size()), sizeof(RTAabb), flags, update, cmd);
|
||||
}
|
||||
|
|
@ -338,7 +438,7 @@ namespace {
|
|||
|
||||
void Mesh::BuildProcedural(std::span<const RTAabb> aabbs, bool opaque, VkCommandBuffer cmd, RTBuildOptions options) {
|
||||
Device::RequireRayTracing();
|
||||
this->opaque = opaque;
|
||||
geometryFlags = GeometryFlags(opaque, options);
|
||||
allowUpdate = options.allowUpdate;
|
||||
builtInputCount = static_cast<std::uint32_t>(aabbs.size());
|
||||
|
||||
|
|
@ -353,7 +453,7 @@ void Mesh::BuildProcedural(VkDeviceAddress aabbAddress, std::uint32_t count, boo
|
|||
// The caller owns that buffer's lifetime + usage flags
|
||||
// (ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY + SHADER_DEVICE_ADDRESS)
|
||||
// and must barrier the producing writes before this build reads them.
|
||||
this->opaque = opaque;
|
||||
geometryFlags = GeometryFlags(opaque, options);
|
||||
allowUpdate = options.allowUpdate;
|
||||
builtInputCount = count;
|
||||
|
||||
|
|
@ -385,6 +485,7 @@ void Mesh::Refit(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32
|
|||
.preference = (buildFlags & VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR)
|
||||
? RTBuildPreference::FastBuild : RTBuildPreference::FastTrace,
|
||||
.allowUpdate = allowUpdate,
|
||||
.noDuplicateAnyHit = (geometryFlags & VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR) != 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -402,15 +503,14 @@ void Mesh::Refit(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32
|
|||
// the rest of the vertex buffer already holds last refit's positions, so a
|
||||
// ranged upload patches just the vertices that moved (#119). The window is
|
||||
// clamped to the array — a caller window past the end would otherwise read
|
||||
// out of bounds / overflow the copy. UploadDeviceLocalRange writes into the
|
||||
// out of bounds / overflow the copy. UploadDeviceLocalRange copies into the
|
||||
// existing same-sized allocation (no Resize), so the address the UPDATE
|
||||
// reads stays stable, then barriers just that sub-range before the build. On
|
||||
// the staged path this re-stages only the dirty slice (the deforming-mesh
|
||||
// fallback, #73).
|
||||
// reads stays stable, after the reads of the frames still in flight and
|
||||
// before the build, staging only the dirty slice.
|
||||
std::uint32_t offset = std::min(dirtyVertexOffset, static_cast<std::uint32_t>(verticies.size()));
|
||||
std::uint32_t count = std::min(dirtyVertexCount, static_cast<std::uint32_t>(verticies.size()) - offset);
|
||||
if (count != 0) {
|
||||
vertexBuffer.UploadDeviceLocalRange(verticies.data() + offset, offset, count, cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
||||
vertexBuffer.UploadDeviceLocalRange(verticies.data() + offset, offset, count, cmd, VK_ACCESS_SHADER_READ_BIT, kGeometryReaders);
|
||||
}
|
||||
|
||||
RecordBLASBuild(*this, static_cast<std::uint32_t>(verticies.size()), static_cast<std::uint32_t>(indicies.size()), buildFlags, /*update*/ true, cmd);
|
||||
|
|
|
|||
Loading…
Reference in a new issue