ImGuiPass.cpp 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <ImGui/ImGuiPass.h>
  9. #include <AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h>
  10. #include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
  11. #include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
  12. #include <AzFramework/Input/Devices/Touch/InputDeviceTouch.h>
  13. #include <AtomCore/Instance/InstanceDatabase.h>
  14. #include <Atom/RHI/CommandList.h>
  15. #include <Atom/RHI.Reflect/InputStreamLayoutBuilder.h>
  16. #include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.h>
  17. #include <Atom/RPI.Public/Image/ImageSystemInterface.h>
  18. #include <Atom/RPI.Public/Image/StreamingImagePool.h>
  19. #include <Atom/RPI.Public/Pass/PassUtils.h>
  20. #include <Atom/RPI.Public/RenderPipeline.h>
  21. #include <Atom/RPI.Public/RPIUtils.h>
  22. #include <Atom/RPI.Public/Scene.h>
  23. #include <Atom_Feature_Traits_Platform.h>
  24. #include <Atom/Feature/ImGui/SystemBus.h>
  25. namespace AZ
  26. {
  27. namespace Render
  28. {
  29. namespace
  30. {
  31. [[maybe_unused]] static const char* PassName = "ImGuiPass";
  32. static const char* ImguiShaderFilePath = "Shaders/imgui/imgui.azshader";
  33. }
  34. class ImguiContextScope
  35. {
  36. public:
  37. explicit ImguiContextScope(ImGuiContext* newContext = nullptr)
  38. : m_savedContext(ImGui::GetCurrentContext())
  39. {
  40. ImGui::SetCurrentContext(newContext);
  41. }
  42. ~ImguiContextScope()
  43. {
  44. ImGui::SetCurrentContext(m_savedContext);
  45. }
  46. private:
  47. ImGuiContext* m_savedContext = nullptr;
  48. };
  49. RPI::Ptr<Render::ImGuiPass> ImGuiPass::Create(const RPI::PassDescriptor& descriptor)
  50. {
  51. return aznew ImGuiPass(descriptor);
  52. }
  53. ImGuiPass::ImGuiPass(const RPI::PassDescriptor& descriptor)
  54. : Base(descriptor)
  55. , AzFramework::InputChannelEventListener(AzFramework::InputChannelEventListener::GetPriorityDebugUI() - 1) // Give ImGui manager priority over the pass
  56. , AzFramework::InputTextEventListener(AzFramework::InputTextEventListener::GetPriorityDebugUI() - 1) // Give ImGui manager priority over the pass
  57. , m_tickHandlerFrameStart(*this)
  58. , m_tickHandlerFrameEnd(*this)
  59. {
  60. const ImGuiPassData* imguiPassData = RPI::PassUtils::GetPassData<ImGuiPassData>(descriptor);
  61. m_requestedAsDefaultImguiPass = imguiPassData != nullptr ? imguiPassData->m_isDefaultImGui : false;
  62. }
  63. ImGuiPass::~ImGuiPass()
  64. {
  65. if (!m_imguiInitialized)
  66. {
  67. return;
  68. }
  69. if (m_isDefaultImGuiPass)
  70. {
  71. ImGuiSystemRequestBus::Broadcast(&ImGuiSystemRequestBus::Events::RemoveDefaultImGuiPass, this);
  72. }
  73. ImGuiContext* contextToRestore = ImGui::GetCurrentContext();
  74. if (contextToRestore == m_imguiContext)
  75. {
  76. contextToRestore = nullptr; // Don't restore this context since it's being deleted.
  77. }
  78. ImGui::SetCurrentContext(m_imguiContext);
  79. ImGui::DestroyContext(m_imguiContext);
  80. m_imguiContext = nullptr;
  81. ImGui::SetCurrentContext(contextToRestore);
  82. AzFramework::InputTextEventListener::BusDisconnect();
  83. AzFramework::InputChannelEventListener::BusDisconnect();
  84. }
  85. ImGuiContext* ImGuiPass::GetContext()
  86. {
  87. return m_imguiContext;
  88. }
  89. void ImGuiPass::RenderImguiDrawData(const ImDrawData& drawData)
  90. {
  91. m_drawData.push_back(drawData);
  92. }
  93. ImGuiPass::TickHandlerFrameStart::TickHandlerFrameStart(ImGuiPass& imGuiPass)
  94. : m_imGuiPass(imGuiPass)
  95. {
  96. TickBus::Handler::BusConnect();
  97. }
  98. int ImGuiPass::TickHandlerFrameStart::GetTickOrder()
  99. {
  100. return AZ::ComponentTickBus::TICK_PRE_RENDER;
  101. }
  102. void ImGuiPass::TickHandlerFrameStart::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
  103. {
  104. auto imguiContextScope = ImguiContextScope(m_imGuiPass.m_imguiContext);
  105. ImGui::NewFrame();
  106. auto& io = ImGui::GetIO();
  107. io.DeltaTime = deltaTime;
  108. }
  109. ImGuiPass::TickHandlerFrameEnd::TickHandlerFrameEnd(ImGuiPass& imGuiPass)
  110. : m_imGuiPass(imGuiPass)
  111. {
  112. TickBus::Handler::BusConnect();
  113. }
  114. int ImGuiPass::TickHandlerFrameEnd::GetTickOrder()
  115. {
  116. // ImGui::NewFrame() must be called (see ImGuiPass::TickHandlerFrameStart::OnTick) after populating
  117. // ImGui::GetIO().NavInputs (see ImGuiPass::OnInputChannelEventFiltered), and paired with a call to
  118. // ImGui::EndFrame() (see ImGuiPass::TickHandlerFrameEnd::OnTick); if this is not called explicitly
  119. // then it will be called from inside ImGui::Render() (see ImGuiPass::SetupFrameGraphDependencies).
  120. //
  121. // ImGui::Render() gets called (indirectly) from OnSystemTick, so we cannot rely on it being paired
  122. // with a matching call to ImGui::NewFrame() that gets called from OnTick, because OnSystemTick and
  123. // OnTick can be called at different frequencies under some circumstances (namely from the editor).
  124. //
  125. // To account for this we must explicitly call ImGui::EndFrame() once a frame from OnTick to ensure
  126. // that every call to ImGui::NewFrame() has been matched with a call to ImGui::EndFrame(), but only
  127. // after ImGui::Render() has had the chance first (if so calling ImGui::EndFrame() again is benign).
  128. //
  129. // Because ImGui::Render() gets called (indirectly) from OnSystemTick, which usually happens at the
  130. // start of every frame, we give TickHandlerFrameEnd::OnTick() the order of TICK_FIRST such that it
  131. // will be called first on the regular tick bus, which is invoked immediately after the system tick.
  132. //
  133. // So while returning TICK_FIRST is incredibly counter-intuitive, hopefully that all explains why.
  134. return AZ::ComponentTickBus::TICK_FIRST;
  135. }
  136. void ImGuiPass::TickHandlerFrameEnd::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
  137. {
  138. auto imguiContextScope = ImguiContextScope(m_imGuiPass.m_imguiContext);
  139. ImGui::EndFrame();
  140. }
  141. bool ImGuiPass::OnInputTextEventFiltered(const AZStd::string& textUTF8)
  142. {
  143. auto imguiContextScope = ImguiContextScope(m_imguiContext);
  144. auto& io = ImGui::GetIO();
  145. io.AddInputCharactersUTF8(textUTF8.c_str());
  146. return io.WantTextInput;
  147. }
  148. bool ImGuiPass::OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel)
  149. {
  150. if (!IsEnabled() || GetRenderPipeline() == nullptr || GetRenderPipeline()->GetScene() == nullptr)
  151. {
  152. return false;
  153. }
  154. auto imguiContextScope = ImguiContextScope(m_imguiContext);
  155. auto& io = ImGui::GetIO();
  156. bool shouldCaptureEvent = false;
  157. // Matches ImGuiKey_ enum
  158. static const AzFramework::InputChannelId ImGuiKeyChannels[] =
  159. {
  160. AzFramework::InputDeviceKeyboard::Key::EditTab, // ImGuiKey_Tab
  161. AzFramework::InputDeviceKeyboard::Key::NavigationArrowLeft, // ImGuiKey_LeftArrow
  162. AzFramework::InputDeviceKeyboard::Key::NavigationArrowRight, // ImGuiKey_RightArrow
  163. AzFramework::InputDeviceKeyboard::Key::NavigationArrowUp, // ImGuiKey_UpArrow
  164. AzFramework::InputDeviceKeyboard::Key::NavigationArrowDown, // ImGuiKey_DownArrow
  165. AzFramework::InputDeviceKeyboard::Key::NavigationPageUp, // ImGuiKey_PageUp
  166. AzFramework::InputDeviceKeyboard::Key::NavigationPageDown, // ImGuiKey_PageDown
  167. AzFramework::InputDeviceKeyboard::Key::NavigationHome, // ImGuiKey_Home
  168. AzFramework::InputDeviceKeyboard::Key::NavigationEnd, // ImGuiKey_End
  169. AzFramework::InputDeviceKeyboard::Key::NavigationInsert, // ImGuiKey_Insert
  170. AzFramework::InputDeviceKeyboard::Key::NavigationDelete, // ImGuiKey_Delete
  171. AzFramework::InputDeviceKeyboard::Key::EditBackspace, // ImGuiKey_Backspace
  172. AzFramework::InputDeviceKeyboard::Key::EditSpace, // ImGuiKey_Space
  173. AzFramework::InputDeviceKeyboard::Key::EditEnter, // ImGuiKey_Enter
  174. AzFramework::InputDeviceKeyboard::Key::Escape, // ImGuiKey_Escape
  175. AzFramework::InputDeviceKeyboard::Key::NumPadEnter, // ImGuiKey_KeyPadEnter
  176. AzFramework::InputDeviceKeyboard::Key::AlphanumericA, // ImGuiKey_A
  177. AzFramework::InputDeviceKeyboard::Key::AlphanumericC, // ImGuiKey_C
  178. AzFramework::InputDeviceKeyboard::Key::AlphanumericV, // ImGuiKey_V
  179. AzFramework::InputDeviceKeyboard::Key::AlphanumericX, // ImGuiKey_X
  180. AzFramework::InputDeviceKeyboard::Key::AlphanumericY, // ImGuiKey_Y
  181. AzFramework::InputDeviceKeyboard::Key::AlphanumericZ // ImGuiKey_Z
  182. };
  183. static_assert(AZ_ARRAY_SIZE(ImGuiKeyChannels) == ImGuiKey_COUNT, "ImGui key input enum does not match input channels array.");
  184. // Matches ImGuiNavInput_ enum
  185. static const AzFramework::InputChannelId ImGuiNavChannels[] =
  186. {
  187. AzFramework::InputDeviceGamepad::Button::A, // ImGuiNavInput_Activate
  188. AzFramework::InputDeviceGamepad::Button::B, // ImGuiNavInput_Cancel
  189. AzFramework::InputDeviceGamepad::Button::Y, // ImGuiNavInput_Input
  190. AzFramework::InputDeviceGamepad::Button::X, // ImGuiNavInput_Menu
  191. AzFramework::InputDeviceGamepad::Button::DL, // ImGuiNavInput_DpadLeft
  192. AzFramework::InputDeviceGamepad::Button::DR, // ImGuiNavInput_DpadRight
  193. AzFramework::InputDeviceGamepad::Button::DU, // ImGuiNavInput_DpadUp
  194. AzFramework::InputDeviceGamepad::Button::DD, // ImGuiNavInput_DpadDown
  195. AzFramework::InputDeviceGamepad::ThumbStickDirection::LL, // ImGuiNavInput_LStickLeft
  196. AzFramework::InputDeviceGamepad::ThumbStickDirection::LR, // ImGuiNavInput_LStickRight
  197. AzFramework::InputDeviceGamepad::ThumbStickDirection::LU, // ImGuiNavInput_LStickUp
  198. AzFramework::InputDeviceGamepad::ThumbStickDirection::LD, // ImGuiNavInput_LStickDown
  199. AzFramework::InputDeviceGamepad::Button::L1, // ImGuiNavInput_FocusPrev
  200. AzFramework::InputDeviceGamepad::Button::R1, // ImGuiNavInput_FocusNext
  201. AzFramework::InputDeviceGamepad::Trigger::L2, // ImGuiNavInput_TweakSlow
  202. AzFramework::InputDeviceGamepad::Trigger::R2, // ImGuiNavInput_TweakFast
  203. };
  204. static_assert(AZ_ARRAY_SIZE(ImGuiNavChannels) == (ImGuiNavInput_InternalStart_), "ImGui nav input enum does not match input channels array.");
  205. const AzFramework::InputChannelId& inputChannelId = inputChannel.GetInputChannelId();
  206. switch (inputChannel.GetState())
  207. {
  208. case AzFramework::InputChannel::State::Began:
  209. case AzFramework::InputChannel::State::Updated: // update the camera rotation
  210. {
  211. /// Mouse Events
  212. if (inputChannelId == AzFramework::InputDeviceMouse::SystemCursorPosition)
  213. {
  214. const auto* position = inputChannel.GetCustomData<AzFramework::InputChannel::PositionData2D>();
  215. AZ_Assert(position, "Expected positiondata2d but found nullptr");
  216. io.MousePos.x = position->m_normalizedPosition.GetX() * static_cast<float>(io.DisplaySize.x);
  217. io.MousePos.y = position->m_normalizedPosition.GetY() * static_cast<float>(io.DisplaySize.y);
  218. shouldCaptureEvent = io.WantCaptureMouse;
  219. }
  220. if (inputChannelId == AzFramework::InputDeviceMouse::Button::Left ||
  221. inputChannelId == AzFramework::InputDeviceTouch::Touch::Index0)
  222. {
  223. io.MouseDown[0] = true;
  224. const auto* position = inputChannel.GetCustomData<AzFramework::InputChannel::PositionData2D>();
  225. AZ_Assert(position, "Expected positiondata2d but found nullptr");
  226. io.MousePos.x = position->m_normalizedPosition.GetX() * static_cast<float>(io.DisplaySize.x);
  227. io.MousePos.y = position->m_normalizedPosition.GetY() * static_cast<float>(io.DisplaySize.y);
  228. shouldCaptureEvent = io.WantCaptureMouse;
  229. }
  230. else if (inputChannelId == AzFramework::InputDeviceMouse::Button::Right)
  231. {
  232. io.MouseDown[1] = true;
  233. shouldCaptureEvent = io.WantCaptureMouse;
  234. }
  235. else if (inputChannelId == AzFramework::InputDeviceMouse::Button::Middle)
  236. {
  237. io.MouseDown[2] = true;
  238. shouldCaptureEvent = io.WantCaptureMouse;
  239. }
  240. else if (inputChannelId == AzFramework::InputDeviceMouse::Movement::Z)
  241. {
  242. const float MouseWheelDeltaScale = 1.0f / 120.0f; // based on WHEEL_DELTA in WinUser.h
  243. m_lastFrameMouseWheel += inputChannel.GetValue() * MouseWheelDeltaScale;
  244. shouldCaptureEvent = io.WantCaptureMouse;
  245. }
  246. /// Keyboard Modifiers
  247. else if (
  248. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierShiftL ||
  249. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierShiftR)
  250. {
  251. io.KeyShift = true;
  252. }
  253. else if (
  254. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierAltL ||
  255. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierAltR)
  256. {
  257. io.KeyAlt = true;
  258. }
  259. else if (
  260. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierCtrlL ||
  261. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierCtrlR)
  262. {
  263. io.KeyCtrl = true;
  264. }
  265. /// Specific Key & Gamepad Events
  266. else
  267. {
  268. for (size_t i = 0; i < AZ_ARRAY_SIZE(ImGuiKeyChannels); ++i)
  269. {
  270. if (inputChannelId == ImGuiKeyChannels[i])
  271. {
  272. io.KeysDown[i] = true;
  273. shouldCaptureEvent = io.WantCaptureKeyboard;
  274. break;
  275. }
  276. }
  277. for (size_t i = 0; i < AZ_ARRAY_SIZE(ImGuiNavChannels); ++i)
  278. {
  279. if (inputChannelId == ImGuiNavChannels[i])
  280. {
  281. io.NavInputs[i] = true;
  282. break;
  283. }
  284. }
  285. }
  286. break;
  287. }
  288. case AzFramework::InputChannel::State::Ended:
  289. {
  290. /// Mouse Events
  291. if (inputChannelId == AzFramework::InputDeviceMouse::Button::Left ||
  292. inputChannelId == AzFramework::InputDeviceTouch::Touch::Index0)
  293. {
  294. io.MouseDown[0] = false;
  295. const auto* position = inputChannel.GetCustomData<AzFramework::InputChannel::PositionData2D>();
  296. AZ_Assert(position, "Expected positiondata2d but found nullptr");
  297. io.MousePos.x = position->m_normalizedPosition.GetX() * static_cast<float>(io.DisplaySize.x);
  298. io.MousePos.y = position->m_normalizedPosition.GetY() * static_cast<float>(io.DisplaySize.y);
  299. shouldCaptureEvent = io.WantCaptureMouse;
  300. }
  301. else if (inputChannelId == AzFramework::InputDeviceMouse::Button::Right)
  302. {
  303. io.MouseDown[1] = false;
  304. shouldCaptureEvent = io.WantCaptureMouse;
  305. }
  306. else if (inputChannelId == AzFramework::InputDeviceMouse::Button::Middle)
  307. {
  308. io.MouseDown[2] = false;
  309. shouldCaptureEvent = io.WantCaptureMouse;
  310. }
  311. /// Keyboard Modifiers
  312. else if (
  313. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierShiftL ||
  314. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierShiftR)
  315. {
  316. io.KeyShift = false;
  317. }
  318. else if (
  319. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierAltL ||
  320. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierAltR)
  321. {
  322. io.KeyAlt = false;
  323. }
  324. else if (
  325. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierCtrlL ||
  326. inputChannelId == AzFramework::InputDeviceKeyboard::Key::ModifierCtrlR)
  327. {
  328. io.KeyCtrl = false;
  329. }
  330. /// Specific Key & Gamepad Events
  331. else
  332. {
  333. for (size_t i = 0; i < AZ_ARRAY_SIZE(ImGuiKeyChannels); ++i)
  334. {
  335. if (inputChannelId == ImGuiKeyChannels[i])
  336. {
  337. io.KeysDown[i] = false;
  338. shouldCaptureEvent = io.WantCaptureKeyboard;
  339. break;
  340. }
  341. }
  342. for (size_t i = 0; i < AZ_ARRAY_SIZE(ImGuiNavChannels); ++i)
  343. {
  344. if (inputChannelId == ImGuiKeyChannels[i])
  345. {
  346. io.NavInputs[i] = false;
  347. break;
  348. }
  349. }
  350. }
  351. break;
  352. }
  353. case AzFramework::InputChannel::State::Idle:
  354. break;
  355. }
  356. return shouldCaptureEvent;
  357. }
  358. void ImGuiPass::InitializeImGui()
  359. {
  360. if (m_requestedAsDefaultImguiPass)
  361. {
  362. m_isDefaultImGuiPass = true;
  363. ImGuiSystemRequestBus::Broadcast(&ImGuiSystemRequestBus::Events::PushDefaultImGuiPass, this);
  364. }
  365. // This ImguiContextScope is just to ensure we set the imgui context to what it was previously at the end of this function
  366. // The nullptr param is irrelevant as the imgui context gets set to the new context in ImGui::CreateContext
  367. auto imguiContextScope = ImguiContextScope(nullptr);
  368. m_imguiContext = ImGui::CreateContext();
  369. ImGui::StyleColorsDark();
  370. auto& io = ImGui::GetIO();
  371. #if defined(AZ_TRAIT_IMGUI_INI_FILENAME)
  372. io.IniFilename = AZ_TRAIT_IMGUI_INI_FILENAME;
  373. #endif
  374. // ImGui IO Setup
  375. {
  376. for (size_t i = 0; i < ImGuiKey_COUNT; ++i)
  377. {
  378. io.KeyMap[static_cast<ImGuiKey_>(i)] = static_cast<int>(i);
  379. }
  380. // Touch input
  381. const AzFramework::InputDevice* inputDevice = nullptr;
  382. AzFramework::InputDeviceRequestBus::EventResult(inputDevice,
  383. AzFramework::InputDeviceTouch::Id,
  384. &AzFramework::InputDeviceRequests::GetInputDevice);
  385. if (inputDevice && inputDevice->IsSupported())
  386. {
  387. io.ConfigFlags |= ImGuiConfigFlags_IsTouchScreen;
  388. }
  389. // Gamepad input
  390. inputDevice = nullptr;
  391. AzFramework::InputDeviceRequestBus::EventResult(inputDevice,
  392. AzFramework::InputDeviceGamepad::IdForIndex0,
  393. &AzFramework::InputDeviceRequests::GetInputDevice);
  394. if (inputDevice && inputDevice->IsSupported())
  395. {
  396. io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
  397. io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
  398. }
  399. // Set initial display size to something reasonable (this will be updated in FramePrepare)
  400. io.DisplaySize.x = 1920;
  401. io.DisplaySize.y = 1080;
  402. }
  403. {
  404. m_shader = RPI::LoadCriticalShader(ImguiShaderFilePath);
  405. m_pipelineState = aznew RPI::PipelineStateForDraw;
  406. m_pipelineState->Init(m_shader);
  407. RHI::InputStreamLayoutBuilder layoutBuilder;
  408. layoutBuilder.AddBuffer()
  409. ->Channel("POSITION", RHI::Format::R32G32_FLOAT)
  410. ->Channel("UV", RHI::Format::R32G32_FLOAT)
  411. ->Channel("COLOR", RHI::Format::R8G8B8A8_UNORM);
  412. layoutBuilder.AddBuffer(RHI::StreamStepFunction::PerInstance)
  413. ->Channel("INSTANCE_DATA", RHI::Format::R32_UINT);
  414. m_pipelineState->InputStreamLayout() = layoutBuilder.End();
  415. }
  416. // Get shader resource group
  417. {
  418. auto perPassSrgLayout = m_shader->FindShaderResourceGroupLayout(RPI::SrgBindingSlot::Pass);
  419. if (!perPassSrgLayout)
  420. {
  421. AZ_Error(PassName, false, "Failed to get shader resource group layout");
  422. return;
  423. }
  424. m_resourceGroup = RPI::ShaderResourceGroup::Create(m_shader->GetAsset(), m_shader->GetSupervariantIndex(), perPassSrgLayout->GetName());
  425. if (!m_resourceGroup)
  426. {
  427. AZ_Error(PassName, false, "Failed to create shader resource group");
  428. return;
  429. }
  430. }
  431. // Find or create font atlas
  432. const char* FontAtlasName = "ImGuiFontAtlas";
  433. m_fontAtlas = Data::InstanceDatabase<RPI::StreamingImage>::Instance().Find(Data::InstanceId::CreateName(FontAtlasName));
  434. if (!m_fontAtlas)
  435. {
  436. uint8_t* pixels;
  437. int32_t width = 0;
  438. int32_t height = 0;
  439. io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
  440. const uint32_t pixelDataSize = width * height * 4;
  441. RHI::Size imageSize;
  442. imageSize.m_width = aznumeric_cast<uint32_t>(width);
  443. imageSize.m_height = aznumeric_cast<uint32_t>(height);
  444. Data::Instance<RPI::StreamingImagePool> streamingImagePool = RPI::ImageSystemInterface::Get()->GetSystemStreamingPool();
  445. // CreateFromCpuData will add the image to the instance database.
  446. m_fontAtlas = RPI::StreamingImage::CreateFromCpuData(*streamingImagePool, RHI::ImageDimension::Image2D, imageSize, RHI::Format::R8G8B8A8_UNORM_SRGB, pixels, pixelDataSize, Uuid::CreateName(FontAtlasName));
  447. AZ_Error(PassName, m_fontAtlas, "Failed to initialize the ImGui font image!");
  448. }
  449. else
  450. {
  451. // GetTexDataAsRGBA32() sets the font default internally, but if a m_fontAtlas already has been retrieved it needs to be done manually.
  452. io.Fonts->AddFontDefault();
  453. io.Fonts->Build();
  454. }
  455. const uint32_t fontTextureIndex = 0;
  456. m_resourceGroup->SetImage(m_texturesIndex, m_fontAtlas, fontTextureIndex);
  457. io.Fonts->TexID = reinterpret_cast<ImTextureID>(m_fontAtlas.get());
  458. m_imguiFontTexId = io.Fonts->TexID;
  459. // ImGuiPass will support binding 16 textures at most per frame.
  460. const uint32_t instanceData[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
  461. RPI::CommonBufferDescriptor desc;
  462. desc.m_poolType = RPI::CommonBufferPoolType::StaticInputAssembly;
  463. desc.m_bufferName = "InstanceBuffer";
  464. desc.m_elementSize = 4;
  465. desc.m_byteCount = 64;
  466. desc.m_bufferData = instanceData;
  467. m_instanceBuffer = RPI::BufferSystemInterface::Get()->CreateBufferFromCommonPool(desc);
  468. m_instanceBufferView = RHI::StreamBufferView(
  469. *m_instanceBuffer->GetRHIBuffer(),
  470. 0,
  471. aznumeric_cast<uint32_t>(desc.m_byteCount),
  472. aznumeric_cast<uint32_t>(desc.m_elementSize));
  473. ImGui::NewFrame();
  474. AzFramework::InputChannelEventListener::Connect();
  475. AzFramework::InputTextEventListener::Connect();
  476. }
  477. void ImGuiPass::InitializeInternal()
  478. {
  479. Base::InitializeInternal();
  480. if (!m_imguiInitialized)
  481. {
  482. InitializeImGui();
  483. m_imguiInitialized = true;
  484. }
  485. // Set output format and finalize pipeline state
  486. m_pipelineState->SetOutputFromPass(this);
  487. m_pipelineState->Finalize();
  488. }
  489. void ImGuiPass::FrameBeginInternal(FramePrepareParams params)
  490. {
  491. auto imguiContextScope = ImguiContextScope(m_imguiContext);
  492. m_viewportWidth = static_cast<uint32_t>(params.m_viewportState.m_maxX - params.m_viewportState.m_minX);
  493. m_viewportHeight = static_cast<uint32_t>(params.m_viewportState.m_maxY - params.m_viewportState.m_minY);
  494. auto& io = ImGui::GetIO();
  495. io.DisplaySize.x = AZStd::max<float>(1.0f, static_cast<float>(m_viewportWidth));
  496. io.DisplaySize.y = AZStd::max<float>(1.0f, static_cast<float>(m_viewportHeight));
  497. Matrix4x4 projectionMatrix =
  498. Matrix4x4::CreateFromRows(
  499. AZ::Vector4(2.0f / m_viewportWidth, 0.0f, 0.0f, -1.0f),
  500. AZ::Vector4(0.0f, -2.0f / m_viewportHeight, 0.0f, 1.0f),
  501. AZ::Vector4(0.0f, 0.0f, 0.5f, 0.5f),
  502. AZ::Vector4(0.0f, 0.0f, 0.0f, 1.0f));
  503. m_resourceGroup->SetConstant(m_projectionMatrixIndex, projectionMatrix);
  504. m_viewportState = params.m_viewportState;
  505. Base::FrameBeginInternal(params);
  506. }
  507. void ImGuiPass::SetupFrameGraphDependencies(RHI::FrameGraphInterface frameGraph)
  508. {
  509. Base::SetupFrameGraphDependencies(frameGraph);
  510. auto imguiContextScope = ImguiContextScope(m_imguiContext);
  511. ImGui::Render();
  512. uint32_t drawCount = UpdateImGuiResources();
  513. frameGraph.SetEstimatedItemCount(drawCount);
  514. m_drawInfos.NextBuffer();
  515. m_drawInfos.Get().clear();
  516. m_drawInfos.Get().reserve(drawCount);
  517. }
  518. void ImGuiPass::CompileResources([[maybe_unused]] const RHI::FrameGraphCompileContext& context)
  519. {
  520. // Create all the DrawIndexeds so they can be submitted in parallel on BuildCommandListInternal()
  521. uint32_t vertexOffset = 0;
  522. uint32_t indexOffset = 0;
  523. m_userTextures.clear();
  524. for (ImDrawData& drawData : m_drawData)
  525. {
  526. for (int32_t cmdListIdx = 0; cmdListIdx < drawData.CmdListsCount; cmdListIdx++)
  527. {
  528. const ImDrawList* drawList = drawData.CmdLists[cmdListIdx];
  529. for (const ImDrawCmd& drawCmd : drawList->CmdBuffer)
  530. {
  531. AZ_Assert(drawCmd.UserCallback == nullptr, "ImGui UserCallbacks are not supported by the ImGui Pass");
  532. uint32_t scissorMaxX = static_cast<uint32_t>(drawCmd.ClipRect.z);
  533. uint32_t scissorMaxY = static_cast<uint32_t>(drawCmd.ClipRect.w);
  534. //scissorMaxX/scissorMaxY can be a frame stale from imgui (ImGui::NewFrame runs after this) hence we clamp it to viewport bounds
  535. //otherwise it is possible to have a frame where scissor bounds can be bigger than window's bounds if we resize the window
  536. scissorMaxX = AZStd::min(scissorMaxX, m_viewportWidth);
  537. scissorMaxY = AZStd::min(scissorMaxY, m_viewportHeight);
  538. // check if texture id needs to be bound to the srg
  539. uint32_t index = 0;
  540. if (drawCmd.TextureId && drawCmd.TextureId != m_imguiFontTexId)
  541. {
  542. if (m_userTextures.size() < MaxUserTextures)
  543. {
  544. Data::Instance<RPI::StreamingImage> img = reinterpret_cast<RPI::StreamingImage*>(drawCmd.TextureId);
  545. // Texture index 0 is reserved for the font atlas, so we start from 1 to 15 for user textures.
  546. index = aznumeric_cast<uint32_t>(m_userTextures.size() + 1);
  547. m_userTextures[img.get()] = index;
  548. }
  549. else
  550. {
  551. AZ_Warning("ImGuiPass", false, "The maximum number of textures ImGui can render per frame is %d", MaxUserTextures);
  552. }
  553. }
  554. RHI::GeometryView geometryView;
  555. geometryView.SetDrawArguments(RHI::DrawIndexed(vertexOffset, drawCmd.ElemCount, indexOffset));
  556. geometryView.SetIndexBufferView(m_indexBufferView);
  557. geometryView.AddStreamBufferView(m_vertexBufferView[0]);
  558. geometryView.AddStreamBufferView(m_vertexBufferView[1]);
  559. m_drawInfos.Get().push_back(
  560. {
  561. RHI::DrawInstanceArguments(1, index),
  562. geometryView,
  563. RHI::Scissor(
  564. static_cast<int32_t>(drawCmd.ClipRect.x),
  565. static_cast<int32_t>(drawCmd.ClipRect.y),
  566. scissorMaxX,
  567. scissorMaxY
  568. )
  569. }
  570. );
  571. indexOffset += drawCmd.ElemCount;
  572. }
  573. vertexOffset += drawList->VtxBuffer.size();
  574. }
  575. }
  576. m_drawData.clear();
  577. auto imguiContextScope = ImguiContextScope(m_imguiContext);
  578. ImGui::GetIO().MouseWheel = m_lastFrameMouseWheel;
  579. m_lastFrameMouseWheel = 0.0;
  580. for (auto& [streamingImage, index] : m_userTextures)
  581. {
  582. m_resourceGroup->SetImage(m_texturesIndex, streamingImage, index);
  583. }
  584. m_resourceGroup->Compile();
  585. }
  586. void ImGuiPass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context)
  587. {
  588. AZ_PROFILE_SCOPE(AzRender, "ImGuiPass: BuildCommandListInternal");
  589. context.GetCommandList()->SetViewport(m_viewportState);
  590. context.GetCommandList()->SetShaderResourceGroupForDraw(*m_resourceGroup->GetRHIShaderResourceGroup()->GetDeviceShaderResourceGroup(context.GetDeviceIndex()));
  591. for (uint32_t i = context.GetSubmitRange().m_startIndex; i < context.GetSubmitRange().m_endIndex; ++i)
  592. {
  593. DrawInfo& drawInfo = m_drawInfos.Get().at(i);
  594. RHI::DeviceDrawItem drawItem;
  595. drawItem.m_drawInstanceArgs = drawInfo.m_drawInstanceArgs;
  596. drawItem.m_geometryView = drawInfo.m_geometryView.GetDeviceGeometryView(context.GetDeviceIndex());
  597. drawItem.m_streamIndices = drawInfo.m_geometryView.GetFullStreamBufferIndices();
  598. drawItem.m_pipelineState = m_pipelineState->GetRHIPipelineState()->GetDevicePipelineState(context.GetDeviceIndex()).get();
  599. drawItem.m_scissorsCount = 1;
  600. drawItem.m_scissors = &drawInfo.m_scissor;
  601. context.GetCommandList()->Submit(drawItem, i);
  602. }
  603. }
  604. uint32_t ImGuiPass::UpdateImGuiResources()
  605. {
  606. AZ_PROFILE_SCOPE(AzRender, "ImGuiPass: UpdateImGuiResources");
  607. auto imguiContextScope = ImguiContextScope(m_imguiContext);
  608. constexpr uint32_t indexSize = aznumeric_cast<uint32_t>(sizeof(ImDrawIdx));
  609. constexpr uint32_t vertexSize = aznumeric_cast<uint32_t>(sizeof(ImDrawVert));
  610. if (ImGui::GetDrawData())
  611. {
  612. m_drawData.push_back(*ImGui::GetDrawData());
  613. }
  614. uint32_t totalIdxBufferSize = 0;
  615. uint32_t totalVtxBufferSize = 0;
  616. for (ImDrawData& drawData : m_drawData)
  617. {
  618. totalIdxBufferSize += drawData.TotalIdxCount * indexSize;
  619. totalVtxBufferSize += drawData.TotalVtxCount * vertexSize;
  620. }
  621. if (totalIdxBufferSize == 0)
  622. {
  623. return 0; // Nothing to draw.
  624. }
  625. auto vertexBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(totalVtxBufferSize, RHI::Alignment::InputAssembly);
  626. auto indexBuffer = RPI::DynamicDrawInterface::Get()->GetDynamicBuffer(totalIdxBufferSize, RHI::Alignment::InputAssembly);
  627. if (!vertexBuffer || !indexBuffer)
  628. {
  629. return 0;
  630. }
  631. auto indexBufferDataAddress = indexBuffer->GetBufferAddress();
  632. auto vertexBufferDataAddress = vertexBuffer->GetBufferAddress();
  633. uint32_t drawCount = 0;
  634. uint32_t indexBufferOffset = 0;
  635. uint32_t vertexBufferOffset = 0;
  636. for (ImDrawData& drawData : m_drawData)
  637. {
  638. for (int32_t cmdListIndex = 0; cmdListIndex < drawData.CmdListsCount; cmdListIndex++)
  639. {
  640. const ImDrawList* drawList = drawData.CmdLists[cmdListIndex];
  641. const uint32_t indexBufferByteSize = drawList->IdxBuffer.size() * indexSize;
  642. for(auto [deviceIndex, indexBufferData] : indexBufferDataAddress)
  643. {
  644. memcpy(static_cast<ImDrawIdx*>(indexBufferData) + indexBufferOffset, drawList->IdxBuffer.Data, indexBufferByteSize);
  645. }
  646. indexBufferOffset += drawList->IdxBuffer.size();
  647. const uint32_t vertexBufferByteSize = drawList->VtxBuffer.size() * vertexSize;
  648. for(auto [deviceIndex, vertexBufferData] : vertexBufferDataAddress)
  649. {
  650. memcpy(static_cast<ImDrawVert*>(vertexBufferData) + vertexBufferOffset, drawList->VtxBuffer.Data, vertexBufferByteSize);
  651. }
  652. vertexBufferOffset += drawList->VtxBuffer.size();
  653. drawCount += drawList->CmdBuffer.size();
  654. }
  655. }
  656. static_assert(indexSize == 2, "Expected index size from ImGui to be 2 to match RHI::IndexFormat::Uint16");
  657. m_indexBufferView = indexBuffer->GetIndexBufferView(RHI::IndexFormat::Uint16);
  658. m_vertexBufferView[0] = vertexBuffer->GetStreamBufferView(vertexSize);
  659. m_vertexBufferView[1] = m_instanceBufferView;
  660. RHI::ValidateStreamBufferViews(m_pipelineState->ConstDescriptor().m_inputStreamLayout, m_vertexBufferView);
  661. return drawCount;
  662. }
  663. } // namespace Render
  664. } // namespace AZ