Skip to content

Commit de4991b

Browse files
committed
update UI shader codegen - allow for even more freedom and gen NBL_TEXTURES_BINDING, NBL_SAMPLER_STATES_BINDING, NBL_RESOURCES_COUNT & NBL_RESOURCES_SET. Validate external descriptor set layout if given (redirects, validate required textures & samplers binding), support generating default one if nullptr passed. Add more comments for API usage, update examples_tests submodule
1 parent 59663ec commit de4991b

File tree

4 files changed

+190
-37
lines changed

4 files changed

+190
-37
lines changed

examples_tests

include/nbl/ext/ImGui/ImGui.h

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,34 @@ class UI final : public core::IReferenceCounted
2323
EBC_COUNT,
2424
};
2525

26-
nbl::core::smart_refctd_ptr<typename COMPOSE_T> streamingTDBufferST; //! composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS]
26+
//! composed buffer layout is [EBC_DRAW_INDIRECT_STRUCTURES] [EBC_ELEMENT_STRUCTURES] [EBC_INDEX_BUFFERS] [EBC_VERTEX_BUFFERS]
27+
nbl::core::smart_refctd_ptr<typename COMPOSE_T> streamingTDBufferST;
2728
};
2829

2930
struct S_CREATION_PARAMETERS
3031
{
3132
struct S_RESOURCE_PARAMETERS
3233
{
33-
uint32_t setIx, bindingIx;
34+
nbl::video::IGPUDescriptorSetLayout* const descriptorSetLayout = nullptr; //! optional, if not provided then default layout will be created declaring:
35+
const uint32_t setIx = 0u, // -> following set for ImGUI resources which consists of textures (ImGUI font atlas + optional user provided textures) & corresponding *immutable* samplers
36+
count = 0x45u, // -> common amount of resources (since for a texture there is a sampler)
37+
texturesBindingIx = 0u, // -> binding index for textures
38+
samplersBindingIx = 1u; // -> binding index for samplers
39+
40+
using binding_flags_t = nbl::video::IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS;
41+
static constexpr auto TEXTURES_REQUIRED_CREATE_FLAGS = nbl::core::bitflag(binding_flags_t::ECF_UPDATE_AFTER_BIND_BIT) | binding_flags_t::ECF_PARTIALLY_BOUND_BIT | binding_flags_t::ECF_UPDATE_UNUSED_WHILE_PENDING_BIT; //! required flags
42+
static constexpr auto SAMPLERS_REQUIRED_CREATE_FLAGS = nbl::core::bitflag(binding_flags_t::ECF_NONE); //! required flags
43+
static constexpr auto RESOURCES_REQUIRED_STAGE_FLAGS = nbl::asset::IShader::E_SHADER_STAGE::ESS_FRAGMENT; //! required stage
3444
};
3545

36-
nbl::asset::IAssetManager* const assetManager; //! required
37-
nbl::video::IUtilities* const utilities; //! required
38-
nbl::video::IQueue* const transfer; //! required
39-
nbl::video::IGPURenderpass* const renderpass; //! required
40-
uint32_t subpassIx = 0u; //! optional, default value used if not provided
41-
nbl::video::IGPUDescriptorSetLayout* const descriptorSetLayout = nullptr; //! optional, default layout used if not provided [STILL TODO, currently its assumed its not nullptr!]
42-
nbl::video::IGPUPipelineCache* const pipelineCache = nullptr; //! optional, no cache used if not provided
43-
typename MDI::COMPOSE_T* const streamingMDIBuffer = nullptr; //! optional, default MDI buffer allocated if not provided
44-
S_RESOURCE_PARAMETERS texturesInfo = { .setIx = 0u, .bindingIx = 0u }, //! optional, default values used if not provided
45-
samplerStateInfo = { .setIx = 0u, .bindingIx = 1u }; //! optional, default values used if not provided
46+
nbl::asset::IAssetManager* const assetManager; //! required
47+
nbl::video::IUtilities* const utilities; //! required
48+
nbl::video::IQueue* const transfer; //! required
49+
nbl::video::IGPURenderpass* const renderpass; //! required
50+
uint32_t subpassIx = 0u; //! optional, default value used if not provided
51+
S_RESOURCE_PARAMETERS resources; //! optional, default parameters used if not provided
52+
nbl::video::IGPUPipelineCache* const pipelineCache = nullptr; //! optional, no cache used if not provided
53+
typename MDI::COMPOSE_T* const streamingMDIBuffer = nullptr; //! optional, default MDI buffer allocated if not provided
4654
};
4755

