Compare commits
2 Commits
7d87503eba
...
ab7c850121
| Author | SHA1 | Date | |
|---|---|---|---|
| ab7c850121 | |||
| 1ba1442cc4 |
@@ -86,22 +86,28 @@ void RenderContext::endFrame() {
|
||||
FrameData& frame = this->frames[this->currentFrameIndex];
|
||||
frame.commandBuffer.end();
|
||||
|
||||
vk::Semaphore waitSemaphore = frame.imageAvailable.get();
|
||||
vk::Semaphore signalSemaphore =
|
||||
this->renderFinishedSemaphores.at(this->currentImageIndex).get();
|
||||
vk::PipelineStageFlags waitStage = SWAPCHAIN_ACQUIRE_STAGE;
|
||||
vk::CommandBuffer commandBuffer = frame.commandBuffer;
|
||||
|
||||
vk::SubmitInfo submitInfo{};
|
||||
submitInfo.waitSemaphoreCount = 1;
|
||||
submitInfo.pWaitSemaphores = &waitSemaphore;
|
||||
submitInfo.pWaitDstStageMask = &waitStage;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &commandBuffer;
|
||||
submitInfo.signalSemaphoreCount = 1;
|
||||
submitInfo.pSignalSemaphores = &signalSemaphore;
|
||||
vk::SemaphoreSubmitInfo waitInfo{};
|
||||
waitInfo.semaphore = frame.imageAvailable.get();
|
||||
waitInfo.stageMask = SWAPCHAIN_ACQUIRE_STAGE;
|
||||
|
||||
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(
|
||||
this->device.getPresentQueue(), this->currentImageIndex, signalSemaphore);
|
||||
|
||||
@@ -55,8 +55,6 @@ void RenderGraph::transitionTo(ImageRef ref, const AccessState& next,
|
||||
pending.barriers.push_back(makeImageBarrier(
|
||||
imported.image->get(), imported.image->getSubresourceRange(),
|
||||
imported.state, next));
|
||||
pending.srcStages |= imported.state.stage;
|
||||
pending.dstStages |= next.stage;
|
||||
imported.state = next;
|
||||
} else {
|
||||
// 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,
|
||||
PendingBarriers& pending) {
|
||||
if (pending.barriers.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
commandBuffer.pipelineBarrier(pending.srcStages, pending.dstStages, {},
|
||||
nullptr, nullptr, pending.barriers);
|
||||
recordImageBarriers(commandBuffer, pending.barriers);
|
||||
pending = PendingBarriers{};
|
||||
}
|
||||
|
||||
|
||||
@@ -104,9 +104,7 @@ class RenderGraph {
|
||||
};
|
||||
|
||||
struct PendingBarriers {
|
||||
std::vector<vk::ImageMemoryBarrier> barriers;
|
||||
vk::PipelineStageFlags srcStages;
|
||||
vk::PipelineStageFlags dstStages;
|
||||
std::vector<vk::ImageMemoryBarrier2> barriers;
|
||||
};
|
||||
|
||||
void transitionTo(ImageRef ref, const AccessState& next,
|
||||
|
||||
@@ -14,10 +14,11 @@ constexpr std::array<Access, 7> ALL_ACCESSES = {
|
||||
Access::Present,
|
||||
};
|
||||
|
||||
constexpr vk::AccessFlags WRITE_ACCESSES =
|
||||
vk::AccessFlagBits::eColorAttachmentWrite |
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentWrite |
|
||||
vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eTransferWrite;
|
||||
constexpr vk::AccessFlags2 WRITE_ACCESSES =
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite |
|
||||
vk::AccessFlagBits2::eDepthStencilAttachmentWrite |
|
||||
vk::AccessFlagBits2::eShaderStorageWrite |
|
||||
vk::AccessFlagBits2::eTransferWrite;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -25,34 +26,37 @@ AccessState getAccessState(Access access) {
|
||||
switch (access) {
|
||||
case Access::ColorWrite:
|
||||
return {vk::ImageLayout::eColorAttachmentOptimal,
|
||||
vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||
vk::AccessFlagBits::eColorAttachmentRead |
|
||||
vk::AccessFlagBits::eColorAttachmentWrite};
|
||||
vk::PipelineStageFlagBits2::eColorAttachmentOutput,
|
||||
vk::AccessFlagBits2::eColorAttachmentRead |
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite};
|
||||
case Access::DepthWrite:
|
||||
return {vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
||||
vk::PipelineStageFlagBits::eEarlyFragmentTests |
|
||||
vk::PipelineStageFlagBits::eLateFragmentTests,
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentRead |
|
||||
vk::AccessFlagBits::eDepthStencilAttachmentWrite};
|
||||
vk::PipelineStageFlagBits2::eEarlyFragmentTests |
|
||||
vk::PipelineStageFlagBits2::eLateFragmentTests,
|
||||
vk::AccessFlagBits2::eDepthStencilAttachmentRead |
|
||||
vk::AccessFlagBits2::eDepthStencilAttachmentWrite};
|
||||
case Access::SampledRead:
|
||||
return {vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::PipelineStageFlagBits::eFragmentShader,
|
||||
vk::AccessFlagBits::eShaderRead};
|
||||
vk::PipelineStageFlagBits2::eFragmentShader,
|
||||
vk::AccessFlagBits2::eShaderSampledRead};
|
||||
case Access::StorageReadWrite:
|
||||
return {
|
||||
vk::ImageLayout::eGeneral, vk::PipelineStageFlagBits::eComputeShader,
|
||||
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite};
|
||||
return {vk::ImageLayout::eGeneral,
|
||||
vk::PipelineStageFlagBits2::eComputeShader,
|
||||
vk::AccessFlagBits2::eShaderStorageRead |
|
||||
vk::AccessFlagBits2::eShaderStorageWrite};
|
||||
case Access::TransferSrc:
|
||||
return {vk::ImageLayout::eTransferSrcOptimal,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::AccessFlagBits::eTransferRead};
|
||||
vk::PipelineStageFlagBits2::eTransfer,
|
||||
vk::AccessFlagBits2::eTransferRead};
|
||||
case Access::TransferDst:
|
||||
return {vk::ImageLayout::eTransferDstOptimal,
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::AccessFlagBits::eTransferWrite};
|
||||
vk::PipelineStageFlagBits2::eTransfer,
|
||||
vk::AccessFlagBits2::eTransferWrite};
|
||||
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,
|
||||
vk::PipelineStageFlagBits::eBottomOfPipe, vk::AccessFlags{}};
|
||||
vk::PipelineStageFlagBits2::eNone, vk::AccessFlags2{}};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
vk::ImageMemoryBarrier makeImageBarrier(vk::Image image,
|
||||
const vk::ImageSubresourceRange& range,
|
||||
const AccessState& from,
|
||||
const AccessState& to) {
|
||||
vk::ImageMemoryBarrier barrier{};
|
||||
vk::ImageMemoryBarrier2 makeImageBarrier(vk::Image image,
|
||||
const vk::ImageSubresourceRange& range,
|
||||
const AccessState& from,
|
||||
const AccessState& to) {
|
||||
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.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;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -25,8 +25,8 @@ enum class Access {
|
||||
*/
|
||||
struct AccessState {
|
||||
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||
vk::PipelineStageFlags stage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||
vk::AccessFlags access;
|
||||
vk::PipelineStageFlags2 stage = vk::PipelineStageFlagBits2::eNone;
|
||||
vk::AccessFlags2 access;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -35,8 +35,8 @@ struct AccessState {
|
||||
* 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;
|
||||
inline constexpr vk::PipelineStageFlags2 SWAPCHAIN_ACQUIRE_STAGE =
|
||||
vk::PipelineStageFlagBits2::eColorAttachmentOutput;
|
||||
|
||||
AccessState getAccessState(Access access);
|
||||
|
||||
@@ -55,9 +55,17 @@ bool writesImage(const AccessState& state);
|
||||
*/
|
||||
bool needsBarrier(const AccessState& from, const AccessState& to);
|
||||
|
||||
vk::ImageMemoryBarrier makeImageBarrier(vk::Image image,
|
||||
const vk::ImageSubresourceRange& range,
|
||||
const AccessState& from,
|
||||
const AccessState& to);
|
||||
/**
|
||||
* A barrier carrying its own stage masks, for recording with
|
||||
* pipelineBarrier2. Barriers flushed together don't share stages.
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -57,6 +57,76 @@ void destroyDebugUtilsMessengerEXT(VkInstance instance,
|
||||
}
|
||||
}
|
||||
|
||||
using DeviceFeatureChain = vk::StructureChain<
|
||||
vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan11Features,
|
||||
vk::PhysicalDeviceVulkan12Features, vk::PhysicalDeviceVulkan13Features>;
|
||||
|
||||
// Single source of truth for the device features Sophia requires. Calls
|
||||
// fn(featureRef, name) for each one, so the same list drives both the
|
||||
// suitability check and device creation.
|
||||
template <typename Fn>
|
||||
static void forEachRequiredFeature(DeviceFeatureChain& chain, Fn&& fn) {
|
||||
auto& f10 = chain.get<vk::PhysicalDeviceFeatures2>().features;
|
||||
auto& f11 = chain.get<vk::PhysicalDeviceVulkan11Features>();
|
||||
auto& f12 = chain.get<vk::PhysicalDeviceVulkan12Features>();
|
||||
auto& f13 = chain.get<vk::PhysicalDeviceVulkan13Features>();
|
||||
|
||||
#define SOPHIA_FEATURE(features, name) fn(features.name, #name)
|
||||
// Indirect drawing
|
||||
SOPHIA_FEATURE(f10, multiDrawIndirect);
|
||||
SOPHIA_FEATURE(f10, drawIndirectFirstInstance);
|
||||
SOPHIA_FEATURE(f11, shaderDrawParameters);
|
||||
SOPHIA_FEATURE(f12, drawIndirectCount);
|
||||
|
||||
// Bindless descriptors
|
||||
SOPHIA_FEATURE(f12, descriptorIndexing);
|
||||
SOPHIA_FEATURE(f12, runtimeDescriptorArray);
|
||||
SOPHIA_FEATURE(f12, descriptorBindingPartiallyBound);
|
||||
SOPHIA_FEATURE(f12, descriptorBindingVariableDescriptorCount);
|
||||
SOPHIA_FEATURE(f12, descriptorBindingUpdateUnusedWhilePending);
|
||||
SOPHIA_FEATURE(f12, descriptorBindingSampledImageUpdateAfterBind);
|
||||
SOPHIA_FEATURE(f12, descriptorBindingStorageImageUpdateAfterBind);
|
||||
SOPHIA_FEATURE(f12, descriptorBindingStorageBufferUpdateAfterBind);
|
||||
SOPHIA_FEATURE(f12, shaderSampledImageArrayNonUniformIndexing);
|
||||
SOPHIA_FEATURE(f12, shaderStorageImageArrayNonUniformIndexing);
|
||||
SOPHIA_FEATURE(f12, shaderStorageBufferArrayNonUniformIndexing);
|
||||
|
||||
// Buffer pointers
|
||||
SOPHIA_FEATURE(f12, bufferDeviceAddress);
|
||||
SOPHIA_FEATURE(f12, scalarBlockLayout);
|
||||
|
||||
// Synchronization
|
||||
SOPHIA_FEATURE(f12, timelineSemaphore);
|
||||
SOPHIA_FEATURE(f13, synchronization2);
|
||||
|
||||
// Rendering
|
||||
SOPHIA_FEATURE(f13, dynamicRendering);
|
||||
#undef SOPHIA_FEATURE
|
||||
}
|
||||
|
||||
static bool checkDeviceFeatureSupport(const vk::PhysicalDevice& device) {
|
||||
vk::PhysicalDeviceProperties properties = device.getProperties();
|
||||
if (properties.apiVersion < SOPHIA_VULKAN_API_VERSION) {
|
||||
log::verbose("Skipping ", properties.deviceName,
|
||||
": Vulkan 1.3 not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto supported = device.getFeatures2<
|
||||
vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan11Features,
|
||||
vk::PhysicalDeviceVulkan12Features, vk::PhysicalDeviceVulkan13Features>();
|
||||
|
||||
bool allSupported = true;
|
||||
forEachRequiredFeature(supported, [&](vk::Bool32 has, const char* name) {
|
||||
if (!has) {
|
||||
log::verbose("Skipping ", properties.deviceName,
|
||||
": missing device feature ", name);
|
||||
allSupported = false;
|
||||
}
|
||||
});
|
||||
return allSupported;
|
||||
}
|
||||
|
||||
Device::Device(Window* window)
|
||||
: headless{window == nullptr},
|
||||
enabledExtensions{headless ? std::vector<const char*>{}
|
||||
@@ -386,6 +456,10 @@ bool Device::isPhysicalDeviceSuitable(const vk::PhysicalDevice& device) {
|
||||
|
||||
bool extensionsSupported = checkDeviceExtensionSupport(device);
|
||||
|
||||
if (!checkDeviceFeatureSupport(device)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->headless) {
|
||||
return indices.graphicsFamily.has_value() && extensionsSupported;
|
||||
}
|
||||
@@ -443,15 +517,17 @@ void Device::createLogicalDevice() {
|
||||
{vk::DeviceQueueCreateFlags(), queueFamily, 1, &queuePriority});
|
||||
}
|
||||
|
||||
vk::PhysicalDeviceVulkan13Features vulkan13Features{};
|
||||
vulkan13Features.dynamicRendering = vk::True;
|
||||
DeviceFeatureChain enabledFeatures;
|
||||
forEachRequiredFeature(enabledFeatures, [](vk::Bool32& feature, const char*) {
|
||||
feature = vk::True;
|
||||
});
|
||||
|
||||
auto deviceFeatures = vk::PhysicalDeviceFeatures();
|
||||
auto createInfo = vk::DeviceCreateInfo(
|
||||
vk::DeviceCreateFlags(), static_cast<uint32_t>(queueCreateInfos.size()),
|
||||
queueCreateInfos.data());
|
||||
createInfo.pEnabledFeatures = &deviceFeatures;
|
||||
createInfo.pNext = &vulkan13Features;
|
||||
// Core features are passed through PhysicalDeviceFeatures2 in the pNext
|
||||
// chain, so pEnabledFeatures must stay null.
|
||||
createInfo.pNext = &enabledFeatures.get<vk::PhysicalDeviceFeatures2>();
|
||||
|
||||
createInfo.enabledExtensionCount =
|
||||
static_cast<uint32_t>(this->enabledExtensions.size());
|
||||
@@ -473,6 +549,7 @@ void Device::createLogicalDevice() {
|
||||
|
||||
void Device::createAllocator() {
|
||||
VmaAllocatorCreateInfo createInfo = {};
|
||||
createInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
|
||||
createInfo.vulkanApiVersion = SOPHIA_VULKAN_API_VERSION;
|
||||
createInfo.instance = this->instance.get();
|
||||
createInfo.physicalDevice = this->physicalDevice;
|
||||
|
||||
@@ -56,10 +56,9 @@ void Image::transitionLayout(vk::CommandBuffer commandBuffer,
|
||||
AccessState to = getStateForLayout(newLayout);
|
||||
|
||||
if (needsBarrier(from, to)) {
|
||||
vk::ImageMemoryBarrier barrier =
|
||||
vk::ImageMemoryBarrier2 barrier =
|
||||
makeImageBarrier(this->get(), this->getSubresourceRange(), from, to);
|
||||
commandBuffer.pipelineBarrier(from.stage, to.stage, {}, nullptr, nullptr,
|
||||
barrier);
|
||||
recordImageBarriers(commandBuffer, barrier);
|
||||
}
|
||||
|
||||
this->layout = newLayout;
|
||||
|
||||
Reference in New Issue
Block a user