BsGUIManager.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. #include "BsGUIManager.h"
  2. #include "BsGUIWidget.h"
  3. #include "BsGUIElement.h"
  4. #include "BsGUIMouseEvent.h"
  5. #include "CmSceneObject.h"
  6. #include "CmMaterial.h"
  7. #include "CmMeshData.h"
  8. #include "CmMesh.h"
  9. #include "CmUtil.h"
  10. #include "CmRenderWindowManager.h"
  11. #include "CmCursor.h"
  12. #include "CmRect.h"
  13. #include "CmApplication.h"
  14. #include "CmException.h"
  15. #include "CmInput.h"
  16. #include "CmPass.h"
  17. using namespace CamelotFramework;
  18. namespace BansheeEngine
  19. {
  20. struct GUIGroupElement
  21. {
  22. GUIGroupElement()
  23. { }
  24. GUIGroupElement(GUIElement* _element, UINT32 _renderElement)
  25. :element(_element), renderElement(_renderElement)
  26. { }
  27. GUIElement* element;
  28. UINT32 renderElement;
  29. };
  30. struct GUIMaterialGroup
  31. {
  32. HMaterial material;
  33. UINT32 numQuads;
  34. UINT32 depth;
  35. Rect bounds;
  36. std::vector<GUIGroupElement> elements;
  37. };
  38. GUIManager::GUIManager()
  39. :mMouseOverElement(nullptr), mMouseOverWidget(nullptr), mSeparateMeshesByWidget(true)
  40. {
  41. for(int i = 0; i < MB_Count; i++)
  42. mLastFrameButtonState[i] = false;
  43. }
  44. GUIManager::~GUIManager()
  45. {
  46. }
  47. void GUIManager::registerWidget(GUIWidget* widget)
  48. {
  49. mWidgets.push_back(widget);
  50. const Viewport* renderTarget = widget->getTarget();
  51. auto findIter = mCachedGUIData.find(renderTarget);
  52. if(findIter == end(mCachedGUIData))
  53. mCachedGUIData[renderTarget] = GUIRenderData();
  54. GUIRenderData& windowData = mCachedGUIData[renderTarget];
  55. windowData.widgets.push_back(widget);
  56. windowData.isDirty = true;
  57. }
  58. void GUIManager::unregisterWidget(GUIWidget* widget)
  59. {
  60. auto findIter = std::find(begin(mWidgets), end(mWidgets), widget);
  61. if(findIter != end(mWidgets))
  62. mWidgets.erase(findIter);
  63. if(mMouseOverWidget == widget)
  64. {
  65. mMouseOverWidget = nullptr;
  66. mMouseOverElement = nullptr;
  67. }
  68. const Viewport* renderTarget = widget->getTarget();
  69. GUIRenderData& renderData = mCachedGUIData[renderTarget];
  70. findIter = std::find(begin(renderData.widgets), end(renderData.widgets), widget);
  71. if(findIter != end(renderData.widgets))
  72. renderData.widgets.erase(findIter);
  73. if(renderData.widgets.size() == 0)
  74. mCachedGUIData.erase(renderTarget);
  75. else
  76. renderData.isDirty = true;
  77. }
  78. void GUIManager::update()
  79. {
  80. // Update layouts
  81. for(auto& widget : mWidgets)
  82. {
  83. widget->_updateLayout();
  84. }
  85. updateMeshes();
  86. updateInput();
  87. }
  88. void GUIManager::render(ViewportPtr& target, RenderContext& renderContext)
  89. {
  90. auto findIter = mCachedGUIData.find(target.get());
  91. if(findIter == mCachedGUIData.end())
  92. return;
  93. renderContext.setViewport(target);
  94. GUIRenderData& renderData = findIter->second;
  95. // Render the meshes
  96. if(mSeparateMeshesByWidget)
  97. {
  98. UINT32 meshIdx = 0;
  99. for(auto& mesh : renderData.cachedMeshes)
  100. {
  101. HMaterial material = renderData.cachedMaterials[meshIdx];
  102. GUIWidget* widget = renderData.cachedWidgetsPerMesh[meshIdx];
  103. // TODO - Possible optimization. I currently divide by width/height inside the shader, while it
  104. // might be more optimal to just scale the mesh as the resolution changes?
  105. float invViewportWidth = 1.0f / (target->getWidth() * 0.5f);
  106. float invViewportHeight = 1.0f / (target->getHeight() * 0.5f);
  107. material->setFloat("invViewportWidth", invViewportWidth);
  108. material->setFloat("invViewportHeight", invViewportHeight);
  109. material->setMat4("worldTransform", widget->SO()->getWorldTfrm());
  110. if(material == nullptr || !material.isLoaded())
  111. continue;
  112. if(mesh == nullptr || !mesh.isLoaded())
  113. continue;
  114. for(UINT32 i = 0; i < material->getNumPasses(); i++)
  115. {
  116. PassPtr pass = material->getPass(i);
  117. pass->activate(renderContext);
  118. PassParametersPtr paramsPtr = material->getPassParameters(i);
  119. pass->bindParameters(renderContext, paramsPtr);
  120. renderContext.render(mesh->getRenderOperation());
  121. }
  122. meshIdx++;
  123. }
  124. }
  125. else
  126. {
  127. // TODO: I want to avoid separating meshes by widget in the future. On DX11 and GL I can set up a shader
  128. // that accepts multiple world transforms (one for each widget). Then I can add some instance information to vertices
  129. // and render elements using multiple different transforms with a single call.
  130. // Separating meshes can then be used as a compatibility mode for DX9
  131. CM_EXCEPT(NotImplementedException, "Not implemented");
  132. }
  133. }
  134. void GUIManager::updateMeshes()
  135. {
  136. for(auto& cachedMeshData : mCachedGUIData)
  137. {
  138. GUIRenderData& renderData = cachedMeshData.second;
  139. // Check if anything is dirty. If nothing is we can skip the update
  140. bool isDirty = renderData.isDirty;
  141. renderData.isDirty = false;
  142. for(auto& widget : renderData.widgets)
  143. {
  144. if(widget->isDirty(true))
  145. {
  146. isDirty = true;
  147. }
  148. }
  149. if(!isDirty)
  150. continue;
  151. // Make a list of all GUI elements, sorted from farthest to nearest (highest depth to lowest)
  152. auto elemComp = [](GUIElement* a, GUIElement* b)
  153. {
  154. return a->_getDepth() > b->_getDepth() || (a->_getDepth() == b->_getDepth() && a > b);
  155. // Compare pointers just to differentiate between two elements with the same depth, their order doesn't really matter, but std::set
  156. // requires all elements to be unique
  157. };
  158. std::set<GUIElement*, std::function<bool(GUIElement*, GUIElement*)>> allElements(elemComp);
  159. for(auto& widget : renderData.widgets)
  160. {
  161. const std::vector<GUIElement*>& elements = widget->getElements();
  162. for(auto& element : elements)
  163. allElements.insert(element);
  164. }
  165. // Group the elements in such a way so that we end up with a smallest amount of
  166. // meshes, without breaking back to front rendering order
  167. std::unordered_map<UINT64, std::vector<GUIMaterialGroup>> materialGroups;
  168. for(auto& elem : allElements)
  169. {
  170. Rect tfrmedBounds = elem->_getBounds();
  171. tfrmedBounds.transform(elem->_getParentWidget().SO()->getWorldTfrm());
  172. UINT32 numRenderElems = elem->getNumRenderElements();
  173. for(UINT32 i = 0; i < numRenderElems; i++)
  174. {
  175. const HMaterial& mat = elem->getMaterial(i);
  176. UINT64 materialId = mat->getInternalID(); // TODO - I group based on material ID. So if two widgets used exact copies of the same material
  177. // this system won't detect it. Find a better way of determining material similarity?
  178. // If this is a new material, add a new list of groups
  179. auto findIterMaterial = materialGroups.find(materialId);
  180. if(findIterMaterial == end(materialGroups))
  181. materialGroups[materialId] = std::vector<GUIMaterialGroup>();
  182. // Try to find a group this material will fit in:
  183. // - Group that has a depth value same or one below elements depth will always be a match
  184. // - Otherwise, we search higher depth values as well, but we only use them if no elements in between those depth values
  185. // overlap the current elements bounds.
  186. std::vector<GUIMaterialGroup>& allGroups = materialGroups[materialId];
  187. GUIMaterialGroup* foundGroup = nullptr;
  188. for(auto groupIter = allGroups.rbegin(); groupIter != allGroups.rend(); ++groupIter)
  189. {
  190. // If we separate meshes by widget, ignore any groups with widget parents other than mine
  191. if(mSeparateMeshesByWidget)
  192. {
  193. if(groupIter->elements.size() > 0)
  194. {
  195. GUIElement* otherElem = groupIter->elements.begin()->element; // We only need to check the first element
  196. if(&otherElem->_getParentWidget() != &elem->_getParentWidget())
  197. continue;
  198. }
  199. }
  200. GUIMaterialGroup& group = *groupIter;
  201. if(group.depth == elem->_getDepth() || group.depth == (elem->_getDepth() - 1))
  202. {
  203. foundGroup = &group;
  204. break;
  205. }
  206. else
  207. {
  208. UINT32 startDepth = elem->_getDepth();
  209. UINT32 endDepth = group.depth;
  210. bool foundOverlap = false;
  211. for(auto& material : materialGroups)
  212. {
  213. for(auto& group : material.second)
  214. {
  215. if(group.depth > startDepth && group.depth < endDepth)
  216. {
  217. if(group.bounds.overlaps(tfrmedBounds))
  218. {
  219. foundOverlap = true;
  220. break;
  221. }
  222. }
  223. }
  224. }
  225. if(!foundOverlap)
  226. {
  227. foundGroup = &group;
  228. break;
  229. }
  230. }
  231. }
  232. if(foundGroup == nullptr)
  233. {
  234. allGroups.push_back(GUIMaterialGroup());
  235. foundGroup = &allGroups[allGroups.size() - 1];
  236. foundGroup->depth = elem->_getDepth();
  237. foundGroup->bounds = tfrmedBounds;
  238. foundGroup->elements.push_back(GUIGroupElement(elem, i));
  239. foundGroup->material = mat;
  240. foundGroup->numQuads = elem->getNumQuads(i);
  241. }
  242. else
  243. {
  244. foundGroup->bounds.encapsulate(tfrmedBounds);
  245. foundGroup->elements.push_back(GUIGroupElement(elem, i));
  246. foundGroup->numQuads += elem->getNumQuads(i);
  247. }
  248. }
  249. }
  250. // Make a list of all GUI elements, sorted from farthest to nearest (highest depth to lowest)
  251. auto groupComp = [](GUIMaterialGroup* a, GUIMaterialGroup* b)
  252. {
  253. return (a->depth > b->depth) || (a->depth == b->depth && a > b);
  254. // Compare pointers just to differentiate between two elements with the same depth, their order doesn't really matter, but std::set
  255. // requires all elements to be unique
  256. };
  257. std::set<GUIMaterialGroup*, std::function<bool(GUIMaterialGroup*, GUIMaterialGroup*)>> sortedGroups(groupComp);
  258. for(auto& material : materialGroups)
  259. {
  260. for(auto& group : material.second)
  261. {
  262. sortedGroups.insert(&group);
  263. }
  264. }
  265. UINT32 numMeshes = (UINT32)sortedGroups.size();
  266. UINT32 oldNumMeshes = (UINT32)renderData.cachedMeshes.size();
  267. if(numMeshes < oldNumMeshes)
  268. renderData.cachedMeshes.resize(numMeshes);
  269. renderData.cachedMaterials.resize(numMeshes);
  270. if(mSeparateMeshesByWidget)
  271. renderData.cachedWidgetsPerMesh.resize(numMeshes);
  272. // Fill buffers for each group and update their meshes
  273. UINT32 groupIdx = 0;
  274. for(auto& group : sortedGroups)
  275. {
  276. renderData.cachedMaterials[groupIdx] = group->material;
  277. if(mSeparateMeshesByWidget)
  278. {
  279. if(group->elements.size() == 0)
  280. renderData.cachedWidgetsPerMesh[groupIdx] = nullptr;
  281. else
  282. {
  283. GUIElement* elem = group->elements.begin()->element;
  284. renderData.cachedWidgetsPerMesh[groupIdx] = &elem->_getParentWidget();
  285. }
  286. }
  287. MeshDataPtr meshData = std::shared_ptr<MeshData>(CM_NEW(MeshData, PoolAlloc) MeshData(group->numQuads * 4),
  288. &MemAllocDeleter<MeshData, PoolAlloc>::deleter);
  289. meshData->beginDesc();
  290. meshData->addVertElem(VET_FLOAT2, VES_POSITION);
  291. meshData->addVertElem(VET_FLOAT2, VES_TEXCOORD);
  292. meshData->addSubMesh(group->numQuads * 6);
  293. meshData->endDesc();
  294. UINT8* vertices = meshData->getElementData(VES_POSITION);
  295. UINT8* uvs = meshData->getElementData(VES_TEXCOORD);
  296. UINT32* indices = meshData->getIndices32();
  297. UINT32 vertexStride = meshData->getVertexStride();
  298. UINT32 indexStride = meshData->getIndexElementSize();
  299. UINT32 quadOffset = 0;
  300. for(auto& matElement : group->elements)
  301. {
  302. matElement.element->fillBuffer(vertices, uvs, indices, quadOffset, group->numQuads, vertexStride, indexStride, matElement.renderElement);
  303. UINT32 numQuads = matElement.element->getNumQuads(matElement.renderElement);
  304. UINT32 indexStart = quadOffset * 6;
  305. UINT32 indexEnd = indexStart + numQuads * 6;
  306. UINT32 vertOffset = quadOffset * 4;
  307. for(UINT32 i = indexStart; i < indexEnd; i++)
  308. indices[i] += vertOffset;
  309. quadOffset += numQuads;
  310. }
  311. if(groupIdx >= (UINT32)renderData.cachedMeshes.size())
  312. {
  313. renderData.cachedMeshes.push_back(Mesh::create());
  314. }
  315. gMainSyncedRC().writeSubresource(renderData.cachedMeshes[groupIdx].getInternalPtr(), 0, *meshData);
  316. gMainSyncedRC().submitToGpu(true); // TODO - Remove this once I make writeSubresource accept a shared_ptr for MeshData
  317. groupIdx++;
  318. }
  319. }
  320. }
  321. void GUIManager::updateInput()
  322. {
  323. #if CM_DEBUG_MODE
  324. // Checks if all referenced windows actually exist
  325. std::vector<RenderWindow*> activeWindows = RenderWindowManager::instance().getRenderWindows();
  326. for(auto& widget : mWidgets)
  327. {
  328. auto iterFind = std::find(begin(activeWindows), end(activeWindows), widget->getOwnerWindow());
  329. if(iterFind == activeWindows.end())
  330. {
  331. CM_EXCEPT(InternalErrorException, "GUI manager has a reference to a window that doesn't exist. \
  332. Please detach all GUIWidgets from windows before destroying a window.");
  333. }
  334. }
  335. #endif
  336. GUIWidget* widgetInFocus = nullptr;
  337. GUIElement* topMostElement = nullptr;
  338. UINT32 topMostDepth = std::numeric_limits<UINT32>::max();
  339. for(auto& widget : mWidgets)
  340. {
  341. const RenderWindow* window = widget->getOwnerWindow();
  342. if(window->hasFocus())
  343. {
  344. widgetInFocus = widget;
  345. break;
  346. }
  347. }
  348. Int2 screenPos;
  349. if(widgetInFocus != nullptr)
  350. {
  351. const RenderWindow* window = widgetInFocus->getOwnerWindow();
  352. screenPos = Cursor::getWindowPosition(*window);
  353. Vector4 vecScreenPos((float)screenPos.x, (float)screenPos.y, 0.0f, 1.0f);
  354. GUIElement* topMostElement = nullptr;
  355. INT32 topMostDepth = std::numeric_limits<INT32>::max();
  356. for(auto& widget : mWidgets)
  357. {
  358. if(widget->inBounds(screenPos))
  359. {
  360. const Matrix4& worldTfrm = widget->SO()->getWorldTfrm();
  361. Vector4 vecLocalPos = worldTfrm.inverse() * vecScreenPos;
  362. Int2 localPos(Math::RoundToInt(vecLocalPos.x), Math::RoundToInt(vecLocalPos.y));
  363. std::vector<GUIElement*> sortedElements = widget->getElements();
  364. std::sort(sortedElements.begin(), sortedElements.end(),
  365. [](GUIElement* a, GUIElement* b)
  366. {
  367. return a->_getDepth() < b->_getDepth();
  368. });
  369. // Elements with lowest depth (most to the front) get handled first
  370. for(auto iter = sortedElements.begin(); iter != sortedElements.end(); ++iter)
  371. {
  372. GUIElement* element = *iter;
  373. const Rect& bounds = element->_getBounds();
  374. if(bounds.contains(localPos) && element->_getDepth() < topMostDepth)
  375. {
  376. topMostElement = element;
  377. topMostDepth = element->_getDepth();
  378. break;
  379. }
  380. }
  381. }
  382. }
  383. }
  384. if(topMostElement != mMouseOverElement)
  385. {
  386. if(mMouseOverElement != nullptr)
  387. {
  388. // Send MouseOut event
  389. const RenderWindow* window = mMouseOverWidget->getOwnerWindow();
  390. Int2 oldScreenPos = Cursor::getWindowPosition(*window);
  391. GUIMouseEvent event(GUIMouseEventType::MouseOut, oldScreenPos); // TODO - This pos will be wrong as it might be related to completely other window
  392. mMouseOverElement->mouseEvent(event);
  393. }
  394. if(topMostElement != nullptr)
  395. {
  396. // Send MouseOver event
  397. GUIMouseEvent event(GUIMouseEventType::MouseOver, screenPos);
  398. topMostElement->mouseEvent(event);
  399. }
  400. mMouseOverElement = topMostElement;
  401. mMouseOverWidget = widgetInFocus;
  402. }
  403. if(mMouseOverElement != nullptr)
  404. {
  405. // Send MouseMove event
  406. if(mLastCursorPos != screenPos)
  407. {
  408. GUIMouseEvent event(GUIMouseEventType::MouseMove, screenPos);
  409. mMouseOverElement->mouseEvent(event);
  410. }
  411. // Send MouseDown and MouseUp events
  412. for(int i = 0; i < MB_Count; i++)
  413. {
  414. bool buttonDown = gInput().isButtonDown((MouseButton)i);
  415. if(mLastFrameButtonState[i] != buttonDown)
  416. {
  417. if(buttonDown)
  418. {
  419. GUIMouseEvent event(GUIMouseEventType::MouseDown, screenPos, (MouseButton)i);
  420. mMouseOverElement->mouseEvent(event);
  421. }
  422. else
  423. {
  424. GUIMouseEvent event(GUIMouseEventType::MouseUp, screenPos, (MouseButton)i);
  425. mMouseOverElement->mouseEvent(event);
  426. }
  427. mLastFrameButtonState[i] = buttonDown;
  428. }
  429. }
  430. }
  431. }
  432. }