diff --git a/.gitmodules b/.gitmodules index 67069a379e..ed1cdadac5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -102,6 +102,9 @@ [submodule "3rdparty/imguizmo"] path = 3rdparty/imguizmo url = git@github.com:Devsh-Graphics-Programming/ImGuizmo.git +[submodule "3rdparty/imgui_test_engine"] + path = 3rdparty/imgui_test_engine + url = git@github.com:ocornut/imgui_test_engine.git [submodule "3rdparty/git-version-tracking"] path = 3rdparty/git-version-tracking url = git@github.com:Devsh-Graphics-Programming/cmake-git-version-tracking.git diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index a7bd6075e5..8f65e1b2e8 100755 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -233,9 +233,6 @@ if(_NBL_COMPILE_WITH_OPEN_EXR_) set(BUILD_TESTING ${_OLD_BUILD_TESTING}) endif() - - - #gli option(_NBL_COMPILE_WITH_GLI_ "Build with GLI library" ON) if(_NBL_COMPILE_WITH_GLI_) @@ -295,48 +292,116 @@ NBL_ADD_GIT_TRACKING_META_LIBRARY(dxc "${CMAKE_CURRENT_SOURCE_DIR}/dxc/dxc") NBL_GENERATE_GIT_TRACKING_META() if(NBL_BUILD_IMGUI) + set(NBL_IMGUI_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/imgui") + set(NBL_IMGUI_USER_CONFIG_ISD "${CMAKE_CURRENT_BINARY_DIR}/imgui/config/include") + set(NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT "${THIRD_PARTY_SOURCE_DIR}/imgui_test_engine") + set(NBL_IMGUI_TEST_ENGINE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_engine") + set(NBL_IMGUI_TEST_SUITE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_suite") + set(NBL_IMPLOT_ROOT "${NBL_IMGUI_TEST_SUITE_ROOT}/thirdparty/implot") + add_library(imgui STATIC - "imgui/imconfig.h" - "imgui/imgui_demo.cpp" - "imgui/imgui_draw.cpp" - "imgui/imgui_internal.h" - "imgui/imgui_tables.cpp" - "imgui/imgui_widgets.cpp" - "imgui/imgui.cpp" - "imgui/imgui.h" - "imgui/misc/cpp/imgui_stdlib.cpp" - "imgui/misc/cpp/imgui_stdlib.h" - "imgui/imstb_rectpack.h" - "imgui/imstb_textedit.h" - "imgui/imstb_truetype.h" + "${NBL_IMGUI_ROOT}/imconfig.h" + "${NBL_IMGUI_ROOT}/imgui_demo.cpp" + "${NBL_IMGUI_ROOT}/imgui_draw.cpp" + "${NBL_IMGUI_ROOT}/imgui_internal.h" + "${NBL_IMGUI_ROOT}/imgui_tables.cpp" + "${NBL_IMGUI_ROOT}/imgui_widgets.cpp" + "${NBL_IMGUI_ROOT}/imgui.cpp" + "${NBL_IMGUI_ROOT}/misc/freetype/imgui_freetype.cpp" + "${NBL_IMGUI_ROOT}/imgui.h" + "${NBL_IMGUI_ROOT}/misc/cpp/imgui_stdlib.cpp" + "${NBL_IMGUI_ROOT}/misc/cpp/imgui_stdlib.h" + "${NBL_IMGUI_ROOT}/imstb_rectpack.h" + "${NBL_IMGUI_ROOT}/imstb_textedit.h" + "${NBL_IMGUI_ROOT}/imstb_truetype.h" ) - target_include_directories(imgui - PUBLIC "imgui" - PUBLIC "imgui/misc/cpp" - PUBLIC "imgui/backends" + + target_link_libraries(imgui PUBLIC freetype) + + target_include_directories(imgui PUBLIC + "${NBL_IMGUI_ROOT}" + "${NBL_IMGUI_ROOT}/misc/cpp" + "${NBL_IMGUI_ROOT}/backends" + "${NBL_IMGUI_TEST_SUITE_ROOT}" + $ ) - # ImPlot add_library(implot STATIC - "implot/implot.h" - "implot/implot_internal.h" - "implot/implot.cpp" - "implot/implot_items.cpp" + "${NBL_IMPLOT_ROOT}/implot.h" + "${NBL_IMPLOT_ROOT}/implot_internal.h" + "${NBL_IMPLOT_ROOT}/implot.cpp" + "${NBL_IMPLOT_ROOT}/implot_items.cpp" + "${NBL_IMPLOT_ROOT}/implot_demo.cpp" ) + target_include_directories(implot - PUBLIC "imgui" + PUBLIC $ + PUBLIC "${NBL_IMPLOT_ROOT}" ) + target_link_libraries(implot PUBLIC imgui) target_compile_definitions(implot PUBLIC IMPLOT_DEBUG IMPLOT_DLL_EXPORT) - set_property(TARGET implot PROPERTY CXX_STANDARD 11) + set_target_properties(implot PROPERTIES CXX_STANDARD 20) + if(MSVC) target_compile_options(implot PRIVATE /MT /W4 /WX /arch:AVX2 /fp:fast /permissive-) else() target_compile_options(implot PRIVATE -Wall -Wextra -pedantic -Werror -mavx2 -Ofast) endif() + + file(GLOB_RECURSE NBL_TEST_SUITE_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test*.cpp") + file(GLOB_RECURSE NBL_TEST_SUITE_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_SUITE_ROOT}/imgui_test*.cpp") + + add_library(imtestsuite STATIC + ${NBL_TEST_SUITE_SOURCES} + ) + + target_include_directories(imtestsuite PUBLIC + "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}" + "${NBL_IMGUI_TEST_ENGINE_ROOT}" + "${NBL_IMGUI_TEST_SUITE_ROOT}" + $ + $ + ) + + set_target_properties(imtestsuite PROPERTIES CXX_STANDARD 14) # NOTE! THOSE TESTS DO NOT COMPILE WITH HIGHER STANDARDS SO WE WRAP SOURCES INTO LIBRARY COMPILED WITH LOWER ONE + target_link_libraries(imtestsuite PUBLIC implot) + + file(GLOB_RECURSE NBL_TEST_ENGINE_SOURCES CONFIGURE_DEPENDS "${NBL_IMGUI_TEST_ENGINE_ROOT}/*.cpp") + + add_library(imtestengine STATIC + ${NBL_TEST_ENGINE_SOURCES} + ) + + target_include_directories(imtestengine PUBLIC + $ + ) + + target_link_libraries(imtestengine PUBLIC imtestsuite) set(IMGUIZMO_BUILD_EXAMPLE OFF) add_subdirectory(imguizmo EXCLUDE_FROM_ALL) + + # override test engine config with our own +string(APPEND NBL_IMPL_IMCONFIG_CONTENT +[=[ +#include "imgui_test_suite_imconfig.h" // use test engine's config + +// note we override it with default imguis (void*) type +#include +#define ImTextureID uint32_t +#define IMGUI_ENABLE_FREETYPE +#define IMGUI_DISABLE_OBSOLETE_KEYIO +#undef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // but remove this cuz it break things +]=] +) + set(NBL_IMGUI_USER_CONFIG_FILEPATH "${NBL_IMGUI_USER_CONFIG_ISD}/nabla_imconfig.h") + file(WRITE "${NBL_IMGUI_USER_CONFIG_FILEPATH}" "${NBL_IMPL_IMCONFIG_CONTENT}") + + # USER DEFINED IMGUI CONFIG, IMPACTS THE BUILD + target_compile_definitions(imgui PUBLIC + IMGUI_USER_CONFIG="${NBL_IMGUI_USER_CONFIG_FILEPATH}" + ) endif() add_library(aesGladman OBJECT @@ -427,7 +492,7 @@ if (NBL_BUILD_MITSUBA_LOADER) list(APPEND NBL_3RDPARTY_TARGETS expat) endif() if (NBL_BUILD_IMGUI) - list(APPEND NBL_3RDPARTY_TARGETS imgui implot imguizmo) + list(APPEND NBL_3RDPARTY_TARGETS imgui implot imtestsuite imtestengine imguizmo) endif() if(ENABLE_HLSL) list(APPEND NBL_3RDPARTY_TARGETS HLSL) diff --git a/3rdparty/imgui b/3rdparty/imgui index e489e40a85..cb16be3a3f 160000 --- a/3rdparty/imgui +++ b/3rdparty/imgui @@ -1 +1 @@ -Subproject commit e489e40a853426767de9ce0637bc0c9ceb431c1e +Subproject commit cb16be3a3fc1f9cd146ae24d52b615f8a05fa93d diff --git a/3rdparty/imgui_test_engine b/3rdparty/imgui_test_engine new file mode 160000 index 0000000000..60b295cc83 --- /dev/null +++ b/3rdparty/imgui_test_engine @@ -0,0 +1 @@ +Subproject commit 60b295cc83a2dd4b530ab2eb5bf37f009a443619 diff --git a/cmake/common.cmake b/cmake/common.cmake index d7d2322fcd..7fcf9b32af 100755 --- a/cmake/common.cmake +++ b/cmake/common.cmake @@ -258,6 +258,7 @@ macro(nbl_create_executable_project _EXTRA_SOURCES _EXTRA_OPTIONS _EXTRA_INCLUDE nbl_project_process_test_module() endmacro() +# TODO this macro needs more love macro(nbl_create_ext_library_project EXT_NAME LIB_HEADERS LIB_SOURCES LIB_INCLUDES LIB_OPTIONS DEF_OPTIONS) set(LIB_NAME "NblExt${EXT_NAME}") project(${LIB_NAME}) @@ -1281,4 +1282,20 @@ endmacro() macro(write_source_definitions NBL_FILE NBL_WRAPPER_CODE_TO_WRITE) file(WRITE "${NBL_FILE}" "${NBL_WRAPPER_CODE_TO_WRITE}") +endmacro() + +function(NBL_GET_ALL_TARGETS NBL_OUTPUT_VAR) + set(NBL_TARGETS) + NBL_GET_ALL_TARGETS_RECURSIVE(NBL_TARGETS ${CMAKE_CURRENT_SOURCE_DIR}) + set(${NBL_OUTPUT_VAR} ${NBL_TARGETS} PARENT_SCOPE) +endfunction() + +macro(NBL_GET_ALL_TARGETS_RECURSIVE NBL_TARGETS NBL_DIRECTORY) + get_property(NBL_SUBDIRECTORIES DIRECTORY ${NBL_DIRECTORY} PROPERTY SUBDIRECTORIES) + foreach(NBL_SUBDIRECTORY ${NBL_SUBDIRECTORIES}) + NBL_GET_ALL_TARGETS_RECURSIVE(${NBL_TARGETS} ${NBL_SUBDIRECTORY}) + endforeach() + + get_property(NBL_GATHERED_TARGETS DIRECTORY ${NBL_DIRECTORY} PROPERTY BUILDSYSTEM_TARGETS) + list(APPEND ${NBL_TARGETS} ${NBL_GATHERED_TARGETS}) endmacro() \ No newline at end of file diff --git a/examples_tests b/examples_tests index 1849b547e0..a83cbf6a47 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 1849b547e05cbbfb50e3f41545373bafa48cf260 +Subproject commit a83cbf6a47232a8e5ea138aea2f43581c20bdf60 diff --git a/include/nbl/asset/ICPUDescriptorSetLayout.h b/include/nbl/asset/ICPUDescriptorSetLayout.h index 437a3f4552..8f45a789ea 100644 --- a/include/nbl/asset/ICPUDescriptorSetLayout.h +++ b/include/nbl/asset/ICPUDescriptorSetLayout.h @@ -24,7 +24,8 @@ class ICPUDescriptorSetLayout : public IDescriptorSetLayout, public public: _NBL_STATIC_INLINE_CONSTEXPR uint32_t IMMUTABLE_SAMPLER_HIERARCHYLEVELS_BELOW = 1u; - ICPUDescriptorSetLayout(const SBinding* const _begin, const SBinding* const _end) : base_t({_begin,_end}) {} + [[deprecated("Use contructor with std::span!")]] ICPUDescriptorSetLayout(const SBinding* const _begin, const SBinding* const _end) : base_t({_begin,_end}) {} + ICPUDescriptorSetLayout(const std::span bindings) : base_t(bindings) {} core::smart_refctd_ptr clone(uint32_t _depth = ~0u) const override { diff --git a/include/nbl/asset/ICPURenderpass.h b/include/nbl/asset/ICPURenderpass.h index 969ed93b46..b9cf31d127 100644 --- a/include/nbl/asset/ICPURenderpass.h +++ b/include/nbl/asset/ICPURenderpass.h @@ -1,11 +1,9 @@ #ifndef _NBL_I_CPU_RENDERPASS_H_INCLUDED_ #define _NBL_I_CPU_RENDERPASS_H_INCLUDED_ - #include "nbl/asset/IAsset.h" #include "nbl/asset/IRenderpass.h" - namespace nbl::asset { @@ -17,7 +15,8 @@ class ICPURenderpass : public IRenderpass, public IAsset const SCreationParamValidationResult validation = validateCreationParams(_params); if (!validation) return nullptr; - return core::smart_refctd_ptr(new ICPURenderpass(_params,validation), core::dont_grab); + + return core::smart_refctd_ptr(new ICPURenderpass(_params, validation), core::dont_grab); } inline core::smart_refctd_ptr clone(uint32_t _depth = ~0u) const override @@ -42,7 +41,7 @@ class ICPURenderpass : public IRenderpass, public IAsset inline size_t getDependantCount() const override {return 0ull;} protected: - using IRenderpass::IRenderpass; + inline ICPURenderpass(const SCreationParams& _params, const SCreationParamValidationResult& _validation) : IRenderpass(_params, _validation) {} inline ~ICPURenderpass() = default; inline IAsset* getDependant_impl(const size_t ix) override {return nullptr;} diff --git a/include/nbl/asset/IRenderpass.h b/include/nbl/asset/IRenderpass.h index 46e89eab05..7595911716 100644 --- a/include/nbl/asset/IRenderpass.h +++ b/include/nbl/asset/IRenderpass.h @@ -11,7 +11,7 @@ namespace nbl::asset { -class IRenderpass +class NBL_API2 IRenderpass { public: enum class LOAD_OP : uint8_t @@ -356,6 +356,7 @@ class IRenderpass protected: IRenderpass(const SCreationParams& params, const SCreationParamValidationResult& counts); + virtual ~IRenderpass() {} SCreationParams m_params; diff --git a/include/nbl/asset/utils/CHLSLCompiler.h b/include/nbl/asset/utils/CHLSLCompiler.h index 82fed3ac3b..395c528ff6 100644 --- a/include/nbl/asset/utils/CHLSLCompiler.h +++ b/include/nbl/asset/utils/CHLSLCompiler.h @@ -32,7 +32,7 @@ class NBL_API2 CHLSLCompiler final : public IShaderCompiler struct SOptions : IShaderCompiler::SCompilerOptions { - std::span dxcOptions; + std::span dxcOptions; // TODO: span is a VIEW to memory, so to something which we should treat immutable - why not span of string_view then? Since its span we force users to keep those std::strings alive anyway but now we cannnot even make nice constexpr & pass such expression here directly IShader::E_CONTENT_TYPE getCodeContentType() const override { return IShader::E_CONTENT_TYPE::ECT_HLSL; }; }; @@ -54,7 +54,7 @@ class NBL_API2 CHLSLCompiler final : public IShaderCompiler std::string preprocessShader(std::string&& code, IShader::E_SHADER_STAGE& stage, const SPreprocessorOptions& preprocessOptions, std::vector& dxc_compile_flags_override, std::vector* dependencies = nullptr) const; void insertIntoStart(std::string& code, std::ostringstream&& ins) const override; - constexpr static inline const wchar_t* RequiredArguments[] = { + constexpr static inline const wchar_t* RequiredArguments[] = { // TODO: and if dxcOptions is span of std::string then why w_chars there? https://en.cppreference.com/w/cpp/string/basic_string L"-spirv", L"-Zpr", L"-enable-16bit-types", diff --git a/include/nbl/asset/utils/IShaderCompiler.h b/include/nbl/asset/utils/IShaderCompiler.h index ffc2f96a00..e92bc7dca9 100644 --- a/include/nbl/asset/utils/IShaderCompiler.h +++ b/include/nbl/asset/utils/IShaderCompiler.h @@ -442,6 +442,10 @@ class NBL_API2 IShaderCompiler : public core::IReferenceCounted return found; } auto retVal = compileToSPIRV_impl(code, options, options.writeCache ? &dependencies : nullptr); + + if (!retVal) + return nullptr; + // compute the SPIR-V shader content hash { auto backingBuffer = retVal->getContent(); diff --git a/include/nbl/builtin/hlsl/glsl_compat/core.hlsl b/include/nbl/builtin/hlsl/glsl_compat/core.hlsl index be57c0306e..081e80e1a7 100644 --- a/include/nbl/builtin/hlsl/glsl_compat/core.hlsl +++ b/include/nbl/builtin/hlsl/glsl_compat/core.hlsl @@ -123,12 +123,17 @@ enable_if_t, T> atomicCompSwap(Ptr_T ptr, T comparator, T /** * GLSL extended math */ + template // NBL_REQUIRES() extents are square SquareMatrix inverse(NBL_CONST_REF_ARG(SquareMatrix) mat) { return spirv::matrixInverse(mat); } +float32_t2 unpackSnorm2x16(uint32_t p) +{ + return spirv::unpackSnorm2x16(p); +} /** * For Vertex Shaders diff --git a/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl b/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl index 2fa6ec897a..2a16e29133 100644 --- a/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl +++ b/include/nbl/builtin/hlsl/spirv_intrinsics/core.hlsl @@ -189,10 +189,14 @@ template enable_if_t,void> store(P pointer, T obj); //! Std 450 Extended set operations + template -[[vk::ext_instruction(GLSLstd450MatrixInverse)]] +[[vk::ext_instruction(GLSLstd450MatrixInverse, "GLSL.std.450")]] SquareMatrix matrixInverse(NBL_CONST_REF_ARG(SquareMatrix) mat); +[[vk::ext_instruction(GLSLstd450UnpackSnorm2x16, "GLSL.std.450")]] +float32_t2 unpackSnorm2x16(uint32_t p); + // Memory Semantics link here: https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Memory_Semantics_-id- // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_memory_semantics_id diff --git a/include/nbl/ext/ImGui/ImGui.h b/include/nbl/ext/ImGui/ImGui.h index 836b976705..7c1e20376b 100644 --- a/include/nbl/ext/ImGui/ImGui.h +++ b/include/nbl/ext/ImGui/ImGui.h @@ -1,76 +1,138 @@ #ifndef _NBL_EXT_IMGUI_UI_H_ #define _NBL_EXT_IMGUI_UI_H_ +#include "nbl/video/declarations.h" +#include "nbl/asset/IAssetManager.h" + namespace nbl::ext::imgui { class UI final : public core::IReferenceCounted { public: - UI(core::smart_refctd_ptr device, int maxFramesInFlight, video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache, core::smart_refctd_ptr window); + struct MDI + { + using COMPOSE_T = nbl::video::IGPUBuffer; //! composes memory available for the allocator which allocates submemory ranges + using ALLOCATOR_TRAITS_T = nbl::core::address_allocator_traits>; //! traits for MDI buffer allocator - requests memory range from the compose memory + using SUBALLOCATOR_TRAITS_T = nbl::core::address_allocator_traits>; //! traits for MDI buffer suballocator - fills the data given the mdi allocator memory request + + enum E_BUFFER_CONTENT : uint8_t + { + EBC_DRAW_INDIRECT_STRUCTURES, + EBC_ELEMENT_STRUCTURES, + EBC_INDEX_BUFFERS, + EBC_VERTEX_BUFFERS, + + EBC_COUNT, + }; + + typename ALLOCATOR_TRAITS_T::allocator_type allocator; //! mdi buffer allocator + nbl::core::smart_refctd_ptr buffer; //! streaming mdi buffer + + static constexpr auto MDI_BUFFER_REQUIRED_ALLOCATE_FLAGS = nbl::core::bitflag(nbl::video::IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); //! required flags + static constexpr auto MDI_BUFFER_REQUIRED_USAGE_FLAGS = nbl::core::bitflag(nbl::asset::IBuffer::EUF_INDIRECT_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT | nbl::asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; //! required flags + }; + + struct S_CREATION_PARAMETERS + { + struct S_RESOURCE_PARAMETERS + { + nbl::video::IGPUPipelineLayout* const pipelineLayout = nullptr; //! optional, default layout used if not provided declaring required UI resources such as textures (required font atlas + optional user defined textures) & corresponding samplers + uint32_t count = 0x45u; //! amount of total UI textures (and corresponding samplers) + + struct S_BINDING_REQUEST_INFO //! for a given pipeline layout we need to know what is intended for UI resources + { + uint32_t setIx, //! descriptor set index for a resource + bindingIx; //! binding index for a given resource + }; + + const S_BINDING_REQUEST_INFO textures = { .setIx = 0u, .bindingIx = 0u }, //! optional, default texture binding request info used if not provided (set & binding index) + samplers = { .setIx = 0u, .bindingIx = 1u }; //! optional, default sampler binding request info used if not provided (set & binding index) + + using binding_flags_t = nbl::video::IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; + 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 + static constexpr auto SAMPLERS_REQUIRED_CREATE_FLAGS = nbl::core::bitflag(binding_flags_t::ECF_NONE); //! required flags + static constexpr auto RESOURCES_REQUIRED_STAGE_FLAGS = nbl::asset::IShader::E_SHADER_STAGE::ESS_FRAGMENT; //! required stage + }; + + nbl::asset::IAssetManager* const assetManager; //! required + nbl::video::IUtilities* const utilities; //! required + nbl::video::IQueue* const transfer; //! required + nbl::video::IGPURenderpass* const renderpass; //! required + uint32_t subpassIx = 0u; //! optional, default value used if not provided + S_RESOURCE_PARAMETERS resources; //! optional, default parameters used if not provided + nbl::video::IGPUPipelineCache* const pipelineCache = nullptr; //! optional, no cache used if not provided + typename MDI::COMPOSE_T* const streamingBuffer = nullptr; //! optional, default MDI buffer allocated if not provided + }; + + //! 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) + struct S_UPDATE_PARAMETERS + { + //! what we pass to ImGuiIO::AddMousePosEvent + nbl::hlsl::float32_t2 mousePosition, + + //! main display size in pixels + displaySize; + + //! Nabla events you want to be handled with the backend + struct S_EVENTS + { + core::SRange mouse; + core::SRange keyboard; + }; + + S_EVENTS events; + }; + + UI(S_CREATION_PARAMETERS&& params); ~UI() override; - bool Render(nbl::video::IGPUCommandBuffer* commandBuffer, int frameIndex); - void Update(float deltaTimeInSec, float mousePosX, float mousePosY, size_t mouseEventsCount, ui::SMouseEvent const * mouseEvents); // TODO: Keyboard events - void BeginWindow(char const* windowName); - void EndWindow(); - int Register(std::function const& listener); - bool UnRegister(int listenerId); - void SetNextItemWidth(float nextItemWidth); - void SetWindowSize(float width, float height); - void Text(char const* label, ...); - void InputFloat(char const* label, float* value); - void InputFloat2(char const* label, float* value); - void InputFloat3(char const* label, float* value); - void InputFloat4(char const* label, float* value); - void InputFloat3(char const* label, nbl::core::vector3df& value); - bool Combo(char const* label, int32_t* selectedItemIndex, char const** items, int32_t itemsCount); - bool Combo(const char* label, int* selectedItemIndex, std::vector& values); - void SliderInt(char const* label, int* value, int minValue, int maxValue); - void SliderFloat(char const* label, float* value, float minValue, float maxValue); - void Checkbox(char const* label, bool* value); - void Spacing(); - void Button(char const* label, std::function const& onPress); - void InputText(char const* label, std::string& outValue); - [[nodiscard]] bool HasFocus(); - [[nodiscard]] bool IsItemActive(); - [[nodiscard]] bool TreeNode(char const* name); - void TreePop(); + //! Nabla ImGUI backend reserves this index for font atlas, any attempt to hook user defined texture within the index will result in undefined behaviour + static constexpr auto NBL_FONT_ATLAS_TEX_ID = 0u; - private: + //! updates ImGuiIO & records ImGUI *cpu* draw command lists, you have to call it before .render + bool update(const S_UPDATE_PARAMETERS& params); + + //! updates mapped mdi buffer & records *gpu* draw command, you are required to bind UI's graphics pipeline & descriptor sets before calling this function - use getPipeline() to get the pipeline & getCreationParameters() to get info about your set resources + bool render(nbl::video::IGPUCommandBuffer* commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const std::span scissors = {}); + + //! registers lambda listener in which ImGUI calls should be recorded + size_t registerListener(std::function const& listener); + std::optional unregisterListener(size_t id); - core::smart_refctd_ptr CreateDescriptorSetLayout(); + //! sets ImGUI context, you are supposed to pass valid ImGuiContext* context + void setContext(void* imguiContext); - void CreatePipeline(video::IGPURenderpass* renderpass, video::IGPUPipelineCache* pipelineCache); - // TODO: just take an intended next submit instead of queue and cmdbuf, so we're consistent across utilities - video::ISemaphore::future_t CreateFontTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* queue); - void UpdateDescriptorSets(); - void createSystem(); - void CreateFontSampler(); - void CreateDescriptorPool(); - void HandleMouseEvents(float mousePosX, float mousePosY, size_t mouseEventsCount, ui::SMouseEvent const * mouseEvents) const; + //! creation parametrs + inline const S_CREATION_PARAMETERS& getCreationParameters() const { return m_creationParams; } - core::smart_refctd_ptr system; - core::smart_refctd_ptr logger; - core::smart_refctd_ptr utilities; + //! ImGUI graphics pipeline + inline nbl::video::IGPUGraphicsPipeline* getPipeline() { return pipeline.get(); } - core::smart_refctd_ptr m_device; - core::smart_refctd_ptr m_fontSampler; - core::smart_refctd_ptr m_descriptorPool; - core::smart_refctd_ptr m_gpuDescriptorSet; + //! image view default font texture + inline nbl::video::IGPUImageView* getFontAtlasView() { return m_fontAtlasTexture.get(); } + + //! mdi streaming buffer + inline typename MDI::COMPOSE_T* getStreamingBuffer() { return m_mdi.buffer.get(); } + + //! mdi buffer allocator + inline typename MDI::ALLOCATOR_TRAITS_T::allocator_type* getStreamingAllocator() { return &m_mdi.allocator; } + + //! ImGUI context, you are supposed to cast it, eg. reinterpret_cast(this->getContext()); + void* getContext(); + private: + void createPipeline(); + void createMDIBuffer(); + void handleMouseEvents(const S_UPDATE_PARAMETERS& params) const; + void handleKeyEvents(const S_UPDATE_PARAMETERS& params) const; + video::ISemaphore::future_t createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer); + + S_CREATION_PARAMETERS m_creationParams; core::smart_refctd_ptr pipeline; - core::smart_refctd_ptr m_fontTexture; - core::smart_refctd_ptr m_window; - std::vector> m_vertexBuffers, m_indexBuffers; - bool hasFocus = false; - - // TODO: Use a signal class instead like Signal<> UIRecordSignal{}; - struct Subscriber { - int id = -1; - std::function listener = nullptr; - }; - std::vector m_subscribers{}; + core::smart_refctd_ptr m_fontAtlasTexture; + MDI m_mdi; + std::vector> m_subscribers {}; }; } diff --git a/include/nbl/ui/IWindow.h b/include/nbl/ui/IWindow.h index b66f8dbd14..385f70d172 100644 --- a/include/nbl/ui/IWindow.h +++ b/include/nbl/ui/IWindow.h @@ -194,7 +194,7 @@ class IWindow : public core::IReferenceCounted inline int32_t getY() const { return m_y; } NBL_API2 virtual IClipboardManager* getClipboardManager() = 0; - NBL_API2 virtual ICursorControl* getCursorControl() = 0; + NBL_API2 virtual ICursorControl* getCursorControl() const = 0; NBL_API2 virtual IWindowManager* getManager() const = 0; inline IEventCallback* getEventCallback() const { return m_cb.get(); } diff --git a/include/nbl/ui/KeyCodes.h b/include/nbl/ui/KeyCodes.h index 98490d0d6f..b6d05aed36 100644 --- a/include/nbl/ui/KeyCodes.h +++ b/include/nbl/ui/KeyCodes.h @@ -139,7 +139,7 @@ enum E_KEY_CODE : uint8_t EKC_COUNT, }; -inline char keyCodeToChar(E_KEY_CODE code, bool shiftPressed) +constexpr char keyCodeToChar(E_KEY_CODE code, bool shiftPressed) { char result = 0; if (!shiftPressed) diff --git a/include/nbl/video/IDeviceMemoryAllocation.h b/include/nbl/video/IDeviceMemoryAllocation.h index 673f1834e6..9940080c5f 100644 --- a/include/nbl/video/IDeviceMemoryAllocation.h +++ b/include/nbl/video/IDeviceMemoryAllocation.h @@ -114,7 +114,7 @@ class IDeviceMemoryAllocation : public virtual core::IReferenceCounted if (m_mappedPtr) m_mappedPtr -= range.offset; m_mappedRange = m_mappedPtr ? range:MemoryRange{}; - m_currentMappingAccess = m_mappedPtr ? EMCAF_NO_MAPPING_ACCESS:accessHint; + m_currentMappingAccess = m_mappedPtr ? accessHint : EMCAF_NO_MAPPING_ACCESS; return m_mappedPtr; } // returns true on success, false on failure diff --git a/include/nbl/video/utilities/IUtilities.h b/include/nbl/video/utilities/IUtilities.h index bc83ad2da0..72641c1d60 100644 --- a/include/nbl/video/utilities/IUtilities.h +++ b/include/nbl/video/utilities/IUtilities.h @@ -144,6 +144,9 @@ class NBL_API2 IUtilities : public core::IReferenceCounted //! inline ILogicalDevice* getLogicalDevice() const { return m_device.get(); } + //! + inline system::ILogger* getLogger() const { return m_logger.getRaw(); } + //! inline StreamingTransientDataBufferMT<>* getDefaultUpStreamingBuffer() { diff --git a/src/nbl/builtin/utils.cmake b/src/nbl/builtin/utils.cmake index bc8a35f673..0a76d1c67e 100644 --- a/src/nbl/builtin/utils.cmake +++ b/src/nbl/builtin/utils.cmake @@ -191,7 +191,7 @@ function(ADD_CUSTOM_BUILTIN_RESOURCES _TARGET_NAME_ _BUNDLE_NAME_ _BUNDLE_SEARCH endif() if(NOT _NBL_INTERNAL_BR_CREATION_) - target_link_libraries(${_TARGET_NAME_} Nabla) + target_link_libraries(${_TARGET_NAME_} PUBLIC Nabla) endif() endif() diff --git a/src/nbl/ext/ImGui/CMakeLists.txt b/src/nbl/ext/ImGui/CMakeLists.txt index 05144787e9..6f905f2ced 100644 --- a/src/nbl/ext/ImGui/CMakeLists.txt +++ b/src/nbl/ext/ImGui/CMakeLists.txt @@ -1,5 +1,3 @@ -include(${NBL_ROOT_PATH}/cmake/common.cmake) - set(NBL_EXT_INTERNAL_INCLUDE_DIR "${NBL_ROOT_PATH}/include") set(NBL_EXT_IMGUI_H @@ -7,12 +5,11 @@ set(NBL_EXT_IMGUI_H ) set(NBL_EXT_IMGUI_SRC - ImGui.cpp + "${CMAKE_CURRENT_SOURCE_DIR}/ImGui.cpp" ) -set(NBL_EXT_IMGUI_EXTERNAL_INCLUDE - "${NBL_ROOT_PATH}/3rdparty" - "${NBL_EXT_INTERNAL_INCLUDE_DIR}" +set(NBL_EXT_IMGUI_INCLUDE_SEARCH_DIRECTORIES + $ ) nbl_create_ext_library_project( @@ -24,62 +21,19 @@ nbl_create_ext_library_project( "" ) -# shaders IO directories -set(NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/shaders") -get_filename_component(_EXT_IMGUI_SPIRV_BR_BUNDLE_SEARCH_DIRECTORY_ "${CMAKE_CURRENT_BINARY_DIR}/shaders/include" ABSOLUTE) -get_filename_component(_EXT_IMGUI_SPIRV_BR_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/builtin/include" ABSOLUTE) -get_filename_component(_EXT_IMGUI_SPIRV_BR_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/builtin/src" ABSOLUTE) -set(NBL_EXT_IMGUI_OUTPUT_SPIRV_DIRECTORY "${_EXT_IMGUI_SPIRV_BR_BUNDLE_SEARCH_DIRECTORY_}/nbl/ext/imgui/spirv") - -# list of input source shaders -set(NBL_EXT_IMGUI_INPUT_SHADERS - "${NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY}/vertex.hlsl" - "${NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY}/fragment.hlsl" -) - -set(NBL_EXT_IMGUI_INPUT_COMMONS - "${NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY}/common.hlsl" -) +target_link_libraries(${LIB_NAME} PUBLIC imtestengine) -include("${NBL_ROOT_PATH}/src/nbl/builtin/utils.cmake") +# (*) -> I wish we could just take NSC, offline-precompile to SPIRV, embed into builtin resource library (as we did!) but then be smart & adjust at runtime OpDecorate of our resources according to wishes - unfortunately no linker yet we have and we are not going to make one ourselves so we compile imgui shaders at runtime +set(_BR_TARGET_ extImguibuiltinResourceData) +set(_RESOURCE_DIR_ "shaders") -foreach(NBL_INPUT_SHADER IN LISTS NBL_EXT_IMGUI_INPUT_SHADERS) - cmake_path(GET NBL_INPUT_SHADER STEM NBL_SHADER_STEM) - set(NBL_OUTPUT_SPIRV_FILENAME "${NBL_SHADER_STEM}.spv") - set(NBL_OUTPUT_SPIRV_PATH "${NBL_EXT_IMGUI_OUTPUT_SPIRV_DIRECTORY}/${NBL_OUTPUT_SPIRV_FILENAME}") +get_filename_component(_SEARCH_DIRECTORIES_ "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) +get_filename_component(_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/src" ABSOLUTE) +get_filename_component(_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/include" ABSOLUTE) - if(NBL_SHADER_STEM STREQUAL vertex) - set(NBL_NSC_COMPILE_OPTIONS -T vs_6_7 -E VSMain) - elseif(NBL_SHADER_STEM STREQUAL fragment) - set(NBL_NSC_COMPILE_OPTIONS -T ps_6_7 -E PSMain) - else() - message(FATAL_ERROR "internal error") - endif() - - set(NBL_NSC_COMPILE_COMMAND - "$" - -Fc "${NBL_OUTPUT_SPIRV_PATH}" - ${NBL_NSC_COMPILE_OPTIONS} # this should come from shader's [#pragma WAVE ] but our NSC doesn't seem to work properly currently - "${NBL_INPUT_SHADER}" - ) - - set(NBL_DEPENDS - "${NBL_INPUT_SHADER}" - ${NBL_EXT_IMGUI_INPUT_COMMONS} - ) - - add_custom_command(OUTPUT "${NBL_OUTPUT_SPIRV_PATH}" - COMMAND ${NBL_NSC_COMPILE_COMMAND} - DEPENDS ${NBL_DEPENDS} - WORKING_DIRECTORY "${NBL_EXT_IMGUI_INPUT_SHADERS_DIRECTORY}" - COMMENT "Generating \"${NBL_OUTPUT_SPIRV_PATH}\"" - VERBATIM - COMMAND_EXPAND_LISTS - ) - - list(APPEND NBL_EXT_IMGUI_OUTPUT_SPIRV_BUILTINS "${NBL_OUTPUT_SPIRV_PATH}") - LIST_BUILTIN_RESOURCE(IMGUI_EXT_SPIRV_RESOURCES_TO_EMBED "ext/imgui/spirv/${NBL_OUTPUT_SPIRV_FILENAME}") -endforeach() +LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "common.hlsl") +LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "vertex.hlsl") # (*) -> this we could precompile [no resources for which set/binding Ixs could be adjusted] but I'm not going to mix stuff +LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "fragment.hlsl") # (*) -> but this we could not since we let users to provide external descriptor set layout + ImGUI textures & sampler state set/binding Ixs (for pipeline layout) at runtime -ADD_CUSTOM_BUILTIN_RESOURCES(extImguiSpirvBuiltinResourceData IMGUI_EXT_SPIRV_RESOURCES_TO_EMBED "${_EXT_IMGUI_SPIRV_BR_BUNDLE_SEARCH_DIRECTORY_}" "nbl" "ext::imgui::spirv::builtin" "${_EXT_IMGUI_SPIRV_BR_OUTPUT_DIRECTORY_HEADER_}" "${_EXT_IMGUI_SPIRV_BR_OUTPUT_DIRECTORY_SOURCE_}" "STATIC" "INTERNAL") -LINK_BUILTIN_RESOURCES_TO_TARGET(${LIB_NAME} extImguiSpirvBuiltinResourceData) \ No newline at end of file +ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_SEARCH_DIRECTORIES_}" "${_RESOURCE_DIR_}" "nbl::ext::imgui::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") +LINK_BUILTIN_RESOURCES_TO_TARGET(${LIB_NAME} ${_BR_TARGET_}) \ No newline at end of file diff --git a/src/nbl/ext/ImGui/ImGui.cpp b/src/nbl/ext/ImGui/ImGui.cpp index 37fd8e4ab1..a6cd513ba3 100644 --- a/src/nbl/ext/ImGui/ImGui.cpp +++ b/src/nbl/ext/ImGui/ImGui.cpp @@ -4,15 +4,12 @@ #include #include -// internal & nabla #include "nbl/system/IApplicationFramework.h" #include "nbl/system/CStdoutLogger.h" #include "nbl/ext/ImGui/ImGui.h" #include "shaders/common.hlsl" -#include "ext/imgui/spirv/builtin/builtinResources.h" -#include "ext/imgui/spirv/builtin/CArchive.h" - -// 3rdparty +#include "nbl/ext/ImGui/builtin/builtinResources.h" +#include "nbl/ext/ImGui/builtin/CArchive.h" #include "imgui/imgui.h" #include "imgui/misc/cpp/imgui_stdlib.h" @@ -23,38 +20,98 @@ using namespace nbl::ui; namespace nbl::ext::imgui { - smart_refctd_ptr UI::CreateDescriptorSetLayout() - { - nbl::video::IGPUDescriptorSetLayout::SBinding bindings[] = { - { - .binding = 0, - .type = asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, - .createFlags = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, - .stageFlags = IShader::E_SHADER_STAGE::ESS_FRAGMENT, - .count = 1, - .immutableSamplers = nullptr // TODO: m_fontSampler? - } - }; - - return m_device->createDescriptorSetLayout(bindings); - } + static constexpr auto MDI_COMPONENT_COUNT = UI::MDI::EBC_COUNT; + static constexpr auto MDI_ALIGNMENTS = std::to_array({ alignof(VkDrawIndexedIndirectCommand), alignof(PerObjectData), alignof(ImDrawIdx), alignof(ImDrawVert) }); + static constexpr auto MDI_MAX_ALIGNMENT = *std::max_element(MDI_ALIGNMENTS.begin(), MDI_ALIGNMENTS.end()); - void UI::CreatePipeline(video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache) + void UI::createPipeline() { - // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix - SPushConstantRange pushConstantRanges[] = { + SPushConstantRange pushConstantRanges[] = + { { - .stageFlags = IShader::E_SHADER_STAGE::ESS_VERTEX, + .stageFlags = IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, .offset = 0, .size = sizeof(PushConstants) } }; - auto descriptorSetLayout = CreateDescriptorSetLayout(); - m_gpuDescriptorSet = m_descriptorPool->createDescriptorSet(descriptorSetLayout); - assert(m_gpuDescriptorSet); + auto pipelineLayout = m_creationParams.resources.pipelineLayout ? smart_refctd_ptr(m_creationParams.resources.pipelineLayout) /* provided? good, at this point its validated and we just use it */ : + [&]() -> smart_refctd_ptr + { + //! if default pipeline layout is not provided, we create it here given your request binding info with certain constraints about samplers - they are immutable separate and part of the default layout + smart_refctd_ptr fontAtlasUISampler, userTexturesSampler; + + using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; + { + IGPUSampler::SParams params; + params.AnisotropicFilter = 1u; + params.TextureWrapU = ISampler::ETC_REPEAT; + params.TextureWrapV = ISampler::ETC_REPEAT; + params.TextureWrapW = ISampler::ETC_REPEAT; + + fontAtlasUISampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params); + fontAtlasUISampler->setObjectDebugName("Nabla default ImGUI font UI sampler"); + } + + { + IGPUSampler::SParams params; + params.MinLod = 0.f; + params.MaxLod = 0.f; + params.TextureWrapU = ISampler::ETC_CLAMP_TO_EDGE; + params.TextureWrapV = ISampler::ETC_CLAMP_TO_EDGE; + params.TextureWrapW = ISampler::ETC_CLAMP_TO_EDGE; + + userTexturesSampler = m_creationParams.utilities->getLogicalDevice()->createSampler(params); + userTexturesSampler->setObjectDebugName("Nabla default ImGUI custom texture sampler"); + } + + //! as mentioned we use immutable separate samplers and they are part of the descriptor set layout + std::vector> immutableSamplers(m_creationParams.resources.count); + for (auto& it : immutableSamplers) + it = smart_refctd_ptr(userTexturesSampler); - auto pipelineLayout = m_device->createPipelineLayout(pushConstantRanges, std::move(descriptorSetLayout)); + immutableSamplers[nbl::ext::imgui::UI::NBL_FONT_ATLAS_TEX_ID] = smart_refctd_ptr(fontAtlasUISampler); + + auto textureBinding = IGPUDescriptorSetLayout::SBinding + { + .binding = m_creationParams.resources.textures.bindingIx, + .type = IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, + .createFlags = m_creationParams.resources.TEXTURES_REQUIRED_CREATE_FLAGS, + .stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS, + .count = m_creationParams.resources.count + }; + + auto samplersBinding = IGPUDescriptorSetLayout::SBinding + { + .binding = m_creationParams.resources.samplers.bindingIx, + .type = IDescriptor::E_TYPE::ET_SAMPLER, + .createFlags = m_creationParams.resources.SAMPLERS_REQUIRED_CREATE_FLAGS, + .stageFlags = m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS, + .count = m_creationParams.resources.count, + .immutableSamplers = immutableSamplers.data() + }; + + auto layouts = std::to_array>({nullptr, nullptr, nullptr, nullptr }); + + if (m_creationParams.resources.textures.setIx == m_creationParams.resources.samplers.setIx) + layouts[m_creationParams.resources.textures.setIx] = m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout({{textureBinding, samplersBinding}}); + else + { + layouts[m_creationParams.resources.textures.setIx] = m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout({{textureBinding}}); + layouts[m_creationParams.resources.samplers.setIx] = m_creationParams.utilities->getLogicalDevice()->createDescriptorSetLayout({{samplersBinding}}); + } + + assert(layouts[m_creationParams.resources.textures.setIx]); + assert(layouts[m_creationParams.resources.samplers.setIx]); + + return m_creationParams.utilities->getLogicalDevice()->createPipelineLayout(pushConstantRanges, std::move(layouts[0u]), std::move(layouts[1u]), std::move(layouts[2u]), std::move(layouts[3u])); + }(); + + if (!pipelineLayout) + { + m_creationParams.utilities->getLogger()->log("Could not create pipeline layout!", system::ILogger::ELL_ERROR); + assert(false); + } struct { @@ -62,22 +119,98 @@ namespace nbl::ext::imgui } shaders; { - struct + constexpr std::string_view NBL_ARCHIVE_ALIAS = "nbl/ext/imgui/shaders"; + + auto system = smart_refctd_ptr(m_creationParams.assetManager->getSystem()); //! proxy the system, we will touch it gently + auto archive = make_smart_refctd_ptr(smart_refctd_ptr(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 + auto compiler = make_smart_refctd_ptr(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 + auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(system)); + auto includeLoader = includeFinder->getDefaultFileSystemLoader(); + includeFinder->addSearchPath(NBL_ARCHIVE_ALIAS.data(), includeLoader); + + auto createShader = [&]() -> core::smart_refctd_ptr { - const system::SBuiltinFile vertex = ::ext::imgui::spirv::builtin::get_resource<"ext/imgui/spirv/vertex.spv">(); - const system::SBuiltinFile fragment = ::ext::imgui::spirv::builtin::get_resource<"ext/imgui/spirv/fragment.spv">(); - } spirv; + asset::IAssetLoader::SAssetLoadParams params = {}; + params.logger = m_creationParams.utilities->getLogger(); + params.workingDirectory = NBL_ARCHIVE_ALIAS.data(); - auto createShader = [&](const system::SBuiltinFile& in, asset::IShader::E_SHADER_STAGE stage) -> core::smart_refctd_ptr - { - const auto buffer = core::make_smart_refctd_ptr, true> >(in.size, /*this cast is awful but my custom buffer won't free it's memory*/ (void*)in.contents, core::adopt_memory); // no copy - const auto shader = make_smart_refctd_ptr(core::smart_refctd_ptr(buffer), stage, IShader::E_CONTENT_TYPE::ECT_SPIRV, ""); - - return m_device->createShader(shader.get()); + auto bundle = m_creationParams.assetManager->getAsset(key.value, params); + const auto assets = bundle.getContents(); + + if (assets.empty()) + { + m_creationParams.utilities->getLogger()->log("Could not load \"%s\" shader!", system::ILogger::ELL_ERROR, key.value); + return nullptr; + } + + const auto shader = IAsset::castDown(assets[0]); + + CHLSLCompiler::SOptions options = {}; + options.stage = stage; + options.preprocessorOptions.sourceIdentifier = key.value; + options.preprocessorOptions.logger = m_creationParams.utilities->getLogger(); + options.preprocessorOptions.includeFinder = includeFinder.get(); + + auto compileToSPIRV = [&]() -> core::smart_refctd_ptr + { + #define NBL_DEFAULT_OPTIONS "-spirv", "-Zpr", "-enable-16bit-types", "-fvk-use-scalar-layout", "-Wno-c++11-extensions", "-Wno-c++1z-extensions", "-Wno-c++14-extensions", "-Wno-gnu-static-float-init", "-fspv-target-env=vulkan1.3", "-HV", "202x" /* default required params, just to not throw warnings */ + const std::string_view code (reinterpret_cast(shader->getContent()->getPointer()), shader->getContent()->getSize()); + + if constexpr (stage == IShader::E_SHADER_STAGE::ESS_VERTEX) + { + const auto VERTEX_COMPILE_OPTIONS = std::to_array({NBL_DEFAULT_OPTIONS, "-T", "vs_6_7", "-E", "VSMain", "-O3"}); + options.dxcOptions = VERTEX_COMPILE_OPTIONS; + + return compiler->compileToSPIRV(code.data(), options); // we good here - no code patching + } + else if (stage == IShader::E_SHADER_STAGE::ESS_FRAGMENT) + { + const auto FRAGMENT_COMPILE_OPTIONS = std::to_array({NBL_DEFAULT_OPTIONS, "-T", "ps_6_7", "-E", "PSMain", "-O3"}); + options.dxcOptions = FRAGMENT_COMPILE_OPTIONS; + + std::stringstream stream; + + stream << "// -> this code has been autogenerated with Nabla ImGUI extension\n" + << "#define NBL_TEXTURES_BINDING_IX " << m_creationParams.resources.textures.bindingIx << "\n" + << "#define NBL_SAMPLER_STATES_BINDING_IX " << m_creationParams.resources.samplers.bindingIx << "\n" + << "#define NBL_TEXTURES_SET_IX " << m_creationParams.resources.textures.setIx << "\n" + << "#define NBL_SAMPLER_STATES_SET_IX " << m_creationParams.resources.samplers.setIx << "\n" + << "#define NBL_RESOURCES_COUNT " << m_creationParams.resources.count << "\n" + << "// <-\n\n"; + + const auto newCode = stream.str() + std::string(code); + return compiler->compileToSPIRV(newCode.c_str(), options); // but here we do patch the code with additional define directives for which values are taken from the creation parameters + } + else + { + static_assert(stage != IShader::E_SHADER_STAGE::ESS_UNKNOWN, "Unknown shader stage!"); + return nullptr; + } + }; + + auto spirv = compileToSPIRV(); + + if (!spirv) + { + m_creationParams.utilities->getLogger()->log("Could not compile \"%s\" shader!", system::ILogger::ELL_ERROR, key.value); + return nullptr; + } + + auto gpu = m_creationParams.utilities->getLogicalDevice()->createShader(spirv.get()); + + if (!gpu) + m_creationParams.utilities->getLogger()->log("Could not create GPU shader for \"%s\"!", system::ILogger::ELL_ERROR, key.value); + + return gpu; }; - shaders.vertex = createShader(spirv.vertex, IShader::E_SHADER_STAGE::ESS_VERTEX); - shaders.fragment = createShader(spirv.fragment, IShader::E_SHADER_STAGE::ESS_FRAGMENT); + system->mount(smart_refctd_ptr(archive), NBL_ARCHIVE_ALIAS.data()); + shaders.vertex = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("vertex.hlsl"), IShader::E_SHADER_STAGE::ESS_VERTEX > (); + shaders.fragment = createShader.template operator() < NBL_CORE_UNIQUE_STRING_LITERAL_TYPE("fragment.hlsl"), IShader::E_SHADER_STAGE::ESS_FRAGMENT > (); + system->unmount(archive.get(), NBL_ARCHIVE_ALIAS.data()); + + assert(shaders.vertex); + assert(shaders.fragment); } SVertexInputParams vertexInputParams{}; @@ -109,22 +242,26 @@ namespace nbl::ext::imgui blendParams.logicOp = ELO_NO_OP; auto& param = blendParams.blendParams[0]; - param.srcColorFactor = EBF_SRC_ALPHA;//VK_BLEND_FACTOR_SRC_ALPHA; - param.dstColorFactor = EBF_ONE_MINUS_SRC_ALPHA;//VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - param.colorBlendOp = EBO_ADD;//VK_BLEND_OP_ADD; - param.srcAlphaFactor = EBF_ONE_MINUS_SRC_ALPHA;//VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - param.dstAlphaFactor = EBF_ZERO;//VK_BLEND_FACTOR_ZERO; - param.alphaBlendOp = EBO_ADD;//VK_BLEND_OP_ADD; - param.colorWriteMask = (1u << 0u) | (1u << 1u) | (1u << 2u) | (1u << 3u);//VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + // color blending factors (for RGB) + param.srcColorFactor = EBF_SRC_ALPHA; + param.dstColorFactor = EBF_ONE_MINUS_SRC_ALPHA; + param.colorBlendOp = EBO_ADD; + + // alpha blending factors (for A) + param.srcAlphaFactor = EBF_ONE; + param.dstAlphaFactor = EBF_ONE_MINUS_SRC_ALPHA; + param.alphaBlendOp = EBO_ADD; + + // Write all components (R, G, B, A) + param.colorWriteMask = (1u << 0u) | (1u << 1u) | (1u << 2u) | (1u << 3u); } SRasterizationParams rasterizationParams{}; { rasterizationParams.faceCullingMode = EFCM_NONE; - rasterizationParams.depthWriteEnable = false; // TODO: check if it disabled depth test? + rasterizationParams.depthWriteEnable = false; rasterizationParams.depthBoundsTestEnable = false; - // rasterizationParams.stencilTestEnable = false; // TODO: check if stencil test is disabled } SPrimitiveAssemblyParams primitiveAssemblyParams{}; @@ -141,22 +278,22 @@ namespace nbl::ext::imgui IGPUGraphicsPipeline::SCreationParams params[1]; { - auto& param = params[0]; + auto& param = params[0u]; param.layout = pipelineLayout.get(); param.shaders = specs; - param.renderpass = renderpass; - param.cached = { .vertexInput = vertexInputParams, .primitiveAssembly = primitiveAssemblyParams, .rasterization = rasterizationParams, .blend = blendParams, .subpassIx = 0u }; // TODO: check "subpassIx" + param.renderpass = m_creationParams.renderpass; + param.cached = { .vertexInput = vertexInputParams, .primitiveAssembly = primitiveAssemblyParams, .rasterization = rasterizationParams, .blend = blendParams, .subpassIx = m_creationParams.subpassIx }; }; - if (!m_device->createGraphicsPipelines(pipelineCache, params, &pipeline)) + if (!m_creationParams.utilities->getLogicalDevice()->createGraphicsPipelines(m_creationParams.pipelineCache, params, &pipeline)) { - logger->log("Could not create pipeline!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not create pipeline!", system::ILogger::ELL_ERROR); assert(false); } } } - ISemaphore::future_t UI::CreateFontTexture(video::IGPUCommandBuffer* cmdBuffer, video::IQueue* transfer) + ISemaphore::future_t UI::createFontAtlasTexture(video::IGPUCommandBuffer* cmdBuffer) { // Load Fonts // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. @@ -177,10 +314,16 @@ namespace nbl::ext::imgui uint8_t* pixels = nullptr; int32_t width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + io.Fonts->SetTexID(NBL_FONT_ATLAS_TEX_ID); + if (!pixels || width<=0 || height<=0) return IQueue::RESULT::OTHER_ERROR; + + const size_t componentsCount = 4, image_size = width * height * componentsCount * sizeof(uint8_t); + + _NBL_STATIC_INLINE_CONSTEXPR auto NBL_FORMAT_FONT = EF_R8G8B8A8_UNORM; + const auto buffer = core::make_smart_refctd_ptr< asset::CCustomAllocatorCPUBuffer, true> >(image_size, pixels, core::adopt_memory); - constexpr auto NBL_FORMAT_FONT = EF_R8G8B8A8_UNORM; IGPUImage::SCreationParams params; params.flags = static_cast(0u); params.type = IImage::ET_2D; @@ -212,44 +355,46 @@ namespace nbl::ext::imgui region->imageSubresource = {}; region->imageSubresource.aspectMask = IImage::EAF_COLOR_BIT; region->imageSubresource.layerCount = 1u; - region->imageOffset = { 0, 0, 0 }; + region->imageOffset = { 0u, 0u, 0u }; region->imageExtent = { params.extent.width, params.extent.height, 1u }; } - auto image = m_device->createImage(std::move(params)); + auto image = m_creationParams.utilities->getLogicalDevice()->createImage(std::move(params)); if (!image) { - logger->log("Could not create font image!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not create font image!", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } image->setObjectDebugName("Nabla IMGUI extension Font Image"); - if (!m_device->allocate(image->getMemoryReqs(), image.get()).isValid()) + if (!m_creationParams.utilities->getLogicalDevice()->allocate(image->getMemoryReqs(), image.get()).isValid()) { - logger->log("Could not allocate memory for font image!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not allocate memory for font image!", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } + + image->setObjectDebugName("Nabla IMGUI extension Font Atlas"); SIntendedSubmitInfo sInfo; { IQueue::SSubmitInfo::SCommandBufferInfo cmdInfo = { cmdBuffer }; - auto scratchSemaphore = m_device->createSemaphore(0); + auto scratchSemaphore = m_creationParams.utilities->getLogicalDevice()->createSemaphore(0); if (!scratchSemaphore) { - logger->log("Could not create scratch semaphore", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not create scratch semaphore", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } scratchSemaphore->setObjectDebugName("Nabla IMGUI extension Scratch Semaphore"); - sInfo.queue = transfer; + sInfo.queue = m_creationParams.transfer; sInfo.waitSemaphores = {}; sInfo.commandBuffers = { &cmdInfo, 1 }; - sInfo.scratchSemaphore = // TODO: do I really need it? YES, ALWAYS! + sInfo.scratchSemaphore = { .semaphore = scratchSemaphore.get(), - .value = 0, + .value = 0u, .stageMask = PIPELINE_STAGE_FLAGS::ALL_TRANSFER_BITS }; @@ -275,9 +420,9 @@ namespace nbl::ext::imgui cmdBuffer->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE,{.imgBarriers=barriers}); // We cannot use the `AutoSubmit` variant of the util because we need to add a pipeline barrier with a transition onto the command buffer after the upload. // old layout is UNDEFINED because we don't want a content preserving transition, we can just put ourselves in transfer right away - if (!utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) + if (!m_creationParams.utilities->updateImageViaStagingBuffer(sInfo,pixels,image->getCreationParameters().format,image.get(),transferLayout,regions.range)) { - logger->log("Could not upload font image contents", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not upload font image contents", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } @@ -290,9 +435,9 @@ namespace nbl::ext::imgui cmdBuffer->end(); const auto submit = sInfo.popSubmit({}); - if (transfer->submit(submit)!=IQueue::RESULT::SUCCESS) + if (m_creationParams.transfer->submit(submit)!=IQueue::RESULT::SUCCESS) { - logger->log("Could not submit workload for font texture upload.", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not submit workload for font texture upload.", system::ILogger::ELL_ERROR); return IQueue::RESULT::OTHER_ERROR; } } @@ -304,7 +449,7 @@ namespace nbl::ext::imgui params.subresourceRange = regions.subresource; params.image = core::smart_refctd_ptr(image); - m_fontTexture = m_device->createImageView(std::move(params)); + m_fontAtlasTexture = m_creationParams.utilities->getLogicalDevice()->createImageView(std::move(params)); } ISemaphore::future_t retval(IQueue::RESULT::SUCCESS); @@ -312,279 +457,466 @@ namespace nbl::ext::imgui return retval; } - void prepareKeyMapForDesktop() + void UI::handleMouseEvents(const S_UPDATE_PARAMETERS& params) const { - ImGuiIO& io = ImGui::GetIO(); - // TODO: - // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. - //io.KeyMap[ImGuiKey_Tab] = MSDL::SDL_SCANCODE_TAB; - //io.KeyMap[ImGuiKey_LeftArrow] = MSDL::SDL_SCANCODE_LEFT; - //io.KeyMap[ImGuiKey_RightArrow] = MSDL::SDL_SCANCODE_RIGHT; - //io.KeyMap[ImGuiKey_UpArrow] = MSDL::SDL_SCANCODE_UP; - //io.KeyMap[ImGuiKey_DownArrow] = MSDL::SDL_SCANCODE_DOWN; - //io.KeyMap[ImGuiKey_PageUp] = MSDL::SDL_SCANCODE_PAGEUP; - //io.KeyMap[ImGuiKey_PageDown] = MSDL::SDL_SCANCODE_PAGEDOWN; - //io.KeyMap[ImGuiKey_Home] = MSDL::SDL_SCANCODE_HOME; - //io.KeyMap[ImGuiKey_End] = MSDL::SDL_SCANCODE_END; - //io.KeyMap[ImGuiKey_Insert] = MSDL::SDL_SCANCODE_INSERT; - //io.KeyMap[ImGuiKey_Delete] = MSDL::SDL_SCANCODE_DELETE; - //io.KeyMap[ImGuiKey_Backspace] = MSDL::SDL_SCANCODE_BACKSPACE; - //io.KeyMap[ImGuiKey_Space] = MSDL::SDL_SCANCODE_SPACE; - //io.KeyMap[ImGuiKey_Enter] = MSDL::SDL_SCANCODE_RETURN; - //io.KeyMap[ImGuiKey_Escape] = MSDL::SDL_SCANCODE_ESCAPE; - //io.KeyMap[ImGuiKey_KeyPadEnter] = MSDL::SDL_SCANCODE_KP_ENTER; - //io.KeyMap[ImGuiKey_A] = MSDL::SDL_SCANCODE_A; - //io.KeyMap[ImGuiKey_C] = MSDL::SDL_SCANCODE_C; - //io.KeyMap[ImGuiKey_V] = MSDL::SDL_SCANCODE_V; - //io.KeyMap[ImGuiKey_X] = MSDL::SDL_SCANCODE_X; - //io.KeyMap[ImGuiKey_Y] = MSDL::SDL_SCANCODE_Y; - //io.KeyMap[ImGuiKey_Z] = MSDL::SDL_SCANCODE_Z; - } + auto& io = ImGui::GetIO(); - static void adjustGlobalFontScale() - { - ImGuiIO& io = ImGui::GetIO(); - io.FontGlobalScale = 1.0f; - } + io.AddMousePosEvent(params.mousePosition.x, params.mousePosition.y); - void UI::UpdateDescriptorSets() - { - IGPUDescriptorSet::SDescriptorInfo info; + for (const auto& e : params.events.mouse) { - info.desc = m_fontTexture; - info.info.combinedImageSampler.sampler = m_fontSampler; - info.info.combinedImageSampler.imageLayout = nbl::asset::IImage::LAYOUT::READ_ONLY_OPTIMAL; - } + switch (e.type) + { + case SMouseEvent::EET_CLICK: + { + ImGuiMouseButton_ button = ImGuiMouseButton_COUNT; + if (e.clickEvent.mouseButton == EMB_LEFT_BUTTON) + button = ImGuiMouseButton_Left; + else if (e.clickEvent.mouseButton == EMB_RIGHT_BUTTON) + button = ImGuiMouseButton_Right; + else if (e.clickEvent.mouseButton == EMB_MIDDLE_BUTTON) + button = ImGuiMouseButton_Middle; + + if (button == ImGuiMouseButton_COUNT) + continue; - IGPUDescriptorSet::SWriteDescriptorSet writeDescriptorSet{}; - writeDescriptorSet.dstSet = m_gpuDescriptorSet.get(); - writeDescriptorSet.binding = 0; - writeDescriptorSet.arrayElement = 0; - writeDescriptorSet.count = 1; - writeDescriptorSet.info = &info; + if (e.clickEvent.action == SMouseEvent::SClickEvent::EA_PRESSED) + io.AddMouseButtonEvent(button, true); + else if (e.clickEvent.action == SMouseEvent::SClickEvent::EA_RELEASED) + io.AddMouseButtonEvent(button, false); + } break; - m_device->updateDescriptorSets(1, &writeDescriptorSet, 0, nullptr); + case SMouseEvent::EET_SCROLL: + { + _NBL_STATIC_INLINE_CONSTEXPR auto scalar = 0.02f; + const auto wheel = nbl::hlsl::float32_t2(e.scrollEvent.horizontalScroll, e.scrollEvent.verticalScroll) * scalar; + + io.AddMouseWheelEvent(wheel.x, wheel.y); + } break; + + case SMouseEvent::EET_MOVEMENT: + + default: + break; + } + } } - void UI::CreateFontSampler() + struct NBL_TO_IMGUI_KEY_BIND { - // TODO: Recheck this settings - IGPUSampler::SParams params{}; - params.MinLod = -1000; - params.MaxLod = 1000; - params.AnisotropicFilter = 1.0f; - params.TextureWrapU = ISampler::ETC_REPEAT; - params.TextureWrapV = ISampler::ETC_REPEAT; - params.TextureWrapW = ISampler::ETC_REPEAT; - - m_fontSampler = m_device->createSampler(params); - } + ImGuiKey target; + char physicalSmall; + char physicalBig; + }; - void UI::CreateDescriptorPool() + // maps Nabla keys to IMGUIs + _NBL_STATIC_INLINE_CONSTEXPR std::array createKeyMap() { - static constexpr int TotalSetCount = 1; - IDescriptorPool::SCreateInfo createInfo = {}; - createInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER)] = TotalSetCount; - createInfo.maxSets = 1; - createInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_NONE; - - m_descriptorPool = m_device->createDescriptorPool(std::move(createInfo)); - assert(m_descriptorPool); + std::array map = { { NBL_TO_IMGUI_KEY_BIND{ImGuiKey_None, '0', '0'} } }; + + #define NBL_REGISTER_KEY(__NBL_KEY__, __IMGUI_KEY__) \ + map[__NBL_KEY__] = NBL_TO_IMGUI_KEY_BIND{__IMGUI_KEY__, keyCodeToChar(__NBL_KEY__, false), keyCodeToChar(__NBL_KEY__, true)}; + + NBL_REGISTER_KEY(EKC_BACKSPACE, ImGuiKey_Backspace); + NBL_REGISTER_KEY(EKC_TAB, ImGuiKey_Tab); + NBL_REGISTER_KEY(EKC_ENTER, ImGuiKey_Enter); + NBL_REGISTER_KEY(EKC_LEFT_SHIFT, ImGuiKey_LeftShift); + NBL_REGISTER_KEY(EKC_RIGHT_SHIFT, ImGuiKey_RightShift); + NBL_REGISTER_KEY(EKC_LEFT_CONTROL, ImGuiKey_LeftCtrl); + NBL_REGISTER_KEY(EKC_RIGHT_CONTROL, ImGuiKey_RightCtrl); + NBL_REGISTER_KEY(EKC_LEFT_ALT, ImGuiKey_LeftAlt); + NBL_REGISTER_KEY(EKC_RIGHT_ALT, ImGuiKey_RightAlt); + NBL_REGISTER_KEY(EKC_PAUSE, ImGuiKey_Pause); + NBL_REGISTER_KEY(EKC_CAPS_LOCK, ImGuiKey_CapsLock); + NBL_REGISTER_KEY(EKC_ESCAPE, ImGuiKey_Escape); + NBL_REGISTER_KEY(EKC_SPACE, ImGuiKey_Space); + NBL_REGISTER_KEY(EKC_PAGE_UP, ImGuiKey_PageUp); + NBL_REGISTER_KEY(EKC_PAGE_DOWN, ImGuiKey_PageDown); + NBL_REGISTER_KEY(EKC_END, ImGuiKey_End); + NBL_REGISTER_KEY(EKC_HOME, ImGuiKey_Home); + NBL_REGISTER_KEY(EKC_LEFT_ARROW, ImGuiKey_LeftArrow); + NBL_REGISTER_KEY(EKC_RIGHT_ARROW, ImGuiKey_RightArrow); + NBL_REGISTER_KEY(EKC_DOWN_ARROW, ImGuiKey_DownArrow); + NBL_REGISTER_KEY(EKC_UP_ARROW, ImGuiKey_UpArrow); + NBL_REGISTER_KEY(EKC_PRINT_SCREEN, ImGuiKey_PrintScreen); + NBL_REGISTER_KEY(EKC_INSERT, ImGuiKey_Insert); + NBL_REGISTER_KEY(EKC_DELETE, ImGuiKey_Delete); + NBL_REGISTER_KEY(EKC_APPS, ImGuiKey_Menu); + NBL_REGISTER_KEY(EKC_COMMA, ImGuiKey_Comma); + NBL_REGISTER_KEY(EKC_PERIOD, ImGuiKey_Period); + NBL_REGISTER_KEY(EKC_SEMICOLON, ImGuiKey_Semicolon); + NBL_REGISTER_KEY(EKC_OPEN_BRACKET, ImGuiKey_LeftBracket); + NBL_REGISTER_KEY(EKC_CLOSE_BRACKET, ImGuiKey_RightBracket); + NBL_REGISTER_KEY(EKC_BACKSLASH, ImGuiKey_Backslash); + NBL_REGISTER_KEY(EKC_APOSTROPHE, ImGuiKey_Apostrophe); + NBL_REGISTER_KEY(EKC_ADD, ImGuiKey_KeypadAdd); + NBL_REGISTER_KEY(EKC_SUBTRACT, ImGuiKey_KeypadSubtract); + NBL_REGISTER_KEY(EKC_MULTIPLY, ImGuiKey_KeypadMultiply); + NBL_REGISTER_KEY(EKC_DIVIDE, ImGuiKey_KeypadDivide); + NBL_REGISTER_KEY(EKC_0, ImGuiKey_0); + NBL_REGISTER_KEY(EKC_1, ImGuiKey_1); + NBL_REGISTER_KEY(EKC_2, ImGuiKey_2); + NBL_REGISTER_KEY(EKC_3, ImGuiKey_3); + NBL_REGISTER_KEY(EKC_4, ImGuiKey_4); + NBL_REGISTER_KEY(EKC_5, ImGuiKey_5); + NBL_REGISTER_KEY(EKC_6, ImGuiKey_6); + NBL_REGISTER_KEY(EKC_7, ImGuiKey_7); + NBL_REGISTER_KEY(EKC_8, ImGuiKey_8); + NBL_REGISTER_KEY(EKC_9, ImGuiKey_9); + NBL_REGISTER_KEY(EKC_A, ImGuiKey_A); + NBL_REGISTER_KEY(EKC_B, ImGuiKey_B); + NBL_REGISTER_KEY(EKC_C, ImGuiKey_C); + NBL_REGISTER_KEY(EKC_D, ImGuiKey_D); + NBL_REGISTER_KEY(EKC_E, ImGuiKey_E); + NBL_REGISTER_KEY(EKC_F, ImGuiKey_F); + NBL_REGISTER_KEY(EKC_G, ImGuiKey_G); + NBL_REGISTER_KEY(EKC_H, ImGuiKey_H); + NBL_REGISTER_KEY(EKC_I, ImGuiKey_I); + NBL_REGISTER_KEY(EKC_J, ImGuiKey_J); + NBL_REGISTER_KEY(EKC_K, ImGuiKey_K); + NBL_REGISTER_KEY(EKC_L, ImGuiKey_L); + NBL_REGISTER_KEY(EKC_M, ImGuiKey_M); + NBL_REGISTER_KEY(EKC_N, ImGuiKey_N); + NBL_REGISTER_KEY(EKC_O, ImGuiKey_O); + NBL_REGISTER_KEY(EKC_P, ImGuiKey_P); + NBL_REGISTER_KEY(EKC_Q, ImGuiKey_Q); + NBL_REGISTER_KEY(EKC_R, ImGuiKey_R); + NBL_REGISTER_KEY(EKC_S, ImGuiKey_S); + NBL_REGISTER_KEY(EKC_T, ImGuiKey_T); + NBL_REGISTER_KEY(EKC_U, ImGuiKey_U); + NBL_REGISTER_KEY(EKC_V, ImGuiKey_V); + NBL_REGISTER_KEY(EKC_W, ImGuiKey_W); + NBL_REGISTER_KEY(EKC_X, ImGuiKey_X); + NBL_REGISTER_KEY(EKC_Y, ImGuiKey_Y); + NBL_REGISTER_KEY(EKC_Z, ImGuiKey_Z); + NBL_REGISTER_KEY(EKC_NUMPAD_0, ImGuiKey_Keypad0); + NBL_REGISTER_KEY(EKC_NUMPAD_1, ImGuiKey_Keypad1); + NBL_REGISTER_KEY(EKC_NUMPAD_2, ImGuiKey_Keypad2); + NBL_REGISTER_KEY(EKC_NUMPAD_3, ImGuiKey_Keypad3); + NBL_REGISTER_KEY(EKC_NUMPAD_4, ImGuiKey_Keypad4); + NBL_REGISTER_KEY(EKC_NUMPAD_5, ImGuiKey_Keypad5); + NBL_REGISTER_KEY(EKC_NUMPAD_6, ImGuiKey_Keypad6); + NBL_REGISTER_KEY(EKC_NUMPAD_7, ImGuiKey_Keypad7); + NBL_REGISTER_KEY(EKC_NUMPAD_8, ImGuiKey_Keypad8); + NBL_REGISTER_KEY(EKC_NUMPAD_9, ImGuiKey_Keypad9); + NBL_REGISTER_KEY(EKC_F1, ImGuiKey_F1); + NBL_REGISTER_KEY(EKC_F2, ImGuiKey_F2); + NBL_REGISTER_KEY(EKC_F3, ImGuiKey_F3); + NBL_REGISTER_KEY(EKC_F4, ImGuiKey_F4); + NBL_REGISTER_KEY(EKC_F5, ImGuiKey_F5); + NBL_REGISTER_KEY(EKC_F6, ImGuiKey_F6); + NBL_REGISTER_KEY(EKC_F7, ImGuiKey_F7); + NBL_REGISTER_KEY(EKC_F8, ImGuiKey_F8); + NBL_REGISTER_KEY(EKC_F9, ImGuiKey_F9); + NBL_REGISTER_KEY(EKC_F10, ImGuiKey_F10); + NBL_REGISTER_KEY(EKC_F11, ImGuiKey_F11); + NBL_REGISTER_KEY(EKC_F12, ImGuiKey_F12); + NBL_REGISTER_KEY(EKC_F13, ImGuiKey_F13); + NBL_REGISTER_KEY(EKC_F14, ImGuiKey_F14); + NBL_REGISTER_KEY(EKC_F15, ImGuiKey_F15); + NBL_REGISTER_KEY(EKC_F16, ImGuiKey_F16); + NBL_REGISTER_KEY(EKC_F17, ImGuiKey_F17); + NBL_REGISTER_KEY(EKC_F18, ImGuiKey_F18); + NBL_REGISTER_KEY(EKC_F19, ImGuiKey_F19); + NBL_REGISTER_KEY(EKC_F20, ImGuiKey_F20); + NBL_REGISTER_KEY(EKC_F21, ImGuiKey_F21); + NBL_REGISTER_KEY(EKC_F22, ImGuiKey_F22); + NBL_REGISTER_KEY(EKC_F23, ImGuiKey_F23); + NBL_REGISTER_KEY(EKC_F24, ImGuiKey_F24); + NBL_REGISTER_KEY(EKC_NUM_LOCK, ImGuiKey_NumLock); + NBL_REGISTER_KEY(EKC_SCROLL_LOCK, ImGuiKey_ScrollLock); + NBL_REGISTER_KEY(EKC_VOLUME_MUTE, ImGuiKey_None); + NBL_REGISTER_KEY(EKC_VOLUME_UP, ImGuiKey_None); + NBL_REGISTER_KEY(EKC_VOLUME_DOWN, ImGuiKey_None); + + return map; } - - void UI::HandleMouseEvents( - float const mousePosX, - float const mousePosY, - size_t const mouseEventsCount, - SMouseEvent const * mouseEvents - ) const + + void UI::handleKeyEvents(const S_UPDATE_PARAMETERS& params) const { auto& io = ImGui::GetIO(); - io.MousePos.x = mousePosX - m_window->getX(); - io.MousePos.y = mousePosY - m_window->getY(); + _NBL_STATIC_INLINE_CONSTEXPR auto keyMap = createKeyMap(); - for (size_t i = 0; i < mouseEventsCount; ++i) + const bool useBigLetters = [&]() // TODO: we can later improve it to check for CAPS, etc { - auto const & event = mouseEvents[i]; - if(event.type == SMouseEvent::EET_CLICK) - { - int buttonIndex = -1; - if (event.clickEvent.mouseButton == EMB_LEFT_BUTTON) - { - buttonIndex = 0; - } else if (event.clickEvent.mouseButton == EMB_RIGHT_BUTTON) - { - buttonIndex = 1; - } else if (event.clickEvent.mouseButton == EMB_MIDDLE_BUTTON) - { - buttonIndex = 2; - } + for (const auto& e : params.events.keyboard) + if (e.keyCode == EKC_LEFT_SHIFT && e.action == SKeyboardEvent::ECA_PRESSED) + return true; - if (buttonIndex == -1) - { - assert(false); - continue; - } + return false; + }(); - if(event.clickEvent.action == SMouseEvent::SClickEvent::EA_PRESSED) { - io.MouseDown[buttonIndex] = true; - } else if (event.clickEvent.action == SMouseEvent::SClickEvent::EA_RELEASED) { - io.MouseDown[buttonIndex] = false; + for (const auto& e : params.events.keyboard) + { + const auto& bind = keyMap[e.keyCode]; + const auto& iCharacter = useBigLetters ? bind.physicalBig : bind.physicalSmall; + + if(bind.target == ImGuiKey_None) + m_creationParams.utilities->getLogger()->log(std::string("Requested physical Nabla key \"") + iCharacter + std::string("\" has yet no mapping to IMGUI key!"), system::ILogger::ELL_ERROR); + else + if (e.action == SKeyboardEvent::ECA_PRESSED) + { + io.AddKeyEvent(bind.target, true); + io.AddInputCharacter(iCharacter); } - } + else if (e.action == SKeyboardEvent::ECA_RELEASED) + io.AddKeyEvent(bind.target, false); } } - UI::UI(smart_refctd_ptr device, int maxFramesInFlight, video::IGPURenderpass* renderpass, IGPUPipelineCache* pipelineCache, smart_refctd_ptr window) - : m_device(core::smart_refctd_ptr(device)), m_window(core::smart_refctd_ptr(window)) + UI::UI(S_CREATION_PARAMETERS&& params) + : m_creationParams(std::move(params)) { - createSystem(); - struct + auto validateResourcesInfo = [&]() -> bool { - struct + auto* pipelineLayout = m_creationParams.resources.pipelineLayout; + + if (pipelineLayout) // provided? we will validate your pipeline layout to check if you declared required UI resources { - uint8_t transfer, graphics; - } id; - } families; + auto validateResource = [&](const IGPUDescriptorSetLayout* const descriptorSetLayout) + { + constexpr std::string_view typeLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "ET_SAMPLED_IMAGE" : "ET_SAMPLER", + ixLiteral = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? "texturesBindingIx" : "samplersBindingIx"; - const nbl::video::IPhysicalDevice* pDevice = device->getPhysicalDevice(); - ILogicalDevice::SCreationParams params = {}; + auto anyBindingCount = [&m_creationParams = m_creationParams, &log = std::as_const(typeLiteral)](const IDescriptorSetLayoutBase::CBindingRedirect* redirect) -> bool + { + if (redirect->getBindingCount()) + { + 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 default ImGUI Font Atlas texture resource & corresponsing sampler resource!", system::ILogger::ELL_ERROR, log.data()); + return false; + } - auto properties = pDevice->getQueueFamilyProperties(); + return true; + }; - auto requestFamilyQueueId = [&](IQueue::FAMILY_FLAGS requried, std::string_view onError) - { - uint8_t index = 0; - for (const auto& fProperty : properties) - { - if (fProperty.queueFlags.hasFlags(requried)) - { - ++params.queueParams[index].count; + if(!descriptorSetLayout) + { + m_creationParams.utilities->getLogger()->log("Provided descriptor set layout for IDescriptor::E_TYPE::%s is nullptr!", system::ILogger::ELL_ERROR, typeLiteral.data()); + return false; + } - return index; - } - ++index; - } + const auto* redirect = &descriptorSetLayout->getDescriptorRedirect(descriptorType); - logger->log(onError.data(), system::ILogger::ELL_ERROR); - assert(false); - }; + if constexpr (descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE) + if (!anyBindingCount(redirect)) + return false; + else + { + if (!anyBindingCount(redirect)) + { + redirect = &descriptorSetLayout->getImmutableSamplerRedirect(); // we must give it another try & request to look for immutable samplers - // get & validate families' capabilities - families.id.transfer = requestFamilyQueueId(IQueue::FAMILY_FLAGS::TRANSFER_BIT, "Could not find any queue with TRANSFER_BIT enabled!"); - families.id.graphics = requestFamilyQueueId(IQueue::FAMILY_FLAGS::GRAPHICS_BIT, "Could not find any queue with GRAPHICS_BIT enabled!"); + if (!anyBindingCount(redirect)) + return false; + } + } - // allocate temporary command buffer - auto* tQueue = device->getThreadSafeQueue(families.id.transfer, 0); + const auto bindingCount = redirect->getBindingCount(); - if (!tQueue) - { - logger->log("Could not get queue!", system::ILogger::ELL_ERROR); - assert(false); - } + bool ok = false; + for (uint32_t i = 0u; i < bindingCount; ++i) + { + const auto rangeStorageIndex = IDescriptorSetLayoutBase::CBindingRedirect::storage_range_index_t(i); + const auto binding = redirect->getBinding(rangeStorageIndex); + const auto requestedBindingIx = descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.textures.bindingIx : m_creationParams.resources.samplers.bindingIx; + + if (binding.data == requestedBindingIx) + { + const auto count = redirect->getCount(binding); + + if (count != m_creationParams.resources.count) + { + 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()); + return false; + } + + const auto stage = redirect->getStageFlags(binding); + + if(!stage.hasFlags(m_creationParams.resources.RESOURCES_REQUIRED_STAGE_FLAGS)) + { + 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()); + return false; + } + + const auto create = redirect->getCreateFlags(rangeStorageIndex); + + if (!create.hasFlags(descriptorType == IDescriptor::E_TYPE::ET_SAMPLED_IMAGE ? m_creationParams.resources.TEXTURES_REQUIRED_CREATE_FLAGS : m_creationParams.resources.SAMPLERS_REQUIRED_CREATE_FLAGS)) + { + 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()); + return false; + } + + ok = true; + break; + } + } + + if (!ok) + { + 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()); + return false; + } + + return true; + }; + + const auto& layouts = pipelineLayout->getDescriptorSetLayouts(); + const bool ok = validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLED_IMAGE > (layouts[m_creationParams.resources.textures.setIx]) && validateResource.template operator() < IDescriptor::E_TYPE::ET_SAMPLER > (layouts[m_creationParams.resources.samplers.setIx]); + + if (!ok) + return false; + } + + return true; + }; + + const auto validation = std::to_array + ({ + std::make_pair(bool(m_creationParams.assetManager), "Invalid `m_creationParams.assetManager` is nullptr!"), + std::make_pair(bool(m_creationParams.assetManager->getSystem()), "Invalid `m_creationParams.assetManager->getSystem()` is nullptr!"), + std::make_pair(bool(m_creationParams.utilities), "Invalid `m_creationParams.utilities` is nullptr!"), + std::make_pair(bool(m_creationParams.transfer), "Invalid `m_creationParams.transfer` is nullptr!"), + std::make_pair(bool(m_creationParams.renderpass), "Invalid `m_creationParams.renderpass` is nullptr!"), + (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!"), + std::make_pair(bool(m_creationParams.resources.count >= 1u), "Invalid `m_creationParams.resources.count` is equal to 0!"), + std::make_pair(bool(m_creationParams.resources.textures.setIx <= 3u), "Invalid `m_creationParams.resources.textures` is outside { 0u, 1u, 2u, 3u } set!"), + std::make_pair(bool(m_creationParams.resources.samplers.setIx <= 3u), "Invalid `m_creationParams.resources.samplers` is outside { 0u, 1u, 2u, 3u } set!"), + std::make_pair(bool(m_creationParams.resources.textures.bindingIx != m_creationParams.resources.samplers.bindingIx), "Invalid `m_creationParams.resources.textures.bindingIx` is equal to `m_creationParams.resources.samplers.bindingIx`!"), + std::make_pair(bool(validateResourcesInfo()), "Invalid `m_creationParams.resources` content!") + }); + + for (const auto& [ok, error] : validation) + if (!ok) + { + m_creationParams.utilities->getLogger()->log(error, system::ILogger::ELL_ERROR); + assert(false); + } smart_refctd_ptr transistentCMD; { using pool_flags_t = IGPUCommandPool::CREATE_FLAGS; - // need to be individually resettable such that we can form a valid SIntendedSubmit out of the commandbuffer allocated from the pool - smart_refctd_ptr pool = device->createCommandPool(families.id.transfer, pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); + + smart_refctd_ptr pool = m_creationParams.utilities->getLogicalDevice()->createCommandPool(m_creationParams.transfer->getFamilyIndex(), pool_flags_t::RESET_COMMAND_BUFFER_BIT|pool_flags_t::TRANSIENT_BIT); if (!pool) { - logger->log("Could not create command pool!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not create command pool!", system::ILogger::ELL_ERROR); assert(false); } - if (!pool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, 1u, &transistentCMD)) { - logger->log("Could not create transistent command buffer!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not create transistent command buffer!", system::ILogger::ELL_ERROR); assert(false); } } - tQueue->startCapture(); - { - // Setup Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - - CreateFontSampler(); - CreateDescriptorPool(); - CreateDescriptorSetLayout(); - CreatePipeline(renderpass, pipelineCache); - CreateFontTexture(transistentCMD.get(), tQueue); - prepareKeyMapForDesktop(); - adjustGlobalFontScale(); - UpdateDescriptorSets(); - } - tQueue->endCapture(); + // Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); - auto & io = ImGui::GetIO(); - io.DisplaySize = ImVec2(m_window->getWidth(), m_window->getHeight()); - io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + createPipeline(); + createMDIBuffer(); + createFontAtlasTexture(transistentCMD.get()); - m_vertexBuffers.resize(maxFramesInFlight); - m_indexBuffers.resize(maxFramesInFlight); + auto & io = ImGui::GetIO(); + io.BackendUsingLegacyKeyArrays = 0; // using AddKeyEvent() - it's new way of handling ImGUI events our backends supports } UI::~UI() = default; - void UI::createSystem() + void UI::createMDIBuffer() { - system = system::IApplicationFramework::createSystem(); - const auto logLevel = nbl::core::bitflag(system::ILogger::ELL_ALL); - logger = core::make_smart_refctd_ptr(logLevel); - auto archive = core::make_smart_refctd_ptr<::ext::imgui::spirv::builtin::CArchive>(smart_refctd_ptr(logger)); - - system->mount(core::smart_refctd_ptr(archive)); + constexpr static uint32_t minStreamingBufferAllocationSize = 32u, maxStreamingBufferAllocationAlignment = 1024u * 64u, mdiBufferDefaultSize = /* 2MB */ 1024u * 1024u * 2u; - utilities = make_smart_refctd_ptr(core::smart_refctd_ptr(m_device), core::smart_refctd_ptr(logger)); + auto getRequiredAccessFlags = [&](const core::bitflag& properties) + { + core::bitflag flags (IDeviceMemoryAllocation::EMCAF_NO_MAPPING_ACCESS); - if (!utilities) + if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_READABLE_BIT)) + flags |= IDeviceMemoryAllocation::EMCAF_READ; + if (properties.hasFlags(IDeviceMemoryAllocation::EMPF_HOST_WRITABLE_BIT)) + flags |= IDeviceMemoryAllocation::EMCAF_WRITE; + + return flags; + }; + + if (m_creationParams.streamingBuffer) + m_mdi.buffer = core::smart_refctd_ptr(m_creationParams.streamingBuffer); + else { - logger->log("Failed to create nbl::video::IUtilities!", system::ILogger::ELL_ERROR); - assert(false); + IGPUBuffer::SCreationParams mdiCreationParams = {}; + mdiCreationParams.usage = MDI::MDI_BUFFER_REQUIRED_USAGE_FLAGS; + mdiCreationParams.size = mdiBufferDefaultSize; + + auto buffer = m_creationParams.utilities->getLogicalDevice()->createBuffer(std::move(mdiCreationParams)); + buffer->setObjectDebugName("MDI Upstream Buffer"); + + auto memoryReqs = buffer->getMemoryReqs(); + memoryReqs.memoryTypeBits &= m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); + + auto allocation = m_creationParams.utilities->getLogicalDevice()->allocate(memoryReqs, buffer.get(), MDI::MDI_BUFFER_REQUIRED_ALLOCATE_FLAGS); + { + const bool allocated = allocation.isValid(); + assert(allocated); + } + auto memory = allocation.memory; + + if (!memory->map({ 0ull, memoryReqs.size }, getRequiredAccessFlags(memory->getMemoryPropertyFlags()))) + m_creationParams.utilities->getLogger()->log("Could not map device memory!", system::ILogger::ELL_ERROR); + + m_mdi.buffer = std::move(buffer); } + + auto buffer = m_mdi.buffer; + auto binding = buffer->getBoundMemory(); + + constexpr auto ALIGN_OFFSET = 0u, MIN_BLOCK_SIZE = 1024u; + m_mdi.allocator = MDI::ALLOCATOR_TRAITS_T::allocator_type(binding.memory->getMappedPointer(), binding.offset, ALIGN_OFFSET, MDI_MAX_ALIGNMENT, binding.memory->getAllocationSize(), MIN_BLOCK_SIZE); + + const auto validation = std::to_array + ({ + std::make_pair(buffer->getCreationParams().usage.hasFlags(MDI::MDI_BUFFER_REQUIRED_USAGE_FLAGS), "MDI buffer must be created with IBuffer::EUF_INDIRECT_BUFFER_BIT | IBuffer::EUF_INDEX_BUFFER_BIT | IBuffer::EUF_VERTEX_BUFFER_BIT | IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(bool(buffer->getMemoryReqs().memoryTypeBits & m_creationParams.utilities->getLogicalDevice()->getPhysicalDevice()->getUpStreamingMemoryTypeBits()), "MDI buffer must have up-streaming memory type bits enabled!"), + std::make_pair(binding.memory->getAllocateFlags().hasFlags(MDI::MDI_BUFFER_REQUIRED_ALLOCATE_FLAGS), "MDI buffer's memory must be allocated with IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT enabled!"), + std::make_pair(binding.memory->isCurrentlyMapped(), "MDI buffer's memory must be mapped!"), // streaming buffer contructor already validates it, but cannot assume user won't unmap its own buffer for some reason (sorry if you have just hit it) + std::make_pair(binding.memory->getCurrentMappingAccess().hasFlags(getRequiredAccessFlags(binding.memory->getMemoryPropertyFlags())), "MDI buffer's memory current mapping access flags don't meet requirements!") + }); + + for (const auto& [ok, error] : validation) + if (!ok) + { + m_creationParams.utilities->getLogger()->log(error, system::ILogger::ELL_ERROR); + assert(false); + } } - //------------------------------------------------------------------------------------------------- - // TODO: Handle mouse cursor for nabla - //static void UpdateMouseCursor() - //{ - //auto & io = ImGui::GetIO(); - - //if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) - //{ - // return; - //} - //ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); - //if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) - //{ - // // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor - // MSDL::SDL_ShowCursor(MSDL::SDL_FALSE); - //} - //else - //{ - // // Show OS mouse cursor - // MSDL::SDL_SetCursor(mouseCursors[imgui_cursor] ? mouseCursors[imgui_cursor] : mouseCursors[ImGuiMouseCursor_Arrow]); - // MSDL::SDL_ShowCursor(MSDL::SDL_TRUE); - //} - //} - - bool UI::Render(IGPUCommandBuffer* commandBuffer, int const frameIndex) + template + concept ImDrawBufferType = std::same_as || std::same_as; + + bool UI::render(nbl::video::IGPUCommandBuffer* commandBuffer, nbl::video::ISemaphore::SWaitInfo waitInfo, const std::span scissors) { + if (!commandBuffer) + { + m_creationParams.utilities->getLogger()->log("Invalid command buffer!", system::ILogger::ELL_ERROR); + return false; + } + + if (commandBuffer->getState() != IGPUCommandBuffer::STATE::RECORDING) + { + m_creationParams.utilities->getLogger()->log("Command buffer is not in recording state!", system::ILogger::ELL_ERROR); + return false; + } + + ImGui::Render(); // note it doesn't touch GPU or graphics API at all, its an internal ImGUI call to update & prepare the data for rendering so we can call GetDrawData() + ImGuiIO& io = ImGui::GetIO(); if (!io.Fonts->IsBuilt()) { - logger->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", system::ILogger::ELL_ERROR); - assert(false); + m_creationParams.utilities->getLogger()->log("Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().", system::ILogger::ELL_ERROR); + return false; } - - auto* rawPipeline = pipeline.get(); - commandBuffer->bindGraphicsPipeline(rawPipeline); - commandBuffer->bindDescriptorSets(EPBP_GRAPHICS, rawPipeline->getLayout(), 0, 1, &m_gpuDescriptorSet.get()); auto const* drawData = ImGui::GetDrawData(); @@ -596,106 +928,280 @@ namespace nbl::ext::imgui float const frameBufferHeight = drawData->DisplaySize.y * drawData->FramebufferScale.y; if (frameBufferWidth > 0 && frameBufferHeight > 0 && drawData->TotalVtxCount > 0) { - // Create or resize the vertex/index buffers - size_t const vertexSize = drawData->TotalVtxCount * sizeof(ImDrawVert); - size_t const indexSize = drawData->TotalIdxCount * sizeof(ImDrawIdx); + const struct + { + ImVec2 off; // (0,0) unless using multi-viewports + ImVec2 scale; // (1,1) unless using retina display which are often (2,2) + ImVec2 framebuffer; // width, height + + // Project scissor/clipping rectangles into frame-buffer space + ImVec4 getClipRectangle(const ImDrawCmd* cmd) const + { + assert(cmd); - IGPUBuffer::SCreationParams vertexCreationParams = {}; - vertexCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; - vertexCreationParams.size = vertexSize; + ImVec4 rectangle; + rectangle.x = (cmd->ClipRect.x - off.x) * scale.x; + rectangle.y = (cmd->ClipRect.y - off.y) * scale.y; + rectangle.z = (cmd->ClipRect.z - off.x) * scale.x; + rectangle.w = (cmd->ClipRect.w - off.y) * scale.y; - auto & vertexBuffer = m_vertexBuffers[frameIndex]; + return rectangle; + } - if (static_cast(vertexBuffer) == false || vertexBuffer->getSize() < vertexSize) + VkRect2D getScissor(ImVec4 clipRectangle) const + { + // Negative offsets are illegal for vkCmdSetScissor + if (clipRectangle.x < 0.0f) + clipRectangle.x = 0.0f; + if (clipRectangle.y < 0.0f) + clipRectangle.y = 0.0f; + + {// Apply scissor/clipping rectangle + VkRect2D scissor {}; + scissor.offset.x = static_cast(clipRectangle.x); + scissor.offset.y = static_cast(clipRectangle.y); + scissor.extent.width = static_cast(clipRectangle.z - clipRectangle.x); + scissor.extent.height = static_cast(clipRectangle.w - clipRectangle.y); + + return scissor; + } + } + } clip { .off = drawData->DisplayPos, .scale = drawData->FramebufferScale, .framebuffer = { frameBufferWidth, frameBufferHeight } }; + + struct TRS { - vertexBuffer = m_device->createBuffer(std::move(vertexCreationParams)); + core::vector2df_SIMD scale; + core::vector2df_SIMD translate; - video::IDeviceMemoryBacked::SDeviceMemoryRequirements memReq = vertexBuffer->getMemoryReqs(); - memReq.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - auto memOffset = m_device->allocate(memReq, vertexBuffer.get()); - assert(memOffset.isValid()); - } + core::vector2df_SIMD toNDC(core::vector2df_SIMD in) const + { + return in * scale + translate; + } + }; - IGPUBuffer::SCreationParams indexCreationParams = {}; - indexCreationParams.usage = nbl::core::bitflag(nbl::asset::IBuffer::EUF_VERTEX_BUFFER_BIT) | nbl::asset::IBuffer::EUF_INDEX_BUFFER_BIT - | nbl::asset::IBuffer::EUF_INLINE_UPDATE_VIA_CMDBUF; - indexCreationParams.size = indexSize; + const TRS trs = [&]() + { + TRS retV; - auto & indexBuffer = m_indexBuffers[frameIndex]; + retV.scale = core::vector2df_SIMD{ 2.0f / drawData->DisplaySize.x , 2.0f / drawData->DisplaySize.y }; + retV.translate = core::vector2df_SIMD { -1.0f, -1.0f } - core::vector2df_SIMD{ drawData->DisplayPos.x, drawData->DisplayPos.y } * trs.scale; - if (static_cast(indexBuffer) == false || indexBuffer->getSize() < indexSize) - { - indexBuffer = m_device->createBuffer(std::move(indexCreationParams)); + return std::move(retV); + }(); - video::IDeviceMemoryBacked::SDeviceMemoryRequirements memReq = indexBuffer->getMemoryReqs(); - memReq.memoryTypeBits &= m_device->getPhysicalDevice()->getUpStreamingMemoryTypeBits(); - auto memOffset = m_device->allocate(memReq, indexBuffer.get()); - assert(memOffset.isValid()); - } + struct MDI_PARAMS + { + std::array bytesToFill = {}; //! used with MDI::E_BUFFER_CONTENT for elements + typename MDI::ALLOCATOR_TRAITS_T::size_type totalByteSizeRequest = {}, //! sum of bytesToFill + drawCount = {}; //! amount of indirect draw objects + }; + const MDI_PARAMS mdiParams = [&]() { - auto vBinding = vertexBuffer->getBoundMemory(); - auto iBinding = indexBuffer->getBoundMemory(); + MDI_PARAMS params; + for (uint32_t i = 0; i < drawData->CmdListsCount; i++) { - if (!vBinding.memory->map({ 0ull, vBinding.memory->getAllocationSize() }, IDeviceMemoryAllocation::EMCAF_READ)) - logger->log("Could not map device memory for vertex buffer data!", system::ILogger::ELL_WARNING); + const ImDrawList* commandList = drawData->CmdLists[i]; - assert(vBinding.memory->isCurrentlyMapped()); + // calculate upper bound byte size for each mdi's content + params.bytesToFill[MDI::EBC_DRAW_INDIRECT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(VkDrawIndexedIndirectCommand); + params.bytesToFill[MDI::EBC_ELEMENT_STRUCTURES] += commandList->CmdBuffer.Size * sizeof(PerObjectData); + params.bytesToFill[MDI::EBC_INDEX_BUFFERS] += commandList->IdxBuffer.Size * sizeof(ImDrawIdx); + params.bytesToFill[MDI::EBC_VERTEX_BUFFERS] += commandList->VtxBuffer.Size * sizeof(ImDrawVert); } + // calculate upper bound byte size limit for mdi buffer + params.totalByteSizeRequest = std::reduce(std::begin(params.bytesToFill), std::end(params.bytesToFill)); + params.drawCount = params.bytesToFill[MDI::EBC_DRAW_INDIRECT_STRUCTURES] / sizeof(VkDrawIndexedIndirectCommand); + + return std::move(params); + }(); + + std::array mdiBytesFilled; + std::fill(mdiBytesFilled.data(), mdiBytesFilled.data() + MDI_COMPONENT_COUNT, false); + std::array mdiOffsets; + std::fill(mdiOffsets.data(), mdiOffsets.data() + MDI_COMPONENT_COUNT, MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address); + + auto mdiBuffer = m_mdi.buffer; + { + auto binding = mdiBuffer->getBoundMemory(); + assert(binding.memory->isCurrentlyMapped()); + + auto* const mdiData = reinterpret_cast(binding.memory->getMappedPointer()) + binding.offset; + + struct { - if (!iBinding.memory->map({ 0ull, iBinding.memory->getAllocationSize() }, IDeviceMemoryAllocation::EMCAF_READ)) - logger->log("Could not map device memory for index buffer data!", system::ILogger::ELL_WARNING); - - assert(iBinding.memory->isCurrentlyMapped()); - } + typename MDI::ALLOCATOR_TRAITS_T::size_type offset = MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address, + alignment = MDI_MAX_ALIGNMENT, + multiAllocationSize = {}; + } requestState; - auto* vertex_ptr = static_cast(vBinding.memory->getMappedPointer()); - auto* index_ptr = static_cast(iBinding.memory->getMappedPointer()); + const auto start = std::chrono::steady_clock::now(); + float blockRequestFactor = 1.f; + requestState.multiAllocationSize = m_mdi.allocator.max_size(); // first we will try with single allocation request given max free block size from the allocator - for (int n = 0; n < drawData->CmdListsCount; n++) + //! we must upload entire MDI buffer data to our streaming buffer, but we cannot guarantee allocation can be done in single request + for (typename MDI::ALLOCATOR_TRAITS_T::size_type uploadedSize = 0ull; uploadedSize < mdiParams.totalByteSizeRequest;) { - const ImDrawList* cmd = drawData->CmdLists[n]; - ::memcpy(vertex_ptr, cmd->VtxBuffer.Data, cmd->VtxBuffer.Size * sizeof(ImDrawVert)); - ::memcpy(index_ptr, cmd->IdxBuffer.Data, cmd->IdxBuffer.Size * sizeof(ImDrawIdx)); - vertex_ptr += cmd->VtxBuffer.Size; - index_ptr += cmd->IdxBuffer.Size; - } + auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start); + + if (elapsed.count() >= 1u) + return false; - vBinding.memory->unmap(); - iBinding.memory->unmap(); + const auto leftSizeToUpload = mdiParams.totalByteSizeRequest - uploadedSize, + maxTotalFreeBlockSizeToAlloc = m_mdi.allocator.max_size(); + + MDI::ALLOCATOR_TRAITS_T::multi_alloc_addr(m_mdi.allocator, 1u, &requestState.offset, &requestState.multiAllocationSize, &requestState.alignment); + + if (requestState.offset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address) + { + requestState.multiAllocationSize = (blockRequestFactor *= 0.5f); // failed? our following allocation reqest will be half of the previous + continue; + } + else + { + static constexpr auto ALIGN_OFFSET_NEEDED = 0u; + MDI::SUBALLOCATOR_TRAITS_T::allocator_type fillSubAllocator(mdiData, requestState.offset, ALIGN_OFFSET_NEEDED, MDI_MAX_ALIGNMENT, requestState.multiAllocationSize); + MDI::SUBALLOCATOR_TRAITS_T::multi_alloc_addr(fillSubAllocator, mdiOffsets.size(), mdiOffsets.data(), mdiParams.bytesToFill.data(), MDI_ALIGNMENTS.data()); + + //! linear allocator is used to fill the mdi data within suballocation memory range, + //! there are a few restrictions regarding how MDI::E_BUFFER_CONTENT(s) can be packed, + //! and we have really 2 options how data can be allocated: + //! - tightly packed data in single request covering all MDI::E_BUFFER_CONTENT within the allocated memory range + //! - each of MDI::E_BUFFER_CONTENT allocated separately & each tightly packed + + auto fillDrawBuffers = [&]() + { + const typename MDI::ALLOCATOR_TRAITS_T::size_type blockOffset = mdiOffsets[type]; + + if (blockOffset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address or mdiBytesFilled[type]) + return 0u; + + auto* const data = mdiData + blockOffset; + size_t localOffset = {}; + + for (int n = 0; n < drawData->CmdListsCount; n++) + { + auto* cmd_list = drawData->CmdLists[n]; + + if constexpr (type == MDI::EBC_INDEX_BUFFERS) + { + ::memcpy(data + localOffset, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + localOffset += cmd_list->IdxBuffer.Size; + } + else if (type == MDI::EBC_VERTEX_BUFFERS) + { + ::memcpy(data, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert)); + localOffset += cmd_list->VtxBuffer.Size; + } + } + + mdiBytesFilled[type] = true; + return mdiParams.bytesToFill[type]; + }; + + auto fillIndirectStructures = [&]() + { + const typename MDI::ALLOCATOR_TRAITS_T::size_type blockOffset = mdiOffsets[type]; + + if (blockOffset == MDI::ALLOCATOR_TRAITS_T::allocator_type::invalid_address or mdiBytesFilled[type]) + return 0u; + + auto* const data = mdiData + blockOffset; + size_t localOffset = {}; + + size_t cmdListIndexOffset = {}, cmdListVertexOffset = {}, drawID = {}; + + for (int n = 0; n < drawData->CmdListsCount; n++) + { + const auto* cmd_list = drawData->CmdLists[n]; + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const auto* pcmd = &cmd_list->CmdBuffer[cmd_i]; + + if constexpr (type == MDI::EBC_DRAW_INDIRECT_STRUCTURES) + { + auto* indirect = reinterpret_cast(data) + drawID; + + indirect->firstInstance = drawID; // use base instance as draw ID + indirect->indexCount = pcmd->ElemCount; + indirect->instanceCount = 1u; + indirect->firstIndex = pcmd->IdxOffset + cmdListIndexOffset; + indirect->vertexOffset = pcmd->VtxOffset + cmdListVertexOffset; + } + else if (type == MDI::EBC_ELEMENT_STRUCTURES) + { + auto* element = reinterpret_cast(data) + drawID; + + const auto clipRectangle = clip.getClipRectangle(pcmd); + const auto scissor = clip.getScissor(clipRectangle); + + auto packSnorm16 = [](float ndc) -> int16_t + { + return std::round(std::clamp(ndc, -1.0f, 1.0f) * 32767.0f); // TODO: ok encodePixels(void* _pix, const double* _input) but iirc we have issues with our encode/decode utils + }; + + const auto vMin = trs.toNDC(core::vector2df_SIMD(scissor.offset.x, scissor.offset.y)); + const auto vMax = trs.toNDC(core::vector2df_SIMD(scissor.offset.x + scissor.extent.width, scissor.offset.y + scissor.extent.height)); + + element->aabbMin.x = packSnorm16(vMin.x); + element->aabbMin.y = packSnorm16(vMin.y); + element->aabbMax.x = packSnorm16(vMax.x); + element->aabbMax.y = packSnorm16(vMax.y); + element->texId = pcmd->TextureId; + } + + ++drawID; + } + + cmdListIndexOffset += cmd_list->IdxBuffer.Size; + cmdListVertexOffset += cmd_list->VtxBuffer.Size; + } + + mdiBytesFilled[type] = true; + return mdiParams.bytesToFill[type]; + }; + + //! from biggest requests to smallest + //! TODO: I should be able to check if I have valid offsets & fire those with MT + uploadedSize += fillDrawBuffers.template operator() < MDI::EBC_VERTEX_BUFFERS > (); + uploadedSize += fillDrawBuffers.template operator() < MDI::EBC_INDEX_BUFFERS > (); + uploadedSize += fillIndirectStructures.template operator() < MDI::EBC_DRAW_INDIRECT_STRUCTURES > (); + uploadedSize += fillIndirectStructures.template operator() < MDI::EBC_ELEMENT_STRUCTURES > (); + } + + MDI::ALLOCATOR_TRAITS_T::multi_free_addr(m_mdi.allocator, 1u, &requestState.offset, &requestState.multiAllocationSize); + } } + const auto offset = mdiBuffer->getBoundMemory().offset; { const asset::SBufferBinding binding = { - .offset = 0, - .buffer = core::smart_refctd_ptr(indexBuffer) + .offset = mdiOffsets[MDI::EBC_INDEX_BUFFERS], + .buffer = smart_refctd_ptr(mdiBuffer) }; if (!commandBuffer->bindIndexBuffer(binding, sizeof(ImDrawIdx) == 2 ? EIT_16BIT : EIT_32BIT)) { - logger->log("Could not bind index buffer!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not bind index buffer!", system::ILogger::ELL_ERROR); assert(false); } } { const asset::SBufferBinding bindings[] = - { - { - .offset = 0, - .buffer = core::smart_refctd_ptr(vertexBuffer) - } - }; + {{ + .offset = mdiOffsets[MDI::EBC_VERTEX_BUFFERS], + .buffer = smart_refctd_ptr(mdiBuffer) + }}; if(!commandBuffer->bindVertexBuffers(0, 1, bindings)) { - logger->log("Could not bind vertex buffer!", system::ILogger::ELL_ERROR); + m_creationParams.utilities->getLogger()->log("Could not bind vertex buffer!", system::ILogger::ELL_ERROR); assert(false); } - } SViewport const viewport @@ -707,253 +1213,90 @@ namespace nbl::ext::imgui .minDepth = 0.0f, .maxDepth = 1.0f, }; - commandBuffer->setViewport(0, 1, &viewport); - // Setup scale and translation: - // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + commandBuffer->setViewport(0, 1, &viewport); { - PushConstants constants{}; - constants.scale[0] = 2.0f / drawData->DisplaySize.x; - constants.scale[1] = 2.0f / drawData->DisplaySize.y; - constants.translate[0] = -1.0f - drawData->DisplayPos.x * constants.scale[0]; - constants.translate[1] = -1.0f - drawData->DisplayPos.y * constants.scale[1]; - - commandBuffer->pushConstants(pipeline->getLayout(), IShader::E_SHADER_STAGE::ESS_VERTEX, 0, sizeof(constants), &constants); + if (scissors.empty()) + { + VkRect2D defaultScisors[] = { {.offset = {(int32_t)viewport.x, (int32_t)viewport.y}, .extent = {(uint32_t)viewport.width, (uint32_t)viewport.height}} }; + commandBuffer->setScissor(defaultScisors); // cover whole viewport (dynamic scissors must be set only to not throw validation errors) + } + else + commandBuffer->setScissor(scissors); } + + /* + Setup scale and translation, our visible imgui space lies from draw_data->DisplayPps (top left) to + draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + */ - // Will project scissor/clipping rectangles into frame-buffer space - ImVec2 const clip_off = drawData->DisplayPos; // (0,0) unless using multi-viewports - ImVec2 const clip_scale = drawData->FramebufferScale; // (1,1) unless using retina display which are often (2,2) - - // Render command lists - // (Because we merged all buffers into a single one, we maintain our own offset into them) - int global_vtx_offset = 0; - int global_idx_offset = 0; - for (int n = 0; n < drawData->CmdListsCount; n++) { - const ImDrawList* cmd_list = drawData->CmdLists[n]; - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + PushConstants constants { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + .elementBDA = { mdiBuffer->getDeviceAddress() + mdiOffsets[MDI::EBC_ELEMENT_STRUCTURES]}, + .elementCount = { mdiParams.drawCount }, + .scale = { trs.scale[0u], trs.scale[1u] }, + .translate = { trs.translate[0u], trs.translate[1u] }, + .viewport = { viewport.x, viewport.y, viewport.width, viewport.height } + }; - // Project scissor/clipping rectangles into frame-buffer space - ImVec4 clip_rect; - clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; - clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; - clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; - clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; + commandBuffer->pushConstants(pipeline->getLayout(), IShader::E_SHADER_STAGE::ESS_VERTEX | IShader::E_SHADER_STAGE::ESS_FRAGMENT, 0u, sizeof(constants), &constants); + } - if (clip_rect.x < frameBufferWidth && clip_rect.y < frameBufferHeight && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) - { - // Negative offsets are illegal for vkCmdSetScissor - if (clip_rect.x < 0.0f) - clip_rect.x = 0.0f; - if (clip_rect.y < 0.0f) - clip_rect.y = 0.0f; - - {// Apply scissor/clipping rectangle - VkRect2D scissor{}; - scissor.offset.x = static_cast(clip_rect.x); - scissor.offset.y = static_cast(clip_rect.y); - scissor.extent.width = static_cast(clip_rect.z - clip_rect.x); - scissor.extent.height = static_cast(clip_rect.w - clip_rect.y); - commandBuffer->setScissor(0, 1, &scissor); - } + const asset::SBufferBinding binding = + { + .offset = mdiOffsets[MDI::EBC_DRAW_INDIRECT_STRUCTURES], + .buffer = core::smart_refctd_ptr(mdiBuffer) + }; - // Draw - commandBuffer->drawIndexed( - pcmd->ElemCount, - 1, - pcmd->IdxOffset + global_idx_offset, - pcmd->VtxOffset + global_vtx_offset, - 0 - ); - } - } - global_idx_offset += cmd_list->IdxBuffer.Size; - global_vtx_offset += cmd_list->VtxBuffer.Size; - } + commandBuffer->drawIndexedIndirect(binding, mdiParams.drawCount, sizeof(VkDrawIndexedIndirectCommand)); } - + return true; - } - void UI::Update(float const deltaTimeInSec, float const mousePosX, float const mousePosY, size_t const mouseEventsCount, ui::SMouseEvent const * mouseEvents) // TODO: Keyboard events + bool UI::update(const S_UPDATE_PARAMETERS& params) { auto & io = ImGui::GetIO(); - io.DeltaTime = deltaTimeInSec; - io.DisplaySize = ImVec2(m_window->getWidth(), m_window->getHeight()); + + io.DisplaySize = ImVec2(params.displaySize.x, params.displaySize.y); - HandleMouseEvents(mousePosX, mousePosY, mouseEventsCount, mouseEvents); + handleMouseEvents(params); + handleKeyEvents(params); ImGui::NewFrame(); - hasFocus = false; - for (auto const& subscriber : m_subscribers) - subscriber.listener(); - - ImGui::Render(); // note it doesn't touch GPU or graphics API at all, internal call - open the function def for more details - } - - void UI::BeginWindow(char const* windowName) - { - ImGui::Begin(windowName); - } - - void UI::EndWindow() - { - if (ImGui::IsWindowFocused()) - hasFocus = true; - ImGui::End(); - } - - int UI::Register(std::function const& listener) - { - assert(listener != nullptr); - static int NextId = 0; - m_subscribers.emplace_back(NextId++, listener); - return m_subscribers.back().id; - } - - bool UI::UnRegister(int const listenerId) - { - for (int i = m_subscribers.size() - 1; i >= 0; --i) - { - if (m_subscribers[i].id == listenerId) - { - m_subscribers.erase(m_subscribers.begin() + i); - return true; - } - } - return false; - } - - void UI::SetNextItemWidth(float const nextItemWidth) - { - ImGui::SetNextItemWidth(nextItemWidth); - } - - void UI::SetWindowSize(float const width, float const height) - { - ImGui::SetWindowSize(ImVec2(width, height)); - } - - void UI::Text(char const* label, ...) - { - va_list args; - va_start(args, label); - ImGui::TextV(label, args); - va_end(args); - } - - void UI::InputFloat(char const* label, float* value) - { - ImGui::InputFloat(label, value); - } - - void UI::InputFloat2(char const* label, float* value) - { - ImGui::InputFloat2(label, value); - } - - void UI::InputFloat3(char const* label, float* value) - { - ImGui::InputFloat3(label, value); - } - - void UI::InputFloat4(char const* label, float* value) - { - ImGui::InputFloat3(label, value); - } - - void UI::InputFloat3(char const* label, nbl::core::vector3df&value) - { - float tempValue[3]{ value.X, value.Y, value.Z }; - InputFloat3(label, tempValue); - - if (memcmp(tempValue, &value.X, sizeof(float) * 3) != 0) - { - memcpy(&value.X, tempValue, sizeof(float) * 3); - } - } - - bool UI::Combo(char const* label, int32_t* selectedItemIndex, char const** items, int32_t const itemsCount) - { - return ImGui::Combo(label, selectedItemIndex, items, itemsCount); - } + for (auto const& subscriber : m_subscribers) + subscriber(); - //------------------------------------------------------------------------------------------------- - // Based on https://eliasdaler.github.io/using-imgui-with-sfml-pt2/ - static auto vector_getter = [](void* vec, int idx, const char** out_text) - { - auto const& vector = *static_cast*>(vec); - if (idx < 0 || idx >= static_cast(vector.size())) { return false; } - *out_text = vector.at(idx).c_str(); return true; - }; - - bool UI::Combo(const char* label, int* selectedItemIndex, std::vector& values) - { - if (values.empty()) - return false; - - return ImGui::Combo(label, selectedItemIndex, vector_getter, &values, static_cast(values.size())); - } - - void UI::SliderInt(char const* label, int* value, int const minValue, int const maxValue) - { - ImGui::SliderInt(label, value, minValue, maxValue); - } - - void UI::SliderFloat(char const* label, float* value, float const minValue, float const maxValue) - { - ImGui::SliderFloat(label, value, minValue, maxValue); - } - - void UI::Checkbox(char const* label, bool* value) - { - ImGui::Checkbox(label, value); } - void UI::Spacing() + size_t UI::registerListener(const std::function& listener) { - ImGui::Spacing(); + assert(listener != nullptr); + m_subscribers.emplace_back(listener); + return m_subscribers.size() - 1u; } - void UI::Button(char const* label, std::function const& onPress) + std::optional UI::unregisterListener(size_t id) { - if (ImGui::Button(label)) + if (id < m_subscribers.size()) { - assert(onPress != nullptr); - //SceneManager::AssignMainThreadTask([onPress]()->void{ - onPress(); - //}); + m_subscribers.erase(m_subscribers.begin() + id); + return id; } - } - - void UI::InputText(char const* label, std::string& outValue) - { - ImGui::InputText(label, &outValue); - } - bool UI::HasFocus() - { - return hasFocus; - } - - bool UI::IsItemActive() - { - return ImGui::IsItemActive(); + return std::nullopt; } - bool UI::TreeNode(char const* name) + void* UI::getContext() { - return ImGui::TreeNode(name); + return reinterpret_cast(ImGui::GetCurrentContext()); } - void UI::TreePop() + void UI::setContext(void* imguiContext) { - ImGui::TreePop(); + ImGui::SetCurrentContext(reinterpret_cast(imguiContext)); } } \ No newline at end of file diff --git a/src/nbl/ext/ImGui/shaders/common.hlsl b/src/nbl/ext/ImGui/shaders/common.hlsl index 320080a5b4..7d3f525dbe 100644 --- a/src/nbl/ext/ImGui/shaders/common.hlsl +++ b/src/nbl/ext/ImGui/shaders/common.hlsl @@ -9,19 +9,48 @@ struct PSInput { float4 position : SV_Position; - float4 color : COLOR0; float2 uv : TEXCOORD0; + float4 color : COLOR0; + uint drawID : SV_InstanceID; + float clip[4] : SV_ClipDistance; }; struct PushConstants { + uint64_t elementBDA; + uint64_t elementCount; float2 scale; float2 translate; + float4 viewport; }; + #else struct PushConstants { + uint64_t elementBDA; + uint64_t elementCount; float scale[2]; float translate[2]; + float viewport[4]; }; #endif // __HLSL_VERSION + +struct emulated_snorm16_t2 +{ + #ifdef __HLSL_VERSION + float32_t2 unpack() // returns in NDC [-1, 1] range + { + return clamp(float32_t2(x, y) / 32767.0f, -1.f, +1.f); + } + #endif + + int16_t x; + int16_t y; +}; + +struct PerObjectData +{ + emulated_snorm16_t2 aabbMin; + emulated_snorm16_t2 aabbMax; + uint16_t texId; +}; \ No newline at end of file diff --git a/src/nbl/ext/ImGui/shaders/fragment.hlsl b/src/nbl/ext/ImGui/shaders/fragment.hlsl index 19fe0da632..fdaf15613c 100644 --- a/src/nbl/ext/ImGui/shaders/fragment.hlsl +++ b/src/nbl/ext/ImGui/shaders/fragment.hlsl @@ -1,9 +1,46 @@ +#ifndef NBL_TEXTURES_BINDING_IX +#error "NBL_TEXTURES_BINDING_IX must be defined!" +#endif + +#ifndef NBL_SAMPLER_STATES_BINDING_IX +#error "NBL_SAMPLER_STATES_BINDING must be defined!" +#endif + +#ifndef NBL_TEXTURES_SET_IX +#error "NBL_TEXTURES_SET_IX must be defined!" +#endif + +#ifndef NBL_SAMPLER_STATES_SET_IX +#error "NBL_SAMPLER_STATES_SET_IX must be defined!" +#endif + +#ifndef NBL_RESOURCES_COUNT +#error "NBL_RESOURCES_COUNT must be defined!" +#endif + #include "common.hlsl" -[[vk::combinedImageSampler]][[vk::binding(0, 0)]] Texture2D sampleTexture : register(t0); -[[vk::combinedImageSampler]][[vk::binding(0, 0)]] SamplerState linearSampler : register(s0); +[[vk::push_constant]] struct PushConstants pc; + +// separable image samplers to handle textures we do descriptor-index +[[vk::binding(NBL_TEXTURES_BINDING_IX, NBL_TEXTURES_SET_IX)]] Texture2D textures[NBL_RESOURCES_COUNT]; +[[vk::binding(NBL_SAMPLER_STATES_BINDING_IX, NBL_SAMPLER_STATES_SET_IX)]] SamplerState samplerStates[NBL_RESOURCES_COUNT]; + +/* + we use Indirect Indexed draw call to render whole GUI, note we do a cross + platform trick and use base instance index as replacement for gl_DrawID + to request per object data with BDA +*/ float4 PSMain(PSInput input) : SV_Target0 { - return input.color * sampleTexture.Sample(linearSampler, input.uv); + // BDA for requesting object data + const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* input.drawID); + + float4 texel = textures[NonUniformResourceIndex(self.texId)].Sample(samplerStates[self.texId], input.uv) * input.color; + + if(self.texId != 0) // TMP! + texel.w = 1.f; + + return texel; } \ No newline at end of file diff --git a/src/nbl/ext/ImGui/shaders/vertex.hlsl b/src/nbl/ext/ImGui/shaders/vertex.hlsl index f3e7895d3f..18479a02db 100644 --- a/src/nbl/ext/ImGui/shaders/vertex.hlsl +++ b/src/nbl/ext/ImGui/shaders/vertex.hlsl @@ -2,12 +2,32 @@ [[vk::push_constant]] struct PushConstants pc; -PSInput VSMain(VSInput input) +/* + we use Indirect Indexed draw call to render whole GUI, note we do a cross + platform trick and use base instance index as replacement for gl_DrawID + to request per object data with BDA +*/ + +PSInput VSMain(VSInput input, uint drawID : SV_InstanceID) { PSInput output; output.color = input.color; output.uv = input.uv; + output.drawID = drawID; + + // BDA for requesting object data + const PerObjectData self = vk::RawBufferLoad(pc.elementBDA + sizeof(PerObjectData)* drawID); + + // NDC [-1, 1] range output.position = float4(input.position * pc.scale + pc.translate, 0, 1); + const float2 vMin = self.aabbMin.unpack(); + const float2 vMax = self.aabbMax.unpack(); + + // clip planes calculations, axis aligned + output.clip[0] = output.position.x - vMin.x; + output.clip[1] = output.position.y - vMin.y; + output.clip[2] = vMax.x - output.position.x; + output.clip[3] = vMax.y - output.position.y; return output; } \ No newline at end of file diff --git a/src/nbl/ui/CWindowAndroid.h b/src/nbl/ui/CWindowAndroid.h index 18a364a4ee..f68dfc11fb 100644 --- a/src/nbl/ui/CWindowAndroid.h +++ b/src/nbl/ui/CWindowAndroid.h @@ -23,7 +23,7 @@ class CWindowAndroid : public IWindowAndroid } virtual inline IClipboardManager* getClipboardManager() override { return nullptr; } - virtual inline ICursorControl* getCursorControl() override { return nullptr; } + virtual inline ICursorControl* getCursorControl() const override { return nullptr; } inline const native_handle_t& getNativeHandle() const override { return m_native; } diff --git a/src/nbl/ui/CWindowWin32.h b/src/nbl/ui/CWindowWin32.h index 295e21e525..fcb675900e 100644 --- a/src/nbl/ui/CWindowWin32.h +++ b/src/nbl/ui/CWindowWin32.h @@ -31,7 +31,7 @@ class NBL_API2 CWindowWin32 final : public IWindowWin32 return m_clipboardManager.get(); } - inline ICursorControl* getCursorControl() override {return m_windowManager.get();} + inline ICursorControl* getCursorControl() const override {return m_windowManager.get();} inline IWindowManager* getManager() const override {return m_windowManager.get();} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 744b551462..1761ea9f07 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,2 +1,6 @@ add_subdirectory(nsc) -add_subdirectory(xxHash256) \ No newline at end of file +add_subdirectory(xxHash256) + +if(NBL_BUILD_IMGUI) + add_subdirectory(nite) +endif() \ No newline at end of file diff --git a/tools/nite/CMakeLists.txt b/tools/nite/CMakeLists.txt new file mode 100644 index 0000000000..91ee8e3a47 --- /dev/null +++ b/tools/nite/CMakeLists.txt @@ -0,0 +1,34 @@ +set(NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT "${THIRD_PARTY_SOURCE_DIR}/imgui_test_engine") +set(NBL_IMGUI_TEST_ENGINE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_engine") +set(NBL_IMGUI_TEST_SUITE_ROOT "${NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT}/imgui_test_suite") + +set(NBL_EXTRA_OPTIONS + # TODO: add if required +) + +set(NBL_EXTRA_LIBS + imtestengine + "${NBL_EXT_IMGUI_UI_LIB}" # Nabla IMGUI backend +) + +nbl_create_executable_project("" "${NBL_EXTRA_OPTIONS}" "${NBL_EXTRA_INCLUDES}" "${NBL_EXTRA_LIBS}") + +add_dependencies(${EXECUTABLE_NAME} argparse) +target_include_directories(${EXECUTABLE_NAME} PUBLIC + $ +) + +nbl_adjust_flags(MAP_RELEASE Release MAP_RELWITHDEBINFO RelWithDebInfo MAP_DEBUG Debug) +nbl_adjust_definitions() + +enable_testing() + +add_test(NAME NBL_NITE_RUN_SUITE_BASIC_TESTS + COMMAND "$" --mode cmd --group test --queued + COMMAND_EXPAND_LISTS +) + +add_test(NAME NBL_NITE_RUN_SUITE_PERF_TESTS + COMMAND "$" --mode cmd --group perf --queued + COMMAND_EXPAND_LISTS +) \ No newline at end of file diff --git a/tools/nite/README.md b/tools/nite/README.md new file mode 100644 index 0000000000..6a8d56e400 --- /dev/null +++ b/tools/nite/README.md @@ -0,0 +1,22 @@ +# Nabla IMGUI Test Engine + +Performs suite tests for Nabla IMGUI backend. + +## CTest + +Build the target with desired configuration eg. `Debug`, open command line in the target's build directory and execute + +```bash +ctest -C Debug --progress --stop-on-failure +``` + +https://github.com/Devsh-Graphics-Programming/Nabla/assets/34793522/2b739994-9900-4789-ba54-6435daded632 + +CTest will execute `NBL_NITE_RUN_SUITE_BASIC_TESTS` first then `NBL_NITE_RUN_SUITE_PERF_TESTS`. Once we pass the first we can say we have 100% working backend! (currently we get SegFault somewhere in half of `NBL_NITE_RUN_SUITE_BASIC_TESTS` because our backend is still WIP) + +## GUI + +If you want to pick individual tests and browse GUI just execute the target's executable with no arguments. + +https://github.com/Devsh-Graphics-Programming/Nabla/assets/34793522/79876d13-cf4e-4476-9ab5-7d9b56dc695e + diff --git a/tools/nite/main.cpp b/tools/nite/main.cpp new file mode 100644 index 0000000000..6c69f83e6e --- /dev/null +++ b/tools/nite/main.cpp @@ -0,0 +1,476 @@ +// Copyright (C) 2018-2020 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#define _NBL_STATIC_LIB_ +#include +#include "nbl/video/utilities/CSimpleResizeSurface.h" + +#include "../common/SimpleWindowedApplication.hpp" +#include "../common/InputSystem.hpp" + +#include "nbl/ext/ImGui/ImGui.h" +#include "nbl/ui/ICursorControl.h" + +// Test Engine +#include "imgui_test_suite_imconfig.h" +#include "imgui_test_suite.h" +#include "imgui_te_engine.h" +#include "imgui_te_ui.h" +#include "imgui_te_utils.h" + +// Argparse +#include + +using namespace nbl; +using namespace core; +using namespace hlsl; +using namespace system; +using namespace asset; +using namespace ui; +using namespace video; + +class CEventCallback : public ISimpleManagedSurface::ICallback +{ +public: + CEventCallback(nbl::core::smart_refctd_ptr&& m_inputSystem, nbl::system::logger_opt_smart_ptr&& logger) : m_inputSystem(std::move(m_inputSystem)), m_logger(std::move(logger)) {} + CEventCallback() {} + + void setLogger(nbl::system::logger_opt_smart_ptr& logger) + { + m_logger = logger; + } + void setInputSystem(nbl::core::smart_refctd_ptr&& m_inputSystem) + { + m_inputSystem = std::move(m_inputSystem); + } +private: + + void onMouseConnected_impl(nbl::core::smart_refctd_ptr&& mch) override + { + m_logger.log("A mouse %p has been connected", nbl::system::ILogger::ELL_INFO, mch.get()); + m_inputSystem.get()->add(m_inputSystem.get()->m_mouse, std::move(mch)); + } + void onMouseDisconnected_impl(nbl::ui::IMouseEventChannel* mch) override + { + m_logger.log("A mouse %p has been disconnected", nbl::system::ILogger::ELL_INFO, mch); + m_inputSystem.get()->remove(m_inputSystem.get()->m_mouse, mch); + } + void onKeyboardConnected_impl(nbl::core::smart_refctd_ptr&& kbch) override + { + m_logger.log("A keyboard %p has been connected", nbl::system::ILogger::ELL_INFO, kbch.get()); + m_inputSystem.get()->add(m_inputSystem.get()->m_keyboard, std::move(kbch)); + } + void onKeyboardDisconnected_impl(nbl::ui::IKeyboardEventChannel* kbch) override + { + m_logger.log("A keyboard %p has been disconnected", nbl::system::ILogger::ELL_INFO, kbch); + m_inputSystem.get()->remove(m_inputSystem.get()->m_keyboard, kbch); + } + +private: + nbl::core::smart_refctd_ptr m_inputSystem = nullptr; + nbl::system::logger_opt_smart_ptr m_logger = nullptr; +}; + +class NITETool final : public examples::SimpleWindowedApplication +{ + using device_base_t = examples::SimpleWindowedApplication; + using clock_t = std::chrono::steady_clock; + + _NBL_STATIC_INLINE_CONSTEXPR uint32_t WIN_W = 1280, WIN_H = 720, SC_IMG_COUNT = 3u, FRAMES_IN_FLIGHT = 5u; + static_assert(FRAMES_IN_FLIGHT > SC_IMG_COUNT); + + constexpr static inline clock_t::duration DisplayImageDuration = std::chrono::milliseconds(900); + +public: + inline NITETool(const path& _localInputCWD, const path& _localOutputCWD, const path& _sharedInputCWD, const path& _sharedOutputCWD) + : IApplicationFramework(_localInputCWD, _localOutputCWD, _sharedInputCWD, _sharedOutputCWD) {} + + inline core::vector getSurfaces() const override + { + if (!m_surface) + { + { + auto windowCallback = core::make_smart_refctd_ptr(smart_refctd_ptr(m_inputSystem), smart_refctd_ptr(m_logger)); + IWindow::SCreationParams params = {}; + params.callback = core::make_smart_refctd_ptr(); + params.width = WIN_W; + params.height = WIN_H; + params.x = 32; + params.y = 32; + params.flags = ui::IWindow::ECF_HIDDEN | IWindow::ECF_BORDERLESS | IWindow::ECF_RESIZABLE; + params.windowCaption = "NITETool"; + params.callback = windowCallback; + const_cast&>(m_window) = m_winMgr->createWindow(std::move(params)); + } + + auto surface = CSurfaceVulkanWin32::create(smart_refctd_ptr(m_api), smart_refctd_ptr_static_cast(m_window)); + const_cast&>(m_surface) = nbl::video::CSimpleResizeSurface::create(std::move(surface)); + } + + if (m_surface) + return { {m_surface->getSurface()/*,EQF_NONE*/} }; + + return {}; + } + + inline bool onAppInitialized(smart_refctd_ptr&& system) override + { + + _NBL_STATIC_INLINE_CONSTEXPR std::string_view NBL_QUEUE_ARG = "--queued"; // flag + _NBL_STATIC_INLINE_CONSTEXPR std::string_view NBL_MODE_ARG = "--mode"; // "cmd" || "gui" value + _NBL_STATIC_INLINE_CONSTEXPR std::string_view NBL_GROUP_ARG = "--group"; // "test" || "perf" value + + argparse::ArgumentParser program("[NITE]: Performs Suite Test for Nabla IMGUI backend"); + + program.add_argument(NBL_QUEUE_ARG.data()) + .flag() + .help("use this argument to queue execution of tests depending on --group argument, otherwise (default) you can browse GUI freely"); + + program.add_argument(NBL_MODE_ARG.data()) + .default_value("gui") + .help("use \"cmd\" for running from command line and \"gui\" for GUI (default)"); + + program.add_argument(NBL_GROUP_ARG.data()) + .default_value("test") + .help("use \"test\" (default) for running basic tests and \"perf\" for performance tests"); + + try + { + program.parse_args({ argv.data(), argv.data() + argv.size() }); + } + catch (const std::exception& err) + { + std::cerr << err.what() << std::endl << program; + return 1; + } + + const auto pQueueArg = program.get(NBL_QUEUE_ARG.data()); + const auto pModeArg = program.get(NBL_MODE_ARG.data()); + const auto pGroupArg = program.get(NBL_GROUP_ARG.data()); + + m_inputSystem = make_smart_refctd_ptr(logger_opt_smart_ptr(smart_refctd_ptr(m_logger))); + + if (!device_base_t::onAppInitialized(smart_refctd_ptr(system))) + return false; + + m_semaphore = m_device->createSemaphore(m_realFrameIx); + if (!m_semaphore) + return logFail("Failed to Create a Semaphore!"); + + ISwapchain::SCreationParams swapchainParams = { .surface = m_surface->getSurface() }; + if (!swapchainParams.deduceFormat(m_physicalDevice)) + return logFail("Could not choose a Surface Format for the Swapchain!"); + + const static IGPURenderpass::SCreationParams::SSubpassDependency dependencies[] = + { + { + .srcSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .dstSubpass = 0, + .memoryBarrier = + { + .srcStageMask = asset::PIPELINE_STAGE_FLAGS::COPY_BIT, + .srcAccessMask = asset::ACCESS_FLAGS::TRANSFER_WRITE_BIT, + .dstStageMask = asset::PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + .dstAccessMask = asset::ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + } + }, + { + .srcSubpass = 0, + .dstSubpass = IGPURenderpass::SCreationParams::SSubpassDependency::External, + .memoryBarrier = + { + .srcStageMask = asset::PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = asset::ACCESS_FLAGS::COLOR_ATTACHMENT_WRITE_BIT + } + }, + IGPURenderpass::SCreationParams::DependenciesEnd + }; + + auto scResources = std::make_unique(m_device.get(), swapchainParams.surfaceFormat.format, dependencies); + auto* renderpass = scResources->getRenderpass(); + + if (!renderpass) + return logFail("Failed to create Renderpass!"); + + auto gQueue = getGraphicsQueue(); + if (!m_surface || !m_surface->init(gQueue, std::move(scResources), swapchainParams.sharedParams)) + return logFail("Could not create Window & Surface or initialize the Surface!"); + + m_maxFramesInFlight = m_surface->getMaxFramesInFlight(); + if (FRAMES_IN_FLIGHT < m_maxFramesInFlight) + { + m_logger->log("Lowering frames in flight!", ILogger::ELL_WARNING); + m_maxFramesInFlight = FRAMES_IN_FLIGHT; + } + + m_cmdPool = m_device->createCommandPool(gQueue->getFamilyIndex(), IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); + + for (auto i = 0u; i < m_maxFramesInFlight; i++) + { + if (!m_cmdPool) + return logFail("Couldn't create Command Pool!"); + if (!m_cmdPool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, { m_cmdBufs.data() + i, 1 })) + return logFail("Couldn't create Command Buffer!"); + } + + ui = core::make_smart_refctd_ptr(smart_refctd_ptr(m_device), (int)m_maxFramesInFlight, renderpass, nullptr, smart_refctd_ptr(m_window)); + auto* ctx = reinterpret_cast(ui->getContext()); + ImGui::SetCurrentContext(ctx); + + // Initialize Test Engine + engine = ImGuiTestEngine_CreateContext(); + ImGuiTestEngineIO& test_io = ImGuiTestEngine_GetIO(engine); + test_io.ConfigVerboseLevel = ImGuiTestVerboseLevel_Info; + test_io.ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Debug; + test_io.ConfigSavedSettings = false; + + // Register tests + RegisterTests_All(engine); + + // Start engine + ImGuiTestEngine_Start(engine, ctx); + ImGuiTestEngine_InstallDefaultCrashHandler(); + + if (pQueueArg) + { + ImGuiTestGroup group = ImGuiTestGroup_Unknown; + { + if (pGroupArg == "test") + group = ImGuiTestGroup_Tests; + else if (pGroupArg == "perf") + group = ImGuiTestGroup_Perfs; + } + ImGuiTestRunFlags flags = ImGuiTestRunFlags_None; + { + if (pModeArg == "cmd") + flags = ImGuiTestRunFlags_RunFromCommandLine; + else if (pModeArg == "gui") + flags = ImGuiTestRunFlags_RunFromGui; + } + ImGuiTestEngine_QueueTests(engine, group, nullptr, flags); + } + + ui->registerListener([this]() -> void + { + ImGuiTestEngine_ShowTestEngineWindows(engine, nullptr); + } + ); + + m_winMgr->setWindowSize(m_window.get(), WIN_W, WIN_H); + m_surface->recreateSwapchain(); + m_winMgr->show(m_window.get()); + + return true; + } + + inline void workLoopBody() override + { + static std::chrono::microseconds previousEventTimestamp{}; + // TODO: Use real deltaTime instead + float deltaTimeInSec = 0.1f; + + struct + { + std::vector mouse {}; + std::vector keyboard {}; + } capturedEvents; + + m_inputSystem->getDefaultMouse(&mouse); + m_inputSystem->getDefaultKeyboard(&keyboard); + + mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void + { + for (const auto& e : events) + { + if (e.timeStamp < previousEventTimestamp) + continue; + + previousEventTimestamp = e.timeStamp; + capturedEvents.mouse.emplace_back(e); + } + }, m_logger.get()); + + keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void + { + for (const auto& e : events) + { + if (e.timeStamp < previousEventTimestamp) + continue; + + previousEventTimestamp = e.timeStamp; + capturedEvents.keyboard.emplace_back(e); + } + }, m_logger.get()); + + const auto mousePosition = m_window->getCursorControl()->getPosition(); + + // C++ no instance of constructor matches the argument list argument types are: (std::_Vector_const_iterator>>, std::_Vector_const_iterator>>) + // const nbl::ui::IMouseEventChannel::range_t mouseEvents(capturedEvents.mouse.data(), capturedEvents.mouse.data() + capturedEvents.mouse.size()); + // const nbl::ui::IKeyboardEventChannel::range_t keyboardEvents(capturedEvents.keyboard.data(), capturedEvents.keyboard.data() + capturedEvents.keyboard.size()); + + core::SRange mouseEvents(capturedEvents.mouse.data(), capturedEvents.mouse.data() + capturedEvents.mouse.size()); + core::SRange keyboardEvents(capturedEvents.keyboard.data(), capturedEvents.keyboard.data() + capturedEvents.keyboard.size()); + + ui->update(deltaTimeInSec, { mousePosition.x , mousePosition.y}, mouseEvents, keyboardEvents); + + const auto resourceIx = m_realFrameIx % m_maxFramesInFlight; + + if (m_realFrameIx >= m_maxFramesInFlight) + { + const ISemaphore::SWaitInfo cbDonePending[] = + { + { + .semaphore = m_semaphore.get(), + .value = m_realFrameIx + 1 - m_maxFramesInFlight + } + }; + if (m_device->blockForSemaphores(cbDonePending) != ISemaphore::WAIT_RESULT::SUCCESS) + return; + } + + m_currentImageAcquire = m_surface->acquireNextImage(); + if (!m_currentImageAcquire) + return; + + auto* const cb = m_cmdBufs.data()[resourceIx].get(); + cb->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); + cb->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); + cb->beginDebugMarker("NITE Tool Frame"); + + auto* queue = getGraphicsQueue(); + + asset::SViewport viewport; + { + viewport.minDepth = 1.f; + viewport.maxDepth = 0.f; + viewport.x = 0u; + viewport.y = 0u; + viewport.width = WIN_W; + viewport.height = WIN_H; + } + cb->setViewport(0u, 1u, &viewport); + { + const VkRect2D currentRenderArea = + { + .offset = {0,0}, + .extent = {m_window->getWidth(),m_window->getHeight()} + }; + + const IGPUCommandBuffer::SClearColorValue clearValue = { .float32 = {0.f,0.f,0.f,1.f} }; + auto scRes = static_cast(m_surface->getSwapchainResources()); + const IGPUCommandBuffer::SRenderpassBeginInfo info = + { + .framebuffer = scRes->getFramebuffer(m_currentImageAcquire.imageIndex), + .colorClearValues = &clearValue, + .depthStencilClearValues = nullptr, + .renderArea = currentRenderArea + }; + cb->beginRenderPass(info, IGPUCommandBuffer::SUBPASS_CONTENTS::INLINE); + } + + ui->render(cb, resourceIx); + cb->endRenderPass(); + cb->end(); + { + const IQueue::SSubmitInfo::SSemaphoreInfo rendered[] = + { + { + .semaphore = m_semaphore.get(), + .value = ++m_realFrameIx, + .stageMask = PIPELINE_STAGE_FLAGS::COLOR_ATTACHMENT_OUTPUT_BIT + } + }; + { + { + const IQueue::SSubmitInfo::SCommandBufferInfo commandBuffers[] = + { + {.cmdbuf = cb } + }; + + const IQueue::SSubmitInfo::SSemaphoreInfo acquired[] = + { + { + .semaphore = m_currentImageAcquire.semaphore, + .value = m_currentImageAcquire.acquireCount, + .stageMask = PIPELINE_STAGE_FLAGS::NONE + } + }; + const IQueue::SSubmitInfo infos[] = + { + { + .waitSemaphores = acquired, + .commandBuffers = commandBuffers, + .signalSemaphores = rendered + } + }; + + if (queue->submit(infos) != IQueue::RESULT::SUCCESS) + m_realFrameIx--; + } + } + + m_window->setCaption("[Nabla IMGUI Test Engine]"); + m_surface->present(m_currentImageAcquire.imageIndex, rendered); + + // Post swap Test Engine + ImGuiTestEngine_PostSwap(engine); + } + } + + inline bool keepRunning() override + { + if (m_surface->irrecoverable()) + return false; + + return true; + } + + inline bool onAppTerminated() override + { + int tested = 0, successed = 0; + ImGuiTestEngine_GetResult(engine, tested, successed); + const bool good = tested == successed; + + ImVector* tests = _NBL_NEW(ImVector); + ImGuiTestEngine_GetTestList(engine, tests); + + if (successed < tested) + { + m_logger->log("Failing Tests: ", ILogger::ELL_ERROR); + + for (auto* test : *tests) + if (test->Output.Status == ImGuiTestStatus_Error) + m_logger->log("- " + std::string(test->Name), ILogger::ELL_ERROR); + } + + m_logger->log(std::string("Tests Result: ") + (good ? "PASSING" : "FAILING"), (good ? ILogger::ELL_PERFORMANCE : ILogger::ELL_ERROR)); + m_logger->log(std::string("(") + std::to_string(successed) + "/" + std::to_string(tested) + " tests passed)", ILogger::ELL_PERFORMANCE); + + _NBL_DELETE(tests); + ImGuiTestEngine_Stop(engine); + + return device_base_t::onAppTerminated() && good; + } + +private: + smart_refctd_ptr m_window; + smart_refctd_ptr> m_surface; + smart_refctd_ptr m_pipeline; + smart_refctd_ptr m_semaphore; + smart_refctd_ptr m_cmdPool; + uint64_t m_realFrameIx : 59 = 0; + uint64_t m_maxFramesInFlight : 5; + std::array, ISwapchain::MaxImages> m_cmdBufs; + ISimpleManagedSurface::SAcquireResult m_currentImageAcquire = {}; + + nbl::core::smart_refctd_ptr ui; + core::smart_refctd_ptr m_inputSystem; + InputSystem::ChannelReader mouse; + InputSystem::ChannelReader keyboard; + + // Test engine + ImGuiTestEngine* engine = nullptr; +}; + +NBL_MAIN_FUNC(NITETool) \ No newline at end of file diff --git a/tools/nsc/main.cpp b/tools/nsc/main.cpp index 59ecf8b459..541598d76d 100644 --- a/tools/nsc/main.cpp +++ b/tools/nsc/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include using namespace nbl; using namespace nbl::system; @@ -46,6 +47,7 @@ class ShaderCompiler final : public system::IApplicationFramework } m_arguments = std::vector(argv.begin() + 1, argv.end()-1); // turn argv into vector for convenience + std::string file_to_compile = argv.back(); if (!m_system->exists(file_to_compile, IFileBase::ECF_READ)) { @@ -137,6 +139,13 @@ class ShaderCompiler final : public system::IApplicationFramework m_arguments.push_back("main"); } + for (size_t i = 0; i < m_arguments.size() - 1; ++i) // -I must be given with second arg, no need to include iteration over last one + { + const auto& arg = m_arguments[i]; + if (arg == "-I") + m_include_search_paths.emplace_back(m_arguments[i + 1]); + } + auto shader = open_shader_file(file_to_compile); if (shader->getContentType() != IShader::E_CONTENT_TYPE::ECT_HLSL) { @@ -173,8 +182,16 @@ class ShaderCompiler final : public system::IApplicationFramework options.stage = shader->getStage(); options.preprocessorOptions.sourceIdentifier = sourceIdentifier; options.preprocessorOptions.logger = m_logger.get(); + options.dxcOptions = std::span(m_arguments); + auto includeFinder = make_smart_refctd_ptr(smart_refctd_ptr(m_system)); + auto includeLoader = includeFinder->getDefaultFileSystemLoader(); + + // because before real compilation we do preprocess the input it doesn't really matter we proxy include search direcotries further with dxcOptions since at the end all includes are resolved to single file + for(const auto& it : m_include_search_paths) + includeFinder->addSearchPath(it, includeLoader); + options.preprocessorOptions.includeFinder = includeFinder.get(); return hlslcompiler->compileToSPIRV((const char*)shader->getContent()->getPointer(), options); @@ -220,7 +237,7 @@ class ShaderCompiler final : public system::IApplicationFramework bool no_nbl_builtins{ false }; smart_refctd_ptr m_system; smart_refctd_ptr m_logger; - std::vector m_arguments; + std::vector m_arguments, m_include_search_paths; core::smart_refctd_ptr m_assetMgr;