4856
//! parameters which may change every frame, used with the .update call to interact with ImGuiIO; we require a very *required* minimum - if you need to cover more IO options simply get the IO with ImGui::GetIO() to customize them (they all have default values you can change before calling the .update)
@@ -89,6 +97,9 @@ class UI final : public core::IReferenceCounted
8997
//! mdi streaming buffer
9098
inline typename MDI::COMPOSE_T* getStreamingBuffer() { return m_mdi.streamingTDBufferST.get(); }
9199

100+
//! ImGUI graphics pipeline
101+
inline nbl::video::IGPUGraphicsPipeline* getPipeline() { return pipeline.get(); }
102+
92103
//! ImGUI context getter, you are supposed to cast it, eg. reinterpret_cast<ImGuiContext*>(this->getContext());
93104
void* getContext();
94105
private:

src/nbl/ext/ImGui/ImGui.cpp

Lines changed: 158 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,93 @@ namespace nbl::ext::imgui
3232
}
3333
};
3434

35-
auto createPipelineLayout = [&](const uint32_t setIx) -> core::smart_refctd_ptr<IGPUPipelineLayout>
35+
auto createPipelineLayout = [&](const uint32_t setIx, core::smart_refctd_ptr<IGPUDescriptorSetLayout> descriptorSetLayout) -> core::smart_refctd_ptr<IGPUPipelineLayout>
3636
{
3737
switch (setIx)
3838
{
3939
case 0u:
40-
return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, core::smart_refctd_ptr<video::IGPUDescriptorSetLayout>(m_creationParams.descriptorSetLayout));
40+
return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, smart_refctd_ptr(descriptorSetLayout));
4141
case 1u:
42-
return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, core::smart_refctd_ptr<video::IGPUDescriptorSetLayout>(m_creationParams.descriptorSetLayout));
42+
return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, smart_refctd_ptr(descriptorSetLayout));
4343
case 2u:
44-
return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, core::smart_refctd_ptr<video::IGPUDescriptorSetLayout>(m_creationParams.descriptorSetLayout));
44+
return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, smart_refctd_ptr(descriptorSetLayout));
4545
case 3u:
46-
return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, nullptr, core::smart_refctd_ptr<video::IGPUDescriptorSetLayout>(m_creationParams.descriptorSetLayout));
46+
return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, nullptr, nullptr, nullptr, smart_refctd_ptr(descriptorSetLayout));
4747
default:
4848
assert(false);
4949
return nullptr;
5050
}
5151
};
5252

