diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/VulkanSplatRenderer.cpp b/packages/splatkit-android/src/main/cpp/rendering/vulkan/VulkanSplatRenderer.cpp index ea2d948..4c44216 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/VulkanSplatRenderer.cpp +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/VulkanSplatRenderer.cpp @@ -4,7 +4,6 @@ #include #include "engine/TraceSection.h" -#include "rendering/vulkan/sort/VulkanRadixSort.h" #include "splatkit/Log.h" @@ -118,6 +117,16 @@ std::optional VulkanSplatRenderer::world() const { return GpuWorldInfo{world_->count, world_->shDegree}; } +// Callers have waited for the GPU to finish with the world being replaced. +void VulkanSplatRenderer::adoptWorld(std::unique_ptr world, + std::unique_ptr compute) { + world_ = std::move(world); + compute_ = std::move(compute); + applyPolicyToCompute(); + splats_->bindWorld(*world_); + worldFrameCompletion_.reset(); +} + bool VulkanSplatRenderer::uploadWorld(const splat::SplatCloud& cloud, int maxShDegree) { const TraceSection section(trace::kUploadWorld); if (!splats_ || cloud.count() > std::numeric_limits::max()) return false; @@ -131,17 +140,12 @@ bool VulkanSplatRenderer::uploadWorld(const splat::SplatCloud& cloud, int maxShD auto world = VulkanWorld::upload(ctx_, cloud, maxShDegree, false); if (!world) return false; ctx_.waitIdle(); // the previous world may still be in flight - world_ = std::move(world); - compute_ = std::move(compute.value()); - applyPolicyToCompute(); - splats_->bindWorld(*world_); - worldFrameCompletion_.reset(); + adoptWorld(std::move(world), std::move(compute.value())); return true; } bool VulkanSplatRenderer::sortsOnGpu() const { - return VulkanVisibilityPass::queryCapabilities(ctx_).supported && - VulkanRadixSort::queryCapabilities(ctx_).supported; + return VulkanFrameCompute::supported(ctx_); } bool VulkanSplatRenderer::selectsLodOnGpu() const { @@ -161,11 +165,7 @@ bool VulkanSplatRenderer::uploadLodWorld(const splat::LodTree& tree, int maxShDe } auto world = VulkanWorld::upload(ctx_, tree.nodes, maxShDegree, false); if (!world) return false; - world_ = std::move(world); - compute_ = std::move(compute.value()); - applyPolicyToCompute(); - splats_->bindWorld(*world_); - worldFrameCompletion_.reset(); + adoptWorld(std::move(world), std::move(compute.value())); return true; } @@ -181,11 +181,7 @@ bool VulkanSplatRenderer::createSlab(uint32_t capacity, int shDegree) { auto world = VulkanWorld::createSlab(ctx_, capacity, shDegree, false); if (!world) return false; ctx_.waitIdle(); // the previous world may still be in flight - world_ = std::move(world); - compute_ = std::move(compute.value()); - applyPolicyToCompute(); - splats_->bindWorld(*world_); - worldFrameCompletion_.reset(); + adoptWorld(std::move(world), std::move(compute.value())); return true; } diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/VulkanSplatRenderer.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/VulkanSplatRenderer.h index 2141866..14330c1 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/VulkanSplatRenderer.h +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/VulkanSplatRenderer.h @@ -85,6 +85,7 @@ class VulkanSplatRenderer final : public SplatRenderer { void rebuildPipelinesIfNeeded(); void rebuildSurface(VulkanFrameLoop::Status status); void applyPolicyToCompute(); + void adoptWorld(std::unique_ptr world, std::unique_ptr compute); VulkanContext& ctx_; VulkanFrameLoop& frameLoop_; diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/cull/VulkanVisibilityPass.cpp b/packages/splatkit-android/src/main/cpp/rendering/vulkan/cull/VulkanVisibilityPass.cpp index 9e4f220..5a946c9 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/cull/VulkanVisibilityPass.cpp +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/cull/VulkanVisibilityPass.cpp @@ -255,29 +255,29 @@ bool VulkanVisibilityPass::createDescriptors() { return false; VkDescriptorPoolSize sizes[2]{}; - sizes[0] = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, kSlots}; + sizes[0] = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, kFramesInFlight}; sizes[1] = {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - (kVisibilityStorageBindings + kPrepareBindings) * kSlots}; + (kVisibilityStorageBindings + kPrepareBindings) * kFramesInFlight}; VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; - poolInfo.maxSets = 2 * kSlots; + poolInfo.maxSets = 2 * kFramesInFlight; poolInfo.poolSizeCount = 2; poolInfo.pPoolSizes = sizes; if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool_) != VK_SUCCESS) return false; - std::array visibilityLayouts{}; - std::array prepareLayouts{}; + std::array visibilityLayouts{}; + std::array prepareLayouts{}; visibilityLayouts.fill(visibilitySetLayout_); prepareLayouts.fill(prepareSetLayout_); VkDescriptorSetAllocateInfo visibilityAlloc{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; visibilityAlloc.descriptorPool = descriptorPool_; - visibilityAlloc.descriptorSetCount = kSlots; + visibilityAlloc.descriptorSetCount = kFramesInFlight; visibilityAlloc.pSetLayouts = visibilityLayouts.data(); if (vkAllocateDescriptorSets(device, &visibilityAlloc, visibilitySets_.data()) != VK_SUCCESS) return false; VkDescriptorSetAllocateInfo prepareAlloc{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; prepareAlloc.descriptorPool = descriptorPool_; - prepareAlloc.descriptorSetCount = kSlots; + prepareAlloc.descriptorSetCount = kFramesInFlight; prepareAlloc.pSetLayouts = prepareLayouts.data(); return vkAllocateDescriptorSets(device, &prepareAlloc, prepareSets_.data()) == VK_SUCCESS; } @@ -299,10 +299,10 @@ bool VulkanVisibilityPass::createPipelines() { if (vkCreatePipelineLayout(device, &prepareLayoutInfo, nullptr, &prepareLayout_) != VK_SUCCESS) return false; - const ShaderModule visibilityModule(device, shaders::visibility_comp, - shaders::visibility_comp_size); - const ShaderModule prepareModule(device, shaders::prepare_indirect_comp, - shaders::prepare_indirect_comp_size); + const VulkanShaderModule visibilityModule(device, shaders::visibility_comp, + shaders::visibility_comp_size); + const VulkanShaderModule prepareModule(device, shaders::prepare_indirect_comp, + shaders::prepare_indirect_comp_size); if (!visibilityModule || !prepareModule) return false; VkPipelineShaderStageCreateInfo visibilityStage{ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; @@ -344,12 +344,12 @@ bool VulkanVisibilityPass::reserve(uint32_t capacity) { if (!dummy || !dummy->upload(zeros, sizeof(zeros))) return false; } - std::array, kSlots> indices; - std::array, kSlots> depthKeys; - std::array, kSlots> counts; - std::array, kSlots> indirect; - std::array, kSlots> status; - for (uint32_t slot = 0; slot < kSlots; ++slot) { + std::array, kFramesInFlight> indices; + std::array, kFramesInFlight> depthKeys; + std::array, kFramesInFlight> counts; + std::array, kFramesInFlight> indirect; + std::array, kFramesInFlight> status; + for (uint32_t slot = 0; slot < kFramesInFlight; ++slot) { indices[slot] = VulkanBuffer::deviceLocal( ctx_, indicesBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); depthKeys[slot] = VulkanBuffer::deviceLocal( @@ -380,7 +380,7 @@ bool VulkanVisibilityPass::reserve(uint32_t capacity) { } VulkanVisibilityPass::Output VulkanVisibilityPass::output(uint32_t slot) const { - if (slot >= kSlots || capacity_ == 0) return {}; + if (slot >= kFramesInFlight || capacity_ == 0) return {}; return {indices_[slot]->handle(), depthKeys_[slot]->handle(), counts_[slot]->handle(), indirect_[slot]->handle(), status_[slot]->handle()}; } @@ -436,7 +436,7 @@ void VulkanVisibilityPass::barrier(VkCommandBuffer cmd, VkPipelineStageFlags src bool VulkanVisibilityPass::encode(VkCommandBuffer cmd, uint32_t slot, const Input& input) const { const uint32_t bound = candidateBound(input); - if (cmd == VK_NULL_HANDLE || slot >= kSlots || capacity_ == 0 || + if (cmd == VK_NULL_HANDLE || slot >= kFramesInFlight || capacity_ == 0 || static_cast(input.mode) > static_cast(CandidateMode::ranges) || static_cast(input.keyBits) > static_cast(KeyBits::low16) || static_cast(input.keyOrder) > static_cast(KeyOrder::descending) || diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/cull/VulkanVisibilityPass.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/cull/VulkanVisibilityPass.h index a650e4f..ba800b4 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/cull/VulkanVisibilityPass.h +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/cull/VulkanVisibilityPass.h @@ -10,6 +10,8 @@ #include "rendering/vulkan/device/VulkanBuffer.h" #include "rendering/vulkan/device/VulkanContext.h" +#include "rendering/vulkan/layout/VulkanFrameSlots.h" +#include "rendering/vulkan/layout/VulkanSortKeys.h" #include "splat/core/Result.h" namespace splatkit { @@ -51,7 +53,6 @@ class VulkanVisibilityPass { const VulkanVisibilityCapabilities& capabilities() const { return capabilities_; } enum class CandidateMode : uint32_t { prefix, indices, ranges }; - enum class KeyBits : uint32_t { full32, low16 }; enum class KeyOrder : uint32_t { ascending, descending }; struct Range { uint32_t offset, count, prefixEnd, pad; @@ -101,7 +102,6 @@ class VulkanVisibilityPass { bool encode(VkCommandBuffer cmd, uint32_t slot, const Input& input) const; Output output(uint32_t slot) const; - static constexpr uint32_t kSlots = 2; static constexpr uint32_t kWorkgroupSize = 128; static constexpr uint32_t kMaxCapacity = 3000000; static constexpr uint32_t kOverflow = 1u; @@ -130,19 +130,19 @@ class VulkanVisibilityPass { VkDescriptorSetLayout visibilitySetLayout_ = VK_NULL_HANDLE; VkDescriptorSetLayout prepareSetLayout_ = VK_NULL_HANDLE; VkDescriptorPool descriptorPool_ = VK_NULL_HANDLE; - std::array visibilitySets_{}; - std::array prepareSets_{}; + std::array visibilitySets_{}; + std::array prepareSets_{}; VkPipelineLayout visibilityLayout_ = VK_NULL_HANDLE; VkPipelineLayout prepareLayout_ = VK_NULL_HANDLE; VkPipeline visibilityPipeline_ = VK_NULL_HANDLE; VkPipeline preparePipeline_ = VK_NULL_HANDLE; std::unique_ptr dummy_; // immutable zero words for inactive input descriptors - std::array, kSlots> indices_{}; - std::array, kSlots> depthKeys_{}; - std::array, kSlots> counts_{}; - std::array, kSlots> indirect_{}; - std::array, kSlots> status_{}; + std::array, kFramesInFlight> indices_{}; + std::array, kFramesInFlight> depthKeys_{}; + std::array, kFramesInFlight> counts_{}; + std::array, kFramesInFlight> indirect_{}; + std::array, kFramesInFlight> status_{}; }; } // namespace splatkit diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/device/VulkanBarriers.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/device/VulkanBarriers.h index 442797f..70b88de 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/device/VulkanBarriers.h +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/device/VulkanBarriers.h @@ -5,9 +5,9 @@ namespace splatkit { // One global memory barrier; the passes order whole buffers, never single ranges. -inline void memoryBarrier(VkCommandBuffer cmd, VkPipelineStageFlags srcStage, - VkAccessFlags srcAccess, VkPipelineStageFlags dstStage, - VkAccessFlags dstAccess) { +inline void vulkanMemoryBarrier(VkCommandBuffer cmd, VkPipelineStageFlags srcStage, + VkAccessFlags srcAccess, VkPipelineStageFlags dstStage, + VkAccessFlags dstAccess) { VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; barrier.srcAccessMask = srcAccess; barrier.dstAccessMask = dstAccess; diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/device/VulkanShaderModule.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/device/VulkanShaderModule.h index 79bb4a1..7d03b20 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/device/VulkanShaderModule.h +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/device/VulkanShaderModule.h @@ -9,20 +9,20 @@ namespace splatkit { // A shader module that lives for one pipeline build and is destroyed on every exit from it. // `size` is in bytes, as the generated shaders::*_size constants are. False on failure. -class ShaderModule { +class VulkanShaderModule { public: - ShaderModule(VkDevice device, const uint32_t* code, size_t size) : device_(device) { + VulkanShaderModule(VkDevice device, const uint32_t* code, size_t size) : device_(device) { VkShaderModuleCreateInfo info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; info.codeSize = size; info.pCode = code; if (vkCreateShaderModule(device, &info, nullptr, &module_) != VK_SUCCESS) module_ = VK_NULL_HANDLE; } - ~ShaderModule() { + ~VulkanShaderModule() { if (module_ != VK_NULL_HANDLE) vkDestroyShaderModule(device_, module_, nullptr); } - ShaderModule(const ShaderModule&) = delete; - ShaderModule& operator=(const ShaderModule&) = delete; + VulkanShaderModule(const VulkanShaderModule&) = delete; + VulkanShaderModule& operator=(const VulkanShaderModule&) = delete; explicit operator bool() const { return module_ != VK_NULL_HANDLE; } VkShaderModule get() const { return module_; } diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanCameraUniforms.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanCameraUniforms.h index 7566f42..5e2400c 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanCameraUniforms.h +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanCameraUniforms.h @@ -8,7 +8,7 @@ #include "rendering/vulkan/device/VulkanBuffer.h" #include "rendering/vulkan/device/VulkanContext.h" -#include "rendering/vulkan/presentation/VulkanFrameLoop.h" +#include "rendering/vulkan/layout/VulkanFrameSlots.h" #include "splat/core/Result.h" #include "splat/math/Mat4.h" #include "splat/math/Vec3.h" @@ -34,7 +34,7 @@ class VulkanCameraUniforms { private: VulkanCameraUniforms() = default; - std::array, VulkanFrameLoop::kFramesInFlight> uniforms_{}; + std::array, kFramesInFlight> uniforms_{}; }; } // namespace splatkit diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanFrameCompute.cpp b/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanFrameCompute.cpp index 563639e..a54d74f 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanFrameCompute.cpp +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanFrameCompute.cpp @@ -40,6 +40,11 @@ static_assert(VulkanLodSelection::kEvaluatedOffset == VulkanFrameCompute::VulkanFrameCompute(const VulkanContext& ctx) : ctx_(ctx) {} +bool VulkanFrameCompute::supported(const VulkanContext& ctx) { + return VulkanVisibilityPass::queryCapabilities(ctx).supported && + VulkanRadixSort::queryCapabilities(ctx).supported; +} + splat::Result> VulkanFrameCompute::create( const VulkanContext& ctx, uint32_t sourceCount, const splat::LodTree* tree, uint32_t budget) { std::unique_ptr pass(new VulkanFrameCompute(ctx)); @@ -79,7 +84,7 @@ bool VulkanFrameCompute::initialize(uint32_t sourceCount, const splat::LodTree* timestampBits_ = families[ctx_.queueFamily()].timestampValidBits; timestampPeriod_ = properties.limits.timestampPeriod; if (!properties.limits.timestampComputeAndGraphics) timestampBits_ = 0; - for (uint32_t slot = 0; slot < kSlots; ++slot) { + for (uint32_t slot = 0; slot < kFramesInFlight; ++slot) { ranges_[slot] = VulkanBuffer::hostVisible(ctx_, kMaxRanges * sizeof(RangeRecord), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); readback_[slot] = @@ -151,11 +156,11 @@ bool VulkanFrameCompute::prepareRanges(uint32_t slot, const SplatRenderer::Frame } void VulkanFrameCompute::submitted(uint32_t slot, uint64_t submission) { - if (slot < kSlots && pending_[slot]) submission_[slot] = submission; + if (slot < kFramesInFlight && pending_[slot]) submission_[slot] = submission; } void VulkanFrameCompute::collectCompleted(uint64_t completedSubmission) { - for (uint32_t slot = 0; slot < kSlots; ++slot) { + for (uint32_t slot = 0; slot < kFramesInFlight; ++slot) { if (pending_[slot] && submission_[slot] != 0 && submission_[slot] <= completedSubmission) collect(slot); } @@ -196,10 +201,10 @@ void VulkanFrameCompute::copyDiagnostics(VkCommandBuffer cmd, uint32_t slot, uint32_t words[kReadbackWords]{}; words[kSelected] = candidates; vkCmdUpdateBuffer(cmd, target, 0, sizeof(words), words); - memoryBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + vulkanMemoryBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); const auto copy = [&](VkBuffer source, VkDeviceSize from, VkDeviceSize to, VkDeviceSize bytes) { const VkBufferCopy region{from, to, bytes}; vkCmdCopyBuffer(cmd, source, target, 1, ®ion); @@ -214,8 +219,8 @@ void VulkanFrameCompute::copyDiagnostics(VkCommandBuffer cmd, uint32_t slot, copy(lod_->output().state, VulkanLodSelection::kLimitedOffset, wordOffset(kLimited), 2 * sizeof(uint32_t)); } - memoryBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, - VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_HOST_READ_BIT); + vulkanMemoryBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_HOST_READ_BIT); pending_[slot] = true; submission_[slot] = 0; } @@ -223,7 +228,8 @@ void VulkanFrameCompute::copyDiagnostics(VkCommandBuffer cmd, uint32_t slot, std::optional VulkanFrameCompute::encode( VkCommandBuffer cmd, uint32_t slot, VkBuffer camera, const VulkanBuffer& splats, const SplatRenderer::Frame& frame) { - if (!cmd || slot >= kSlots || !camera || frame.orderSource != SplatRenderer::OrderSource::gpu) + if (!cmd || slot >= kFramesInFlight || !camera || + frame.orderSource != SplatRenderer::OrderSource::gpu) return std::nullopt; collect(slot); VulkanVisibilityPass::Input input; @@ -231,14 +237,13 @@ std::optional VulkanFrameCompute::encode( input.splats = splats.handle(); input.splatsBytes = splats.size(); input.sourceCount = sourceCount_; - input.keyBits = sortKeyBits_ == SortKeyBits::low16 ? VulkanVisibilityPass::KeyBits::low16 - : VulkanVisibilityPass::KeyBits::full32; + input.keyBits = keyBits(); input.keyOrder = VulkanVisibilityPass::KeyOrder::descending; // Hardware uses back-to-front over. if (!lod_ && !prepareRanges(slot, frame, input)) return std::nullopt; - memoryBarrier(cmd, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT); + vulkanMemoryBarrier(cmd, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT); if (queries_[slot]) vkCmdResetQueryPool(cmd, queries_[slot], 0, kTimestampCount); const auto timestamp = [&](uint32_t index) { if (queries_[slot]) @@ -265,19 +270,18 @@ std::optional VulkanFrameCompute::encode( sort.count = visible.count; sort.keysBytes = sort.valuesBytes = VkDeviceSize{capacity_} * sizeof(uint32_t); sort.countBytes = sizeof(uint32_t); - sort.keyBits = sortKeyBits_ == SortKeyBits::low16 ? VulkanRadixSort::KeyBits::low16 - : VulkanRadixSort::KeyBits::full32; + sort.keyBits = keyBits(); if (!radix_->encode(cmd, slot, sort)) return std::nullopt; timestamp(3); // The sorter's checked GPU count is authoritative even if visibility succeeded. - memoryBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + vulkanMemoryBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); const VkBufferCopy countToDraw{0, offsetof(VkDrawIndirectCommand, instanceCount), sizeof(uint32_t)}; vkCmdCopyBuffer(cmd, radix_->output(slot).count, visible.indirect, 1, &countToDraw); - memoryBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, - VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, VK_ACCESS_INDIRECT_COMMAND_READ_BIT); + vulkanMemoryBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, VK_ACCESS_INDIRECT_COMMAND_READ_BIT); copyDiagnostics(cmd, slot, visible, input.candidateCapacity); return Draw{radix_->output(slot).values, visible.indirect, capacity_}; } diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanFrameCompute.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanFrameCompute.h index f9c5143..ec4781f 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanFrameCompute.h +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/frame/VulkanFrameCompute.h @@ -6,6 +6,8 @@ #include "rendering/vulkan/cull/VulkanVisibilityPass.h" #include "rendering/vulkan/device/VulkanBuffer.h" +#include "rendering/vulkan/layout/VulkanFrameSlots.h" +#include "rendering/vulkan/layout/VulkanSortKeys.h" #include "rendering/vulkan/selection/VulkanLodSelection.h" #include "splatkit/rendering/SplatRenderer.h" @@ -22,6 +24,8 @@ class VulkanFrameCompute { public: // Most splats one frame can keep, which bounds the visibility output and the sort. static constexpr uint32_t kMaxVisible = VulkanVisibilityPass::kMaxCapacity; + // True when the device can run the GPU visibility and sort path. + static bool supported(const VulkanContext& ctx); static splat::Result> create( const VulkanContext& ctx, uint32_t sourceCount, const splat::LodTree* tree = nullptr, uint32_t budget = VulkanLodSelection::kMaxBudget); @@ -52,7 +56,7 @@ class VulkanFrameCompute { // Per-instance renderer policy. Render thread, while idle. Only sortDepth and // subpixelThreshold are honoured; the caller resolves the rest. bool applyRenderPolicy(const RenderPolicy& policy, std::string* reason); - uint32_t sortKeyBits() const { return sortKeyBits_ == SortKeyBits::low16 ? 16u : 32u; } + uint32_t sortKeyBits() const { return keyBitCount(keyBits()); } float minPixelRadius() const { return visibility_ ? visibility_->minPixelRadius() : 0.0f; } const VulkanVisibilityCapabilities& visibilityCapabilities() const { return visibility_->capabilities(); @@ -60,6 +64,9 @@ class VulkanFrameCompute { private: explicit VulkanFrameCompute(const VulkanContext& ctx); + KeyBits keyBits() const { + return sortKeyBits_ == SortKeyBits::low16 ? KeyBits::low16 : KeyBits::full32; + } bool initialize(uint32_t sourceCount, const splat::LodTree* tree, uint32_t budget); bool prepareRanges(uint32_t slot, const SplatRenderer::Frame& frame, VulkanVisibilityPass::Input& input); @@ -70,7 +77,6 @@ class VulkanFrameCompute { struct RangeRecord { uint32_t offset, count, prefixEnd, pad; }; - static constexpr uint32_t kSlots = 2; static constexpr uint32_t kMaxRanges = 65536; const VulkanContext& ctx_; uint32_t sourceCount_ = 0, capacity_ = 0; @@ -80,10 +86,11 @@ class VulkanFrameCompute { std::unique_ptr lod_; std::unique_ptr visibility_; std::unique_ptr radix_; - std::array, kSlots> ranges_, readback_; - std::array queries_{}; - std::array pending_{}; - std::array submission_{}; // of the pending encode; 0 when not submitted + std::array, kFramesInFlight> ranges_, readback_; + std::array queries_{}; + std::array pending_{}; + std::array + submission_{}; // of the pending encode; 0 when not submitted uint64_t collectedSubmission_ = 0; std::vector rangeRecords_; Stats stats_; diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/layout/VulkanFrameSlots.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/layout/VulkanFrameSlots.h new file mode 100644 index 0000000..1e05fb4 --- /dev/null +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/layout/VulkanFrameSlots.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace splatkit { + +// Frames the CPU records while the GPU still draws earlier ones. Every per-frame slot array +// and descriptor pool in the Vulkan renderer is sized from this one count. +inline constexpr uint32_t kFramesInFlight = 2; + +} // namespace splatkit diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/layout/VulkanSortKeys.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/layout/VulkanSortKeys.h new file mode 100644 index 0000000..62d7452 --- /dev/null +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/layout/VulkanSortKeys.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace splatkit { + +// Width of the depth keys the visibility pass writes and the radix sort orders. +enum class KeyBits : uint32_t { full32, low16 }; + +inline constexpr uint32_t kFullKeyBits = 32; +inline constexpr uint32_t kLowKeyBits = 16; + +constexpr uint32_t keyBitCount(KeyBits bits) { + return bits == KeyBits::low16 ? kLowKeyBits : kFullKeyBits; +} + +} // namespace splatkit diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/presentation/VulkanFrameLoop.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/presentation/VulkanFrameLoop.h index ab6c9ea..2a4fc8d 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/presentation/VulkanFrameLoop.h +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/presentation/VulkanFrameLoop.h @@ -6,6 +6,7 @@ #include #include "rendering/vulkan/device/VulkanContext.h" +#include "rendering/vulkan/layout/VulkanFrameSlots.h" #include "rendering/vulkan/presentation/VulkanSwapchain.h" namespace splatkit { @@ -19,8 +20,6 @@ namespace splatkit { // because presentation may still be reading it when the frame slot comes around again. class VulkanFrameLoop { public: - static constexpr uint32_t kFramesInFlight = 2; - // `swapchainSuboptimal`: the frame was presented, but the surface reports the swapchain // no longer matches it. The owner decides whether that needs a rebuild. enum class Status { ok, swapchainSuboptimal, swapchainOutOfDate, error }; diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/raster/VulkanSplatPipeline.cpp b/packages/splatkit-android/src/main/cpp/rendering/vulkan/raster/VulkanSplatPipeline.cpp index 16ae58f..94bbd33 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/raster/VulkanSplatPipeline.cpp +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/raster/VulkanSplatPipeline.cpp @@ -62,24 +62,23 @@ bool VulkanSplatPipeline::createDescriptors(const VulkanCameraUniforms& cameras) return false; VkDescriptorPoolSize sizes[2]{}; - sizes[0] = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VulkanFrameLoop::kFramesInFlight}; - sizes[1] = {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - kStorageBindings * VulkanFrameLoop::kFramesInFlight}; + sizes[0] = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, kFramesInFlight}; + sizes[1] = {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, kStorageBindings * kFramesInFlight}; VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; - poolInfo.maxSets = VulkanFrameLoop::kFramesInFlight; + poolInfo.maxSets = kFramesInFlight; poolInfo.poolSizeCount = 2; poolInfo.pPoolSizes = sizes; if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &pool_) != VK_SUCCESS) return false; - std::array layouts; + std::array layouts; layouts.fill(setLayout_); VkDescriptorSetAllocateInfo allocInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; allocInfo.descriptorPool = pool_; - allocInfo.descriptorSetCount = VulkanFrameLoop::kFramesInFlight; + allocInfo.descriptorSetCount = kFramesInFlight; allocInfo.pSetLayouts = layouts.data(); if (vkAllocateDescriptorSets(device, &allocInfo, sets_.data()) != VK_SUCCESS) return false; - for (uint32_t i = 0; i < VulkanFrameLoop::kFramesInFlight; ++i) { + for (uint32_t i = 0; i < kFramesInFlight; ++i) { const VkDescriptorBufferInfo info{cameras.buffer(i), 0, sizeof(CameraUniform)}; VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; write.dstSet = sets_[i]; @@ -94,8 +93,8 @@ bool VulkanSplatPipeline::createDescriptors(const VulkanCameraUniforms& cameras) bool VulkanSplatPipeline::createPipelines(VkRenderPass renderPass) { VkDevice device = ctx_.device(); - const ShaderModule vert(device, shaders::splat_vert, shaders::splat_vert_size); - const ShaderModule frag(device, shaders::splat_frag, shaders::splat_frag_size); + const VulkanShaderModule vert(device, shaders::splat_vert, shaders::splat_vert_size); + const VulkanShaderModule frag(device, shaders::splat_frag, shaders::splat_frag_size); if (!vert || !frag) return false; // constant_id 0 of the vertex shader is the SH degree; each pipeline gets its own. @@ -184,7 +183,7 @@ bool VulkanSplatPipeline::createPipelines(VkRenderPass renderPass) { } void VulkanSplatPipeline::bindWorld(const VulkanWorld& world) { - for (uint32_t i = 0; i < VulkanFrameLoop::kFramesInFlight; ++i) { + for (uint32_t i = 0; i < kFramesInFlight; ++i) { const VkDescriptorBufferInfo splats{world.splats->handle(), 0, VK_WHOLE_SIZE}; const VkDescriptorBufferInfo order{world.order->handle(), 0, VK_WHOLE_SIZE}; const VkDescriptorBufferInfo sh{world.sh->handle(), 0, VK_WHOLE_SIZE}; diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/raster/VulkanSplatPipeline.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/raster/VulkanSplatPipeline.h index a6e9214..c05c9ca 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/raster/VulkanSplatPipeline.h +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/raster/VulkanSplatPipeline.h @@ -8,7 +8,7 @@ #include "rendering/vulkan/device/VulkanContext.h" #include "rendering/vulkan/frame/VulkanCameraUniforms.h" -#include "rendering/vulkan/presentation/VulkanFrameLoop.h" +#include "rendering/vulkan/layout/VulkanFrameSlots.h" #include "rendering/vulkan/world/VulkanWorld.h" #include "splat/core/Result.h" @@ -51,7 +51,7 @@ class VulkanSplatPipeline { const VulkanContext& ctx_; VkDescriptorSetLayout setLayout_ = VK_NULL_HANDLE; VkDescriptorPool pool_ = VK_NULL_HANDLE; - std::array sets_{}; + std::array sets_{}; VkPipelineLayout layout_ = VK_NULL_HANDLE; // One pipeline per SH degree, specialised so degree 0 worlds pay nothing for SH. std::array pipelines_{}; diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/selection/VulkanLodSelection.cpp b/packages/splatkit-android/src/main/cpp/rendering/vulkan/selection/VulkanLodSelection.cpp index 5fc0064..00b569e 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/selection/VulkanLodSelection.cpp +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/selection/VulkanLodSelection.cpp @@ -79,18 +79,19 @@ bool VulkanLodSelection::initialize() { set.pBindings = bindings; if (vkCreateDescriptorSetLayout(ctx_.device(), &set, nullptr, &setLayout_) != VK_SUCCESS) return false; - VkDescriptorPoolSize sizes[] = {{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, kSlots}, - {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, kStorageBindings * kSlots}}; + VkDescriptorPoolSize sizes[] = { + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, kFramesInFlight}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, kStorageBindings * kFramesInFlight}}; VkDescriptorPoolCreateInfo pool{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; - pool.maxSets = kSlots; + pool.maxSets = kFramesInFlight; pool.poolSizeCount = 2; pool.pPoolSizes = sizes; if (vkCreateDescriptorPool(ctx_.device(), &pool, nullptr, &pool_) != VK_SUCCESS) return false; - std::array layouts{}; + std::array layouts{}; layouts.fill(setLayout_); VkDescriptorSetAllocateInfo allocate{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; allocate.descriptorPool = pool_; - allocate.descriptorSetCount = kSlots; + allocate.descriptorSetCount = kFramesInFlight; allocate.pSetLayouts = layouts.data(); if (vkAllocateDescriptorSets(ctx_.device(), &allocate, sets_.data()) != VK_SUCCESS) return false; const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(Config)}; @@ -100,8 +101,8 @@ bool VulkanLodSelection::initialize() { layout.pushConstantRangeCount = 1; layout.pPushConstantRanges = &push; if (vkCreatePipelineLayout(ctx_.device(), &layout, nullptr, &layout_) != VK_SUCCESS) return false; - const ShaderModule module(ctx_.device(), shaders::lod_selection_comp, - shaders::lod_selection_comp_size); + const VulkanShaderModule module(ctx_.device(), shaders::lod_selection_comp, + shaders::lod_selection_comp_size); if (!module) return false; VkComputePipelineCreateInfo pipeline{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; pipeline.layout = layout_; @@ -193,7 +194,7 @@ VulkanLodSelection::Output VulkanLodSelection::output() const { } bool VulkanLodSelection::encode(VkCommandBuffer cmd, uint32_t slot, const Input& input) const { - if (!cmd || slot >= kSlots || !scratch_ || !input.camera || + if (!cmd || slot >= kFramesInFlight || !scratch_ || !input.camera || input.cameraOffset % limits_.minUniformBufferOffsetAlignment != 0 || input.cameraOffset > std::numeric_limits::max() - sizeof(CameraUniform)) return false; @@ -215,7 +216,7 @@ bool VulkanLodSelection::encode(VkCommandBuffer cmd, uint32_t slot, const Input& } vkUpdateDescriptorSets(ctx_.device(), kBindings, writes, 0, nullptr); // Inter-frame WAR/WAW plus upload/host writes, including consumers on previous submissions. - memoryBarrier( + vulkanMemoryBarrier( cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, @@ -231,10 +232,10 @@ bool VulkanLodSelection::encode(VkCommandBuffer cmd, uint32_t slot, const Input& vkCmdDispatch(cmd, 1, 1, 1); else vkCmdDispatchIndirect(cmd, scratch_->handle(), indirectOffset); - memoryBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | - VK_ACCESS_INDIRECT_COMMAND_READ_BIT); + vulkanMemoryBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, + VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_INDIRECT_COMMAND_READ_BIT); }; dispatch(kReset); for (uint32_t round = 0; round < rounds_; ++round) { @@ -250,11 +251,11 @@ bool VulkanLodSelection::encode(VkCommandBuffer cmd, uint32_t slot, const Input& dispatch(kAdvance); } dispatch(kCopyPackets, kPacketGroupsOffset); - memoryBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | - VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT | - VK_ACCESS_TRANSFER_READ_BIT); + vulkanMemoryBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | + VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT | + VK_ACCESS_TRANSFER_READ_BIT); return true; } } // namespace splatkit diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/selection/VulkanLodSelection.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/selection/VulkanLodSelection.h index 1e6660c..5ec5a6c 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/selection/VulkanLodSelection.h +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/selection/VulkanLodSelection.h @@ -5,6 +5,7 @@ #include #include "rendering/vulkan/device/VulkanBuffer.h" +#include "rendering/vulkan/layout/VulkanFrameSlots.h" #include "splat/lod/LodTree.h" namespace splatkit { @@ -32,7 +33,6 @@ class VulkanLodSelection { VkBuffer state = VK_NULL_HANDLE; // count/diagnostics at constants below, TRANSFER_SRC capable uint32_t capacity = 0; }; - static constexpr uint32_t kSlots = 2; static constexpr uint32_t kMaxBudget = 2200000; static constexpr VkDeviceSize kCountOffset = 0; static constexpr VkDeviceSize kLimitedOffset = 16; @@ -73,7 +73,7 @@ class VulkanLodSelection { uint32_t rounds_ = 0; VkDescriptorSetLayout setLayout_ = VK_NULL_HANDLE; VkDescriptorPool pool_ = VK_NULL_HANDLE; - std::array sets_{}; + std::array sets_{}; VkPipelineLayout layout_ = VK_NULL_HANDLE; VkPipeline pipeline_ = VK_NULL_HANDLE; std::unique_ptr clusters_, leaves_, scratch_, indices_; diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/sort/VulkanRadixSort.cpp b/packages/splatkit-android/src/main/cpp/rendering/vulkan/sort/VulkanRadixSort.cpp index 2c32e99..c6cbd9e 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/sort/VulkanRadixSort.cpp +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/sort/VulkanRadixSort.cpp @@ -42,8 +42,8 @@ uint32_t passSet(uint32_t pass) { } void computeBarrier(VkCommandBuffer cmd) { - memoryBarrier(cmd, kCompute, VK_ACCESS_SHADER_WRITE_BIT, kCompute, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + vulkanMemoryBarrier(cmd, kCompute, VK_ACCESS_SHADER_WRITE_BIT, kCompute, + VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); } // Bind after each pipeline transition: MoltenVK/gfxstream otherwise retained stale @@ -128,9 +128,10 @@ bool VulkanRadixSort::initialize() { setInfo.pBindings = bindings.data(); if (vkCreateDescriptorSetLayout(device, &setInfo, nullptr, &setLayout_) != VK_SUCCESS) return false; - const VkDescriptorPoolSize size{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, kBindings * kSlots * kSets}; + const VkDescriptorPoolSize size{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + kBindings * kFramesInFlight * kSets}; VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; - poolInfo.maxSets = kSlots * kSets; + poolInfo.maxSets = kFramesInFlight * kSets; poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &size; if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &pool_) != VK_SUCCESS) return false; @@ -156,7 +157,7 @@ bool VulkanRadixSort::initialize() { const size_t sizes[] = {shaders::radix_prepare_comp_size, shaders::radix_histogram_comp_size, shaders::radix_scan_comp_size, shaders::radix_scatter_comp_size}; for (uint32_t i = 0; i < kStageCount; ++i) { - const ShaderModule module(device, code[i], sizes[i]); + const VulkanShaderModule module(device, code[i], sizes[i]); if (!module) return false; VkComputePipelineCreateInfo pipeline{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; pipeline.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; @@ -183,7 +184,7 @@ bool VulkanRadixSort::reserve(uint32_t capacity) { uint64_t{blocks} > uint64_t{limits.maxComputeWorkGroupCount[0]} * limits.maxComputeWorkGroupCount[1]) return false; - std::array next; + std::array next; for (auto& slot : next) { for (auto& keys : slot.keys) keys = VulkanBuffer::deviceLocal(ctx_, pairBytes, kStorage); for (auto& values : slot.values) values = VulkanBuffer::deviceLocal(ctx_, pairBytes, kStorage); @@ -203,7 +204,7 @@ bool VulkanRadixSort::reserve(uint32_t capacity) { } VulkanRadixSort::Output VulkanRadixSort::output(uint32_t slot) const { - if (slot >= kSlots || !capacity_) return {}; + if (slot >= kFramesInFlight || !capacity_) return {}; const auto& s = slots_[slot]; return {s.keys[0]->handle(), s.values[0]->handle(), s.count->handle(), s.status->handle()}; } @@ -274,11 +275,11 @@ void VulkanRadixSort::recordSort(VkCommandBuffer cmd, uint32_t slot, KeyBits key PushConstants push{capacity_, 0, limits.maxComputeWorkGroupCount[0]}; bindStage(cmd, layout_, pipelines_[kPrepare], sets_[slot][0], push); vkCmdDispatch(cmd, 1, 1, 1); - memoryBarrier( + vulkanMemoryBarrier( cmd, kCompute, VK_ACCESS_SHADER_WRITE_BIT, kCompute | VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT); const VkBuffer state = slots_[slot].state->handle(); - const uint32_t passes = (keyBits == KeyBits::low16 ? 16 : 32) / kDigitBits; + const uint32_t passes = keyBitCount(keyBits) / kDigitBits; for (uint32_t pass = 0; pass < passes; ++pass) { push.shift = pass * kDigitBits; const VkDescriptorSet set = sets_[slot][passSet(pass)]; @@ -293,20 +294,22 @@ void VulkanRadixSort::recordSort(VkCommandBuffer cmd, uint32_t slot, KeyBits key vkCmdDispatchIndirect(cmd, state, 0); computeBarrier(cmd); } - memoryBarrier(cmd, kCompute, VK_ACCESS_SHADER_WRITE_BIT, - kCompute | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT); + vulkanMemoryBarrier( + cmd, kCompute, VK_ACCESS_SHADER_WRITE_BIT, + kCompute | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT); } bool VulkanRadixSort::encode(VkCommandBuffer cmd, uint32_t slot, const Input& input) const { - if (!cmd || slot >= kSlots || !capacity_ || + if (!cmd || slot >= kFramesInFlight || !capacity_ || (input.keyBits != KeyBits::full32 && input.keyBits != KeyBits::low16)) return false; if (!acceptsInput(input)) return false; writeDescriptorSets(slot, input); - memoryBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, - VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_HOST_WRITE_BIT, - kCompute, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + vulkanMemoryBarrier( + cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, + VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_HOST_WRITE_BIT, kCompute, + VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); recordSort(cmd, slot, input.keyBits); return true; } diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/sort/VulkanRadixSort.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/sort/VulkanRadixSort.h index ddb9507..2315bdb 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/sort/VulkanRadixSort.h +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/sort/VulkanRadixSort.h @@ -5,6 +5,8 @@ #include #include "rendering/vulkan/device/VulkanBuffer.h" +#include "rendering/vulkan/layout/VulkanFrameSlots.h" +#include "rendering/vulkan/layout/VulkanSortKeys.h" #include "splat/core/Result.h" namespace splatkit { @@ -17,7 +19,6 @@ namespace splatkit { // VkBuffer handles must not alias the same memory. External offsets/sizes must be truthful. class VulkanRadixSort { public: - enum class KeyBits : uint32_t { full32, low16 }; struct Capabilities { bool supported = false; uint32_t subgroupSize = 0; @@ -35,7 +36,7 @@ class VulkanRadixSort { VkBuffer keys = VK_NULL_HANDLE, values = VK_NULL_HANDLE; VkBuffer count = VK_NULL_HANDLE, status = VK_NULL_HANDLE; }; - static constexpr uint32_t kSlots = 2, kBlock = 2048, kMaxCapacity = 3000000; + static constexpr uint32_t kBlock = 2048, kMaxCapacity = 3000000; static constexpr uint32_t kInvalidCount = 1; static Capabilities queryCapabilities(const VulkanContext& ctx); static splat::Result> create(const VulkanContext& ctx); @@ -71,7 +72,7 @@ class VulkanRadixSort { void recordSort(VkCommandBuffer cmd, uint32_t slot, KeyBits keyBits) const; const VulkanContext& ctx_; uint32_t capacity_ = 0; - std::array slots_; + std::array slots_; VkDescriptorSetLayout setLayout_ = VK_NULL_HANDLE; VkDescriptorPool pool_ = VK_NULL_HANDLE; VkPipelineLayout layout_ = VK_NULL_HANDLE; @@ -79,6 +80,6 @@ class VulkanRadixSort { std::array pipelines_{}; // First pass external->B; second B->A; subsequent A->B and B->A. static constexpr uint32_t kSets = 3; - std::array, kSlots> sets_{}; + std::array, kFramesInFlight> sets_{}; }; } // namespace splatkit diff --git a/packages/splatkit-android/src/main/cpp/rendering/vulkan/world/VulkanWorld.h b/packages/splatkit-android/src/main/cpp/rendering/vulkan/world/VulkanWorld.h index 68bb4ab..ee252e1 100644 --- a/packages/splatkit-android/src/main/cpp/rendering/vulkan/world/VulkanWorld.h +++ b/packages/splatkit-android/src/main/cpp/rendering/vulkan/world/VulkanWorld.h @@ -8,7 +8,7 @@ #include "rendering/vulkan/device/VulkanBuffer.h" #include "rendering/vulkan/device/VulkanContext.h" -#include "rendering/vulkan/presentation/VulkanFrameLoop.h" +#include "rendering/vulkan/layout/VulkanFrameSlots.h" #include "splat/formats/SplatCloud.h" namespace splatkit { @@ -47,7 +47,7 @@ class VulkanWorld { int shDegree = 0; // One host visible staging buffer per frame slot: the slot's fence guarantees the GPU // finished reading it before the CPU writes the next order into it. - std::array, VulkanFrameLoop::kFramesInFlight> orderStaging; + std::array, kFramesInFlight> orderStaging; uint32_t count = 0; private: diff --git a/packages/splatkit-android/tests/rendering/vulkan/cull/VulkanVisibilityPassTest.cpp b/packages/splatkit-android/tests/rendering/vulkan/cull/VulkanVisibilityPassTest.cpp index f94febb..41f45c9 100644 --- a/packages/splatkit-android/tests/rendering/vulkan/cull/VulkanVisibilityPassTest.cpp +++ b/packages/splatkit-android/tests/rendering/vulkan/cull/VulkanVisibilityPassTest.cpp @@ -14,6 +14,8 @@ namespace { using splatkit::CameraUniform; using splatkit::GpuSplat; +using splatkit::KeyBits; +using splatkit::kFramesInFlight; using splatkit::packSplats; using splatkit::VulkanBuffer; using splatkit::VulkanVisibilityPass; @@ -182,7 +184,7 @@ void testSparseIndexed(const Gpu& gpu, const VulkanVisibilityPass& pass, const F auto invalidProjection = camera(); invalidProjection.proj = splat::Mat4::identity(); f.setCamera(invalidProjection); - sparse.keyBits = VulkanVisibilityPass::KeyBits::low16; + sparse.keyBits = KeyBits::low16; failed(run(gpu, pass, sparse), VulkanVisibilityPass::kInvalidProjection); f.setCamera(camera()); std::puts("PASS sparse indexed workgroups, poisoned tails and empty diagnostics"); @@ -246,12 +248,11 @@ void testOverflow(const Gpu& gpu, const VulkanVisibilityPass& pass, const Fixtur std::puts("PASS output overflow fails closed and recovers"); } -uint32_t expectedDepthKey(float depth, VulkanVisibilityPass::KeyBits bits, - VulkanVisibilityPass::KeyOrder order) { +uint32_t expectedDepthKey(float depth, KeyBits bits, VulkanVisibilityPass::KeyOrder order) { const auto cam = camera(); const float nearPlane = cam.proj.at(2, 3) / cam.proj.at(2, 2); const float farPlane = cam.proj.at(2, 3) / (cam.proj.at(2, 2) + 1); - const bool low16 = bits == VulkanVisibilityPass::KeyBits::low16; + const bool low16 = bits == KeyBits::low16; uint32_t expected = 0; std::memcpy(&expected, &depth, 4); if (low16) @@ -262,12 +263,11 @@ uint32_t expectedDepthKey(float depth, VulkanVisibilityPass::KeyBits bits, return expected; } -void requireDepthKeys(const Result& result, VulkanVisibilityPass::KeyBits bits, - VulkanVisibilityPass::KeyOrder order) { +void requireDepthKeys(const Result& result, KeyBits bits, VulkanVisibilityPass::KeyOrder order) { for (size_t i = 0; i < result.count; ++i) { const auto depth = static_cast(1 + result.indices[i] % 10); const uint32_t expected = expectedDepthKey(depth, bits, order); - if (bits == VulkanVisibilityPass::KeyBits::full32) + if (bits == KeyBits::full32) require(result.keys[i] == expected, "32-bit depth key preserves float bits and BTF inversion"); else @@ -280,7 +280,7 @@ void requireDepthKeys(const Result& result, VulkanVisibilityPass::KeyBits bits, void testDepthKeys(const Gpu& gpu, const VulkanVisibilityPass& pass, const Fixture& f) { auto in = f.input(); in.candidateCapacity = 3; - for (auto bits : {VulkanVisibilityPass::KeyBits::full32, VulkanVisibilityPass::KeyBits::low16}) { + for (auto bits : {KeyBits::full32, KeyBits::low16}) { for (auto order : {VulkanVisibilityPass::KeyOrder::ascending, VulkanVisibilityPass::KeyOrder::descending}) { in.keyBits = bits; @@ -293,7 +293,7 @@ void testDepthKeys(const Gpu& gpu, const VulkanVisibilityPass& pass, const Fixtu auto badCamera = camera(); badCamera.proj = splat::Mat4::identity(); f.setCamera(badCamera); - in.keyBits = VulkanVisibilityPass::KeyBits::low16; + in.keyBits = KeyBits::low16; failed(run(gpu, pass, in), VulkanVisibilityPass::kInvalidProjection); f.setCamera(camera()); std::puts("PASS full32/low16 ascending/BTF keys and invalid projection"); @@ -330,7 +330,7 @@ void testDescriptorRejection(const Gpu& gpu, VulkanVisibilityPass& pass, const F bad.candidatesBytes = indexBuffer->size(); bad.candidateCountBytes = 3; require(!pass.encode(cmd, 0, bad), "reject undersized count descriptor"); - require(!pass.encode(cmd, VulkanVisibilityPass::kSlots, in), "reject invalid slot"); + require(!pass.encode(cmd, kFramesInFlight, in), "reject invalid slot"); require(!pass.encode(VK_NULL_HANDLE, 0, in), "reject null command"); }); membership(run(gpu, pass, in), {0, 1, 2}); diff --git a/packages/splatkit-android/tests/rendering/vulkan/selection/VulkanLodSelectionTest.cpp b/packages/splatkit-android/tests/rendering/vulkan/selection/VulkanLodSelectionTest.cpp index 34ff527..6cfa060 100644 --- a/packages/splatkit-android/tests/rendering/vulkan/selection/VulkanLodSelectionTest.cpp +++ b/packages/splatkit-android/tests/rendering/vulkan/selection/VulkanLodSelectionTest.cpp @@ -13,6 +13,7 @@ namespace { using splatkit::CameraUniform; +using splatkit::kFramesInFlight; using splatkit::VulkanBuffer; using splatkit::VulkanLodSelection; namespace test = splatkit::test; @@ -171,7 +172,7 @@ void tests(const test::VulkanTestContext& gpu) { require(select(gpu, *lod, camera()).cut == denied.cut, "old world survives failed upload"); require(!lod->encode(VK_NULL_HANDLE, 0, {}), "reject null command"); gpu.submit([&](VkCommandBuffer cmd) { - require(!lod->encode(cmd, VulkanLodSelection::kSlots, {}), "reject invalid slot"); + require(!lod->encode(cmd, kFramesInFlight, {}), "reject invalid slot"); require(!lod->encode(cmd, 0, {}), "reject null camera"); }); std::puts("PASS invalid inputs and transactional upload"); diff --git a/packages/splatkit-android/tests/rendering/vulkan/sort/VulkanRadixSortTest.cpp b/packages/splatkit-android/tests/rendering/vulkan/sort/VulkanRadixSortTest.cpp index 1536b65..a6d2ac9 100644 --- a/packages/splatkit-android/tests/rendering/vulkan/sort/VulkanRadixSortTest.cpp +++ b/packages/splatkit-android/tests/rendering/vulkan/sort/VulkanRadixSortTest.cpp @@ -9,6 +9,8 @@ #include "rendering/vulkan/sort/VulkanRadixSort.h" #include "support/VulkanTestContext.h" +using splatkit::KeyBits; +using splatkit::kFramesInFlight; using splatkit::VulkanBuffer; using splatkit::VulkanRadixSort; using splatkit::test::require; @@ -90,7 +92,7 @@ void requireInvalidInputsRejected(VulkanTestContext& gpu, const VulkanRadixSort& const VulkanRadixSort::Input& input) { require(!sort.encode(VK_NULL_HANDLE, 0, input), "reject null command buffer"); gpu.submit([&](VkCommandBuffer cmd) { - require(!sort.encode(cmd, VulkanRadixSort::kSlots, input), "reject invalid slot"); + require(!sort.encode(cmd, kFramesInFlight, input), "reject invalid slot"); auto bad = input; bad.keysBytes = input.keysBytes - 1; require(!sort.encode(cmd, 0, bad), "reject undersized input"); @@ -100,7 +102,7 @@ void requireInvalidInputsRejected(VulkanTestContext& gpu, const VulkanRadixSort& bad = input; // Fixed uint32_t underlying type permits this value; exercise enum validation. // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) - bad.keyBits = static_cast(9); + bad.keyBits = static_cast(9); require(!sort.encode(cmd, 0, bad), "reject key mode"); bad = input; bad.keys = sort.output(1).keys; @@ -156,7 +158,7 @@ void requireSortedOutput(VulkanTestContext& gpu, const VulkanRadixSort::Output& void runCase(VulkanTestContext& gpu, const VulkanRadixSort& sort, const InputBuffers& buffers, const VulkanRadixSort::Input& input, uint32_t n, uint32_t pattern, std::mt19937& random, uint32_t& cases) { - const bool low16 = input.keyBits == VulkanRadixSort::KeyBits::low16; + const bool low16 = input.keyBits == KeyBits::low16; std::printf("radix case=%u bits=%u n=%u pattern=%u stage=upload\n", cases, low16 ? 16u : 32u, n, pattern); const auto inKeys = makeKeys(n, pattern, low16, random); @@ -170,7 +172,7 @@ void runCase(VulkanTestContext& gpu, const VulkanRadixSort& sort, const InputBuf const uint32_t mask = low16 ? 65535u : UINT32_MAX; std::stable_sort(expected.begin(), expected.end(), [&](uint32_t a, uint32_t b) { return (inKeys[a] & mask) < (inKeys[b] & mask); }); - const uint32_t slot = cases++ % VulkanRadixSort::kSlots; + const uint32_t slot = cases++ % kFramesInFlight; std::puts("radix stage=encode-submit-wait"); gpu.submit([&](VkCommandBuffer cmd) { require(sort.encode(cmd, slot, input), "encode radix"); }); requireSortedOutput(gpu, sort.output(slot), n, inKeys, expected); @@ -181,7 +183,7 @@ uint32_t runAllCases(VulkanTestContext& gpu, const VulkanRadixSort& sort, const std::vector& sizes) { std::mt19937 random(0x51a7u); uint32_t cases = 0; - for (const auto bits : {VulkanRadixSort::KeyBits::full32, VulkanRadixSort::KeyBits::low16}) { + for (const auto bits : {KeyBits::full32, KeyBits::low16}) { input.keyBits = bits; for (const uint32_t n : sizes) for (uint32_t pattern = 0; pattern < 4; ++pattern) diff --git a/packages/splatkit-android/tests/rendering/vulkan/world/VulkanWorldTest.cpp b/packages/splatkit-android/tests/rendering/vulkan/world/VulkanWorldTest.cpp index d8e0507..20bfd1a 100644 --- a/packages/splatkit-android/tests/rendering/vulkan/world/VulkanWorldTest.cpp +++ b/packages/splatkit-android/tests/rendering/vulkan/world/VulkanWorldTest.cpp @@ -7,8 +7,8 @@ #include "support/VulkanTestContext.h" namespace { +using splatkit::kFramesInFlight; using splatkit::shStride; -using splatkit::VulkanFrameLoop; using splatkit::VulkanWorld; namespace test = splatkit::test; using test::require; @@ -119,7 +119,7 @@ int main() { test::VulkanTestContext gpu; std::printf("GPU: %s; validation: %s\n", gpu.context->deviceDescription().c_str(), gpu.context->validationEnabled() ? "on" : "off"); - static_assert(VulkanFrameLoop::kFramesInFlight >= 2, "the order test uses slots 0 and 1"); + static_assert(kFramesInFlight >= 2, "the order test uses slots 0 and 1"); uploadShape(gpu); std::puts("PASS world upload shape"); uploadShDegree(gpu);