Skip to content

Commit

Permalink
Move descriptor binding code to a single pipeline function.
Browse files Browse the repository at this point in the history
  • Loading branch information
Flone-dnb committed Jul 27, 2024
1 parent 36d2c88 commit 5f36beb
Show file tree
Hide file tree
Showing 16 changed files with 461 additions and 596 deletions.
1 change: 1 addition & 0 deletions src/engine_lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ set(PROJECT_SOURCES
public/misc/Error.h
private/misc/UniqueValueGenerator.h
private/misc/UniqueValueGenerator.cpp
private/misc/StdHashes.hpp
private/window/Window.cpp
public/game/Window.h
private/game/GameInstance.cpp
Expand Down
34 changes: 34 additions & 0 deletions src/engine_lib/private/misc/StdHashes.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

// Standard.
#include <string_view>
#include <cstddef>

namespace ne {
/** Used for `find` in `map` to work with `std::string_view` while the key is `std::string`. */
struct StdStringHash {
/** Tag. */
using is_transparent = void;

/**
* Calculates hash.
* @param pText Text to hash.
* @return Hash.
*/
std::size_t operator()(const char* pText) const { return std::hash<std::string_view>{}(pText); }

/**
* Calculates hash.
* @param text Text to hash.
* @return Hash.
*/
std::size_t operator()(std::string_view text) const { return std::hash<std::string_view>{}(text); }

/**
* Calculates hash.
* @param text Text to hash.
* @return Hash.
*/
std::size_t operator()(std::string const& text) const { return std::hash<std::string_view>{}(text); }
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace ne {
Error error(std::format(
"\"{}\" index manager is being destroyed but there are still {} registered shadow map "
"handle(s) alive",
*getShaderArrayResourceName(),
getShaderArrayResourceName(),
mtxRegisteredShadowMaps.second.size()));
error.showError();
return; // don't throw in destructor
Expand Down Expand Up @@ -106,7 +106,7 @@ namespace ne {
return Error(std::format(
"\"{}\" was requested to register a shadow map handle \"{}\" but this shadow map handle was "
"already registered",
*getShaderArrayResourceName(),
getShaderArrayResourceName(),
pDepthTexture->getResourceName()));
}

Expand All @@ -115,7 +115,7 @@ namespace ne {
return Error(std::format(
"\"{}\" was requested to register a shadow map handle \"{}\" but the GPU resource of this "
"shadow map handle already has an SRV binded to it which is unexpected",
*getShaderArrayResourceName(),
getShaderArrayResourceName(),
pSrvResource->getResourceName()));
}

Expand Down Expand Up @@ -154,7 +154,7 @@ namespace ne {
return Error(std::format(
"\"{}\" index manager is unable to unregister the specified shadow map handle because it was "
"not registered previously",
*getShaderArrayResourceName()));
getShaderArrayResourceName()));
}

// Remove handle.
Expand Down Expand Up @@ -203,7 +203,7 @@ namespace ne {
"\"{}\" index manager failed to calculate descriptor offset from range start (descriptor "
"offset from heap: {}, range offset from heap: {}) because resulting descriptor offset from "
"range start is negative: {})",
*getShaderArrayResourceName(),
getShaderArrayResourceName(),
iDescriptorOffsetFromHeapStart,
iRangeStartFromHeapStart,
iDescriptorOffsetFromRangeStart));
Expand Down
71 changes: 71 additions & 0 deletions src/engine_lib/private/render/general/pipeline/PipelineManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,81 @@
#include "shader/general/resources/LightingShaderResourceManager.h"
#include "shader/general/resources/cpuwrite/ShaderCpuWriteResourceManager.h"
#include "shader/general/resources/texture/ShaderTextureResourceManager.h"
#include "render/vulkan/pipeline/VulkanPipeline.h"

namespace ne {
PipelineManager::PipelineManager(Renderer* pRenderer) { this->pRenderer = pRenderer; }

std::optional<Error> PipelineManager::bindBuffersToAllVulkanPipelinesIfUsed(
const std::array<GpuResource*, FrameResourcesManager::getFrameResourcesCount()>& vResources,
const std::string& sShaderResourceName,
VkDescriptorType descriptorType) {
std::scoped_lock pipelinesGuard(mtxGraphicsPipelines.first);

// Iterate over graphics pipelines of all types.
for (auto& pipelinesOfSpecificType : mtxGraphicsPipelines.second.vPipelineTypes) {

// Iterate over all active shader combinations.
for (const auto& [sShaderNames, pipelines] : pipelinesOfSpecificType) {

// Iterate over all active unique material macros combinations.
for (const auto& [materialMacros, pPipeline] : pipelines.shaderPipelines) {
// Get pipeline.
const auto pVulkanPipeline = dynamic_cast<VulkanPipeline*>(pPipeline.get());
if (pVulkanPipeline == nullptr) [[unlikely]] {
return Error("expected a Vulkan pipeline");
}

// Rebind resources to pipeline.
auto optionalError =
pVulkanPipeline->bindBuffersIfUsed(vResources, sShaderResourceName, descriptorType);
if (optionalError.has_value()) [[unlikely]] {
optionalError->addCurrentLocationToErrorStack();
return optionalError;
}
}
}
}

return {};
}

std::optional<Error> PipelineManager::bindImageToAllVulkanPipelinesIfUsed(
GpuResource* pImageResourceToBind,
const std::string& sShaderResourceName,
VkDescriptorType descriptorType,
VkImageLayout imageLayout,
VkSampler pSampler) {
std::scoped_lock pipelinesGuard(mtxGraphicsPipelines.first);

// Iterate over graphics pipelines of all types.
for (auto& pipelinesOfSpecificType : mtxGraphicsPipelines.second.vPipelineTypes) {

// Iterate over all active shader combinations.
for (const auto& [sShaderNames, pipelines] : pipelinesOfSpecificType) {

// Iterate over all active unique material macros combinations.
for (const auto& [materialMacros, pPipeline] : pipelines.shaderPipelines) {
// Get pipeline.
const auto pVulkanPipeline = dynamic_cast<VulkanPipeline*>(pPipeline.get());
if (pVulkanPipeline == nullptr) [[unlikely]] {
return Error("expected a Vulkan pipeline");
}

// Rebind image to pipeline.
auto optionalError = pVulkanPipeline->bindImageIfUsed(
pImageResourceToBind, sShaderResourceName, descriptorType, imageLayout, pSampler);
if (optionalError.has_value()) [[unlikely]] {
optionalError->addCurrentLocationToErrorStack();
return optionalError;
}
}
}
}

return {};
}

DelayedPipelineResourcesCreation
PipelineManager::clearGraphicsPipelinesInternalResourcesAndDelayRestoring() {
return DelayedPipelineResourcesCreation(this);
Expand Down
41 changes: 41 additions & 0 deletions src/engine_lib/private/render/general/pipeline/PipelineManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
#include "render/general/pipeline/PipelineType.hpp"
#include "render/general/pipeline/PipelineConfiguration.h"
#include "render/general/pipeline/PipelineRegistry.hpp"
#include "render/general/resources/frame/FrameResourcesManager.h"

// External.
#include "vulkan/vulkan_core.h"

namespace ne {
class Renderer;
Expand All @@ -26,6 +30,9 @@ namespace ne {
/**
* RAII class that once acquired waits for the GPU to finish work up to this point, pauses the rendering,
* releases all internal resources from all graphics pipelines, then in destructor restores them.
*
* @remark This can be useful when some render resource (like MSAA render target) has changed or about to
* be changed so that we can make sure all pipelines are refreshed to use the new/changed resource.
*/
class DelayedPipelineResourcesCreation {
public:
Expand Down Expand Up @@ -112,6 +119,40 @@ namespace ne {
PipelineManager(const PipelineManager&) = delete;
PipelineManager& operator=(const PipelineManager&) = delete;

/**
* Binds the specified GPU resources (buffers, not images) to all Vulkan pipelines that use the
* specified shader resource.
*
* @param vResources Resources to bind.
* @param sShaderResourceName Name of the shader resource (name from shader code) to bind to.
* @param descriptorType Type of the descriptor to bind.
*
* @return Error if something went wrong.
*/
[[nodiscard]] std::optional<Error> bindBuffersToAllVulkanPipelinesIfUsed(
const std::array<GpuResource*, FrameResourcesManager::getFrameResourcesCount()>& vResources,
const std::string& sShaderResourceName,
VkDescriptorType descriptorType);

/**
* Binds the specified GPU image (not buffer) to all Vulkan pipelines that use the specified shader
* resource.
*
* @param pImageResourceToBind Image to bind.
* @param sShaderResourceName Name of the shader resource (name from shader code) to bind to.
* @param descriptorType Type of the descriptor to bind.
* @param imageLayout Layout of the image.
* @param pSampler Sampler to use for the image.
*
* @return Error if something went wrong.
*/
[[nodiscard]] std::optional<Error> bindImageToAllVulkanPipelinesIfUsed(
GpuResource* pImageResourceToBind,
const std::string& sShaderResourceName,
VkDescriptorType descriptorType,
VkImageLayout imageLayout,
VkSampler pSampler);

/**
* Returns a RAII object that once acquired waits for the GPU to finish work up to this point,
* pauses the rendering, releases all internal resources from all graphics pipelines,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ namespace ne {
return Error("unsupported renderer");
}

std::string const* ShadowMapArrayIndexManager::getShaderArrayResourceName() {
return &sShaderArrayResourceName;
std::string_view ShadowMapArrayIndexManager::getShaderArrayResourceName() {
return sShaderArrayResourceName;
}

Renderer* ShadowMapArrayIndexManager::getRenderer() const { return pRenderer; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,9 @@ namespace ne {
/**
* Returns name of the array (defined in shaders) that this manager controls.
*
* @warning Do not delete (free) returned pointer.
*
* @return Array name.
*/
std::string const* getShaderArrayResourceName();
std::string_view getShaderArrayResourceName();

/**
* Returns renderer.
Expand Down
127 changes: 126 additions & 1 deletion src/engine_lib/private/render/vulkan/pipeline/VulkanPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,131 @@ namespace ne {
return pPipeline;
}

std::optional<Error> VulkanPipeline::bindBuffersIfUsed(
const std::array<GpuResource*, FrameResourcesManager::getFrameResourcesCount()>& vResources,
const std::string& sShaderResourceName,
VkDescriptorType descriptorType) {
std::scoped_lock guard(mtxInternalResources.first);

// See if this pipeline uses this resource.
auto resourceIt = mtxInternalResources.second.resourceBindings.find(sShaderResourceName);
if (resourceIt == mtxInternalResources.second.resourceBindings.end()) { // don't mark as unlikely
// That's OK, a common situation, just return.
return {};
}
const auto iBindingIndex = resourceIt->second;

// Gather VkBuffers to bind.
std::array<VkBuffer, FrameResourcesManager::getFrameResourcesCount()> vVkBuffersToBind;
for (size_t i = 0; i < vVkBuffersToBind.size(); i++) {
// Convert to Vulkan resource.
const auto pVulkanResource = dynamic_cast<VulkanResource*>(vResources[i]);
if (pVulkanResource == nullptr) [[unlikely]] {
return Error("expected a Vulkan resource");
}

// Save buffer resource.
vVkBuffersToBind[i] = pVulkanResource->getInternalBufferResource();
}

// Get renderer.
const auto pVulkanRenderer = dynamic_cast<VulkanRenderer*>(getRenderer());
if (pVulkanRenderer == nullptr) [[unlikely]] {
return Error("expected a vulkan renderer");
}

// Get logical device.
const auto pLogicalDevice = pVulkanRenderer->getLogicalDevice();
if (pLogicalDevice == nullptr) [[unlikely]] {
return Error("expected logical device to be valid");
}

// Update one descriptor in set per frame resource.
for (unsigned int i = 0; i < FrameResourcesManager::getFrameResourcesCount(); i++) {
// Prepare info to bind storage buffer slot to descriptor.
VkDescriptorBufferInfo bufferInfo{};
bufferInfo.buffer = vVkBuffersToBind[i];
bufferInfo.offset = 0;
bufferInfo.range = vResources[0]->getElementCount() * vResources[0]->getElementSizeInBytes();

// Bind reserved space to descriptor.
VkWriteDescriptorSet descriptorUpdateInfo{};
descriptorUpdateInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorUpdateInfo.dstSet =
mtxInternalResources.second.vDescriptorSets[i]; // descriptor set to update
descriptorUpdateInfo.dstBinding = iBindingIndex; // descriptor binding index
descriptorUpdateInfo.dstArrayElement = 0; // first descriptor in array to update
descriptorUpdateInfo.descriptorType = descriptorType;
descriptorUpdateInfo.descriptorCount = 1; // how much descriptors in array to update
descriptorUpdateInfo.pBufferInfo = &bufferInfo; // descriptor refers to buffer data

// Update descriptor.
vkUpdateDescriptorSets(pLogicalDevice, 1, &descriptorUpdateInfo, 0, nullptr);
}

return {};
}

std::optional<Error> VulkanPipeline::bindImageIfUsed(
GpuResource* pImageResourceToBind,
std::string_view sShaderResourceName,
VkDescriptorType descriptorType,
VkImageLayout imageLayout,
VkSampler pSampler) {
std::scoped_lock guard(mtxInternalResources.first);

// See if this pipeline uses this resource.
auto resourceIt = mtxInternalResources.second.resourceBindings.find(sShaderResourceName);
if (resourceIt == mtxInternalResources.second.resourceBindings.end()) {
// That's OK, a common situation, just return.
return {};
}
const auto iBindingIndex = resourceIt->second;

// Get renderer.
const auto pVulkanRenderer = dynamic_cast<VulkanRenderer*>(getRenderer());
if (pVulkanRenderer == nullptr) [[unlikely]] {
return Error("expected a vulkan renderer");
}

// Get logical device.
const auto pLogicalDevice = pVulkanRenderer->getLogicalDevice();
if (pLogicalDevice == nullptr) [[unlikely]] {
return Error("expected logical device to be valid");
}

// Convert to Vulkan resource.
const auto pVulkanImage = dynamic_cast<VulkanResource*>(pImageResourceToBind);
if (pVulkanImage == nullptr) [[unlikely]] {
return Error("expected a Vulkan resource");
}

// Update one descriptor in set per frame resource.
for (unsigned int i = 0; i < FrameResourcesManager::getFrameResourcesCount(); i++) {
// Prepare info to bind an image view to descriptor.
VkDescriptorImageInfo imageInfo{};
imageInfo.imageLayout = imageLayout;
imageInfo.imageView = pVulkanImage->getInternalImageView();
imageInfo.sampler = pSampler;

// Bind reserved space to descriptor.
VkWriteDescriptorSet descriptorUpdateInfo{};
descriptorUpdateInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorUpdateInfo.dstSet =
mtxInternalResources.second.vDescriptorSets[i]; // descriptor set to update
descriptorUpdateInfo.dstBinding = iBindingIndex; // descriptor binding index
descriptorUpdateInfo.dstArrayElement = 0; // first descriptor in array to update
descriptorUpdateInfo.descriptorType = descriptorType;
descriptorUpdateInfo.descriptorCount = 1; // how much descriptors in array to update
descriptorUpdateInfo.pImageInfo = &imageInfo; // descriptor refers to image data

// Update descriptor.
vkUpdateDescriptorSets(pLogicalDevice, 1, &descriptorUpdateInfo, 0, nullptr);
}

return {};
}

std::optional<Error> VulkanPipeline::releaseInternalResources() {
std::scoped_lock resourcesGuard(mtxInternalResources.first);

Expand Down Expand Up @@ -171,7 +296,7 @@ namespace ne {

std::variant<VkPushConstantRange, Error> VulkanPipeline::definePushConstants(
const std::unordered_map<std::string, size_t>& pushConstantUintFieldOffsets,
const std::unordered_map<std::string, uint32_t>& resourceBindings) {
const std::unordered_map<std::string, uint32_t, StdStringHash, std::equal_to<>>& resourceBindings) {
std::scoped_lock guard(mtxInternalResources.first);

// Make sure push constants are specified.
Expand Down
Loading

0 comments on commit 5f36beb

Please sign in to comment.