53-
auto pipelineLayout = createPipelineLayout(m_creationParams.texturesInfo.setIx); //! its okay to take the Ix from textures info because we force user to use the same set for both textures and samplers [also validated at this point]
53+
auto pipelineLayout = createPipelineLayout(m_creationParams.resources.setIx,
54+
[&]() -> smart_refctd_ptr<IGPUDescriptorSetLayout>
55+
{
56+
if (m_creationParams.resources.descriptorSetLayout)
57+
return smart_refctd_ptr<IGPUDescriptorSetLayout>(m_creationParams.resources.descriptorSetLayout); // provided? good we just use it, we are validated at this point
58+
else
59+
{
60+
//! if default descriptor set layout is not provided, we create it here
61+
smart_refctd_ptr<IGPUSampler> fontAtlasUISampler, userTexturesSampler;
62+
63+
using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS;
64+
{
65+
IGPUSampler::SParams params;
66+
params.AnisotropicFilter = 1u;
67+
params.TextureWrapU = ISampler::ETC_REPEAT;
68+
params.TextureWrapV = ISampler::ETC_REPEAT;
69+
params.TextureWrapW = ISampler::ETC_REPEAT;
70+
71+
fontAtlasUISampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params);
72+
fontAtlasUISampler->setObjectDebugName("Nabla default ImGUI font UI sampler");
73+
}
74+
75+
{
76+
IGPUSampler::SParams params;
77+
params.MinLod = 0.f;
78+
params.MaxLod = 0.f;
79+
params.TextureWrapU = ISampler::ETC_CLAMP_TO_EDGE;
80+
params.TextureWrapV = ISampler::ETC_CLAMP_TO_EDGE;
81+
params.TextureWrapW = ISampler::ETC_CLAMP_TO_EDGE;
82+
83+
userTexturesSampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params);
84+
userTexturesSampler->setObjectDebugName("Nabla default ImGUI custom texture sampler");
85+
}
86+
87+
//! note we use immutable separate samplers and they are part of the descriptor set layout
88+
std::vector<core::smart_refctd_ptr<IGPUSampler>> immutableSamplers(m_creationParams.resources.count);
89+
for (auto& it : immutableSamplers)
90+
it = smart_refctd_ptr(userTexturesSampler);
91+
92+
immutableSamplers[nbl::ext::imgui::UI::NBL_FONT_ATLAS_TEX_ID] = smart_refctd_ptr(fontAtlasUISampler);
93+
94+
const IGPUDescriptorSetLayout::SBinding bindings[] =
95+
{
96+
{
97+
.binding = m_creationParams.resources.texturesBindingIx,
98+
.type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE,
99+
.createFlags = m_creationParams.resources.TEXTURES_REQUIRED_CREATE_FLAGS,
100+
.stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS,
101+
.count = m_creationParams.resources.count
102+
},
103+
{
104+
.binding = m_creationParams.resources.samplersBindingIx,
105+
.type = IDescriptor::E_TYPE::ET_SAMPLER,
106+
.createFlags = m_creationParams.resources.SAMPLERS_REQUIRED_CREATE_FLAGS,
107+
.stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS,
108+
.count = m_creationParams.resources.count,
109+
.immutableSamplers = immutableSamplers.data()
110+
}
111+
};
112+
113+
return m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout(bindings);
114+
}
115+
}());
116+
117+
if (!pipelineLayout)
118+
{
119+
m_creationParams.utilities->getLogger()->log("Could not create pipeline layout!", system::ILogger::ELL_ERROR);
120+
assert(false);
121+
}
54122

