diff --git a/40_PathTracer/CMakeLists.txt b/40_PathTracer/CMakeLists.txt index 8c0fbae51..a3b514b48 100644 --- a/40_PathTracer/CMakeLists.txt +++ b/40_PathTracer/CMakeLists.txt @@ -4,6 +4,7 @@ set(NBL_INCLUDE_SERACH_DIRECTORIES "${NBL_EXT_MITSUBA_LOADER_INCLUDE_DIRS}" "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/src" + "${NBL_ROOT_PATH}/3rdparty/portable-file-dialogs" ) set(NBL_LIBRARIES "${NBL_EXT_MITSUBA_LOADER_LIB}" @@ -12,12 +13,29 @@ set(NBL_LIBRARIES "${NBL_EXT_IMGUI_UI_LIB}" ) set(NBL_EXAMPLE_SOURCES + "${CMAKE_CURRENT_SOURCE_DIR}/include/common.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/include/io/CSceneLoader.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/gui/CSceneWindow.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/gui/CSessionWindow.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/gui/CUIManager.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/renderer/SAASequence.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/renderer/CScene.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/renderer/CRenderer.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/renderer/CSession.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/renderer/resolve/CBasicRWMCResolver.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/renderer/resolve/IResolver.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/renderer/present/CWindowPresenter.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/renderer/present/IPresenter.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/io/CSceneLoader.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/renderer/CSession.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/renderer/CScene.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/renderer/CRenderer.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/renderer/resolve/CBasicRWMCResolver.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/renderer/present/CWindowPresenter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/gui/CUIManager.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/gui/CSceneWindow.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/gui/CSessionWindow.cpp" ) nbl_create_executable_project("${NBL_EXAMPLE_SOURCES}" "" "${NBL_INCLUDE_SERACH_DIRECTORIES}" "${NBL_LIBRARIES}") diff --git a/40_PathTracer/include/gui/CSceneWindow.h b/40_PathTracer/include/gui/CSceneWindow.h new file mode 100644 index 000000000..c76110003 --- /dev/null +++ b/40_PathTracer/include/gui/CSceneWindow.h @@ -0,0 +1,73 @@ +// Copyright (C) 2025-2026 - 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 +#ifndef _NBL_THIS_EXAMPLE_C_SCENE_WINDOW_H_INCLUDED_ +#define _NBL_THIS_EXAMPLE_C_SCENE_WINDOW_H_INCLUDED_ + +#include "imgui.h" + +#include +#include +#include + +namespace nbl::this_example +{ +// Forward declarations +class CScene; +class CSession; +} + +namespace nbl::this_example::gui +{ + +class CSceneWindow final +{ +public: + // Callbacks for user actions - main app handles the actual logic + struct SCallbacks + { + std::function onSensorSelected = nullptr; + std::function onLoadRequested = nullptr; + std::function onReloadRequested = nullptr; + }; + void setCallbacks(const SCallbacks& callbacks) { m_callbacks = callbacks; } + + CSceneWindow() = default; + ~CSceneWindow() = default; + + // Set the scene to display (can be null) + void setScene(const CScene* scene) { m_scene = scene; } + + // Get current scene path for display + void setScenePath(const std::string& path) { m_scenePath = path; } + + // Main draw call - renders the window + // forceReposition: if true, window will reposition to default location + void draw(bool forceReposition = false); + + // Current selection state + int getSelectedSensorIndex() const { return m_selectedSensorIndex; } + void setSelectedSensorIndex(int idx) { m_selectedSensorIndex = idx; } + + // Window visibility + bool isOpen() const { return m_isOpen; } + void setOpen(bool open) { m_isOpen = open; } + +private: + const CScene* m_scene = nullptr; + std::string m_scenePath = ""; + int m_selectedSensorIndex = -1; + bool m_isOpen = true; + SCallbacks m_callbacks; + + // Section drawing helpers + void drawLoadSection(); + void drawSensorsSection(); + void drawGlobalsSection(); + void drawEmittersSection(); + void drawEditorSection(); +}; + +} // namespace nbl::this_example::gui + +#endif // _NBL_THIS_EXAMPLE_C_SCENE_WINDOW_H_INCLUDED_ diff --git a/40_PathTracer/include/gui/CSessionWindow.h b/40_PathTracer/include/gui/CSessionWindow.h new file mode 100644 index 000000000..0cda18037 --- /dev/null +++ b/40_PathTracer/include/gui/CSessionWindow.h @@ -0,0 +1,114 @@ +// Copyright (C) 2025-2026 - 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 +#ifndef _NBL_THIS_EXAMPLE_C_SESSION_WINDOW_H_INCLUDED_ +#define _NBL_THIS_EXAMPLE_C_SESSION_WINDOW_H_INCLUDED_ + +#include "imgui.h" +#include "renderer/shaders/pathtrace/push_constants.hlsl" +#include +#include +#include +#include + +namespace nbl::this_example +{ + class CSession; +} + +namespace nbl::this_example::gui +{ + +class CSessionWindow final +{ +public: + // Buffer types that can be displayed + enum class BufferType : uint32_t + { + Beauty = 0, + Albedo, + Normal, + Motion, + Mask, + RWMCCascades, + SampleCount, + Count + }; + + // Callbacks to main app + struct SCallbacks + { + // Requires session recreation + std::function onRenderModeChanged = nullptr; + std::function onResolutionChanged = nullptr; + + // Requires reset() + std::function onMutablesChanged = nullptr; + + // Immediate update() + std::function onDynamicsChanged = nullptr; + + // Buffer view change + std::function onBufferSelected = nullptr; + }; + void setCallbacks(const SCallbacks& callbacks) { m_callbacks = callbacks; } + + CSessionWindow() = default; + ~CSessionWindow() = default; + + // Set active session to display/control + void setSession(CSession* session); + + // Set texture IDs for buffer thumbnails (called by CUIManager) + void setBufferTextureIDs(const std::array(BufferType::Count)>& textureIDs); + + // forceReposition: if true, window will reposition to default location + void draw(bool forceReposition = false); + + bool isOpen() const { return m_isOpen; } + void setOpen(bool open) { m_isOpen = open; } + + // Get currently selected buffer + BufferType getSelectedBuffer() const { return static_cast(m_state.selectedBufferIndex); } + +private: + CSession* m_session = nullptr; + bool m_isOpen = true; + SCallbacks m_callbacks; + + // Texture IDs for buffer thumbnails + std::array(BufferType::Count)> m_bufferTextureIDs = {}; + + // Local state for UI controls + struct SState + { + // Mode + CSession::RenderMode renderMode = CSession::RenderMode::Beauty; + + // Dynamics + float cropOffsetX = 0.0f; + float cropOffsetY = 0.0f; + float tMax = 10000.0f; + + // Mutables + int cropWidth = 1920; + int cropHeight = 1080; + float nearClip = 0.1f; + float farClip = 10000.0f; + + // View + int selectedBufferIndex = 0; + } m_state; + + // Copies to check for changes + SSensorDynamics m_cachedDynamics; + + void drawRenderModeSection(); + void drawDynamicsSection(); + void drawMutablesSection(); + void drawOutputBufferSection(); +}; + +} // namespace nbl::this_example::gui + +#endif // _NBL_THIS_EXAMPLE_C_SESSION_WINDOW_H_INCLUDED_ diff --git a/40_PathTracer/include/gui/CUIManager.h b/40_PathTracer/include/gui/CUIManager.h new file mode 100644 index 000000000..e56259510 --- /dev/null +++ b/40_PathTracer/include/gui/CUIManager.h @@ -0,0 +1,142 @@ +// Copyright (C) 2025-2026 - 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 +#ifndef _NBL_THIS_EXAMPLE_C_UI_MANAGER_H_INCLUDED_ +#define _NBL_THIS_EXAMPLE_C_UI_MANAGER_H_INCLUDED_ + +#include "nbl/ext/ImGui/ImGui.h" +#include "nbl/video/alloc/SubAllocatedDescriptorSet.h" +#include "gui/CSceneWindow.h" +#include "gui/CSessionWindow.h" +#include "renderer/shaders/pathtrace/push_constants.hlsl" + +#include + +namespace nbl::this_example +{ +// Forward declarations +class CScene; +class CSession; +class CWindowPresenter; +} + +namespace nbl::this_example::gui +{ + +class CUIManager final : public core::IReferenceCounted +{ +public: + static constexpr uint32_t MaxUITextureCount = 16u; + static constexpr uint32_t TexturesBindingIndex = 0u; + + // Texture indices for session buffers (after font atlas at index 0) + enum class SessionTextureIndex : uint32_t + { + Beauty = 0, + Albedo, + Normal, + Motion, + Mask, + RWMCCascades, + SampleCount, + Count + }; + + struct SCreationParams + { + core::smart_refctd_ptr assetManager; + core::smart_refctd_ptr utilities; + video::IQueue* transferQueue = nullptr; + system::logger_opt_smart_ptr logger = nullptr; + }; + + struct SCachedParams : SCreationParams + { + }; + + CUIManager(SCachedParams&& params) : m_params(std::move(params)) {} + + struct SInitParams + { + video::IGPURenderpass* renderpass = nullptr; + std::function onSensorSelected = nullptr; + std::function onLoadSceneRequested = nullptr; + std::function onReloadSceneRequested = nullptr; + + // Session Callbacks + std::function onRenderModeChanged = nullptr; + std::function onResolutionChanged = nullptr; + std::function onMutablesChanged = nullptr; + std::function onDynamicsChanged = nullptr; + std::function onBufferSelected = nullptr; + + }; + + static core::smart_refctd_ptr create(SCreationParams&& params); + + // Initialize GPU resources + bool init(const SInitParams& params); + + // Cleanup (call before destruction) + void deinit(); + + // Set current scene for the scene window + void setScene(const CScene* scene, const std::string& scenePath = ""); + + // Set current active session for session window and bind its textures + void setSession(CSession* session, video::ISemaphore* semaphore = nullptr, uint64_t semaphoreValue = 0); + + // Update ImGui state with input events + void update(const nbl::ext::imgui::UI::SUpdateParameters& params); + + // Draw all UI windows - called between beginRenderpass() and endRenderpassAndPresent() + void drawWindows(); + bool render(video::IGPUCommandBuffer* cmdbuf, video::ISemaphore::SWaitInfo waitInfo = {}); + + nbl::ext::imgui::UI* getImGuiManager() { return m_imguiManager.get(); } + video::IGPUDescriptorSet* getDescriptorSet() { return m_subAllocDS ? m_subAllocDS->getDescriptorSet() : nullptr; } + + CSceneWindow& getSceneWindow() { return m_sceneWindow; } + CSessionWindow& getSessionWindow() { return m_sessionWindow; } + + // Reset window positions (call when viewport/resolution changes) + void resetWindowPositions() { m_needsRepositionWindows = true; } + +private: + // Bind session textures to descriptor set + void bindSessionTextures(CSession* session); + // Unbind session textures (deallocate indices) + void unbindSessionTextures(video::ISemaphore* semaphore, uint64_t semaphoreValue); + + SCachedParams m_params; + + // ImGui extension manager + core::smart_refctd_ptr m_imguiManager; + + // SubAllocated descriptor set for dynamic texture management + core::smart_refctd_ptr m_subAllocDS; + + // Allocated texture indices for session buffers + std::array(SessionTextureIndex::Count)> m_sessionTextureIndices; + + // Samplers + struct + { + core::smart_refctd_ptr gui; + core::smart_refctd_ptr user; + } m_samplers; + + // Current session (for tracking when it changes) + CSession* m_currentSession = nullptr; + + // UI Windows + CSceneWindow m_sceneWindow; + CSessionWindow m_sessionWindow; + + bool m_initialized = false; + bool m_needsRepositionWindows = true; // Start true to position on first frame +}; + +} // namespace nbl::this_example::gui + +#endif // _NBL_THIS_EXAMPLE_C_UI_MANAGER_H_INCLUDED_ diff --git a/40_PathTracer/include/renderer/present/CWindowPresenter.h b/40_PathTracer/include/renderer/present/CWindowPresenter.h index e936e1ae5..f390eb634 100644 --- a/40_PathTracer/include/renderer/present/CWindowPresenter.h +++ b/40_PathTracer/include/renderer/present/CWindowPresenter.h @@ -2,7 +2,7 @@ // This file is part of the "Nabla Engine". // For conditions of distribution and use, see copyright notice in nabla.h #ifndef _NBL_THIS_EXAMPLE_C_WINDOW_PRESENTER_H_INCLUDED_ -#define _NBL_THIS_EXAMPLE_C_BASIC_RWMC_RESOLVER_H_INCLUDED_ +#define _NBL_THIS_EXAMPLE_C_WINDOW_PRESENTER_H_INCLUDED_ #include "nbl/ext/FullScreenTriangle/FullScreenTriangle.h" @@ -49,6 +49,8 @@ class CWindowPresenter : public IPresenter // inline const video::IGPURenderpass* getRenderpass() const {return getSwapchainResources()->getRenderpass();} + inline video::IGPURenderpass* getRenderpass() {return getSwapchainResources()->getRenderpass();} + inline ui::IWindow* getWindow() {return m_construction.window;} // bool irrecoverable() const {return m_construction.surface->irrecoverable() || !m_construction.surface->isWindowOpen();} diff --git a/40_PathTracer/include/renderer/present/IPresenter.h b/40_PathTracer/include/renderer/present/IPresenter.h index 405e60289..4b8b049a5 100644 --- a/40_PathTracer/include/renderer/present/IPresenter.h +++ b/40_PathTracer/include/renderer/present/IPresenter.h @@ -71,6 +71,9 @@ class IPresenter : public core::IReferenceCounted, public core::InterfaceUnmovab inline video::IQueue* getQueue() const {return m_queue;} // inline video::ILogicalDevice* getDevice() const {return const_cast(m_semaphore->getOriginDevice());} + + inline const video::ISemaphore* getSemaphore() const { return m_semaphore.get(); } + inline uint64_t getPresentCount() const { return m_presentCount; } // virtual bool irrecoverable() const {return false;} diff --git a/40_PathTracer/main.cpp b/40_PathTracer/main.cpp index 8fd248f0a..f443f6062 100644 --- a/40_PathTracer/main.cpp +++ b/40_PathTracer/main.cpp @@ -9,9 +9,13 @@ #include "renderer/resolve/CBasicRWMCResolver.h" #include "renderer/present/CWindowPresenter.h" +#include "gui/CUIManager.h" +#include "nbl/ui/ICursorControl.h" + #include "nlohmann/json.hpp" + using namespace nbl::core; using namespace nbl::hlsl; using namespace nbl::system; @@ -25,7 +29,7 @@ using namespace nbl::this_example; // TODO: move to argument parsing class struct AppArguments { - bool headless = false; + bool headless; // set in onAppInitialized() for now }; @@ -216,378 +220,381 @@ class PathTracingApp final : public SimpleWindowedApplication, public BuiltinRes // TODO: tmp code { m_api->startCapture(); - auto scene_daily_pt = m_renderer->createScene({ + m_currentScenePath = (sharedInputCWD / "mitsuba/daily_pt.xml").string(); + m_currentScene = m_renderer->createScene({ .load = m_sceneLoader->load({ - .relPath = sharedInputCWD/"mitsuba/daily_pt.xml", - .workingDirectory = localOutputCWD + .relPath = m_currentScenePath, + .workingDirectory = localOutputCWD }), .converter = nullptr }); + auto scene_daily_pt = m_currentScene; + // the UI would have you load the zip first, then present a dropdown of what to load // but still need to support archive mount for cmdline load #if 0 // this particular zip goes down an unsupported path in our zip loader auto scene_bedroom = m_sceneLoader->load({ - .relPath = sharedInputCWD/"mitsuba/bedroom.zip/scene.xml", + .relPath = sharedInputCWD / "mitsuba/bedroom.zip/scene.xml", .workingDirectory = localOutputCWD }); #endif m_api->endCapture(); - - // quick test code - nbl::core::vector sensors(3,scene_daily_pt->getSensors().front()); - { - sensors[1].mutableDefaults.cropWidth = 640; - sensors[1].mutableDefaults.cropHeight = 360; - sensors[1].mutableDefaults.cropOffsetX = 0; - sensors[1].mutableDefaults.cropOffsetY = 0; - } - { - sensors[2].mutableDefaults.cropWidth = 5120; - sensors[2].mutableDefaults.cropHeight = 2880; - sensors[2].mutableDefaults.cropOffsetX = 128; - sensors[2].mutableDefaults.cropOffsetY = 128; - } - for (auto i=1; i<3; i++) - { - sensors[i].constants.width = sensors[i].mutableDefaults.cropWidth+2*sensors[i].mutableDefaults.cropOffsetX; - sensors[i].constants.height = sensors[i].mutableDefaults.cropHeight+2*sensors[i].mutableDefaults.cropOffsetY; - } - sensors.erase(sensors.begin()); - for (const auto& sensor : sensors) - m_sessionQueue.push( - scene_daily_pt->createSession({ - {.mode=CSession::RenderMode::Debug},&sensor - }) - ); - } - - return true; - -#if 0 // ui - // gui descriptor setup - { - using binding_flags_t = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS; - { - IGPUSampler::SParams params; - params.AnisotropicFilter = 1u; - params.TextureWrapU = ETC_REPEAT; - params.TextureWrapV = ETC_REPEAT; - params.TextureWrapW = ETC_REPEAT; - - m_ui.samplers.gui = m_device->createSampler(params); - m_ui.samplers.gui->setObjectDebugName("Nabla IMGUI UI Sampler"); } - - std::array, 69u> immutableSamplers; - for (auto& it : immutableSamplers) - it = smart_refctd_ptr(m_ui.samplers.scene); - - immutableSamplers[nbl::ext::imgui::UI::FontAtlasTexId] = smart_refctd_ptr(m_ui.samplers.gui); - - nbl::ext::imgui::UI::SCreationParameters params; - - params.resources.texturesInfo = { .setIx = 0u, .bindingIx = 0u }; - params.resources.samplersInfo = { .setIx = 0u, .bindingIx = 1u }; - params.assetManager = m_assetMgr; - params.pipelineCache = nullptr; - params.pipelineLayout = nbl::ext::imgui::UI::createDefaultPipelineLayout(m_utils->getLogicalDevice(), params.resources.texturesInfo, params.resources.samplersInfo, MaxUITextureCount); - params.renderpass = smart_refctd_ptr(renderpass); - params.streamingBuffer = nullptr; - params.subpassIx = 0u; - params.transfer = getGraphicsQueue(); - params.utilities = m_utils; - { - m_ui.manager = ext::imgui::UI::create(std::move(params)); - - // note that we use default layout provided by our extension, but you are free to create your own by filling nbl::ext::imgui::UI::S_CREATION_PARAMETERS::resources - const auto* descriptorSetLayout = m_ui.manager->getPipeline()->getLayout()->getDescriptorSetLayout(0u); - const auto& params = m_ui.manager->getCreationParameters(); - - IDescriptorPool::SCreateInfo descriptorPoolInfo = {}; - descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLER)] = (uint32_t)nbl::ext::imgui::UI::DefaultSamplerIx::COUNT; - descriptorPoolInfo.maxDescriptorCount[static_cast(asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE)] = MaxUITextureCount; - descriptorPoolInfo.maxSets = 1u; - descriptorPoolInfo.flags = IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT; - - m_guiDescriptorSetPool = m_device->createDescriptorPool(std::move(descriptorPoolInfo)); - assert(m_guiDescriptorSetPool); - - m_guiDescriptorSetPool->createDescriptorSets(1u, &descriptorSetLayout, &m_ui.descriptorSet); - assert(m_ui.descriptorSet); - } - } - - m_ui.manager->registerListener( - [this]() -> void { - ImGuiIO& io = ImGui::GetIO(); - - m_camera.setProjectionMatrix([&]() - { - static matrix4SIMD projection; - - projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovRH( - core::radians(m_cameraSetting.fov), - io.DisplaySize.x / io.DisplaySize.y, - m_cameraSetting.zNear, - m_cameraSetting.zFar); - - return projection; - }()); - - ImGui::SetNextWindowPos(ImVec2(1024, 100), ImGuiCond_Appearing); - ImGui::SetNextWindowSize(ImVec2(256, 256), ImGuiCond_Appearing); - - // create a window and insert the inspector - ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Appearing); - ImGui::SetNextWindowSize(ImVec2(320, 340), ImGuiCond_Appearing); - ImGui::Begin("Controls"); - - ImGui::SameLine(); - - ImGui::Text("Camera"); - - ImGui::SliderFloat("Move speed", &m_cameraSetting.moveSpeed, 0.1f, 10.f); - ImGui::SliderFloat("Rotate speed", &m_cameraSetting.rotateSpeed, 0.1f, 10.f); - ImGui::SliderFloat("Fov", &m_cameraSetting.fov, 20.f, 150.f); - ImGui::SliderFloat("zNear", &m_cameraSetting.zNear, 0.1f, 100.f); - ImGui::SliderFloat("zFar", &m_cameraSetting.zFar, 110.f, 10000.f); - Light m_oldLight = m_light; - int light_type = m_light.type; - ImGui::ListBox("LightType", &light_type, s_lightTypeNames, ELT_COUNT); - m_light.type = static_cast(light_type); - if (m_light.type == ELT_DIRECTIONAL) - { - ImGui::SliderFloat3("Light Direction", &m_light.direction.x, -1.f, 1.f); - } - else if (m_light.type == ELT_POINT) - { - ImGui::SliderFloat3("Light Position", &m_light.position.x, -20.f, 20.f); - } - else if (m_light.type == ELT_SPOT) - { - ImGui::SliderFloat3("Light Direction", &m_light.direction.x, -1.f, 1.f); - ImGui::SliderFloat3("Light Position", &m_light.position.x, -20.f, 20.f); - - float32_t dOuterCutoff = hlsl::degrees(acos(m_light.outerCutoff)); - if (ImGui::SliderFloat("Light Outer Cutoff", &dOuterCutoff, 0.0f, 45.0f)) - { - m_light.outerCutoff = cos(hlsl::radians(dOuterCutoff)); - } - } - ImGui::Checkbox("Use Indirect Command", &m_useIndirectCommand); - if (m_light != m_oldLight) - { - m_frameAccumulationCounter = 0; - } - - ImGui::Text("X: %f Y: %f", io.MousePos.x, io.MousePos.y); - - ImGui::End(); - } - ); -#endif - } - -#if 0 // gui - bool updateGUIDescriptorSet() - { - // texture atlas, note we don't create info & write pair for the font sampler because UI extension's is immutable and baked into DS layout - static std::array descriptorInfo; - static IGPUDescriptorSet::SWriteDescriptorSet writes[MaxUITextureCount]; - - descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; - descriptorInfo[nbl::ext::imgui::UI::FontAtlasTexId].desc = smart_refctd_ptr(m_ui.manager->getFontAtlasView()); - - for (uint32_t i = 0; i < descriptorInfo.size(); ++i) - { - writes[i].dstSet = m_ui.descriptorSet.get(); - writes[i].binding = 0u; - writes[i].arrayElement = i; - writes[i].count = 1u; - } - writes[nbl::ext::imgui::UI::FontAtlasTexId].info = descriptorInfo.data() + nbl::ext::imgui::UI::FontAtlasTexId; - - return m_device->updateDescriptorSets(writes, {}); - } -#endif - - inline void workLoopBody() override - { - CSession* session; - volatile bool skip = false; // skip using the debugger - for (session=m_resolver->getActiveSession(); !session || session->getProgress()>=1.f || skip;) - { - skip = false; - if (m_sessionQueue.empty()) - { - if (!m_args.headless) - handleInputs(); - return; - } - session = m_sessionQueue.front().get(); - // init - m_utils->autoSubmit({.queue=getGraphicsQueue()},[&session](SIntendedSubmitInfo& info)->bool - { - return session->init(info.getCommandBufferForRecording()->cmdbuf); - } - ); - m_resolver->changeSession(std::move(m_sessionQueue.front())); - m_sessionQueue.pop(); - } - - m_api->startCapture(); - IQueue::SSubmitInfo::SSemaphoreInfo rendered = {}; - { - auto deferredSubmit = m_renderer->render(session); - if (deferredSubmit) - { - IGPUCommandBuffer* const cb = deferredSubmit; - if (!m_args.headless || session->getProgress()>=1.f) - { - m_resolver->resolve(cb,nullptr); - } - rendered = deferredSubmit({}); - } - } - m_api->endCapture(); - - if (m_args.headless) - return; - handleInputs(); - if (!keepRunning()) - return; - - m_presenter->acquire(session); - auto* const cb = m_presenter->beginRenderpass(); - { - // can do additional stuff like ImGUI work here - } - m_presenter->endRenderpassAndPresent(rendered); -#if 0 // gui - -// ... - const auto uiParams = m_ui.manager->getCreationParameters(); - auto* uiPipeline = m_ui.manager->getPipeline(); - cmdbuf->bindGraphicsPipeline(uiPipeline); - cmdbuf->bindDescriptorSets(EPBP_GRAPHICS, uiPipeline->getLayout(), uiParams.resources.texturesInfo.setIx, 1u, &m_ui.descriptorSet.get()); - ISemaphore::SWaitInfo waitInfo = { .semaphore = m_semaphore.get(), .value = m_realFrameIx + 1u }; - m_ui.manager->render(cmdbuf, waitInfo); - - { - { - - updateGUIDescriptorSet(); - - } - } -#endif - } - - inline void handleInputs() - { - if (m_args.headless) - return; - - m_inputSystem->getDefaultMouse(&m_mouse); - m_inputSystem->getDefaultKeyboard(&m_keyboard); - - struct - { - std::vector mouse{}; - std::vector keyboard{}; - } capturedEvents; - -// const auto& io = ImGui::GetIO(); - static std::chrono::microseconds previousEventTimestamp{}; - m_mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void - { - for (const auto& e : events) // here capture - { - if (e.timeStamp < previousEventTimestamp) - continue; - - previousEventTimestamp = e.timeStamp; - capturedEvents.mouse.emplace_back(e); - - } - }, m_logger.get() - ); - m_keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void - { - for (const auto& e : events) // here capture - { - if (e.timeStamp < previousEventTimestamp) - continue; - - previousEventTimestamp = e.timeStamp; - capturedEvents.keyboard.emplace_back(e); - } - }, m_logger.get() - ); -#if 0 // ui - const SRange mouseEvents(capturedEvents.mouse.data(), capturedEvents.mouse.data() + capturedEvents.mouse.size()); - const SRange keyboardEvents(capturedEvents.keyboard.data(), capturedEvents.keyboard.data() + capturedEvents.keyboard.size()); - const auto cursorPosition = m_window->getCursorControl()->getPosition(); - const auto mousePosition = float32_t2(cursorPosition.x, cursorPosition.y) - float32_t2(m_window->getX(), m_window->getY()); - - const nbl::ext::imgui::UI::SUpdateParameters params = - { - .mousePosition = mousePosition, - .displaySize = { m_window->getWidth(), m_window->getHeight() }, - .mouseEvents = mouseEvents, - .keyboardEvents = keyboardEvents - }; - m_ui.manager->update(params); -#endif - } - - inline bool keepRunning() override - { - if (m_args.headless) + + // Initialize UI Manager (non-headless only) + if (!m_args.headless) { - if (auto* const currentSession=m_resolver->getActiveSession(); m_sessionQueue.empty() && (!currentSession || currentSession->getProgress()>=1.f)) - return false; - return true; - } - else - return !m_presenter->irrecoverable(); - } - - inline bool onAppTerminated() override - { - return device_base_t::onAppTerminated(); - } - - private: - AppArguments m_args = {}; - // - smart_refctd_ptr m_inputSystem; - InputSystem::ChannelReader m_mouse; - InputSystem::ChannelReader m_keyboard; - // - smart_refctd_ptr m_presenter; - // - smart_refctd_ptr m_renderer; - smart_refctd_ptr m_resolver; - // - smart_refctd_ptr m_sceneLoader; - // - nbl::core::queue> m_sessionQueue; - -#if 0 // gui - struct C_UI - { - nbl::core::smart_refctd_ptr manager; - - struct - { - core::smart_refctd_ptr gui, scene; - } samplers; - - core::smart_refctd_ptr descriptorSet; - } m_ui; - core::smart_refctd_ptr m_guiDescriptorSetPool; -#endif + m_uiManager = gui::CUIManager::create({ .assetManager = smart_refctd_ptr(m_assetMgr),.utilities = smart_refctd_ptr(m_utils),.transferQueue = getGraphicsQueue(),.logger = smart_refctd_ptr(m_logger) }); + if (!m_uiManager) + return logFail("Failed to create CUIManager"); + + gui::CUIManager::SInitParams uiInitParams = { + .renderpass = m_presenter->getRenderpass(), + .onSensorSelected = [this](size_t sensorIdx) { + // Create a new session from the selected sensor (GUI mode) + if (m_currentScene) + { + const auto sensors = m_currentScene->getSensors(); + if (sensorIdx < sensors.size()) + { + auto newSession = m_currentScene->createSession({ + {.mode = CSession::RenderMode::Debug}, + &sensors[sensorIdx] + }); + if (newSession) + { + m_pendingSession = std::move(newSession); + } + } + } + }, + .onLoadSceneRequested = [this](const std::string& path) { + if (path.empty()) + return; + + m_logger->log("Loading scene: %s", ILogger::ELL_INFO, path.c_str()); + + // Load the scene + auto loadResult = m_sceneLoader->load({ + .relPath = path, + .workingDirectory = localOutputCWD + }); + + if (!loadResult) + { + m_logger->log("Failed to load scene: %s", ILogger::ELL_ERROR, path.c_str()); + return; + } + + // Create the scene + auto newScene = m_renderer->createScene({ + .load = std::move(loadResult), + .converter = nullptr + }); + + if (!newScene) + { + m_logger->log("Failed to create scene from: %s", ILogger::ELL_ERROR, path.c_str()); + return; + } + + // Update current scene + m_currentScene = std::move(newScene); + m_currentScenePath = path; + + // Update UI + if (m_uiManager) + m_uiManager->setScene(m_currentScene.get(), m_currentScenePath); + + m_logger->log("Scene loaded successfully: %s", ILogger::ELL_INFO, path.c_str()); + }, + .onReloadSceneRequested = [this]() { + if (m_currentScenePath.empty()) + { + m_logger->log("No scene to reload", ILogger::ELL_WARNING); + return; + } + + m_logger->log("Reloading scene: %s", ILogger::ELL_INFO, m_currentScenePath.c_str()); + + // Reload the scene + auto loadResult = m_sceneLoader->load({ + .relPath = m_currentScenePath, + .workingDirectory = localOutputCWD + }); + + if (!loadResult) + { + m_logger->log("Failed to reload scene: %s", ILogger::ELL_ERROR, m_currentScenePath.c_str()); + return; + } + + auto newScene = m_renderer->createScene({ + .load = std::move(loadResult), + .converter = nullptr + }); + + if (!newScene) + { + m_logger->log("Failed to create scene from: %s", ILogger::ELL_ERROR, m_currentScenePath.c_str()); + return; + } + + m_currentScene = std::move(newScene); + + if (m_uiManager) + m_uiManager->setScene(m_currentScene.get(), m_currentScenePath); + + m_logger->log("Scene reloaded successfully", ILogger::ELL_INFO); + }, + // Session callbacks + .onRenderModeChanged = [this](CSession::RenderMode mode, CSession* session) { + // Recreate session with new mode + if (session) + { + const CSession::SConstructionParams& params = session->getConstructionParams(); + auto creationParams = params; // Copy params + creationParams.mode = mode; + + // TODO: Actually recreate the session. For now just log. + m_logger->log("Render mode changed to %d (Recreation TODO)", ILogger::ELL_INFO, mode); + } + }, + .onResolutionChanged = [this](uint16_t w, uint16_t h) { + m_logger->log("Resolution changed to %dx%d (TODO)", ILogger::ELL_INFO, w, h); + }, + .onMutablesChanged = [this](const SSensorDynamics& dyn, CSession* session) { + session->update(dyn); + m_logger->log("Mutables changed (Reset TODO)", ILogger::ELL_INFO); + }, + .onDynamicsChanged = [this](const SSensorDynamics& dyn, CSession* session) { + session->update(dyn); + }, + .onBufferSelected = [this](int id) { + m_logger->log("Buffer %d selected (TODO)", ILogger::ELL_INFO, id); + } + }; + + if (!m_uiManager->init(uiInitParams)) + return logFail("Failed to initialize CUIManager"); + + + // Set up UI with the initially loaded scene + if (m_currentScene) + m_uiManager->setScene(m_currentScene.get(), m_currentScenePath); + + // Create initial session from first sensor so GUI has something to display + if (m_currentScene && !m_currentScene->getSensors().empty()) + { + const auto& sensors = m_currentScene->getSensors(); + auto initialSession = m_currentScene->createSession({ + {.mode = CSession::RenderMode::Debug}, + &sensors.front() + }); + + m_pendingSession = std::move(initialSession); + } + } + + return true; + } + + inline void workLoopBody() override + { + if (m_args.headless) + { + CSession* session = m_resolver->getActiveSession(); + while (!session || session->getProgress() >= 1.f) + { + if (m_sessionQueue.empty()) + return; + session = m_sessionQueue.front().get(); + // init + m_utils->autoSubmit({ .queue = getGraphicsQueue() }, [&session](SIntendedSubmitInfo& info)->bool + { + return session->init(info.getCommandBufferForRecording()->cmdbuf); + } + ); + m_resolver->changeSession(std::move(m_sessionQueue.front())); + m_sessionQueue.pop(); + } + + // Headless rendering + m_api->startCapture(); + IQueue::SSubmitInfo::SSemaphoreInfo rendered = {}; + { + auto deferredSubmit = m_renderer->render(session); + if (deferredSubmit) + { + IGPUCommandBuffer* const cb = deferredSubmit; + if (session->getProgress() >= 1.f) + m_resolver->resolve(cb, nullptr); + rendered = deferredSubmit({}); + } + } + m_api->endCapture(); + } + else + { + // GUI mode: check for pending session from double-click + if (m_pendingSession) + { + auto pendingSession = m_pendingSession.get(); + m_utils->autoSubmit({ .queue = getGraphicsQueue() }, [pendingSession](SIntendedSubmitInfo& info)->bool + { + return pendingSession->init(info.getCommandBufferForRecording()->cmdbuf); + } + ); + m_resolver->changeSession(std::move(m_pendingSession)); + + // Reposition UI windows after session change (window will resize) + if (m_uiManager) + m_uiManager->resetWindowPositions(); + } + CSession* session = m_resolver->getActiveSession(); + + // Render session if we have one + IQueue::SSubmitInfo::SSemaphoreInfo rendered = {}; + if (session) + { + m_api->startCapture(); + { + auto deferredSubmit = m_renderer->render(session); + if (deferredSubmit) + { + IGPUCommandBuffer* const cb = deferredSubmit; + m_resolver->resolve(cb, nullptr); + rendered = deferredSubmit({}); + } + } + m_api->endCapture(); + } + + // Acquire swapchain image (may resize window based on session resolution) + m_presenter->acquire(session); + + // Handle inputs AFTER acquire so ImGui viewport has correct size + handleInputs(); + if (!keepRunning()) + return; + + if (m_uiManager) + { + m_uiManager->setSession(session); + m_uiManager->drawWindows(); + + const ISemaphore::SWaitInfo drawFinished = { + .semaphore = m_presenter->getSemaphore(), + .value = m_presenter->getPresentCount() + 1 + }; + + // Render ImGui + auto* const cb = m_presenter->beginRenderpass(); + if (!m_uiManager->render(cb, drawFinished)) + m_logger->log("UI Render failed", ILogger::ELL_ERROR); + m_presenter->endRenderpassAndPresent(rendered); + } + } + } + + inline void handleInputs() + { + if (m_args.headless) + return; + + m_inputSystem->getDefaultMouse(&m_mouse); + m_inputSystem->getDefaultKeyboard(&m_keyboard); + + struct + { + std::vector mouse{}; + std::vector keyboard{}; + } capturedEvents; + + static std::chrono::microseconds previousEventTimestamp{}; + m_mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void + { + for (const auto& e : events) // here capture + { + if (e.timeStamp < previousEventTimestamp) + continue; + + previousEventTimestamp = e.timeStamp; + capturedEvents.mouse.emplace_back(e); + + } + }, m_logger.get() + ); + m_keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void + { + for (const auto& e : events) // here capture + { + if (e.timeStamp < previousEventTimestamp) + continue; + + previousEventTimestamp = e.timeStamp; + capturedEvents.keyboard.emplace_back(e); + } + }, m_logger.get() + ); + + if (m_uiManager) + { + const SRange mouseEvents(capturedEvents.mouse.data(), capturedEvents.mouse.data() + capturedEvents.mouse.size()); + const SRange keyboardEvents(capturedEvents.keyboard.data(), capturedEvents.keyboard.data() + capturedEvents.keyboard.size()); + + auto* window = m_presenter->getWindow(); + const auto cursorPosition = window->getCursorControl()->getPosition(); + const auto mousePosition = float32_t2(cursorPosition.x, cursorPosition.y) - float32_t2(window->getX(), window->getY()); + + const nbl::ext::imgui::UI::SUpdateParameters params = + { + .mousePosition = mousePosition, + .displaySize = { window->getWidth(), window->getHeight() }, + .mouseEvents = mouseEvents, + .keyboardEvents = keyboardEvents + }; + m_uiManager->update(params); + } + } + + inline bool keepRunning() override + { + if (m_args.headless) + { + if (auto* const currentSession = m_resolver->getActiveSession(); m_sessionQueue.empty() && (!currentSession || currentSession->getProgress() >= 1.f)) + return false; + return true; + } + else + return !m_presenter->irrecoverable(); + } + + inline bool onAppTerminated() override + { + return device_base_t::onAppTerminated(); + } + + private: + AppArguments m_args = {}; + // + smart_refctd_ptr m_inputSystem; + InputSystem::ChannelReader m_mouse; + InputSystem::ChannelReader m_keyboard; + // + smart_refctd_ptr m_presenter; + // + smart_refctd_ptr m_renderer; + smart_refctd_ptr m_resolver; + // + smart_refctd_ptr m_sceneLoader; + // + nbl::core::queue> m_sessionQueue; // for headless mode + smart_refctd_ptr m_pendingSession; // for GUI mode (set by double-clicking sensor) + // + smart_refctd_ptr m_currentScene; + std::string m_currentScenePath; + smart_refctd_ptr m_uiManager; }; NBL_MAIN_FUNC(PathTracingApp) \ No newline at end of file diff --git a/40_PathTracer/src/gui/CSceneWindow.cpp b/40_PathTracer/src/gui/CSceneWindow.cpp new file mode 100644 index 000000000..f669f723a --- /dev/null +++ b/40_PathTracer/src/gui/CSceneWindow.cpp @@ -0,0 +1,227 @@ +// Copyright (C) 2025-2026 - 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 +#include "gui/CSceneWindow.h" +#include "renderer/CRenderer.h" +#include "renderer/CScene.h" +#include "renderer/CSession.h" + +#include "portable-file-dialogs.h" +#include + +namespace nbl::this_example::gui +{ + + void CSceneWindow::draw(const bool forceReposition) + { + if (!m_isOpen) + return; + + // Position on the right side of the viewport + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + if (viewport->WorkSize.x > 64.0f && viewport->WorkSize.y > 64.0f) // workaround because for some reason viewport size is wrong on first frame. + { + const float windowWidth = 320.0f; + const ImGuiCond cond = forceReposition ? ImGuiCond_Always : ImGuiCond_Appearing; + + ImGui::SetNextWindowPos(ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - windowWidth - 20, viewport->WorkPos.y + 20), cond); + ImGui::SetNextWindowSize(ImVec2(windowWidth, viewport->WorkSize.y - 40), cond); + ImGui::SetNextWindowSizeConstraints(ImVec2(280, 400), ImVec2(FLT_MAX, FLT_MAX)); + + if (ImGui::Begin("Scene", &m_isOpen, ImGuiWindowFlags_NoCollapse)) + { + drawLoadSection(); + + ImGui::Separator(); + + // Only show scene contents if a scene is loaded + if (m_scene) + { + drawGlobalsSection(); + drawSensorsSection(); + drawEmittersSection(); + + ImGui::Separator(); + + drawEditorSection(); + } + else + { + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "No scene loaded"); + } + + } + ImGui::End(); + } + } + + void CSceneWindow::drawLoadSection() + { + // Load button + if (ImGui::Button("Load Scene")) + { + // Get starting directory (extract parent dir if m_scenePath is a file) + std::string startDir = "."; + if (!m_scenePath.empty()) + { + std::filesystem::path p(m_scenePath); + if (std::filesystem::is_regular_file(p)) + startDir = p.parent_path().string(); + else if (std::filesystem::is_directory(p)) + startDir = m_scenePath; + } + + pfd::open_file fileDialog("Choose Mitsuba Scene", + startDir, + { + "Mitsuba Scene Files (*.xml)", "*.xml", + "All Files", "*" + } + ); + + auto result = fileDialog.result(); + if (!result.empty() && m_callbacks.onLoadRequested) + m_callbacks.onLoadRequested(result[0]); + } + + ImGui::SameLine(); + + // Reload button - disabled if no scene loaded + ImGui::BeginDisabled(m_scene == nullptr); + if (ImGui::Button("Reload")) + { + if (m_callbacks.onReloadRequested) + m_callbacks.onReloadRequested(); + } + ImGui::EndDisabled(); + + // Show current scene path + if (!m_scenePath.empty()) + { + ImGui::TextWrapped("Path: %s", m_scenePath.c_str()); + } + else + { + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "No file loaded"); + } + } + + void CSceneWindow::drawSensorsSection() + { + if (ImGui::CollapsingHeader("Sensors", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (!m_scene) + { + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "No scene"); + return; + } + + const auto sensors = m_scene->getSensors(); + if (sensors.empty()) + { + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "No sensors in scene"); + return; + } + + // Table for sensors + if (ImGui::BeginTable("SensorsTable", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) + { + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 30.0f); + ImGui::TableSetupColumn("Resolution", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("Crop", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("Offset", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableHeadersRow(); + + for (size_t i = 0; i < sensors.size(); ++i) + { + const auto& sensor = sensors[i]; + const bool isSelected = (static_cast(i) == m_selectedSensorIndex); + + ImGui::TableNextRow(); + + // Highlight selected sensor + if (isSelected) + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::GetColorU32(ImVec4(0.2f, 0.4f, 0.6f, 0.5f))); + + // ID column (clickable) + ImGui::TableNextColumn(); + char idLabel[32]; + snprintf(idLabel, sizeof(idLabel), "%zu", i); + + ImGui::PushID(static_cast(i)); + if (ImGui::Selectable(idLabel, isSelected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) + { + m_selectedSensorIndex = static_cast(i); + + // Double-click triggers the callback + if (ImGui::IsMouseDoubleClicked(0)) + { + if (m_callbacks.onSensorSelected) + m_callbacks.onSensorSelected(i); + } + } + ImGui::PopID(); + + // Resolution column + ImGui::TableNextColumn(); + ImGui::Text("%ux%u", sensor.constants.width, sensor.constants.height); + + // Crop column + ImGui::TableNextColumn(); + ImGui::Text("%dx%d", sensor.mutableDefaults.cropWidth, sensor.mutableDefaults.cropHeight); + + // Offset column + ImGui::TableNextColumn(); + ImGui::Text("(%d, %d)", sensor.mutableDefaults.cropOffsetX, sensor.mutableDefaults.cropOffsetY); + } + + ImGui::EndTable(); + } + + ImGui::Text("Total: %zu sensor(s)", sensors.size()); + } + } + + void CSceneWindow::drawGlobalsSection() + { + if (ImGui::CollapsingHeader("Globals")) + { + // Placeholder - will show scene-wide settings + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Scene globals (placeholder)"); + + if (m_scene) + { + // Could show scene bounds, etc. + ImGui::Text("Scene loaded"); + } + } + } + + void CSceneWindow::drawEmittersSection() + { + if (ImGui::CollapsingHeader("Emitters")) + { + // Placeholder - will show lights/emitters + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Emitters list (placeholder)"); + ImGui::Text("Not yet implemented"); + } + } + + void CSceneWindow::drawEditorSection() + { + if (ImGui::CollapsingHeader("Editor")) + { + // Placeholder - will show properties of selected item + if (m_selectedSensorIndex >= 0) + { + ImGui::Text("Selected: Sensor [%d]", m_selectedSensorIndex); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Properties editor (placeholder)"); + } + else + { + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Select an item to edit"); + } + } + } + +} // namespace nbl::this_example::gui diff --git a/40_PathTracer/src/gui/CSessionWindow.cpp b/40_PathTracer/src/gui/CSessionWindow.cpp new file mode 100644 index 000000000..ee349785a --- /dev/null +++ b/40_PathTracer/src/gui/CSessionWindow.cpp @@ -0,0 +1,274 @@ +// Copyright (C) 2025-2026 - 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 +#include "gui/CSessionWindow.h" +#include "renderer/CSession.h" +#include "renderer/CScene.h" +#include "renderer/CRenderer.h" +#include "nbl/ext/ImGui/ImGui.h" +#include "nbl/system/to_string.h" + +namespace nbl::this_example::gui +{ + +void CSessionWindow::setSession(CSession* session) +{ + m_session = session; + if (m_session) + { + // Read initial state + const auto& dynamics = m_session->getActiveResources().currentSensorState; + m_cachedDynamics = dynamics; + m_state.tMax = dynamics.tMax; + + // Read render mode from creation params + const auto& params = m_session->getConstructionParams(); + m_state.renderMode = params.mode; + + m_state.cropWidth = params.cropResolution.x; + m_state.cropHeight = params.cropResolution.y; + m_state.cropOffsetX = params.cropOffsets.x; + m_state.cropOffsetY = params.cropOffsets.y; + } +} + +void CSessionWindow::setBufferTextureIDs(const std::array(BufferType::Count)>& textureIDs) +{ + m_bufferTextureIDs = textureIDs; +} + +void CSessionWindow::draw(const bool forceReposition) +{ + if (!m_isOpen) + return; + + // Position on LEFT side + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + if (viewport->WorkSize.x > 64.0f && viewport->WorkSize.y > 64.0f) + { + const float windowWidth = 320.0f; + const ImGuiCond cond = forceReposition ? ImGuiCond_Always : ImGuiCond_Appearing; + + ImGui::SetNextWindowPos(ImVec2(viewport->WorkPos.x + 20, viewport->WorkPos.y + 20), cond); + ImGui::SetNextWindowSize(ImVec2(windowWidth, viewport->WorkSize.y - 40), cond); + ImGui::SetNextWindowSizeConstraints(ImVec2(280, 400), ImVec2(FLT_MAX, FLT_MAX)); + + if (ImGui::Begin("Session", &m_isOpen)) + { + if (m_session) + { + drawRenderModeSection(); + ImGui::Separator(); + drawDynamicsSection(); + ImGui::Separator(); + drawMutablesSection(); + ImGui::Separator(); + drawOutputBufferSection(); + } + else + { + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "No active session"); + } + } + ImGui::End(); + } +} + +void CSessionWindow::drawRenderModeSection() +{ + if (ImGui::CollapsingHeader("Render Mode", ImGuiTreeNodeFlags_DefaultOpen)) + { + const char* modes[] = { "Previs", "Beauty", "Debug" }; + if (ImGui::Combo("Mode", reinterpret_cast(&m_state.renderMode), modes, IM_ARRAYSIZE(modes))) + { + if (m_callbacks.onRenderModeChanged) + { + m_callbacks.onRenderModeChanged(m_state.renderMode, m_session); + } + } + + if (m_session) + ImGui::ProgressBar(m_session->getProgress(), ImVec2(-1, 0), "Progress"); + } +} + +void CSessionWindow::drawDynamicsSection() +{ + if (ImGui::CollapsingHeader("Dynamics", ImGuiTreeNodeFlags_DefaultOpen)) + { + bool changed = false; + + if (ImGui::DragFloat("Crop Offset X", &m_state.cropOffsetX, 1.0f, 0.0f, 10000.0f)) changed = true; + if (ImGui::DragFloat("Crop Offset Y", &m_state.cropOffsetY, 1.0f, 0.0f, 10000.0f)) changed = true; + if (ImGui::SliderFloat("T Max", &m_state.tMax, 0.0f, 20000.0f, "%.1f", ImGuiSliderFlags_Logarithmic)) changed = true; + + if (changed && m_callbacks.onDynamicsChanged) + { + SSensorDynamics newDynamics = m_cachedDynamics; + newDynamics.tMax = m_state.tMax; + // TODO: updated crop offsets when theyre moved too + m_callbacks.onDynamicsChanged(newDynamics, m_session); + } + } +} + +void CSessionWindow::drawMutablesSection() +{ + if (ImGui::CollapsingHeader("Mutables")) + { + ImGui::InputInt("Crop Width", &m_state.cropWidth); + ImGui::InputInt("Crop Height", &m_state.cropHeight); + + ImGui::InputFloat("Near Clip", &m_state.nearClip); + ImGui::InputFloat("Far Clip", &m_state.farClip); + + if (ImGui::Button("Apply Changes")) + { + if (m_callbacks.onMutablesChanged) + { + SSensorDynamics newDynamics = m_cachedDynamics; + m_callbacks.onMutablesChanged(newDynamics, m_session); + } + + if (m_callbacks.onResolutionChanged) + { + m_callbacks.onResolutionChanged((uint16_t)m_state.cropWidth, (uint16_t)m_state.cropHeight); + } + } + + ImGui::SameLine(); + if (ImGui::Button("Discard")) + { + if (m_session) + { + const auto& params = m_session->getConstructionParams(); + m_state.cropWidth = params.cropResolution.x; + m_state.cropHeight = params.cropResolution.y; + } + } + } +} + +void CSessionWindow::drawOutputBufferSection() +{ + if (ImGui::CollapsingHeader("Output Buffers", ImGuiTreeNodeFlags_DefaultOpen)) + { + const auto& resources = m_session->getActiveResources(); + const auto& immutables = resources.immutables; + + // Thumbnail size + constexpr float thumbnailSize = 80.0f; + constexpr float padding = 4.0f; + + // Calculate how many thumbnails fit per row + const float availableWidth = ImGui::GetContentRegionAvail().x; + const int thumbsPerRow = std::max(1, static_cast((availableWidth + padding) / (thumbnailSize + padding))); + + int currentColumn = 0; + + // Helper to show a buffer thumbnail + auto showBufferThumbnail = [&](BufferType type, const CSession::SImageWithViews& imageWithViews) + { + const int idx = static_cast(type); + const uint32_t texID = m_bufferTextureIDs[idx]; + const std::string name = nbl::system::to_string(type); + const bool isValid = imageWithViews && (texID != 0xFFFFFFFF); + const bool isSelected = (m_state.selectedBufferIndex == idx); + + // Start new line if needed + if (currentColumn > 0) + ImGui::SameLine(); + + ImGui::BeginGroup(); + + // Draw selection highlight + if (isSelected) + { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.8f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.4f, 0.6f, 0.9f, 1.0f)); + } + else + { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.2f, 0.2f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.3f, 1.0f)); + } + + bool clicked; + if (isValid) + { + // Create SImResourceInfo for the texture + SImResourceInfo texInfo; + texInfo.textureID = texID; + texInfo.samplerIx = static_cast(nbl::ext::imgui::UI::DefaultSamplerIx::USER); + + // Use ImageButton for clickable thumbnail + ImGui::PushID(idx); + clicked = ImGui::ImageButton(name.c_str(), texInfo, ImVec2(thumbnailSize, thumbnailSize)); + ImGui::PopID(); + } + else + { + // Show placeholder for unavailable buffers + ImGui::PushID(idx); + clicked = ImGui::Button("N/A", ImVec2(thumbnailSize, thumbnailSize)); + ImGui::PopID(); + } + + ImGui::PopStyleColor(2); + + // Show label below thumbnail + ImGui::TextUnformatted(name.c_str()); + + ImGui::EndGroup(); + + // Handle click + if (clicked) + { + m_state.selectedBufferIndex = idx; + if (m_callbacks.onBufferSelected) + m_callbacks.onBufferSelected(idx); + } + + // Update column counter + currentColumn++; + if (currentColumn >= thumbsPerRow) + currentColumn = 0; + }; + + // Show all buffer thumbnails + showBufferThumbnail(BufferType::Beauty, immutables.beauty); + showBufferThumbnail(BufferType::Albedo, immutables.albedo); + showBufferThumbnail(BufferType::Normal, immutables.normal); + showBufferThumbnail(BufferType::Motion, immutables.motion); + showBufferThumbnail(BufferType::Mask, immutables.mask); + showBufferThumbnail(BufferType::RWMCCascades, immutables.rwmcCascades); + showBufferThumbnail(BufferType::SampleCount, immutables.sampleCount); + + ImGui::Spacing(); + ImGui::TextDisabled("Click to select buffer for viewing"); + } +} + +} // namespace nbl::this_example::gui + +namespace nbl::system::impl +{ + template<> + struct to_string_helper + { + static std::string __call(const this_example::gui::CSessionWindow::BufferType& value) + { + switch (value) + { + case this_example::gui::CSessionWindow::BufferType::Beauty: return "Beauty"; + case this_example::gui::CSessionWindow::BufferType::Albedo: return "Albedo"; + case this_example::gui::CSessionWindow::BufferType::Normal: return "Normal"; + case this_example::gui::CSessionWindow::BufferType::Motion: return "Motion"; + case this_example::gui::CSessionWindow::BufferType::Mask: return "Mask"; + case this_example::gui::CSessionWindow::BufferType::RWMCCascades: return "RWMC Cascades"; + case this_example::gui::CSessionWindow::BufferType::SampleCount: return "Sample Count"; + default: return "Unknown"; + } + } + }; +} \ No newline at end of file diff --git a/40_PathTracer/src/gui/CUIManager.cpp b/40_PathTracer/src/gui/CUIManager.cpp new file mode 100644 index 000000000..5308e02f4 --- /dev/null +++ b/40_PathTracer/src/gui/CUIManager.cpp @@ -0,0 +1,324 @@ +// Copyright (C) 2025-2026 - 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 +#include "gui/CUIManager.h" +#include "renderer/CScene.h" +#include "renderer/CSession.h" +#include "renderer/CRenderer.h" + +namespace nbl::this_example::gui +{ + + using namespace nbl::core; + using namespace nbl::asset; + using namespace nbl::video; + + core::smart_refctd_ptr CUIManager::create(SCreationParams&& params) + { + if (!params.assetManager || !params.utilities || !params.transferQueue) + return nullptr; + + return core::make_smart_refctd_ptr(SCachedParams{ std::move(params) }); + } + + bool CUIManager::init(const SInitParams& params) + { + if (!params.renderpass) + return false; + + auto* device = m_params.utilities->getLogicalDevice(); + + // Set callbacks + m_sceneWindow.setCallbacks({ + .onSensorSelected = params.onSensorSelected, + .onLoadRequested = params.onLoadSceneRequested, + .onReloadRequested = params.onReloadSceneRequested + }); + m_sessionWindow.setCallbacks({ + .onRenderModeChanged = params.onRenderModeChanged, + .onResolutionChanged = params.onResolutionChanged, + .onMutablesChanged = params.onMutablesChanged, + .onDynamicsChanged = params.onDynamicsChanged, + .onBufferSelected = params.onBufferSelected + }); + + // Initialize session texture indices to invalid + for (auto& idx : m_sessionTextureIndices) + idx = SubAllocatedDescriptorSet::invalid_value; + + // Create samplers + { + IGPUSampler::SParams samplerParams; + samplerParams.AnisotropicFilter = 1u; + samplerParams.TextureWrapU = hlsl::ETC_REPEAT; + samplerParams.TextureWrapV = hlsl::ETC_REPEAT; + samplerParams.TextureWrapW = hlsl::ETC_REPEAT; + + m_samplers.gui = device->createSampler(samplerParams); + if (!m_samplers.gui) + return false; + m_samplers.gui->setObjectDebugName("UI GUI Sampler"); + + // User sampler for session textures + m_samplers.user = device->createSampler(samplerParams); + if (!m_samplers.user) + return false; + m_samplers.user->setObjectDebugName("UI User Sampler"); + } + + // Create ImGui manager + { + nbl::ext::imgui::UI::SCreationParameters imguiParams; + + imguiParams.resources.texturesInfo = { .setIx = 0u, .bindingIx = TexturesBindingIndex }; + imguiParams.resources.samplersInfo = { .setIx = 0u, .bindingIx = 1u }; + imguiParams.assetManager = m_params.assetManager; + imguiParams.pipelineCache = nullptr; + imguiParams.pipelineLayout = nbl::ext::imgui::UI::createDefaultPipelineLayout(device, imguiParams.resources.texturesInfo, imguiParams.resources.samplersInfo, MaxUITextureCount); + imguiParams.renderpass = core::smart_refctd_ptr(params.renderpass); + imguiParams.streamingBuffer = nullptr; + imguiParams.subpassIx = 0u; + imguiParams.transfer = m_params.transferQueue; + imguiParams.utilities = m_params.utilities; + + m_imguiManager = nbl::ext::imgui::UI::create(std::move(imguiParams)); + if (!m_imguiManager) + { + if (m_params.logger.get()) + m_params.logger.log("Failed to create ImGui manager", system::ILogger::ELL_ERROR); + return false; + } + // Increase double-click time threshold (default 0.30s is too fast) + ImGui::GetIO().MouseDoubleClickTime = 0.5f; + } + + // Create SubAllocated descriptor set + { + const auto* layout = m_imguiManager->getPipeline()->getLayout()->getDescriptorSetLayout(0u); + auto pool = device->createDescriptorPoolForDSLayouts(IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT, { &layout, 1 }); + if (!pool) + { + if (m_params.logger.get()) + m_params.logger.log("Failed to create UI descriptor pool", system::ILogger::ELL_ERROR); + return false; + } + + auto ds = pool->createDescriptorSet(smart_refctd_ptr(layout)); + if (!ds) + { + if (m_params.logger.get()) + m_params.logger.log("Failed to create UI descriptor set", system::ILogger::ELL_ERROR); + return false; + } + + m_subAllocDS = make_smart_refctd_ptr(std::move(ds)); + if (!m_subAllocDS) + { + if (m_params.logger.get()) + m_params.logger.log("Failed to create SubAllocatedDescriptorSet", system::ILogger::ELL_ERROR); + return false; + } + + // make sure Texture Atlas slot is taken for eternity + { + auto dummy = SubAllocatedDescriptorSet::invalid_value; + m_subAllocDS->multi_allocate(0, 1, &dummy); + assert(dummy == ext::imgui::UI::FontAtlasTexId); + } + + // Write font atlas descriptor + IGPUDescriptorSet::SDescriptorInfo info = {}; + info.desc = smart_refctd_ptr(m_imguiManager->getFontAtlasView()); + info.info.image.imageLayout = IGPUImage::LAYOUT::READ_ONLY_OPTIMAL; + const IGPUDescriptorSet::SWriteDescriptorSet write = { + .dstSet = m_subAllocDS->getDescriptorSet(), + .binding = TexturesBindingIndex, + .arrayElement = nbl::ext::imgui::UI::FontAtlasTexId, + .count = 1, + .info = &info + }; + if (!device->updateDescriptorSets({ &write, 1 }, {})) + { + if (m_params.logger.get()) + m_params.logger.log("Failed to write font atlas descriptor", system::ILogger::ELL_ERROR); + return false; + } + } + + m_initialized = true; + return true; + } + + void CUIManager::deinit() + { + if (!m_subAllocDS) + return; + + auto* device = m_params.utilities->getLogicalDevice(); + + // Deallocate session texture indices + unbindSessionTextures(nullptr, 0); + + // Deallocate font atlas + SubAllocatedDescriptorSet::value_type fontAtlasIdx = nbl::ext::imgui::UI::FontAtlasTexId; + IGPUDescriptorSet::SDropDescriptorSet dummy[1]; + m_subAllocDS->multi_deallocate(dummy, TexturesBindingIndex, 1, &fontAtlasIdx); + + m_subAllocDS = nullptr; + m_initialized = false; + } + + void CUIManager::setScene(const CScene* scene, const std::string& scenePath) + { + m_sceneWindow.setScene(scene); + m_sceneWindow.setScenePath(scenePath); + } + + // TODO: actually test this + void CUIManager::setSession(CSession* session, ISemaphore* semaphore, uint64_t semaphoreValue) + { + // Only rebind if session actually changed + if (m_currentSession == session) + { + m_sessionWindow.setSession(session); + return; + } + + // Unbind old session textures + if (m_currentSession) + unbindSessionTextures(semaphore, semaphoreValue); + + m_currentSession = session; + m_sessionWindow.setSession(session); + + // Bind new session textures and pass IDs to session window + if (session) + { + bindSessionTextures(session); + + std::array(CSessionWindow::BufferType::Count)> textureIDs; + for (size_t i = 0; i < textureIDs.size(); i++) + textureIDs[i] = m_sessionTextureIndices[i]; + m_sessionWindow.setBufferTextureIDs(textureIDs); + } + else + { + std::array(CSessionWindow::BufferType::Count)> invalidIDs; + invalidIDs.fill(SubAllocatedDescriptorSet::invalid_value); + m_sessionWindow.setBufferTextureIDs(invalidIDs); + } + } + + void CUIManager::bindSessionTextures(CSession* session) + { + if (!session || !m_subAllocDS) + return; + + auto* device = m_params.utilities->getLogicalDevice(); + const auto& resources = session->getActiveResources(); + const auto& immutables = resources.immutables; + + // Helper to bind a single texture + auto bindTexture = [&](SessionTextureIndex texIdx, const CSession::SImageWithViews& imageWithViews, asset::E_FORMAT format) { + auto& allocIdx = m_sessionTextureIndices[static_cast(texIdx)]; + + if (!imageWithViews) + { + allocIdx = SubAllocatedDescriptorSet::invalid_value; + return; + } + + auto* view = imageWithViews.getView(format); + if (!view) + { + allocIdx = SubAllocatedDescriptorSet::invalid_value; + return; + } + + // Allocate descriptor index + m_subAllocDS->multi_allocate(TexturesBindingIndex, 1, &allocIdx); + + if (allocIdx == SubAllocatedDescriptorSet::invalid_value) + return; + + // Write descriptor + IGPUDescriptorSet::SDescriptorInfo info = {}; + info.desc = smart_refctd_ptr(view); + info.info.image.imageLayout = IGPUImage::LAYOUT::READ_ONLY_OPTIMAL; + const IGPUDescriptorSet::SWriteDescriptorSet write = { + .dstSet = m_subAllocDS->getDescriptorSet(), + .binding = TexturesBindingIndex, + .arrayElement = allocIdx, + .count = 1, + .info = &info + }; + device->updateDescriptorSets({ &write, 1 }, {}); + }; + + // Bind all session textures + // Note: Using RGBA32F format as that's what the session uses, adjust if needed + bindTexture(SessionTextureIndex::Beauty, immutables.beauty, asset::EF_R32G32B32A32_SFLOAT); + bindTexture(SessionTextureIndex::Albedo, immutables.albedo, asset::EF_R32G32B32A32_SFLOAT); + bindTexture(SessionTextureIndex::Normal, immutables.normal, asset::EF_R32G32B32A32_SFLOAT); + bindTexture(SessionTextureIndex::Motion, immutables.motion, asset::EF_R32G32B32A32_SFLOAT); + bindTexture(SessionTextureIndex::Mask, immutables.mask, asset::EF_R32G32B32A32_SFLOAT); + bindTexture(SessionTextureIndex::RWMCCascades, immutables.rwmcCascades, asset::EF_R32G32B32A32_SFLOAT); + bindTexture(SessionTextureIndex::SampleCount, immutables.sampleCount, asset::EF_R32_UINT); + } + + void CUIManager::unbindSessionTextures(ISemaphore* semaphore, uint64_t semaphoreValue) + { + if (!m_subAllocDS) + return; + + ISemaphore::SWaitInfo waitInfo = {}; + if (semaphore) + { + waitInfo.semaphore = semaphore; + waitInfo.value = semaphoreValue; + } + + for (auto& idx : m_sessionTextureIndices) + { + if (idx != SubAllocatedDescriptorSet::invalid_value) + { + m_subAllocDS->multi_deallocate(TexturesBindingIndex, 1, &idx, waitInfo); + idx = SubAllocatedDescriptorSet::invalid_value; + } + } + } + + void CUIManager::update(const nbl::ext::imgui::UI::SUpdateParameters& params) + { + if (m_imguiManager) + m_imguiManager->update(params); + } + + void CUIManager::drawWindows() + { + const bool reposition = m_needsRepositionWindows; + m_needsRepositionWindows = false; + + // Draw Scene Window + m_sceneWindow.draw(reposition); + + // Draw Session Window + m_sessionWindow.draw(reposition); + } + + bool CUIManager::render(IGPUCommandBuffer* cmdbuf, ISemaphore::SWaitInfo waitInfo) + { + if (!m_initialized || !m_imguiManager || !m_subAllocDS) + return false; + + const auto& uiParams = m_imguiManager->getCreationParameters(); + auto* uiPipeline = m_imguiManager->getPipeline(); + + cmdbuf->bindGraphicsPipeline(uiPipeline); + const auto* ds = m_subAllocDS->getDescriptorSet(); + cmdbuf->bindDescriptorSets(asset::EPBP_GRAPHICS, uiPipeline->getLayout(), uiParams.resources.texturesInfo.setIx, 1u, &ds); + + return m_imguiManager->render(cmdbuf, waitInfo); + } + +} // namespace nbl::this_example::gui diff --git a/40_PathTracer/src/io/CSceneLoader.cpp b/40_PathTracer/src/io/CSceneLoader.cpp index 4507c64dd..cd2b4f934 100644 --- a/40_PathTracer/src/io/CSceneLoader.cpp +++ b/40_PathTracer/src/io/CSceneLoader.cpp @@ -528,7 +528,41 @@ auto CSceneLoader::load(SLoadParams&& _params) -> SLoadResult // TODO: any CPU-side touch-ups we need to do, like Material IR options - +#define TEST +#ifdef TEST + // Create dummy sensors with different configurations for GUI testing + if (!sensors.empty()) + { + const auto& baseSensor = sensors.front(); + + // Dummy sensor 1: 640x360 no offset + { + auto dummy = baseSensor; + dummy.mutableDefaults.cropWidth = 640; + dummy.mutableDefaults.cropHeight = 360; + dummy.mutableDefaults.cropOffsetX = 0; + dummy.mutableDefaults.cropOffsetY = 0; + dummy.constants.width = dummy.mutableDefaults.cropWidth; + dummy.constants.height = dummy.mutableDefaults.cropHeight; + sensors.push_back(std::move(dummy)); + } + + // Dummy sensor 2: 5120x2880 with 128 offset + { + auto dummy = baseSensor; + dummy.mutableDefaults.cropWidth = 5120; + dummy.mutableDefaults.cropHeight = 2880; + dummy.mutableDefaults.cropOffsetX = 128; + dummy.mutableDefaults.cropOffsetY = 128; + dummy.constants.width = dummy.mutableDefaults.cropWidth + 2 * dummy.mutableDefaults.cropOffsetX; + dummy.constants.height = dummy.mutableDefaults.cropHeight + 2 * dummy.mutableDefaults.cropOffsetY; + sensors.push_back(std::move(dummy)); + } + + logger.log("Added 2 dummy test sensors (total: %d)", ILogger::ELL_INFO, sensors.size()); + } +#endif + // empty out the cache from individual images and meshes taht are not used by the scene assMan->clearAllAssetCache(); // return diff --git a/40_PathTracer/src/renderer/CSession.cpp b/40_PathTracer/src/renderer/CSession.cpp index a20a8a7ae..9a61c6ec1 100644 --- a/40_PathTracer/src/renderer/CSession.cpp +++ b/40_PathTracer/src/renderer/CSession.cpp @@ -5,302 +5,302 @@ namespace nbl::this_example { -using namespace nbl::core; -using namespace nbl::system; -using namespace nbl::asset; -using namespace nbl::hlsl; -using namespace nbl::video; + using namespace nbl::core; + using namespace nbl::system; + using namespace nbl::asset; + using namespace nbl::hlsl; + using namespace nbl::video; -// -bool CSession::init(video::IGPUCommandBuffer* cb) -{ - auto renderer = m_params.scene->getRenderer(); - auto& logger = renderer->getCreationParams().logger; - auto device = renderer->getDevice(); - - auto& immutables = m_active.immutables; - - // create the descriptors - core::vector infos; - core::vector writes; + // + bool CSession::init(video::IGPUCommandBuffer* cb) { - auto addWrite = [&](const uint32_t binding, IGPUDescriptorSet::SDescriptorInfo&& info)->void - { - writes.emplace_back() = { - .binding = binding, - .arrayElement = 0, - .count = 1, - .info = reinterpret_cast(infos.size()) - }; - infos.push_back(std::move(info)); - }; - - // - auto dedicatedAllocate = [&](IDeviceMemoryBacked* memBacked, const std::string_view debugName)->bool - { - if (!memBacked) - { - logger.log("Failed to create Sensor \"%s\"'s \"%s\" in CSession::init()",ILogger::ELL_ERROR,m_params.name.c_str(),debugName.data()); - return false; - } - memBacked->setObjectDebugName(debugName.data()); + auto renderer = m_params.scene->getRenderer(); + auto& logger = renderer->getCreationParams().logger; + auto device = renderer->getDevice(); - auto mreqs = memBacked->getMemoryReqs(); - mreqs.memoryTypeBits &= device->getPhysicalDevice()->getDeviceLocalMemoryTypeBits(); - if (!device->allocate(mreqs,memBacked,IDeviceMemoryAllocation::E_MEMORY_ALLOCATE_FLAGS::EMAF_NONE).isValid()) - { - logger.log("Could not allocate memory for Sensor \"%s\"'s \"%s\" in CSession::init()",ILogger::ELL_ERROR,m_params.name.c_str(),debugName.data()); - return false; - } - return true; - }; + auto& immutables = m_active.immutables; - // create UBO + // create the descriptors + core::vector infos; + core::vector writes; { - IGPUBuffer::SCreationParams params = {}; - params.size = sizeof(m_params.uniforms); - using usage_flags_e = IGPUBuffer::E_USAGE_FLAGS; - params.usage = usage_flags_e::EUF_UNIFORM_BUFFER_BIT |usage_flags_e::EUF_TRANSFER_DST_BIT | usage_flags_e::EUF_INLINE_UPDATE_VIA_CMDBUF; - auto ubo = device->createBuffer(std::move(params)); - if (!dedicatedAllocate(ubo.get(),"Sensor UBO")) - return false; - // pipeline barrier in `reset` will take care of sync for this - cb->updateBuffer({.size=sizeof(m_params.uniforms),.buffer=ubo},&m_params.uniforms); - addWrite(SensorDSBindings::UBO,SBufferRange{.offset=0,.size=sizeof(m_params.uniforms),.buffer=ubo}); - } + auto addWrite = [&](const uint32_t binding, IGPUDescriptorSet::SDescriptorInfo&& info)->void + { + writes.emplace_back() = { + .binding = binding, + .arrayElement = 0, + .count = 1, + .info = reinterpret_cast(infos.size()) + }; + infos.push_back(std::move(info)); + }; - const auto allowedFormatUsages = device->getPhysicalDevice()->getImageFormatUsagesOptimalTiling(); - auto createImage = [&]( - const std::string_view debugName, const E_FORMAT format, const uint16_t2 resolution, const uint16_t layers, std::bitset viewFormats={}, - const IGPUImage::E_USAGE_FLAGS extraUsages=IGPUImage::E_USAGE_FLAGS::EUF_STORAGE_BIT|IGPUImage::E_USAGE_FLAGS::EUF_SAMPLED_BIT - ) -> SImageWithViews - { - SImageWithViews retval = {}; - { + // + auto dedicatedAllocate = [&](IDeviceMemoryBacked* memBacked, const std::string_view debugName)->bool { - IGPUImage::SCreationParams params = {}; - params.type = IGPUImage::E_TYPE::ET_2D; - params.samples = IGPUImage::E_SAMPLE_COUNT_FLAGS::ESCF_1_BIT; - params.format = format; - params.extent.width = resolution[0]; - params.extent.height = resolution[1]; - params.extent.depth = 1; - params.mipLevels = 1; - params.arrayLayers = layers; - using image_usage_e = IGPUImage::E_USAGE_FLAGS; - params.usage = image_usage_e::EUF_TRANSFER_DST_BIT|extraUsages; - if (m_params.type==sensor_type_e::Env) + if (!memBacked) { - params.arrayLayers *= 6; - params.flags |= IGPUImage::E_CREATE_FLAGS::ECF_CUBE_COMPATIBLE_BIT; + logger.log("Failed to create Sensor \"%s\"'s \"%s\" in CSession::init()", ILogger::ELL_ERROR, m_params.name.c_str(), debugName.data()); + return false; } - viewFormats.set(format); - if (viewFormats.count()>1) + memBacked->setObjectDebugName(debugName.data()); + + auto mreqs = memBacked->getMemoryReqs(); + mreqs.memoryTypeBits &= device->getPhysicalDevice()->getDeviceLocalMemoryTypeBits(); + if (!device->allocate(mreqs, memBacked, IDeviceMemoryAllocation::E_MEMORY_ALLOCATE_FLAGS::EMAF_NONE).isValid()) { - params.flags |= IGPUImage::E_CREATE_FLAGS::ECF_MUTABLE_FORMAT_BIT; - params.flags |= IGPUImage::E_CREATE_FLAGS::ECF_EXTENDED_USAGE_BIT; + logger.log("Could not allocate memory for Sensor \"%s\"'s \"%s\" in CSession::init()", ILogger::ELL_ERROR, m_params.name.c_str(), debugName.data()); + return false; } - params.viewFormats = viewFormats; - retval.image = device->createImage(std::move(params)); - if (!dedicatedAllocate(retval.image.get(),debugName)) - return {}; - } - const auto& params = retval.image->getCreationParameters(); - for (uint8_t f=0; fcreateBuffer(std::move(params)); + if (!dedicatedAllocate(ubo.get(), "Sensor UBO")) + return false; + // pipeline barrier in `reset` will take care of sync for this + cb->updateBuffer({ .size = sizeof(m_params.uniforms),.buffer = ubo }, &m_params.uniforms); + addWrite(SensorDSBindings::UBO, SBufferRange{.offset = 0, .size = sizeof(m_params.uniforms), .buffer = ubo}); + } + + const auto allowedFormatUsages = device->getPhysicalDevice()->getImageFormatUsagesOptimalTiling(); + auto createImage = [&]( + const std::string_view debugName, const E_FORMAT format, const uint16_t2 resolution, const uint16_t layers, std::bitset viewFormats = {}, + const IGPUImage::E_USAGE_FLAGS extraUsages = IGPUImage::E_USAGE_FLAGS::EUF_STORAGE_BIT | IGPUImage::E_USAGE_FLAGS::EUF_SAMPLED_BIT + ) -> SImageWithViews { - const auto viewFormat = static_cast(f); - const auto thisFormatUsages = static_cast>(allowedFormatUsages[viewFormat]); - auto view = device->createImageView({ - .subUsages = retval.image->getCreationParameters().usage & thisFormatUsages, - .image = retval.image, - .viewType = IGPUImageView::E_TYPE::ET_2D_ARRAY, - .format = viewFormat - }); - string viewDebugName = string(debugName)+" "+to_string(viewFormat)+" View"; - if (!view) + SImageWithViews retval = {}; { - logger.log("Failed to create Sensor \"%s\"'s \"%s\" in CSession::init()",ILogger::ELL_ERROR,m_params.name.c_str(),viewDebugName.c_str()); - return {}; + { + IGPUImage::SCreationParams params = {}; + params.type = IGPUImage::E_TYPE::ET_2D; + params.samples = IGPUImage::E_SAMPLE_COUNT_FLAGS::ESCF_1_BIT; + params.format = format; + params.extent.width = resolution[0]; + params.extent.height = resolution[1]; + params.extent.depth = 1; + params.mipLevels = 1; + params.arrayLayers = layers; + using image_usage_e = IGPUImage::E_USAGE_FLAGS; + params.usage = image_usage_e::EUF_TRANSFER_DST_BIT | extraUsages; + if (m_params.type == sensor_type_e::Env) + { + params.arrayLayers *= 6; + params.flags |= IGPUImage::E_CREATE_FLAGS::ECF_CUBE_COMPATIBLE_BIT; + } + viewFormats.set(format); + if (viewFormats.count() > 1) + { + params.flags |= IGPUImage::E_CREATE_FLAGS::ECF_MUTABLE_FORMAT_BIT; + params.flags |= IGPUImage::E_CREATE_FLAGS::ECF_EXTENDED_USAGE_BIT; + } + params.viewFormats = viewFormats; + retval.image = device->createImage(std::move(params)); + if (!dedicatedAllocate(retval.image.get(), debugName)) + return {}; + } + const auto& params = retval.image->getCreationParameters(); + for (uint8_t f = 0; f < viewFormats.size(); f++) + if (viewFormats.test(f)) + { + const auto viewFormat = static_cast(f); + const auto thisFormatUsages = static_cast>(allowedFormatUsages[viewFormat]); + auto view = device->createImageView({ + .subUsages = retval.image->getCreationParameters().usage & thisFormatUsages, + .image = retval.image, + .viewType = IGPUImageView::E_TYPE::ET_2D_ARRAY, + .format = viewFormat + }); + string viewDebugName = string(debugName) + " " + to_string(viewFormat) + " View"; + if (!view) + { + logger.log("Failed to create Sensor \"%s\"'s \"%s\" in CSession::init()", ILogger::ELL_ERROR, m_params.name.c_str(), viewDebugName.c_str()); + return {}; + } + view->setObjectDebugName(viewDebugName.c_str()); + retval.views[viewFormat] = std::move(view); + } } - view->setObjectDebugName(viewDebugName.c_str()); - retval.views[viewFormat] = std::move(view); - } - } - return retval; - }; - auto addImageWrite = [&](const uint32_t binding, const smart_refctd_ptr& view)->void - { - IGPUDescriptorSet::SDescriptorInfo info = {}; - info.desc = view; - info.info.image.imageLayout = IGPUImage::LAYOUT::GENERAL; - addWrite(binding,std::move(info)); - }; - immutables.scrambleKey = createImage("Scramble Key",E_FORMAT::EF_R32G32_UINT,promote(SSensorUniforms::ScrambleKeyTextureSize),1); - auto scrambleKeyView = immutables.scrambleKey.views[E_FORMAT::EF_R32G32_UINT]; - addImageWrite(SensorDSBindings::ScrambleKey,scrambleKeyView); + return retval; + }; + auto addImageWrite = [&](const uint32_t binding, const smart_refctd_ptr& view)->void + { + IGPUDescriptorSet::SDescriptorInfo info = {}; + info.desc = view; + info.info.image.imageLayout = IGPUImage::LAYOUT::GENERAL; + addWrite(binding, std::move(info)); + }; + immutables.scrambleKey = createImage("Scramble Key", E_FORMAT::EF_R32G32_UINT, promote(SSensorUniforms::ScrambleKeyTextureSize), 1); + auto scrambleKeyView = immutables.scrambleKey.views[E_FORMAT::EF_R32G32_UINT]; + addImageWrite(SensorDSBindings::ScrambleKey, scrambleKeyView); - // create the render-sized images - auto createScreenSizedImage = [&](const std::string_view debugName, const E_FORMAT format, Args&&... args)->SImageWithViews - { - return createImage(debugName,format,m_params.uniforms.renderSize,std::forward(args)...); - }; - immutables.sampleCount = createScreenSizedImage("Current Sample Count",E_FORMAT::EF_R16_UINT,1); - auto sampleCountView = immutables.sampleCount.views[E_FORMAT::EF_R16_UINT]; - addImageWrite(SensorDSBindings::SampleCount,sampleCountView); - immutables.rwmcCascades = createScreenSizedImage("RWMC Cascades",E_FORMAT::EF_R32G32_UINT,m_params.uniforms.lastCascadeIndex+1); - auto rwmcCascadesView = immutables.rwmcCascades.views[E_FORMAT::EF_R32G32_UINT]; - addImageWrite(SensorDSBindings::RWMCCascades,rwmcCascadesView); - immutables.beauty = createScreenSizedImage("Beauty",E_FORMAT::EF_E5B9G9R9_UFLOAT_PACK32,1,std::bitset().set(E_FORMAT::EF_R32_UINT)); - addImageWrite(SensorDSBindings::Beauty,immutables.beauty.views[E_FORMAT::EF_R32_UINT]); - immutables.albedo = createScreenSizedImage("Albedo",E_FORMAT::EF_A2B10G10R10_UNORM_PACK32,1); - auto albedoView = immutables.albedo.views[E_FORMAT::EF_A2B10G10R10_UNORM_PACK32]; - addImageWrite(SensorDSBindings::Albedo,albedoView); - // Normal and Albedo should have used `EF_A2B10G10R10_SNORM_PACK32` but Nvidia doesn't support - immutables.normal = createScreenSizedImage("Normal",E_FORMAT::EF_A2B10G10R10_UNORM_PACK32,1); - auto normalView = immutables.normal.views[E_FORMAT::EF_A2B10G10R10_UNORM_PACK32]; - addImageWrite(SensorDSBindings::Normal,normalView); - immutables.motion = createScreenSizedImage("Motion",E_FORMAT::EF_A2B10G10R10_UNORM_PACK32,1); - auto motionView = immutables.motion.views[E_FORMAT::EF_A2B10G10R10_UNORM_PACK32]; - addImageWrite(SensorDSBindings::Motion,motionView); - immutables.mask = createScreenSizedImage("Mask",E_FORMAT::EF_R16_UNORM,1); - auto maskView = immutables.mask.views[E_FORMAT::EF_R16_UNORM]; - addImageWrite(SensorDSBindings::Mask,maskView); - // shorthand a little bit - addImageWrite(SensorDSBindings::AsSampledImages,scrambleKeyView); - writes.back().count = SensorDSBindingCounts::AsSampledImages; - { - const auto oldSize = infos.size(); - infos.resize(oldSize +SensorDSBindingCounts::AsSampledImages,infos.back()); - const auto viewInfos = infos.data()+oldSize-1; - using index_e = SensorDSBindings::SampledImageIndex; - viewInfos[uint8_t(index_e::ScrambleKey)].desc = scrambleKeyView; - viewInfos[uint8_t(index_e::SampleCount)].desc = sampleCountView; - viewInfos[uint8_t(index_e::RWMCCascades)].desc = rwmcCascadesView; - viewInfos[uint8_t(index_e::Beauty)].desc = immutables.beauty.views[E_FORMAT::EF_E5B9G9R9_UFLOAT_PACK32]; - viewInfos[uint8_t(index_e::Albedo)].desc = albedoView; - viewInfos[uint8_t(index_e::Normal)].desc = normalView; - viewInfos[uint8_t(index_e::Motion)].desc = motionView; - viewInfos[uint8_t(index_e::Mask)].desc = maskView; + // create the render-sized images + auto createScreenSizedImage = [&](const std::string_view debugName, const E_FORMAT format, Args&&... args)->SImageWithViews + { + return createImage(debugName, format, m_params.uniforms.renderSize, std::forward(args)...); + }; + immutables.sampleCount = createScreenSizedImage("Current Sample Count", E_FORMAT::EF_R16_UINT, 1); + auto sampleCountView = immutables.sampleCount.views[E_FORMAT::EF_R16_UINT]; + addImageWrite(SensorDSBindings::SampleCount, sampleCountView); + immutables.rwmcCascades = createScreenSizedImage("RWMC Cascades", E_FORMAT::EF_R32G32_UINT, m_params.uniforms.lastCascadeIndex + 1); + auto rwmcCascadesView = immutables.rwmcCascades.views[E_FORMAT::EF_R32G32_UINT]; + addImageWrite(SensorDSBindings::RWMCCascades, rwmcCascadesView); + immutables.beauty = createScreenSizedImage("Beauty", E_FORMAT::EF_E5B9G9R9_UFLOAT_PACK32, 1, std::bitset().set(E_FORMAT::EF_R32_UINT)); + addImageWrite(SensorDSBindings::Beauty, immutables.beauty.views[E_FORMAT::EF_R32_UINT]); + immutables.albedo = createScreenSizedImage("Albedo", E_FORMAT::EF_A2B10G10R10_UNORM_PACK32, 1); + auto albedoView = immutables.albedo.views[E_FORMAT::EF_A2B10G10R10_UNORM_PACK32]; + addImageWrite(SensorDSBindings::Albedo, albedoView); + // Normal and Albedo should have used `EF_A2B10G10R10_SNORM_PACK32` but Nvidia doesn't support + immutables.normal = createScreenSizedImage("Normal", E_FORMAT::EF_A2B10G10R10_UNORM_PACK32, 1); + auto normalView = immutables.normal.views[E_FORMAT::EF_A2B10G10R10_UNORM_PACK32]; + addImageWrite(SensorDSBindings::Normal, normalView); + immutables.motion = createScreenSizedImage("Motion", E_FORMAT::EF_A2B10G10R10_UNORM_PACK32, 1); + auto motionView = immutables.motion.views[E_FORMAT::EF_A2B10G10R10_UNORM_PACK32]; + addImageWrite(SensorDSBindings::Motion, motionView); + immutables.mask = createScreenSizedImage("Mask", E_FORMAT::EF_R16_UNORM, 1); + auto maskView = immutables.mask.views[E_FORMAT::EF_R16_UNORM]; + addImageWrite(SensorDSBindings::Mask, maskView); + // shorthand a little bit + addImageWrite(SensorDSBindings::AsSampledImages, scrambleKeyView); + writes.back().count = SensorDSBindingCounts::AsSampledImages; + { + const auto oldSize = infos.size(); + infos.resize(oldSize + SensorDSBindingCounts::AsSampledImages, infos.back()); + const auto viewInfos = infos.data() + oldSize - 1; + using index_e = SensorDSBindings::SampledImageIndex; + viewInfos[uint8_t(index_e::ScrambleKey)].desc = scrambleKeyView; + viewInfos[uint8_t(index_e::SampleCount)].desc = sampleCountView; + viewInfos[uint8_t(index_e::RWMCCascades)].desc = rwmcCascadesView; + viewInfos[uint8_t(index_e::Beauty)].desc = immutables.beauty.views[E_FORMAT::EF_E5B9G9R9_UFLOAT_PACK32]; + viewInfos[uint8_t(index_e::Albedo)].desc = albedoView; + viewInfos[uint8_t(index_e::Normal)].desc = normalView; + viewInfos[uint8_t(index_e::Motion)].desc = motionView; + viewInfos[uint8_t(index_e::Mask)].desc = maskView; + } } - } - // create descriptor set - { - auto layout = renderer->getConstructionParams().sensorDSLayout; - auto pool = device->createDescriptorPoolForDSLayouts(IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT,{&layout.get(),1}); - immutables.ds = pool->createDescriptorSet(std::move(layout)); - const char* DebugName = "Sensor Descriptor Set"; - if (!immutables.ds) - { - logger.log("Failed to create Sensor \"%s\"'s \"%s\" in CSession::init()",ILogger::ELL_ERROR,m_params.name.c_str(),DebugName); - return false; - } - immutables.ds->setObjectDebugName(DebugName); - for (auto& write : writes) + // create descriptor set { - write.dstSet = immutables.ds.get(); - write.info = infos.data()+reinterpret_cast(write.info); + auto layout = renderer->getConstructionParams().sensorDSLayout; + auto pool = device->createDescriptorPoolForDSLayouts(IDescriptorPool::E_CREATE_FLAGS::ECF_UPDATE_AFTER_BIND_BIT, { &layout.get(),1 }); + immutables.ds = pool->createDescriptorSet(std::move(layout)); + const char* DebugName = "Sensor Descriptor Set"; + if (!immutables.ds) + { + logger.log("Failed to create Sensor \"%s\"'s \"%s\" in CSession::init()", ILogger::ELL_ERROR, m_params.name.c_str(), DebugName); + return false; + } + immutables.ds->setObjectDebugName(DebugName); + for (auto& write : writes) + { + write.dstSet = immutables.ds.get(); + write.info = infos.data() + reinterpret_cast(write.info); + } + if (!device->updateDescriptorSets(writes, {})) + { + logger.log("Failed to write Sensor \"%s\"'s \"%s\" in CSession::init()", ILogger::ELL_ERROR, m_params.name.c_str(), DebugName); + return false; + } } - if (!device->updateDescriptorSets(writes,{})) + + if (!immutables || !reset(m_params.initDynamics, cb)) { - logger.log("Failed to write Sensor \"%s\"'s \"%s\" in CSession::init()",ILogger::ELL_ERROR,m_params.name.c_str(),DebugName); + logger.log("Could not Init Session for sensor \"%s\" failed to reset!", ILogger::ELL_ERROR, m_params.name.c_str()); + deinit(); return false; } - } - - if (!immutables || !reset(m_params.initDynamics,cb)) - { - logger.log("Could not Init Session for sensor \"%s\" failed to reset!",ILogger::ELL_ERROR,m_params.name.c_str()); - deinit(); - return false; - } -// TODO: fill scramble Key with noise + // TODO: fill scramble Key with noise - return true; -} - -bool CSession::reset(const SSensorDynamics& newVal, IGPUCommandBuffer* cb) -{ - if (!isInitialized()) - return false; - - auto* const renderer = m_params.scene->getRenderer(); - auto* const device = renderer->getDevice(); - const auto& immutables = m_active.immutables; + return true; + } - bool success = true; - // slam the barriers as big as possible, it wont happen frequently - using image_barrier_t = IGPUCommandBuffer::SPipelineBarrierDependencyInfo::image_barrier_t; - core::vector before; + bool CSession::reset(const SSensorDynamics& newVal, IGPUCommandBuffer* cb) { - constexpr image_barrier_t beforeBase = { - .barrier = { - .dep = { - .srcStageMask = PIPELINE_STAGE_FLAGS::ALL_COMMANDS_BITS, - .srcAccessMask = ACCESS_FLAGS::NONE, // because we don't care about reading previously written values - .dstStageMask = PIPELINE_STAGE_FLAGS::CLEAR_BIT, - .dstAccessMask = ACCESS_FLAGS::MEMORY_WRITE_BITS - } - }, - .subresourceRange = {}, - .newLayout = IGPUImage::LAYOUT::GENERAL - }; - before.reserve(SensorDSBindingCounts::AsSampledImages); + if (!isInitialized()) + return false; + + auto* const renderer = m_params.scene->getRenderer(); + auto* const device = renderer->getDevice(); + const auto& immutables = m_active.immutables; - auto enqueueClear = [&before,beforeBase](const SImageWithViews& img)->void + bool success = true; + // slam the barriers as big as possible, it wont happen frequently + using image_barrier_t = IGPUCommandBuffer::SPipelineBarrierDependencyInfo::image_barrier_t; + core::vector before; { - auto& out = before.emplace_back(beforeBase); - out.image = img.image.get(); - out.subresourceRange = { - .aspectMask = IGPUImage::E_ASPECT_FLAGS::EAF_COLOR_BIT, - .levelCount = 1, - .layerCount = out.image->getCreationParameters().arrayLayers + constexpr image_barrier_t beforeBase = { + .barrier = { + .dep = { + .srcStageMask = PIPELINE_STAGE_FLAGS::ALL_COMMANDS_BITS, + .srcAccessMask = ACCESS_FLAGS::NONE, // because we don't care about reading previously written values + .dstStageMask = PIPELINE_STAGE_FLAGS::CLEAR_BIT, + .dstAccessMask = ACCESS_FLAGS::MEMORY_WRITE_BITS + } + }, + .subresourceRange = {}, + .newLayout = IGPUImage::LAYOUT::GENERAL }; - }; - enqueueClear(immutables.sampleCount); - enqueueClear(immutables.beauty); - enqueueClear(immutables.rwmcCascades); - enqueueClear(immutables.albedo); - enqueueClear(immutables.normal); - enqueueClear(immutables.motion); - enqueueClear(immutables.mask); - success = success && cb->pipelineBarrier(asset::EDF_NONE,{.imgBarriers=before}); - } + before.reserve(SensorDSBindingCounts::AsSampledImages); - { - IGPUCommandBuffer::SClearColorValue color; - memset(&color,0,sizeof(color)); - for (const auto& entry : before) - { - success = success && cb->clearColorImage(const_cast(entry.image),IGPUImage::LAYOUT::GENERAL,&color,1,&entry.subresourceRange); + auto enqueueClear = [&before, beforeBase](const SImageWithViews& img)->void + { + auto& out = before.emplace_back(beforeBase); + out.image = img.image.get(); + out.subresourceRange = { + .aspectMask = IGPUImage::E_ASPECT_FLAGS::EAF_COLOR_BIT, + .levelCount = 1, + .layerCount = out.image->getCreationParameters().arrayLayers + }; + }; + enqueueClear(immutables.sampleCount); + enqueueClear(immutables.beauty); + enqueueClear(immutables.rwmcCascades); + enqueueClear(immutables.albedo); + enqueueClear(immutables.normal); + enqueueClear(immutables.motion); + enqueueClear(immutables.mask); + success = success && cb->pipelineBarrier(asset::EDF_NONE, { .imgBarriers = before }); } - } - const SMemoryBarrier after[] = { { - .srcStageMask = PIPELINE_STAGE_FLAGS::CLEAR_BIT, - .srcAccessMask = ACCESS_FLAGS::MEMORY_WRITE_BITS, - .dstStageMask = PIPELINE_STAGE_FLAGS::ALL_COMMANDS_BITS, - .dstAccessMask = ACCESS_FLAGS::SHADER_READ_BITS|ACCESS_FLAGS::SHADER_WRITE_BITS + IGPUCommandBuffer::SClearColorValue color; + memset(&color, 0, sizeof(color)); + for (const auto& entry : before) + { + success = success && cb->clearColorImage(const_cast(entry.image), IGPUImage::LAYOUT::GENERAL, &color, 1, &entry.subresourceRange); + } } - }; - success = success && cb->pipelineBarrier(asset::EDF_NONE,{.memBarriers=after}); - if (success) - m_active.prevSensorState = m_active.currentSensorState = newVal; - return success; -} + const SMemoryBarrier after[] = { + { + .srcStageMask = PIPELINE_STAGE_FLAGS::CLEAR_BIT, + .srcAccessMask = ACCESS_FLAGS::MEMORY_WRITE_BITS, + .dstStageMask = PIPELINE_STAGE_FLAGS::ALL_COMMANDS_BITS, + .dstAccessMask = ACCESS_FLAGS::SHADER_READ_BITS | ACCESS_FLAGS::SHADER_WRITE_BITS + } + }; + success = success && cb->pipelineBarrier(asset::EDF_NONE, { .memBarriers = after }); -bool CSession::update(const SSensorDynamics& newVal) -{ - if (!isInitialized()) - return false; + if (success) + m_active.prevSensorState = m_active.currentSensorState = newVal; + return success; + } - m_active.prevSensorState = m_active.currentSensorState; - m_active.currentSensorState = newVal; - return true; -} + bool CSession::update(const SSensorDynamics& newVal) + { + if (!isInitialized()) + return false; + + m_active.prevSensorState = m_active.currentSensorState; + m_active.currentSensorState = newVal; + return true; + } } \ No newline at end of file