From 2569eff1305b3208dff8a73a06a13903faf4e76a Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Mon, 21 Sep 2026 00:30:11 -0700 Subject: [PATCH] changes --- engine/src/image.cpp | 101 ++++++++----------------------- engine/src/image.hpp | 16 +++++ engine/src/rendering/access.cpp | 104 ++++++++++++++++++++++++++++++++ engine/src/rendering/access.hpp | 63 +++++++++++++++++++ 4 files changed, 208 insertions(+), 76 deletions(-) create mode 100644 engine/src/rendering/access.cpp create mode 100644 engine/src/rendering/access.hpp diff --git a/engine/src/image.cpp b/engine/src/image.cpp index e2b8a70..a54f08b 100644 --- a/engine/src/image.cpp +++ b/engine/src/image.cpp @@ -8,86 +8,35 @@ namespace sophia { -void Image::transitionLayout(vk::CommandBuffer commandBuffer, - vk::ImageLayout newLayout) { - vk::ImageMemoryBarrier barrier{}; - barrier.oldLayout = this->layout; - barrier.newLayout = newLayout; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = this->get(); - barrier.subresourceRange.aspectMask = this->aspectMask; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = this->mipLevels; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = this->arrayLayers; +vk::ImageSubresourceRange Image::getSubresourceRange() const { + return vk::ImageSubresourceRange{this->aspectMask, 0, this->mipLevels, 0, + this->arrayLayers}; +} - vk::PipelineStageFlags srcStage; - vk::PipelineStageFlags dstStage; +AccessState Image::getAccessState() const { + AccessState state = getStateForLayout(this->layout); - if (this->layout == vk::ImageLayout::eUndefined && - newLayout == vk::ImageLayout::eTransferDstOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; - srcStage = vk::PipelineStageFlagBits::eTopOfPipe; - dstStage = vk::PipelineStageFlagBits::eTransfer; - } else if (this->layout == vk::ImageLayout::eTransferDstOptimal && - newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - srcStage = vk::PipelineStageFlagBits::eTransfer; - dstStage = vk::PipelineStageFlagBits::eFragmentShader; - } else if (this->layout == vk::ImageLayout::eUndefined && - newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | - vk::AccessFlagBits::eDepthStencilAttachmentWrite; - srcStage = vk::PipelineStageFlagBits::eTopOfPipe; - dstStage = vk::PipelineStageFlagBits::eEarlyFragmentTests; - } else if (this->layout == vk::ImageLayout::eUndefined && - newLayout == vk::ImageLayout::eColorAttachmentOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; - srcStage = vk::PipelineStageFlagBits::eTopOfPipe; - dstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput; - } else if (this->layout == vk::ImageLayout::eUndefined && - newLayout == vk::ImageLayout::eGeneral) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = - vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite; - srcStage = vk::PipelineStageFlagBits::eTopOfPipe; - dstStage = vk::PipelineStageFlagBits::eComputeShader; - } else if (this->layout == vk::ImageLayout::eColorAttachmentOptimal && - newLayout == vk::ImageLayout::ePresentSrcKHR) { - barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; - barrier.dstAccessMask = {}; - srcStage = vk::PipelineStageFlagBits::eColorAttachmentOutput; - dstStage = vk::PipelineStageFlagBits::eBottomOfPipe; - } else if (this->layout == vk::ImageLayout::eColorAttachmentOptimal && - newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; - barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; - srcStage = vk::PipelineStageFlagBits::eColorAttachmentOutput; - dstStage = vk::PipelineStageFlagBits::eFragmentShader; - } else if (this->layout == vk::ImageLayout::eShaderReadOnlyOptimal && - newLayout == vk::ImageLayout::eColorAttachmentOptimal) { - barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead; - barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; - srcStage = vk::PipelineStageFlagBits::eFragmentShader; - dstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput; - } else if (this->layout == vk::ImageLayout::ePresentSrcKHR && - newLayout == vk::ImageLayout::eColorAttachmentOptimal) { - barrier.srcAccessMask = {}; - barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; - srcStage = vk::PipelineStageFlagBits::eTopOfPipe; - dstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput; - } else { - log::fatal("Unsupported image layout transition."); - throw std::runtime_error("Unsupported image layout transition."); + // A wrapped (swapchain) image is handed to us by the acquire semaphore, so + // whatever we do next has to be ordered after that wait. + if (this->isExternal() && (this->layout == vk::ImageLayout::eUndefined || + this->layout == vk::ImageLayout::ePresentSrcKHR)) { + state.stage = SWAPCHAIN_ACQUIRE_STAGE; } - commandBuffer.pipelineBarrier(srcStage, dstStage, {}, nullptr, nullptr, - barrier); + return state; +} + +void Image::transitionLayout(vk::CommandBuffer commandBuffer, + vk::ImageLayout newLayout) { + AccessState from = this->getAccessState(); + AccessState to = getStateForLayout(newLayout); + + if (needsBarrier(from, to)) { + vk::ImageMemoryBarrier barrier = + makeImageBarrier(this->get(), this->getSubresourceRange(), from, to); + commandBuffer.pipelineBarrier(from.stage, to.stage, {}, nullptr, nullptr, + barrier); + } this->layout = newLayout; } diff --git a/engine/src/image.hpp b/engine/src/image.hpp index b93095f..af62af7 100644 --- a/engine/src/image.hpp +++ b/engine/src/image.hpp @@ -4,6 +4,8 @@ #include #include +#include "rendering/access.hpp" + namespace sophia { class Device; @@ -28,6 +30,20 @@ class Image { vk::Format getFormat() const { return this->format; } vk::Extent3D getExtent() const { return this->extent; } vk::ImageLayout getLayout() const { return this->layout; } + vk::ImageSubresourceRange getSubresourceRange() const; + + /** + * The layout this image was last left in, plus the stages and accesses that + * a barrier out of it has to wait on. + */ + AccessState getAccessState() const; + + /** + * Updates the tracked layout without recording a barrier. For code that + * does its own transitions (the RenderGraph) and needs to hand the final + * layout back. + */ + void recordLayout(vk::ImageLayout newLayout) { this->layout = newLayout; } void transitionLayout(vk::CommandBuffer commandBuffer, vk::ImageLayout newLayout); diff --git a/engine/src/rendering/access.cpp b/engine/src/rendering/access.cpp new file mode 100644 index 0000000..4dd417c --- /dev/null +++ b/engine/src/rendering/access.cpp @@ -0,0 +1,104 @@ +#include "rendering/access.hpp" + +#include +#include +#include + +namespace sophia { + +namespace { + +constexpr std::array ALL_ACCESSES = { + Access::ColorWrite, Access::DepthWrite, Access::SampledRead, + Access::StorageReadWrite, Access::TransferSrc, Access::TransferDst, + Access::Present, +}; + +constexpr vk::AccessFlags WRITE_ACCESSES = + vk::AccessFlagBits::eColorAttachmentWrite | + vk::AccessFlagBits::eDepthStencilAttachmentWrite | + vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eTransferWrite; + +} // namespace + +AccessState getAccessState(Access access) { + switch (access) { + case Access::ColorWrite: + return {vk::ImageLayout::eColorAttachmentOptimal, + vk::PipelineStageFlagBits::eColorAttachmentOutput, + vk::AccessFlagBits::eColorAttachmentRead | + vk::AccessFlagBits::eColorAttachmentWrite}; + case Access::DepthWrite: + return {vk::ImageLayout::eDepthStencilAttachmentOptimal, + vk::PipelineStageFlagBits::eEarlyFragmentTests | + vk::PipelineStageFlagBits::eLateFragmentTests, + vk::AccessFlagBits::eDepthStencilAttachmentRead | + vk::AccessFlagBits::eDepthStencilAttachmentWrite}; + case Access::SampledRead: + return {vk::ImageLayout::eShaderReadOnlyOptimal, + vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eShaderRead}; + case Access::StorageReadWrite: + return { + vk::ImageLayout::eGeneral, vk::PipelineStageFlagBits::eComputeShader, + vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite}; + case Access::TransferSrc: + return {vk::ImageLayout::eTransferSrcOptimal, + vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferRead}; + case Access::TransferDst: + return {vk::ImageLayout::eTransferDstOptimal, + vk::PipelineStageFlagBits::eTransfer, + vk::AccessFlagBits::eTransferWrite}; + case Access::Present: + return {vk::ImageLayout::ePresentSrcKHR, + vk::PipelineStageFlagBits::eBottomOfPipe, vk::AccessFlags{}}; + } + + log::fatal("Unknown Access value."); + throw std::runtime_error("Unknown Access value."); +} + +AccessState getStateForLayout(vk::ImageLayout layout) { + if (layout == vk::ImageLayout::eUndefined) { + return AccessState{}; + } + + for (Access access : ALL_ACCESSES) { + AccessState state = getAccessState(access); + if (state.layout == layout) { + return state; + } + } + + log::fatal("Unsupported image layout:", vk::to_string(layout)); + throw std::runtime_error("Unsupported image layout."); +} + +bool writesImage(const AccessState& state) { + return static_cast(state.access & WRITE_ACCESSES); +} + +bool needsBarrier(const AccessState& from, const AccessState& to) { + return from.layout != to.layout || writesImage(from) || writesImage(to); +} + +vk::ImageMemoryBarrier makeImageBarrier(vk::Image image, + const vk::ImageSubresourceRange& range, + const AccessState& from, + const AccessState& to) { + vk::ImageMemoryBarrier barrier{}; + barrier.oldLayout = from.layout; + barrier.newLayout = to.layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange = range; + // Only writes need to be made available; reads just need the execution + // dependency the stage masks already give. + barrier.srcAccessMask = from.access & WRITE_ACCESSES; + barrier.dstAccessMask = to.access; + return barrier; +} + +} // namespace sophia diff --git a/engine/src/rendering/access.hpp b/engine/src/rendering/access.hpp new file mode 100644 index 0000000..67c35f4 --- /dev/null +++ b/engine/src/rendering/access.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include + +namespace sophia { + +/** + * How an image is used at a point in a frame. Each value implies an image + * layout plus the pipeline stages and memory accesses that go with it, so a + * barrier between any two of them can be derived instead of hand-written. + */ +enum class Access { + ColorWrite, + DepthWrite, + SampledRead, + StorageReadWrite, + TransferSrc, + TransferDst, + Present, +}; + +/** + * The layout, stages and accesses an image is in after some use. Barriers go + * from one of these to another. + */ +struct AccessState { + vk::ImageLayout layout = vk::ImageLayout::eUndefined; + vk::PipelineStageFlags stage = vk::PipelineStageFlagBits::eTopOfPipe; + vk::AccessFlags access; +}; + +/** + * The stage RenderContext waits on for the swapchain image-available + * semaphore. A barrier that takes a swapchain image out of its + * just-acquired state has to use this as its source stage, otherwise the + * layout transition isn't ordered after the acquire. + */ +inline constexpr vk::PipelineStageFlags SWAPCHAIN_ACQUIRE_STAGE = + vk::PipelineStageFlagBits::eColorAttachmentOutput; + +AccessState getAccessState(Access access); + +/** + * The state of an image sitting in `layout` with no outstanding work. + * eUndefined maps to "nothing to wait on"; layouts with no matching Access + * throw. + */ +AccessState getStateForLayout(vk::ImageLayout layout); + +bool writesImage(const AccessState& state); + +/** + * Whether going from `from` to `to` needs a barrier: a layout change, or any + * hazard involving a write. Read-after-read in the same layout doesn't. + */ +bool needsBarrier(const AccessState& from, const AccessState& to); + +vk::ImageMemoryBarrier makeImageBarrier(vk::Image image, + const vk::ImageSubresourceRange& range, + const AccessState& from, + const AccessState& to); + +} // namespace sophia