2D scene rendering + image loading #15

Merged
burkkyy merged 18 commits from issue-14/2d-scene-support-spritecomponent-2d-render-system into main 2026-09-30 15:56:33 -07:00
6 changed files with 87 additions and 64 deletions
Showing only changes of commit ab7c850121 - Show all commits
+18 -12
View File
@@ -86,22 +86,28 @@ void RenderContext::endFrame() {
FrameData& frame = this->frames[this->currentFrameIndex]; FrameData& frame = this->frames[this->currentFrameIndex];
frame.commandBuffer.end(); frame.commandBuffer.end();
vk::Semaphore waitSemaphore = frame.imageAvailable.get();
vk::Semaphore signalSemaphore = vk::Semaphore signalSemaphore =
this->renderFinishedSemaphores.at(this->currentImageIndex).get(); this->renderFinishedSemaphores.at(this->currentImageIndex).get();
vk::PipelineStageFlags waitStage = SWAPCHAIN_ACQUIRE_STAGE;
vk::CommandBuffer commandBuffer = frame.commandBuffer;
vk::SubmitInfo submitInfo{}; vk::SemaphoreSubmitInfo waitInfo{};
submitInfo.waitSemaphoreCount = 1; waitInfo.semaphore = frame.imageAvailable.get();
submitInfo.pWaitSemaphores = &waitSemaphore; waitInfo.stageMask = SWAPCHAIN_ACQUIRE_STAGE;
submitInfo.pWaitDstStageMask = &waitStage;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &signalSemaphore;
this->device.getGraphicsQueue().submit(submitInfo, frame.inFlight.get()); // Present has no pipeline stage to wait on, so signal once everything in
// the submission is done.
vk::SemaphoreSubmitInfo signalInfo{};
signalInfo.semaphore = signalSemaphore;
signalInfo.stageMask = vk::PipelineStageFlagBits2::eAllCommands;
vk::CommandBufferSubmitInfo commandBufferInfo{};
commandBufferInfo.commandBuffer = frame.commandBuffer;
vk::SubmitInfo2 submitInfo{};
submitInfo.setWaitSemaphoreInfos(waitInfo);
submitInfo.setCommandBufferInfos(commandBufferInfo);
submitInfo.setSignalSemaphoreInfos(signalInfo);
this->device.getGraphicsQueue().submit2(submitInfo, frame.inFlight.get());
vk::Result result = this->swapchain->present( vk::Result result = this->swapchain->present(
this->device.getPresentQueue(), this->currentImageIndex, signalSemaphore); this->device.getPresentQueue(), this->currentImageIndex, signalSemaphore);
+1 -8
View File
@@ -55,8 +55,6 @@ void RenderGraph::transitionTo(ImageRef ref, const AccessState& next,
pending.barriers.push_back(makeImageBarrier( pending.barriers.push_back(makeImageBarrier(
imported.image->get(), imported.image->getSubresourceRange(), imported.image->get(), imported.image->getSubresourceRange(),
imported.state, next)); imported.state, next));
pending.srcStages |= imported.state.stage;
pending.dstStages |= next.stage;
imported.state = next; imported.state = next;
} else { } else {
// Same-layout read after read. Nothing to record, but a later write has // Same-layout read after read. Nothing to record, but a later write has
@@ -68,12 +66,7 @@ void RenderGraph::transitionTo(ImageRef ref, const AccessState& next,
void RenderGraph::flush(vk::CommandBuffer commandBuffer, void RenderGraph::flush(vk::CommandBuffer commandBuffer,
PendingBarriers& pending) { PendingBarriers& pending) {
if (pending.barriers.empty()) { recordImageBarriers(commandBuffer, pending.barriers);
return;
}
commandBuffer.pipelineBarrier(pending.srcStages, pending.dstStages, {},
nullptr, nullptr, pending.barriers);
pending = PendingBarriers{}; pending = PendingBarriers{};
} }
+1 -3
View File
@@ -104,9 +104,7 @@ class RenderGraph {
}; };
struct PendingBarriers { struct PendingBarriers {
std::vector<vk::ImageMemoryBarrier> barriers; std::vector<vk::ImageMemoryBarrier2> barriers;
vk::PipelineStageFlags srcStages;
vk::PipelineStageFlags dstStages;
}; };
void transitionTo(ImageRef ref, const AccessState& next, void transitionTo(ImageRef ref, const AccessState& next,
+49 -30
View File
@@ -14,10 +14,11 @@ constexpr std::array<Access, 7> ALL_ACCESSES = {
Access::Present, Access::Present,
}; };
constexpr vk::AccessFlags WRITE_ACCESSES = constexpr vk::AccessFlags2 WRITE_ACCESSES =
vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits2::eColorAttachmentWrite |
vk::AccessFlagBits::eDepthStencilAttachmentWrite | vk::AccessFlagBits2::eDepthStencilAttachmentWrite |
vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eTransferWrite; vk::AccessFlagBits2::eShaderStorageWrite |
vk::AccessFlagBits2::eTransferWrite;
} // namespace } // namespace
@@ -25,34 +26,37 @@ AccessState getAccessState(Access access) {
switch (access) { switch (access) {
case Access::ColorWrite: case Access::ColorWrite:
return {vk::ImageLayout::eColorAttachmentOptimal, return {vk::ImageLayout::eColorAttachmentOptimal,
vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits2::eColorAttachmentOutput,
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits2::eColorAttachmentRead |
vk::AccessFlagBits::eColorAttachmentWrite}; vk::AccessFlagBits2::eColorAttachmentWrite};
case Access::DepthWrite: case Access::DepthWrite:
return {vk::ImageLayout::eDepthStencilAttachmentOptimal, return {vk::ImageLayout::eDepthStencilAttachmentOptimal,
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eEarlyFragmentTests |
vk::PipelineStageFlagBits::eLateFragmentTests, vk::PipelineStageFlagBits2::eLateFragmentTests,
vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentRead |
vk::AccessFlagBits::eDepthStencilAttachmentWrite}; vk::AccessFlagBits2::eDepthStencilAttachmentWrite};
case Access::SampledRead: case Access::SampledRead:
return {vk::ImageLayout::eShaderReadOnlyOptimal, return {vk::ImageLayout::eShaderReadOnlyOptimal,
vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits2::eFragmentShader,
vk::AccessFlagBits::eShaderRead}; vk::AccessFlagBits2::eShaderSampledRead};
case Access::StorageReadWrite: case Access::StorageReadWrite:
return { return {vk::ImageLayout::eGeneral,
vk::ImageLayout::eGeneral, vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits2::eComputeShader,
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite}; vk::AccessFlagBits2::eShaderStorageRead |
vk::AccessFlagBits2::eShaderStorageWrite};
case Access::TransferSrc: case Access::TransferSrc:
return {vk::ImageLayout::eTransferSrcOptimal, return {vk::ImageLayout::eTransferSrcOptimal,
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits2::eTransfer,
vk::AccessFlagBits::eTransferRead}; vk::AccessFlagBits2::eTransferRead};
case Access::TransferDst: case Access::TransferDst:
return {vk::ImageLayout::eTransferDstOptimal, return {vk::ImageLayout::eTransferDstOptimal,
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits2::eTransfer,
vk::AccessFlagBits::eTransferWrite}; vk::AccessFlagBits2::eTransferWrite};
case Access::Present: case Access::Present:
// Presentation isn't a pipeline stage; the present waits on a semaphore
// instead, so the barrier into this state has nothing to block.
return {vk::ImageLayout::ePresentSrcKHR, return {vk::ImageLayout::ePresentSrcKHR,
vk::PipelineStageFlagBits::eBottomOfPipe, vk::AccessFlags{}}; vk::PipelineStageFlagBits2::eNone, vk::AccessFlags2{}};
} }
log::fatal("Unknown Access value."); log::fatal("Unknown Access value.");
@@ -83,22 +87,37 @@ bool needsBarrier(const AccessState& from, const AccessState& to) {
return from.layout != to.layout || writesImage(from) || writesImage(to); return from.layout != to.layout || writesImage(from) || writesImage(to);
} }
vk::ImageMemoryBarrier makeImageBarrier(vk::Image image, vk::ImageMemoryBarrier2 makeImageBarrier(vk::Image image,
const vk::ImageSubresourceRange& range, const vk::ImageSubresourceRange& range,
const AccessState& from, const AccessState& from,
const AccessState& to) { const AccessState& to) {
vk::ImageMemoryBarrier barrier{}; vk::ImageMemoryBarrier2 barrier{};
barrier.srcStageMask = from.stage;
barrier.dstStageMask = to.stage;
// 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;
barrier.oldLayout = from.layout; barrier.oldLayout = from.layout;
barrier.newLayout = to.layout; barrier.newLayout = to.layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = image; barrier.image = image;
barrier.subresourceRange = range; 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; return barrier;
} }
void recordImageBarriers(
vk::CommandBuffer commandBuffer,
vk::ArrayProxy<const vk::ImageMemoryBarrier2> barriers) {
if (barriers.empty()) {
return;
}
vk::DependencyInfo dependency{};
dependency.imageMemoryBarrierCount = barriers.size();
dependency.pImageMemoryBarriers = barriers.data();
commandBuffer.pipelineBarrier2(dependency);
}
} // namespace sophia } // namespace sophia
+16 -8
View File
@@ -25,8 +25,8 @@ enum class Access {
*/ */
struct AccessState { struct AccessState {
vk::ImageLayout layout = vk::ImageLayout::eUndefined; vk::ImageLayout layout = vk::ImageLayout::eUndefined;
vk::PipelineStageFlags stage = vk::PipelineStageFlagBits::eTopOfPipe; vk::PipelineStageFlags2 stage = vk::PipelineStageFlagBits2::eNone;
vk::AccessFlags access; vk::AccessFlags2 access;
}; };
/** /**
@@ -35,8 +35,8 @@ struct AccessState {
* just-acquired state has to use this as its source stage, otherwise the * just-acquired state has to use this as its source stage, otherwise the
* layout transition isn't ordered after the acquire. * layout transition isn't ordered after the acquire.
*/ */
inline constexpr vk::PipelineStageFlags SWAPCHAIN_ACQUIRE_STAGE = inline constexpr vk::PipelineStageFlags2 SWAPCHAIN_ACQUIRE_STAGE =
vk::PipelineStageFlagBits::eColorAttachmentOutput; vk::PipelineStageFlagBits2::eColorAttachmentOutput;
AccessState getAccessState(Access access); AccessState getAccessState(Access access);
@@ -55,9 +55,17 @@ bool writesImage(const AccessState& state);
*/ */
bool needsBarrier(const AccessState& from, const AccessState& to); bool needsBarrier(const AccessState& from, const AccessState& to);
vk::ImageMemoryBarrier makeImageBarrier(vk::Image image, /**
const vk::ImageSubresourceRange& range, * A barrier carrying its own stage masks, for recording with
const AccessState& from, * pipelineBarrier2. Barriers flushed together don't share stages.
const AccessState& to); */
vk::ImageMemoryBarrier2 makeImageBarrier(vk::Image image,
const vk::ImageSubresourceRange& range,
const AccessState& from,
const AccessState& to);
void recordImageBarriers(
vk::CommandBuffer commandBuffer,
vk::ArrayProxy<const vk::ImageMemoryBarrier2> barriers);
} // namespace sophia } // namespace sophia
+2 -3
View File
@@ -56,10 +56,9 @@ void Image::transitionLayout(vk::CommandBuffer commandBuffer,
AccessState to = getStateForLayout(newLayout); AccessState to = getStateForLayout(newLayout);
if (needsBarrier(from, to)) { if (needsBarrier(from, to)) {
vk::ImageMemoryBarrier barrier = vk::ImageMemoryBarrier2 barrier =
makeImageBarrier(this->get(), this->getSubresourceRange(), from, to); makeImageBarrier(this->get(), this->getSubresourceRange(), from, to);
commandBuffer.pipelineBarrier(from.stage, to.stage, {}, nullptr, nullptr, recordImageBarriers(commandBuffer, barrier);
barrier);
} }
this->layout = newLayout; this->layout = newLayout;