Add offscreen render targets and rename AssetRegistry to ResourceRegistry #8

Merged
burkkyy merged 26 commits from dev into main 2026-09-25 20:39:26 -07:00
4 changed files with 208 additions and 76 deletions
Showing only changes of commit 2569eff130 - Show all commits
+26 -77
View File
@@ -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::PipelineStageFlags srcStage;
vk::PipelineStageFlags dstStage;
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.");
vk::ImageSubresourceRange Image::getSubresourceRange() const {
return vk::ImageSubresourceRange{this->aspectMask, 0, this->mipLevels, 0,
this->arrayLayers};
}
commandBuffer.pipelineBarrier(srcStage, dstStage, {}, nullptr, nullptr,
AccessState Image::getAccessState() const {
AccessState state = getStateForLayout(this->layout);
// 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;
}
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;
}
+16
View File
@@ -4,6 +4,8 @@
#include <sophia/types.hpp>
#include <vulkan/vulkan.hpp>
#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);
+104
View File
@@ -0,0 +1,104 @@
#include "rendering/access.hpp"
#include <array>
#include <sophia/util/logger.hpp>
#include <stdexcept>
namespace sophia {
namespace {
constexpr std::array<Access, 7> 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<bool>(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
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#include <vulkan/vulkan.hpp>
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