Add offscreen render targets and rename AssetRegistry to ResourceRegistry #8
+25
-76
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user