55123
struct
56124
{
@@ -61,8 +129,8 @@ namespace nbl::ext::imgui
61129
constexpr std::string_view NBL_ARCHIVE_ALIAS = "nbl/ext/imgui/shaders";
62130

63131
auto system = smart_refctd_ptr<system::ISystem>(m_creationParams.assetManager->getSystem()); //! proxy the system, we will touch it gently
64-
auto archive = make_smart_refctd_ptr<nbl::ext::imgui::builtin::CArchive>(smart_refctd_ptr<system::ILogger>(m_creationParams.utilities->getLogger())); //! we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount only to compile our extension sources then unmount the archive
65-
auto compiler = make_smart_refctd_ptr<CHLSLCompiler>(smart_refctd_ptr(system)); //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV
132+
auto archive = make_smart_refctd_ptr<nbl::ext::imgui::builtin::CArchive>(smart_refctd_ptr<system::ILogger>(m_creationParams.utilities->getLogger())); //! we should never assume user will mount our internal archive since its the extension and not user's job to do it, hence we mount only to compile our extension sources then unmount the archive
133+
auto compiler = make_smart_refctd_ptr<CHLSLCompiler>(smart_refctd_ptr(system)); //! note we are out of default logical device's compiler set scope so also a few special steps are required to compile our extension shaders to SPIRV
66134
auto includeFinder = make_smart_refctd_ptr<IShaderCompiler::CIncludeFinder>(smart_refctd_ptr(system));
67135
auto includeLoader = includeFinder->getDefaultFileSystemLoader();
68136
includeFinder->addSearchPath(NBL_ARCHIVE_ALIAS.data(), includeLoader);
@@ -110,10 +178,10 @@ namespace nbl::ext::imgui
110178
std::stringstream stream;
111179

112180
stream << "// -> this code has been autogenerated with Nabla ImGUI extension\n"
113-
<< "#define NBL_TEXTURES_BINDING " << m_creationParams.texturesInfo.bindingIx << "\n"
114-
<< "#define NBL_TEXTURES_SET " << m_creationParams.texturesInfo.setIx << "\n"
115-
<< "#define NBL_SAMPLER_STATES_BINDING " << m_creationParams.samplerStateInfo.bindingIx << "\n"
116-
<< "#define NBL_SAMPLER_STATES_SET " << m_creationParams.samplerStateInfo.setIx << "\n"
181+
<< "#define NBL_TEXTURES_BINDING " << m_creationParams.resources.texturesBindingIx << "\n"
182+
<< "#define NBL_SAMPLER_STATES_BINDING " << m_creationParams.resources.samplersBindingIx << "\n"
183+
<< "#define NBL_RESOURCES_SET " << m_creationParams.resources.setIx << "\n"
184+
<< "#define NBL_RESOURCES_COUNT " << m_creationParams.resources.count << "\n"
117185
<< "// <-\n\n";
118186

119187
const auto newCode = stream.str() + std::string(code);
@@ -606,6 +674,81 @@ namespace nbl::ext::imgui
606674
UI::UI(S_CREATION_PARAMETERS&& params)
607675
: m_creationParams(std::move(params))
608676
{
677+
auto validateResourcesInfo = [&]() -> bool
678+
{
679+
if (m_creationParams.resources.descriptorSetLayout) // provided? we will validate your layout
680+
{
681+
auto validateResource = [&]<IDescriptor::E_TYPE descriptorType>()
682+
{
683+
constexpr std::string_view typeLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "ET_SAMPLED_IMAGE" : "ET_SAMPLER",
684+
ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx";
685+
686+
const auto& redirect = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.descriptorSetLayout->getDescriptorRedirect(descriptorType) : m_creationParams.resources.descriptorSetLayout->getImmutableSamplerRedirect();
687+
688+
const auto bindingCount = redirect.getBindingCount();
689+
690+
if (!bindingCount)
691+
{
692+
m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has no bindings for IDescriptor::E_TYPE::%s, you are required to provide at least single one for default ImGUI Font Atlas texture!", system::ILogger::ELL_ERROR, typeLiteral.data());
693+
return false;
694+
}
695+
696+
bool ok = false;
697+
for (uint32_t i = 0u; i < bindingCount; ++i)
698+
{
699+
const auto rangeStorageIndex = IDescriptorSetLayoutBase::CBindingRedirect::storage_range_index_t(i);
700+
const auto binding = redirect.getBinding(rangeStorageIndex);
701+
const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.texturesBindingIx : m_creationParams.resources.samplersBindingIx;
702+
703+
if (binding.data == requestedBindingIx)
704+
{
705+
const auto count = redirect.getCount(binding);
706+
707+
if (count != m_creationParams.resources.count)
708+
{
709+
m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index but with different binding count!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data());
710+
return false;
711+
}
712+
713+
const auto stage = redirect.getStageFlags(binding);
714+
715+
if(!stage.hasFlags(m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS))
716+
{
717+
m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index but doesn't meet stage flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data());
718+
return false;
719+
}
720+
721+
const auto create = redirect.getCreateFlags(rangeStorageIndex);
722+
723+
if (!create.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.TEXTURES_REQUIRED_CREATE_FLAGS : m_creationParams.resources.SAMPLERS_REQUIRED_CREATE_FLAGS))
724+
{
725+
m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index but doesn't meet create flags requirements!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data());
726+
return false;
727+
}
728+
729+
ok = true;
730+
break;
731+
}
732+
}
733+
734+
if (!ok)
735+
{
736+
m_creationParams.utilities->getLogger()->log("Provided descriptor set layout has no IDescriptor::E_TYPE::%s binding for requested `m_creationParams.resources.%s` index or it is invalid!", system::ILogger::ELL_ERROR, typeLiteral.data(), ixLiteral.data());
737+
return false;
738+
}
739+
740+
return true;
741+
};
742+
743+
const bool ok = validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLED_IMAGE > () && validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLER > ();
744+
745+
if (!ok)
746+
return false;
747+
}
748+
749+
return true;
750+
};
751+
609752
const auto validation = std::to_array
610753
({
611754
std::make_pair(bool(m_creationParams.assetManager), "Invalid `m_creationParams.assetManager` is nullptr!"),
@@ -614,10 +757,9 @@ namespace nbl::ext::imgui
614757
std::make_pair(bool(m_creationParams.transfer), "Invalid `m_creationParams.transfer` is nullptr!"),
615758
std::make_pair(bool(m_creationParams.renderpass), "Invalid `m_creationParams.renderpass` is nullptr!"),
616759
(m_creationParams.assetManager && m_creationParams.utilities && m_creationParams.transfer && m_creationParams.renderpass) ? std::make_pair(bool(m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getQueueFamilyProperties()[m_creationParams.transfer->getFamilyIndex()].queueFlags.hasFlags(IQueue::FAMILY_FLAGS::TRANSFER_BIT)), "Invalid `m_creationParams.transfer` is not capable of transfer operations!") : std::make_pair(false, "Pass valid required UI::S_CREATION_PARAMETERS!"),
617-
std::make_pair(bool(m_creationParams.texturesInfo.setIx <= 3u), "Invalid `m_creationParams.texturesInfo.setIx` is outside { 0u, 1u, 2u, 3u } set!"),
618-
std::make_pair(bool(m_creationParams.samplerStateInfo.setIx <= 3u), "Invalid `m_creationParams.samplerStateInfo.setIx` is outside { 0u, 1u, 2u, 3u } set!"),
619-
std::make_pair(bool(m_creationParams.texturesInfo.setIx == m_creationParams.samplerStateInfo.setIx), "Invalid `m_creationParams.texturesInfo.setIx` is not equal to `m_creationParams.samplerStateInfo.setIx`!"),
620-
std::make_pair(bool(m_creationParams.texturesInfo.bindingIx != m_creationParams.samplerStateInfo.bindingIx), "Invalid `m_creationParams.texturesInfo.bindingIx` is equal to `m_creationParams.samplerStateInfo.bindingIx`!")
760+
std::make_pair(bool(m_creationParams.resources.setIx <= 3u), "Invalid `m_creationParams.resources.setIx` is outside { 0u, 1u, 2u, 3u } set!"),
761+
std::make_pair(bool(m_creationParams.resources.texturesBindingIx != m_creationParams.resources.samplersBindingIx), "Invalid `m_creationParams.resources.texturesBindingIx` is equal to `m_creationParams.resources.samplersBindingIx`!"),
762+
std::make_pair(bool(validateResourcesInfo()), "Invalid `m_creationParams.resources`!")
621763
});
622764

