From ffb074e9cdf0b536669c45256dfb226731921475 Mon Sep 17 00:00:00 2001 From: Raydelto Hernandez Date: Wed, 7 Oct 2026 16:41:54 -0300 Subject: [PATCH 1/6] NAB-7: Build the examples API and 01 on Linux - Win32 window manager and surface only on Windows; other platforms log that no window manager exists yet instead of #error (XCB is NAB-9 scope). - Depth attachment ref: brace-init SDepthStencilLayout explicitly (Clang doesn't elide braces in designated initializers). - 01_HelloCoreSystemAsset: ANSI stdout logger off Windows. Co-Authored-By: Claude Opus 5.5 --- 01_HelloCoreSystemAsset/main.cpp | 4 ++++ .../nbl/examples/common/CSwapchainFramebuffersAndDepth.hpp | 2 +- common/include/nbl/examples/common/MonoWindowApplication.hpp | 2 ++ .../include/nbl/examples/common/SimpleWindowedApplication.hpp | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/01_HelloCoreSystemAsset/main.cpp b/01_HelloCoreSystemAsset/main.cpp index 7ca4badb4..2ec216c93 100644 --- a/01_HelloCoreSystemAsset/main.cpp +++ b/01_HelloCoreSystemAsset/main.cpp @@ -82,7 +82,11 @@ int main(int argc, char** argv) } else { + #ifdef _NBL_PLATFORM_WINDOWS_ logger = make_smart_refctd_ptr(LogLevel); + #else + logger = make_smart_refctd_ptr(LogLevel); + #endif logger->log("Could not create \"%s\\log.txt\" logging to STDOUT instead!\n",ILogger::ELL_ERROR,CWD.string().c_str()); } } diff --git a/common/include/nbl/examples/common/CSwapchainFramebuffersAndDepth.hpp b/common/include/nbl/examples/common/CSwapchainFramebuffersAndDepth.hpp index 949026a3c..a64886add 100644 --- a/common/include/nbl/examples/common/CSwapchainFramebuffersAndDepth.hpp +++ b/common/include/nbl/examples/common/CSwapchainFramebuffersAndDepth.hpp @@ -50,7 +50,7 @@ class CSwapchainFramebuffersAndDepth final : public video::CDefaultSwapchainFram m_params.subpasses[0], IGPURenderpass::SCreationParams::SubpassesEnd }; - subpasses[0].depthStencilAttachment.render = { .attachmentIndex = 0,.layout = IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL }; + subpasses[0].depthStencilAttachment.render = { .attachmentIndex = 0,.layout = {.depth = IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL} }; m_params.subpasses = subpasses; } diff --git a/common/include/nbl/examples/common/MonoWindowApplication.hpp b/common/include/nbl/examples/common/MonoWindowApplication.hpp index ab7cff307..374a74189 100644 --- a/common/include/nbl/examples/common/MonoWindowApplication.hpp +++ b/common/include/nbl/examples/common/MonoWindowApplication.hpp @@ -47,8 +47,10 @@ class MonoWindowApplication : public virtual SimpleWindowedApplication const_cast&>(m_window) = m_winMgr->createWindow(std::move(params)); } + #ifdef _NBL_PLATFORM_WINDOWS_ auto surface = CSurfaceVulkanWin32::create(smart_refctd_ptr(m_api), smart_refctd_ptr_static_cast(m_window)); const_cast&>(m_surface) = CSimpleResizeSurface::create(std::move(surface)); + #endif } if (m_surface) diff --git a/common/include/nbl/examples/common/SimpleWindowedApplication.hpp b/common/include/nbl/examples/common/SimpleWindowedApplication.hpp index ddb510eb7..d4c00998f 100644 --- a/common/include/nbl/examples/common/SimpleWindowedApplication.hpp +++ b/common/include/nbl/examples/common/SimpleWindowedApplication.hpp @@ -64,7 +64,7 @@ class SimpleWindowedApplication : public virtual application_templates::BasicMul #ifdef _NBL_PLATFORM_WINDOWS_ m_winMgr = nbl::ui::IWindowManagerWin32::create(); #else - #error "Unimplemented!" + m_logger->log("No window manager is implemented for this platform yet!",system::ILogger::ELL_ERROR); #endif if (!m_winMgr) return false; From c337709e48b235225483f8877508c67ef140713a Mon Sep 17 00:00:00 2001 From: Raydelto Hernandez Date: Wed, 7 Oct 2026 16:43:51 -0300 Subject: [PATCH 2/6] NAB-7: Cap 23 workgroup size by maxComputeWorkgroupSubgroups With a required subgroup size the workgroup can't exceed requiredSubgroupSize*maxComputeWorkgroupSubgroups. Intel ANV (TGL GT1) reports 64, so SIMD8 x 1024 was invalid usage and hung the GPU. Co-Authored-By: Claude Opus 5.5 --- 23_Arithmetic2UnitTest/main.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/23_Arithmetic2UnitTest/main.cpp b/23_Arithmetic2UnitTest/main.cpp index 2fa978953..a42bcaa9d 100644 --- a/23_Arithmetic2UnitTest/main.cpp +++ b/23_Arithmetic2UnitTest/main.cpp @@ -181,6 +181,9 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu const auto MaxWorkgroupSize = m_physicalDevice->getLimits().maxComputeWorkGroupInvocations; const auto MinSubgroupSize = m_physicalDevice->getLimits().minSubgroupSize; const auto MaxSubgroupSize = m_physicalDevice->getLimits().maxSubgroupSize; + // a required subgroup size caps the workgroup at `requiredSubgroupSize*maxComputeWorkgroupSubgroups` (VUID-VkPipelineShaderStageCreateInfo-pNext-02756), + // Intel ANV reports 64 subgroups so SIMD8 can't go past 512 invocations and the GPU hangs + const auto MaxWorkgroupSubgroups = m_physicalDevice->getLimits().maxComputeWorkgroupSubgroups; for (uint32_t useNative = 0; useNative <= uint32_t(m_physicalDevice->getProperties().limits.shaderSubgroupArithmetic); useNative++) { if (useNative) @@ -191,7 +194,7 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu for (auto subgroupSize = MinSubgroupSize; subgroupSize <= MaxSubgroupSize; subgroupSize *= 2u) { const uint8_t subgroupSizeLog2 = hlsl::findMSB(subgroupSize); - for (uint32_t workgroupSize = subgroupSize; workgroupSize <= MaxWorkgroupSize; workgroupSize *= 2u) + for (uint32_t workgroupSize = subgroupSize; workgroupSize <= hlsl::min(MaxWorkgroupSize,subgroupSize*MaxWorkgroupSubgroups); workgroupSize *= 2u) { // make sure renderdoc captures everything for debugging m_api->startCapture(); From d4e15b343462cfadab9978487ab74aed585135a7 Mon Sep 17 00:00:00 2001 From: Raydelto Hernandez Date: Thu, 8 Oct 2026 00:47:50 -0300 Subject: [PATCH 3/6] NAB-9: Create XCB windows and surfaces on Linux in the windowed examples SimpleWindowedApplication and MonoWindowApplication create an IWindowManagerXcb and a CSurfaceVulkanXcb on Linux; 08_HelloSwapchain picks the surface class per platform. Windows paths are unchanged. Co-Authored-By: Claude Opus 5.5 --- 08_HelloSwapchain/main.cpp | 7 ++++++- .../include/nbl/examples/common/MonoWindowApplication.hpp | 4 +++- .../nbl/examples/common/SimpleWindowedApplication.hpp | 4 ++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/08_HelloSwapchain/main.cpp b/08_HelloSwapchain/main.cpp index d2bc076a5..045e05fa7 100644 --- a/08_HelloSwapchain/main.cpp +++ b/08_HelloSwapchain/main.cpp @@ -160,7 +160,12 @@ class HelloSwapchainApp final : public examples::SimpleWindowedApplication auto window = m_winMgr->createWindow(std::move(params)); // uncomment for some nasty testing of swapchain creation! //m_winMgr->minimize(window.get()); - const_cast&>(m_surface) = CSmoothResizeSurface::create(CSurfaceVulkanWin32::create(smart_refctd_ptr(m_api),move_and_static_cast(window))); + #ifdef _NBL_PLATFORM_WINDOWS_ + auto surface = CSurfaceVulkanWin32::create(smart_refctd_ptr(m_api),move_and_static_cast(window)); + #elif defined(_NBL_PLATFORM_LINUX_) + auto surface = CSurfaceVulkanXcb::create(smart_refctd_ptr(m_api),move_and_static_cast(window)); + #endif + const_cast&>(m_surface) = CSmoothResizeSurface::create(std::move(surface)); } return {{m_surface->getSurface()/*,EQF_NONE*/}}; } diff --git a/common/include/nbl/examples/common/MonoWindowApplication.hpp b/common/include/nbl/examples/common/MonoWindowApplication.hpp index 374a74189..9b5540976 100644 --- a/common/include/nbl/examples/common/MonoWindowApplication.hpp +++ b/common/include/nbl/examples/common/MonoWindowApplication.hpp @@ -49,8 +49,10 @@ class MonoWindowApplication : public virtual SimpleWindowedApplication #ifdef _NBL_PLATFORM_WINDOWS_ auto surface = CSurfaceVulkanWin32::create(smart_refctd_ptr(m_api), smart_refctd_ptr_static_cast(m_window)); - const_cast&>(m_surface) = CSimpleResizeSurface::create(std::move(surface)); + #elif defined(_NBL_PLATFORM_LINUX_) + auto surface = CSurfaceVulkanXcb::create(smart_refctd_ptr(m_api), smart_refctd_ptr_static_cast(m_window)); #endif + const_cast&>(m_surface) = CSimpleResizeSurface::create(std::move(surface)); } if (m_surface) diff --git a/common/include/nbl/examples/common/SimpleWindowedApplication.hpp b/common/include/nbl/examples/common/SimpleWindowedApplication.hpp index d4c00998f..573a37772 100644 --- a/common/include/nbl/examples/common/SimpleWindowedApplication.hpp +++ b/common/include/nbl/examples/common/SimpleWindowedApplication.hpp @@ -63,6 +63,10 @@ class SimpleWindowedApplication : public virtual application_templates::BasicMul #ifdef _NBL_PLATFORM_WINDOWS_ m_winMgr = nbl::ui::IWindowManagerWin32::create(); + #elif defined(_NBL_PLATFORM_LINUX_) + m_winMgr = nbl::ui::IWindowManagerXcb::create(); + if (!m_winMgr) + m_logger->log("Could not connect to an X server, is DISPLAY set?",system::ILogger::ELL_ERROR); #else m_logger->log("No window manager is implemented for this platform yet!",system::ILogger::ELL_ERROR); #endif From f6d661623b1f569e1671081edbf6b09bed7e7ae6 Mon Sep 17 00:00:00 2001 From: Raydelto Hernandez Date: Thu, 8 Oct 2026 01:04:31 -0300 Subject: [PATCH 4/6] NAB-9: Fix 09_GeometryCreator clamp on LP64 size_t is unsigned long on Linux, so clamping against 0ull failed to deduce. Co-Authored-By: Claude Opus 5.5 --- 09_GeometryCreator/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/09_GeometryCreator/main.cpp b/09_GeometryCreator/main.cpp index 56f850e5f..d17407a17 100644 --- a/09_GeometryCreator/main.cpp +++ b/09_GeometryCreator/main.cpp @@ -278,7 +278,7 @@ class GeometryCreatorApp final : public MonoWindowApplication, public BuiltinRes if (ev.type==nbl::ui::SMouseEvent::EET_SCROLL && m_renderer) { gcIndex += int16_t(core::sign(ev.scrollEvent.verticalScroll)); - gcIndex = core::clamp(gcIndex,0ull,m_renderer->getGeometries().size()-1); + gcIndex = core::clamp(gcIndex,size_t(0),m_renderer->getGeometries().size()-1); } } } From 3fe42026aaefcf0c959889db91f0e36671ae135e Mon Sep 17 00:00:00 2001 From: Raydelto Hernandez Date: Thu, 8 Oct 2026 11:44:02 -0300 Subject: [PATCH 5/6] NAB-13: Run 70_FLIPFluids on Linux (X11) (#3) * NAB-13: Run 70_FLIPFluids on Linux - Create an XCB surface on Linux, as the other windowed examples do. - Drop the local CSwapchainFramebuffersAndDepth and CEventCallback, which clash with the nbl::examples versions now in common, and fix two narrowing/explicit-constructor errors Clang reports. - Don't block the frame loop waiting for an input device. Without one the camera orbits the tank and the drop restarts every 12 s. Co-Authored-By: Claude Opus 5.5 * NAB-16: Acquire FLIPFluids input without blocking Checking for a device and then calling the blocking getDefault left a gap: if the last mouse or keyboard disconnected in between, the frame waited forever instead of falling back to autoplay. InputSystem gains tryGetDefault{,Mouse,Keyboard}, which check for a device and bind the reader under the same lock and return false when none is connected. getDefault keeps blocking and shares the selection code. Adds the NBL_EXAMPLES_INPUT_SYSTEM CTest (no GPU or display needed), which covers absent and connected devices, disconnect after and during acquisition, and the blocking getDefault still waiting for a device. Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- 70_FLIPFluids/README.md | 9 + 70_FLIPFluids/main.cpp | 167 ++++-------------- common/CMakeLists.txt | 2 + .../nbl/examples/common/InputSystem.hpp | 49 +++-- common/test/CInputSystemTest.cpp | 164 +++++++++++++++++ common/test/CMakeLists.txt | 19 ++ 6 files changed, 269 insertions(+), 141 deletions(-) create mode 100644 common/test/CInputSystemTest.cpp create mode 100644 common/test/CMakeLists.txt diff --git a/70_FLIPFluids/README.md b/70_FLIPFluids/README.md index 5dc4e9fe4..9f6b09c3a 100644 --- a/70_FLIPFluids/README.md +++ b/70_FLIPFluids/README.md @@ -1 +1,10 @@ https://github.com/user-attachments/assets/fc8bfac9-c8fb-49f1-87d6-251aae015945 + +## Running on Linux (X11) + +```bash +cmake --build build/linux-clang-release -j4 --target 70_flipfluids +(cd examples_tests/70_FLIPFluids/bin && DISPLAY=:1 ./70_flipfluids) +``` + +XCB windows don't deliver mouse or keyboard input yet, so when no input device is connected the camera orbits the tank on its own and the water is dropped again every 12 seconds. Stop it with Ctrl+C. diff --git a/70_FLIPFluids/main.cpp b/70_FLIPFluids/main.cpp index 714b9d6b4..fa952eef4 100644 --- a/70_FLIPFluids/main.cpp +++ b/70_FLIPFluids/main.cpp @@ -44,132 +44,6 @@ struct SMVPParams float P[4*4]; }; -class CSwapchainFramebuffersAndDepth final : public nbl::video::CDefaultSwapchainFramebuffers -{ - using scbase_t = CDefaultSwapchainFramebuffers; - -public: - template - inline CSwapchainFramebuffersAndDepth(ILogicalDevice* device, const asset::E_FORMAT _desiredDepthFormat, Args&&... args) - : CDefaultSwapchainFramebuffers(device, std::forward(args)...) - { - const IPhysicalDevice::SImageFormatPromotionRequest req = { - .originalFormat = _desiredDepthFormat, - .usages = {IGPUImage::EUF_RENDER_ATTACHMENT_BIT} - }; - m_depthFormat = m_device->getPhysicalDevice()->promoteImageFormat(req, IGPUImage::TILING::OPTIMAL); - - const static IGPURenderpass::SCreationParams::SDepthStencilAttachmentDescription depthAttachments[] = { - {{ - { - .format = m_depthFormat, - .samples = IGPUImage::ESCF_1_BIT, - .mayAlias = false - }, - /*.loadOp = */{IGPURenderpass::LOAD_OP::CLEAR}, - /*.storeOp = */{IGPURenderpass::STORE_OP::STORE}, - /*.initialLayout = */{IGPUImage::LAYOUT::UNDEFINED}, // because we clear we don't care about contents - /*.finalLayout = */{IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL} // transition to presentation right away so we can skip a barrier - }}, - IGPURenderpass::SCreationParams::DepthStencilAttachmentsEnd - }; - m_params.depthStencilAttachments = depthAttachments; - - static IGPURenderpass::SCreationParams::SSubpassDescription subpasses[] = { - m_params.subpasses[0], - IGPURenderpass::SCreationParams::SubpassesEnd - }; - subpasses[0].depthStencilAttachment.render = { .attachmentIndex = 0,.layout = IGPUImage::LAYOUT::ATTACHMENT_OPTIMAL }; - m_params.subpasses = subpasses; - - // TODO: Two subpass external dependencies SRC and DST needed! - } - -protected: - inline bool onCreateSwapchain_impl(const uint8_t qFam) override - { - auto device = const_cast(m_renderpass->getOriginDevice()); - - const auto depthFormat = m_renderpass->getCreationParameters().depthStencilAttachments[0].format; - const auto& sharedParams = getSwapchain()->getCreationParameters().sharedParams; - auto image = device->createImage({ IImage::SCreationParams{ - .type = IGPUImage::ET_2D, - .samples = IGPUImage::ESCF_1_BIT, - .format = depthFormat, - .extent = {sharedParams.width,sharedParams.height,1}, - .mipLevels = 1, - .arrayLayers = 1, - .depthUsage = IGPUImage::EUF_RENDER_ATTACHMENT_BIT - } }); - - device->allocate(image->getMemoryReqs(), { image.get() }); - - m_depthBuffer = device->createImageView({ - .flags = IGPUImageView::ECF_NONE, - .subUsages = IGPUImage::EUF_RENDER_ATTACHMENT_BIT, - .image = std::move(image), - .viewType = IGPUImageView::ET_2D, - .format = depthFormat, - .subresourceRange = {IGPUImage::EAF_DEPTH_BIT,0,1,0,1} - }); - - const auto retval = scbase_t::onCreateSwapchain_impl(qFam); - m_depthBuffer = nullptr; - return retval; - } - - inline smart_refctd_ptr createFramebuffer(IGPUFramebuffer::SCreationParams&& params) override - { - params.depthStencilAttachments = &m_depthBuffer.get(); - return m_device->createFramebuffer(std::move(params)); - } - - E_FORMAT m_depthFormat; - smart_refctd_ptr m_depthBuffer; -}; - -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 FLIPFluidsApp final : public SimpleWindowedApplication, public BuiltinResourcesApplication { using device_base_t = SimpleWindowedApplication; @@ -211,7 +85,11 @@ class FLIPFluidsApp final : public SimpleWindowedApplication, public BuiltinReso const_cast&>(m_window) = m_winMgr->createWindow(std::move(params)); } + #ifdef _NBL_PLATFORM_WINDOWS_ auto surface = CSurfaceVulkanWin32::create(smart_refctd_ptr(m_api), smart_refctd_ptr_static_cast(m_window)); + #elif defined(_NBL_PLATFORM_LINUX_) + auto surface = CSurfaceVulkanXcb::create(smart_refctd_ptr(m_api), smart_refctd_ptr_static_cast(m_window)); + #endif const_cast&>(m_surface) = nbl::video::CSimpleResizeSurface::create(std::move(surface)); } @@ -295,7 +173,7 @@ class FLIPFluidsApp final : public SimpleWindowedApplication, public BuiltinReso params.size = numParticles * 6 * sizeof(VertexInfo); createBuffer(particleVertexBuffer, params, IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT); - asset::VkExtent3D gridExtent = { m_gridData.gridSize.x, m_gridData.gridSize.y, m_gridData.gridSize.z }; + asset::VkExtent3D gridExtent = { uint32_t(m_gridData.gridSize.x), uint32_t(m_gridData.gridSize.y), uint32_t(m_gridData.gridSize.z) }; // cell materials createGridTexture(gridCellMaterialImageView, asset::EF_R16_UINT, gridExtent, asset::IImage::EUF_STORAGE_BIT, "cell material0"); @@ -908,8 +786,9 @@ class FLIPFluidsApp final : public SimpleWindowedApplication, public BuiltinReso const auto resourceIx = m_realFrameIx % MaxFramesInFlight; - m_inputSystem->getDefaultMouse(&mouse); - m_inputSystem->getDefaultKeyboard(&keyboard); + // `getDefault*` blocks until a device connects and windows without input (XCB for now) never get one, + // the `tryGetDefault*` variants check and bind under one lock, so a disconnect falls back to autoplay + const bool hasInput = m_inputSystem->tryGetDefaultMouse(&mouse) && m_inputSystem->tryGetDefaultKeyboard(&keyboard); auto updatePresentationTimestamp = [&]() { @@ -931,6 +810,9 @@ class FLIPFluidsApp final : public SimpleWindowedApplication, public BuiltinReso cmdbuf->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); cmdbuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); cmdbuf->beginDebugMarker("Frame Debug FLIP sim begin"); + if (!hasInput) + autoplay(); + else { std::vector cameraMouseEvents; std::vector cameraKeyboardEvents; @@ -1554,7 +1436,7 @@ class FLIPFluidsApp final : public SimpleWindowedApplication, public BuiltinReso return logFail("Failed to create render semaphore!\n"); ISwapchain::SCreationParams swapchainParams{ - .surface = m_surface->getSurface() + .surface = smart_refctd_ptr(m_surface->getSurface()) }; if (!swapchainParams.deduceFormat(m_physicalDevice)) return logFail("Could not choose a surface format for the swapchain!\n"); @@ -1711,6 +1593,27 @@ class FLIPFluidsApp final : public SimpleWindowedApplication, public BuiltinReso } } + // Without input devices, orbit the camera around the tank and drop the water again periodically + void autoplay() + { + const auto now = clock_t::now(); + if (m_autoplayStart == clock_t::time_point{}) + m_autoplayStart = now; + if (now - m_lastDropTime > AutoplayDropPeriod) + { + m_lastDropTime = now; + m_shouldInitParticles = true; + } + + const double t = std::chrono::duration(now - m_autoplayStart).count(); + const double angle = t * 2.0 * core::PI() / AutoplayOrbitSeconds; + const auto center = hlsl::float64_t3(m_gridData.worldMax.x, m_gridData.worldMax.y * 0.5f, m_gridData.worldMax.z) * 0.5; + const auto eye = center + hlsl::float64_t3(std::cos(angle) * 13.0, 6.0, std::sin(angle) * 13.0); + hlsl::math::quaternion orientation; + if (ext::cameras::CCameraMathUtilities::tryCreateQuaternionFromLookAt(eye, center, hlsl::float64_t3(0.0, 1.0, 0.0), orientation)) + camera = core::make_smart_refctd_ptr(eye, orientation); + } + // in-loop functions void initializeParticles(IGPUCommandBuffer* cmdbuf) @@ -1861,6 +1764,10 @@ class FLIPFluidsApp final : public SimpleWindowedApplication, public BuiltinReso InputSystem::ChannelReader keyboard; core::smart_refctd_ptr camera; + constexpr static inline clock_t::duration AutoplayDropPeriod = std::chrono::seconds(12); + constexpr static inline double AutoplayOrbitSeconds = 40.0; + clock_t::time_point m_autoplayStart = {}; + clock_t::time_point m_lastDropTime = clock_t::now(); ext::cameras::CCameraMouseKeyboardController cameraController; hlsl::float32_t4x4 cameraProjection = hlsl::float32_t4x4(1.0f); video::CDumbPresentationOracle oracle; diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 65d2b0ced..e9b764666 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -108,3 +108,5 @@ set(NBL_EXAMPLES_API_TARGET ${LIB_NAME} PARENT_SCOPE) set(NBL_EXAMPLES_API_LIBRARIES ${TARGETS} PARENT_SCOPE) NBL_ADJUST_FOLDERS(common) + +add_subdirectory(test) diff --git a/common/include/nbl/examples/common/InputSystem.hpp b/common/include/nbl/examples/common/InputSystem.hpp index f99d53932..dc8116aed 100644 --- a/common/include/nbl/examples/common/InputSystem.hpp +++ b/common/include/nbl/examples/common/InputSystem.hpp @@ -55,6 +55,15 @@ class InputSystem : public core::IReferenceCounted { getDefault(m_keyboard,reader); } + // Nonblocking variants, return false and leave the reader alone when no device is connected + bool tryGetDefaultMouse(ChannelReader* reader) + { + return tryGetDefault(m_mouse,reader); + } + bool tryGetDefaultKeyboard(ChannelReader* reader) + { + return tryGetDefault(m_keyboard,reader); + } template void add(Channels& channels, core::smart_refctd_ptr&& channel) { @@ -83,6 +92,35 @@ class InputSystem : public core::IReferenceCounted } template void getDefault(Channels& channels, ChannelReader* reader) + { + std::unique_lock lock(channels.lock); + while (channels.channels.empty()) + { + m_logger.log("Waiting For Input Device to be connected...",system::ILogger::ELL_INFO); + channels.added.wait(lock); + } + selectDefault(channels,reader); + } + // Checks for a device and binds the reader under the same lock, so a disconnect can't slip in between. + // The reader holds a reference, so a channel removed afterwards stays valid until the next selection. + template + bool tryGetDefault(Channels& channels, ChannelReader* reader) + { + std::unique_lock lock(channels.lock); + if (channels.channels.empty()) + return false; + selectDefault(channels,reader); + return true; + } + + system::logger_opt_smart_ptr m_logger; + Channels m_mouse; + Channels m_keyboard; + + private: + // `channels.lock` must be held and `channels.channels` must not be empty + template + void selectDefault(Channels& channels, ChannelReader* reader) { /* * TODO: Improve default device switching. @@ -93,13 +131,6 @@ class InputSystem : public core::IReferenceCounted * Switch the channel choice, but prune away all events younger than the old default's consumption timestamp. * (Basically switch keyboards but dont try to process events older than the events you've processed from the old keyboard) */ - - std::unique_lock lock(channels.lock); - while (channels.channels.empty()) - { - m_logger.log("Waiting For Input Device to be connected...",system::ILogger::ELL_INFO); - channels.added.wait(lock); - } using namespace std::chrono; constexpr long long DefaultChannelTimeoutInMicroSeconds = 100*1e3; // 100 mili-seconds @@ -179,10 +210,6 @@ class InputSystem : public core::IReferenceCounted // no `size - capacity` underflow on a channel younger than the ring. reader->consumedCounter = defaultChannel->getEvents().size(); } - - system::logger_opt_smart_ptr m_logger; - Channels m_mouse; - Channels m_keyboard; }; } diff --git a/common/test/CInputSystemTest.cpp b/common/test/CInputSystemTest.cpp new file mode 100644 index 000000000..d45887b90 --- /dev/null +++ b/common/test/CInputSystemTest.cpp @@ -0,0 +1,164 @@ +// Regression test for `InputSystem::tryGetDefault*`: it must never block, whether devices are absent, connected, +// or disconnected while a frame is acquiring them. The blocking `getDefault*` must keep waiting for a device. +// Exit codes: 0 pass, 1 failed check, 2 timed out (blocked). +#include "nabla.h" +#include "nbl/examples/common/InputSystem.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace nbl; +using namespace nbl::ui; +using namespace nbl::examples; + +namespace +{ +constexpr auto Timeout = std::chrono::seconds(10); +constexpr size_t ChannelCapacity = 64u; + +std::atomic_bool g_failed = false; +#define NBL_CHECK(COND) if (!(COND)) { std::fprintf(stderr,"FAILED: %s (%s:%d)\n",#COND,__FILE__,__LINE__); g_failed = true; } + +// Runs a case on its own thread, a case still running after `Timeout` is blocked and can't be joined +template +void runBounded(const char* name, F&& testCase) +{ + std::packaged_task task(std::forward(testCase)); + auto done = task.get_future(); + std::thread(std::move(task)).detach(); + if (done.wait_for(Timeout)!=std::future_status::ready) + { + std::fprintf(stderr,"TIMED OUT: %s blocked for more than %lld s\n",name,static_cast(Timeout.count())); + std::fflush(stderr); + std::_Exit(2); + } + done.get(); + std::printf("%s: done\n",name); +} + +auto makeMouse() { return core::make_smart_refctd_ptr(ChannelCapacity); } +auto makeKeyboard() { return core::make_smart_refctd_ptr(ChannelCapacity); } +auto makeInputSystem() { return core::make_smart_refctd_ptr(system::logger_opt_smart_ptr(nullptr)); } +} + +int main() +{ + runBounded("absent devices",[]() -> void + { + auto input = makeInputSystem(); + InputSystem::ChannelReader mouse; + InputSystem::ChannelReader keyboard; + NBL_CHECK(!input->tryGetDefaultMouse(&mouse)); + NBL_CHECK(!input->tryGetDefaultKeyboard(&keyboard)); + NBL_CHECK(!mouse.channel && !keyboard.channel); + }); + + runBounded("connected devices",[]() -> void + { + auto input = makeInputSystem(); + auto mouseChannel = makeMouse(); + auto keyboardChannel = makeKeyboard(); + input->add(input->m_mouse,core::smart_refctd_ptr(mouseChannel)); + input->add(input->m_keyboard,core::smart_refctd_ptr(keyboardChannel)); + + InputSystem::ChannelReader mouse; + InputSystem::ChannelReader keyboard; + NBL_CHECK(input->tryGetDefaultMouse(&mouse) && input->tryGetDefaultKeyboard(&keyboard)); + NBL_CHECK(mouse.channel==mouseChannel && keyboard.channel==keyboardChannel); + // a repeat selection of the same channel keeps the reader's position + mouse.consumedCounter = 7u; + NBL_CHECK(input->tryGetDefaultMouse(&mouse) && mouse.consumedCounter==7u); + }); + + runBounded("disconnect after acquisition",[]() -> void + { + auto input = makeInputSystem(); + input->add(input->m_mouse,makeMouse()); + InputSystem::ChannelReader mouse; + NBL_CHECK(input->tryGetDefaultMouse(&mouse)); + const IMouseEventChannel* const acquired = mouse.channel.get(); + + // the last mouse goes away, the next frame must fall back instead of waiting + input->remove(input->m_mouse,acquired); + NBL_CHECK(!input->tryGetDefaultMouse(&mouse)); + // the reader's reference keeps the removed channel alive and readable + NBL_CHECK(mouse.channel.get()==acquired && mouse.channel->getEvents().size()==0u); + + // reconnecting binds the new device + auto reconnected = makeMouse(); + input->add(input->m_mouse,core::smart_refctd_ptr(reconnected)); + NBL_CHECK(input->tryGetDefaultMouse(&mouse) && mouse.channel==reconnected); + }); + + // The NAB-16 interleaving: the event thread drops the last mouse while the frame acquires it, and nothing + // reconnects until that frame is done. The old check-then-`getDefault` sequence waits forever when it loses. + runBounded("disconnect during acquisition",[]() -> void + { + constexpr uint32_t Rounds = 20000u; + auto input = makeInputSystem(); + input->add(input->m_keyboard,makeKeyboard()); + + std::atomic_uint32_t go = 0u, removed = 0u; + std::atomic pending = nullptr; + std::thread eventThread([&]() -> void + { + for (uint32_t round=1u; round<=Rounds; round++) + { + while (go.load()!=round) + std::this_thread::yield(); + input->remove(input->m_mouse,pending.load()); + removed = round; + } + }); + + uint32_t withInput = 0u; + InputSystem::ChannelReader mouse; + InputSystem::ChannelReader keyboard; + for (uint32_t round=1u; round<=Rounds; round++) + { + auto channel = makeMouse(); + pending = channel.get(); + input->add(input->m_mouse,std::move(channel)); + go = round; + // sweep the interleaving, so the disconnect lands before, during and after the acquisition + for (volatile uint32_t spin=(round*2654435761u)%2048u; spin; spin--) {} + const bool hasInput = input->tryGetDefaultMouse(&mouse) && input->tryGetDefaultKeyboard(&keyboard); + if (hasInput) + { + withInput++; + NBL_CHECK(mouse.channel && keyboard.channel); + } + while (removed.load()!=round) + std::this_thread::yield(); + NBL_CHECK(!input->tryGetDefaultMouse(&mouse)); + } + eventThread.join(); + std::printf(" %u rounds, %u acquired before the disconnect\n",Rounds,withInput); + }); + + runBounded("blocking getDefault still waits",[]() -> void + { + auto input = makeInputSystem(); + auto mouseChannel = makeMouse(); + std::atomic_bool added = false; + std::thread connector([&]() -> void + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + added = true; + input->add(input->m_mouse,core::smart_refctd_ptr(mouseChannel)); + }); + InputSystem::ChannelReader mouse; + input->getDefaultMouse(&mouse); + NBL_CHECK(added && mouse.channel==mouseChannel); + connector.join(); + }); + + if (g_failed) + return 1; + std::printf("PASSED\n"); + return 0; +} diff --git a/common/test/CMakeLists.txt b/common/test/CMakeLists.txt new file mode 100644 index 000000000..d2b5bec47 --- /dev/null +++ b/common/test/CMakeLists.txt @@ -0,0 +1,19 @@ +# Regression test for `InputSystem::tryGetDefault*`, needs neither a GPU nor a display. +# Run with `ctest --test-dir /examples_tests/common/test`. +enable_testing() + +add_executable(nbl_examples_input_system_test CInputSystemTest.cpp) +target_link_libraries(nbl_examples_input_system_test PRIVATE Nabla) +target_compile_features(nbl_examples_input_system_test PRIVATE cxx_std_20) + +# examples_tests/CMakeLists.txt links every target under it to the examples API and reuses its PCH, +# so build with the same flags as the examples (see `nbl_create_executable_project`) +nbl_adjust_flags(TARGET nbl_examples_input_system_test MAP_RELEASE Release MAP_RELWITHDEBINFO RelWithDebInfo MAP_DEBUG Debug) +nbl_adjust_definitions() +if(NOT WIN32 AND NOT ANDROID AND CMAKE_POSITION_INDEPENDENT_CODE) + set_target_properties(nbl_examples_input_system_test PROPERTIES POSITION_INDEPENDENT_CODE OFF) + target_compile_options(nbl_examples_input_system_test PRIVATE -fPIC) +endif() + +add_test(NAME NBL_EXAMPLES_INPUT_SYSTEM COMMAND $) +set_tests_properties(NBL_EXAMPLES_INPUT_SYSTEM PROPERTIES TIMEOUT 60) From 3ecfd05b90f0e24b9ee42847a25718e62c9ede65 Mon Sep 17 00:00:00 2001 From: Raydelto Hernandez Date: Wed, 14 Oct 2026 00:16:35 -0300 Subject: [PATCH 6/6] NAB-12: Materialize image creation aggregates for GCC 13 (#4) --- .../nbl/examples/common/CSwapchainFramebuffersAndDepth.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/include/nbl/examples/common/CSwapchainFramebuffersAndDepth.hpp b/common/include/nbl/examples/common/CSwapchainFramebuffersAndDepth.hpp index a64886add..48eb4f851 100644 --- a/common/include/nbl/examples/common/CSwapchainFramebuffersAndDepth.hpp +++ b/common/include/nbl/examples/common/CSwapchainFramebuffersAndDepth.hpp @@ -66,7 +66,7 @@ class CSwapchainFramebuffersAndDepth final : public video::CDefaultSwapchainFram const auto depthFormat = m_renderpass->getCreationParameters().depthStencilAttachments[0].format; const auto& sharedParams = getSwapchain()->getCreationParameters().sharedParams; - auto image = device->createImage({ IImage::SCreationParams{ + auto image = device->createImage(IGPUImage::SCreationParams{ IImage::SCreationParams{ .type = IGPUImage::ET_2D, .samples = IGPUImage::ESCF_1_BIT, .format = depthFormat, @@ -78,7 +78,7 @@ class CSwapchainFramebuffersAndDepth final : public video::CDefaultSwapchainFram device->allocate(image->getMemoryReqs(), { image.get() }); - m_depthBuffer = device->createImageView({ + m_depthBuffer = device->createImageView(IGPUImageView::SCreationParams{ .flags = IGPUImageView::ECF_NONE, .subUsages = IGPUImage::EUF_RENDER_ATTACHMENT_BIT, .image = std::move(image),