623765
for (const auto& [ok, error] : validation)

src/nbl/ext/ImGui/shaders/fragment.hlsl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,25 @@
22
#error "NBL_TEXTURES_BINDING must be defined!"
33
#endif
44

5-
#ifndef NBL_TEXTURES_SET
6-
#error "NBL_TEXTURES_SET must be defined!"
7-
#endif
8-
95
#ifndef NBL_SAMPLER_STATES_BINDING
106
#error "NBL_SAMPLER_STATES_BINDING must be defined!"
117
#endif
128

13-
#ifndef NBL_SAMPLER_STATES_SET
14-
#error "NBL_SAMPLER_STATES_SET must be defined!"
9+
#ifndef NBL_RESOURCES_SET
10+
#error "NBL_RESOURCES_SET must be defined!"
11+
#endif
12+
13+
#ifndef NBL_RESOURCES_COUNT
14+
#error "NBL_RESOURCES_COUNT must be defined!"
1515
#endif
1616

1717
#include "common.hlsl"
1818

1919
[[vk::push_constant]] struct PushConstants pc;
2020

2121
// separable image samplers to handle textures we do descriptor-index
22-
[[vk::binding(NBL_TEXTURES_BINDING, NBL_TEXTURES_SET)]] Texture2D textures[NBL_MAX_IMGUI_TEXTURES];
23-
[[vk::binding(NBL_SAMPLER_STATES_BINDING, NBL_SAMPLER_STATES_SET)]] SamplerState samplerStates[NBL_MAX_IMGUI_TEXTURES];
22+
[[vk::binding(NBL_TEXTURES_BINDING, NBL_RESOURCES_SET)]] Texture2D textures[NBL_RESOURCES_COUNT];
23+
[[vk::binding(NBL_SAMPLER_STATES_BINDING, NBL_RESOURCES_SET)]] SamplerState samplerStates[NBL_RESOURCES_COUNT];
2424

2525
/*
2626
we use Indirect Indexed draw call to render whole GUI, note we do a cross

0 commit comments

Comments
 (0)