Browse Source

Widget rendering now happens in GUIManager

Marko Pintera 12 years ago
parent
commit
edee70b158

+ 2 - 0
BansheeEngine/Include/BsGUIElement.h

@@ -3,6 +3,7 @@
 #include "BsPrerequisites.h"
 #include "BsGUILayoutOptions.h"
 #include "CmRect.h"
+#include "CmInt2.h"
 
 namespace BansheeEngine
 {
@@ -75,6 +76,7 @@ namespace BansheeEngine
 
 		const CM::Rect& getBounds() const { return mBounds; }
 		INT32 getDepth() const { return mDepth; }
+		GUIWidget& getParentWidget() const { return mParent; }
 		bool isDirty() const { return mIsDirty; }
 
 		static void destroy(GUIElement* element);

+ 19 - 1
BansheeEngine/Include/BsGUIManager.h

@@ -3,14 +3,27 @@
 #include "BsPrerequisites.h"
 #include "CmModule.h"
 #include "CmInputHandler.h"
+#include "CmDeferredRenderContextFwd.h"
 
 namespace BansheeEngine
 {
 	/**
-	 * @brief	Manages the rendering of all GUI widgets in the scene. 
+	 * @brief	Manages the rendering and input of all GUI widgets in the scene. 
 	 */
 	class BS_EXPORT GUIManager : public CM::Module<GUIManager>
 	{
+		struct GUIRenderData
+		{
+			GUIRenderData()
+				:isDirty(true)
+			{ }
+
+			std::vector<CM::HMesh> cachedMeshes;
+			std::vector<CM::HMaterial> cachedMaterials;
+			std::vector<GUIWidget*> widgets;
+			bool isDirty;
+		};
+
 	public:
 		GUIManager();
 		~GUIManager();
@@ -19,13 +32,18 @@ namespace BansheeEngine
 		void unregisterWidget(GUIWidget* widget);
 
 		void update();
+		void render(CM::ViewportPtr& target, CM::RenderContext& renderContext);
 	private:
 		std::vector<GUIWidget*> mWidgets;
+		std::unordered_map<const CM::Viewport*, GUIRenderData> mCachedGUIData;
 
 		GUIWidget* mMouseOverWidget;
 		GUIElement* mMouseOverElement;
 
 		bool mLastFrameButtonState[CM::MB_Count];
 		CM::Int2 mLastCursorPos;
+
+		void updateMeshes();
+		void updateInput();
 	};
 }

+ 15 - 7
BansheeEngine/Include/BsGUIWidget.h

@@ -1,14 +1,12 @@
 #pragma once
 
 #include "BsPrerequisites.h"
-#include "CmDeferredRenderContext.h"
-#include "BsOverlay.h"
-#include "BsTextSprite.h"
-#include "CmORect.h"
+#include "CmComponent.h"
+#include "CmRect.h"
 
 namespace BansheeEngine
 {
-	class BS_EXPORT GUIWidget : public Overlay
+	class BS_EXPORT GUIWidget : public CM::Component
 	{
 	public:
 		virtual ~GUIWidget();
@@ -27,9 +25,17 @@ namespace BansheeEngine
 
 		bool inBounds(const CM::Int2& position) const;
 
-		virtual void render(CM::RenderContext& renderContext) const;
+		/**
+		 * @brief	Return true if widget or any of its elements are dirty.
+		 *
+		 * @param	cleanIfDirty	If true, all dirty elements will be updated and widget will be marked as clean.
+		 *
+		 * @return	True if dirty, false if not. If "cleanIfDirty" is true, the returned state is the one before cleaning.
+		 */
+		bool isDirty(bool cleanIfDirty);
 
 		const CM::RenderWindow* getOwnerWindow() const { return mOwnerWindow; }
+		CM::Viewport* getTarget() const { return mTarget; }
 		const std::vector<GUIElement*>& getElements() const { return mElements; }
 	protected:
 		friend class CM::SceneObject;
@@ -44,12 +50,14 @@ namespace BansheeEngine
 		void registerArea(GUIArea* area);
 		void unregisterArea(GUIArea* area);
 	private:
-		void updateMeshes() const;
 		void updateBounds() const;
 
 		void ownerWindowResized(CM::RenderWindow* window);
 
+		virtual void update() {}
+
 		const CM::RenderWindow* mOwnerWindow;
+		CM::Viewport* mTarget;
 		std::vector<GUIElement*> mElements;
 		std::vector<GUIArea*> mAreas;
 

+ 301 - 4
BansheeEngine/Source/BsGUIManager.cpp

@@ -9,13 +9,38 @@
 #include "CmUtil.h"
 #include "CmRenderWindowManager.h"
 #include "CmCursor.h"
+#include "CmRect.h"
+#include "CmApplication.h"
 #include "CmException.h"
 #include "CmInput.h"
+#include "CmPass.h"
 
 using namespace CamelotFramework;
 
 namespace BansheeEngine
 {
+	struct GUIGroupElement
+	{
+		GUIGroupElement()
+		{ }
+
+		GUIGroupElement(GUIElement* _element, UINT32 _renderElement)
+			:element(_element), renderElement(_renderElement)
+		{ }
+
+		GUIElement* element;
+		UINT32 renderElement;
+	};
+
+	struct GUIMaterialGroup
+	{
+		HMaterial material;
+		UINT32 numQuads;
+		UINT32 depth;
+		Rect bounds;
+		std::vector<GUIGroupElement> elements;
+	};
+
 	GUIManager::GUIManager()
 		:mMouseOverElement(nullptr), mMouseOverWidget(nullptr)
 	{
@@ -31,6 +56,17 @@ namespace BansheeEngine
 	void GUIManager::registerWidget(GUIWidget* widget)
 	{
 		mWidgets.push_back(widget);
+
+		const Viewport* renderTarget = widget->getTarget();
+
+		auto findIter = mCachedGUIData.find(renderTarget);
+
+		if(findIter == end(mCachedGUIData))
+			mCachedGUIData[renderTarget] = GUIRenderData();
+
+		GUIRenderData& windowData = mCachedGUIData[renderTarget];
+		windowData.widgets.push_back(widget);
+		windowData.isDirty = true;
 	}
 
 	void GUIManager::unregisterWidget(GUIWidget* widget)
@@ -45,9 +81,265 @@ namespace BansheeEngine
 			mMouseOverWidget = nullptr;
 			mMouseOverElement = nullptr;
 		}
+
+		const Viewport* renderTarget = widget->getTarget();
+		GUIRenderData& renderData = mCachedGUIData[renderTarget];
+
+		findIter = std::find(begin(renderData.widgets), end(renderData.widgets), widget);
+		if(findIter != end(renderData.widgets))
+			renderData.widgets.erase(findIter);
+
+		if(renderData.widgets.size() == 0)
+			mCachedGUIData.erase(renderTarget);
+		else
+			renderData.isDirty = true;
 	}
 
 	void GUIManager::update()
+	{
+		updateMeshes();
+		updateInput();
+	}
+
+	void GUIManager::render(ViewportPtr& target, RenderContext& renderContext)
+	{
+		auto findIter = mCachedGUIData.find(target.get());
+
+		if(findIter == mCachedGUIData.end())
+			return;
+
+		renderContext.setViewport(target);
+
+		GUIRenderData& renderData = findIter->second;
+
+		// Render the meshes
+		UINT32 meshIdx = 0;
+		for(auto& mesh : renderData.cachedMeshes)
+		{
+			HMaterial material = renderData.cachedMaterials[meshIdx];
+
+			// TODO - Possible optimization. I currently divide by width/height inside the shader, while it
+			// might be more optimal to just scale the mesh as the resolution changes?
+			float invViewportWidth = 1.0f / (target->getWidth() * 0.5f);
+			float invViewportHeight = 1.0f / (target->getHeight() * 0.5f);
+
+			material->setFloat("invViewportWidth", invViewportWidth);
+			material->setFloat("invViewportHeight", invViewportHeight);
+			material->setMat4("worldTransform", Matrix4::IDENTITY);
+			//material->setMat4("worldTransform", SO()->getWorldTfrm());
+
+			if(material == nullptr || !material.isLoaded())
+				continue;
+
+			if(mesh == nullptr || !mesh.isLoaded())
+				continue;
+
+			for(UINT32 i = 0; i < material->getNumPasses(); i++)
+			{
+				PassPtr pass = material->getPass(i);
+				pass->activate(renderContext);
+
+				PassParametersPtr paramsPtr = material->getPassParameters(i);
+				pass->bindParameters(renderContext, paramsPtr);
+
+				renderContext.render(mesh->getRenderOperation());
+			}
+
+			meshIdx++;
+		}
+	}
+
+	void GUIManager::updateMeshes()
+	{
+		for(auto& cachedMeshData : mCachedGUIData)
+		{
+			GUIRenderData& renderData = cachedMeshData.second;
+
+			// Check if anything is dirty. If nothing is we can skip the update
+			bool isDirty = renderData.isDirty;
+			renderData.isDirty = false;
+
+			for(auto& widget : renderData.widgets)
+			{
+				if(widget->isDirty(true))
+				{
+					isDirty = true;
+				}
+			}
+
+			if(!isDirty)
+				continue;
+
+			// Make a list of all GUI elements, sorted from farthest to nearest (highest depth to lowest)
+			auto elemComp = [](GUIElement* a, GUIElement* b)
+			{
+				return a->getDepth() > b->getDepth() || (a->getDepth() == b->getDepth() && a > b); 
+				// Compare pointers just to differentiate between two elements with the same depth, their order doesn't really matter, but std::set
+				// requires all elements to be unique
+			};
+
+			std::set<GUIElement*, std::function<bool(GUIElement*, GUIElement*)>> allElements(elemComp);
+
+			for(auto& widget : renderData.widgets)
+			{
+				const std::vector<GUIElement*>& elements = widget->getElements();
+
+				for(auto& element : elements)
+					allElements.insert(element);
+			}
+
+			// Group the elements in such a way so that we end up with a smallest amount of
+			// meshes, without breaking back to front rendering order
+			std::unordered_map<UINT64, std::vector<GUIMaterialGroup>> materialGroups;
+			for(auto& elem : allElements)
+			{
+				Rect tfrmedBounds = elem->getBounds();
+				tfrmedBounds.transform(elem->getParentWidget().SO()->getWorldTfrm());
+
+				UINT32 numRenderElems = elem->getNumRenderElements();
+
+				for(UINT32 i = 0; i < numRenderElems; i++)
+				{
+					const HMaterial& mat = elem->getMaterial(i);
+
+					UINT64 materialId = mat->getInternalID(); // TODO - I group based on material ID. So if two widgets used exact copies of the same material
+					// this system won't detect it. Find a better way of determining material similarity?
+
+					// If this is a new material, add a new list of groups
+					auto findIterMaterial = materialGroups.find(materialId);
+					if(findIterMaterial == end(materialGroups))
+						materialGroups[materialId] = std::vector<GUIMaterialGroup>();
+
+					// Try to find a group this material will fit in:
+					//  - Group that has a depth value same or one below elements depth will always be a match
+					//  - Otherwise, we search higher depth values as well, but we only use them if no elements in between those depth values
+					//    overlap the current elements bounds.
+					std::vector<GUIMaterialGroup>& allGroups = materialGroups[materialId];
+					GUIMaterialGroup* foundGroup = nullptr;
+					for(auto groupIter = allGroups.rbegin(); groupIter != allGroups.rend(); ++groupIter)
+					{
+						GUIMaterialGroup& group = *groupIter;
+
+						if(group.depth == elem->getDepth() || group.depth == (elem->getDepth() - 1))
+						{
+							foundGroup = &group;
+							break;
+						}
+						else
+						{
+							UINT32 startDepth = elem->getDepth();
+							UINT32 endDepth = group.depth;
+
+							bool foundOverlap = false;
+							for(auto& material : materialGroups)
+							{
+								for(auto& group : material.second)
+								{
+									if(group.depth > startDepth && group.depth < endDepth)
+									{
+										if(group.bounds.overlaps(tfrmedBounds))
+										{
+											foundOverlap = true;
+											break;
+										}
+									}
+								}
+							}
+
+							if(!foundOverlap)
+							{
+								foundGroup = &group;
+								break;
+							}
+						}
+					}
+
+					if(foundGroup == nullptr)
+					{
+						allGroups.push_back(GUIMaterialGroup());
+						foundGroup = &allGroups[allGroups.size() - 1];
+
+						foundGroup->depth = elem->getDepth();
+						foundGroup->bounds = tfrmedBounds;
+						foundGroup->elements.push_back(GUIGroupElement(elem, i));
+						foundGroup->material = mat;
+						foundGroup->numQuads = elem->getNumQuads(i);
+					}
+					else
+					{
+						foundGroup->bounds.encapsulate(tfrmedBounds);
+						foundGroup->elements.push_back(GUIGroupElement(elem, i));
+						foundGroup->numQuads += elem->getNumQuads(i);
+					}
+				}
+			}
+
+			// Make a list of all GUI elements, sorted from farthest to nearest (highest depth to lowest)
+			auto groupComp = [](GUIMaterialGroup* a, GUIMaterialGroup* b)
+			{
+				return (a->depth > b->depth) || (a->depth == b->depth && a > b);
+				// Compare pointers just to differentiate between two elements with the same depth, their order doesn't really matter, but std::set
+				// requires all elements to be unique
+			};
+
+			std::set<GUIMaterialGroup*, std::function<bool(GUIMaterialGroup*, GUIMaterialGroup*)>> sortedGroups(groupComp);
+			for(auto& material : materialGroups)
+			{
+				for(auto& group : material.second)
+				{
+					sortedGroups.insert(&group);
+				}
+			}
+
+			UINT32 numMeshes = (UINT32)sortedGroups.size();
+			UINT32 oldNumMeshes = (UINT32)renderData.cachedMeshes.size();
+			if(numMeshes < oldNumMeshes)
+				renderData.cachedMeshes.resize(numMeshes);
+
+			renderData.cachedMaterials.resize(numMeshes);
+
+			// Fill buffers for each group and update their meshes
+			UINT32 groupIdx = 0;
+			for(auto& group : sortedGroups)
+			{
+				renderData.cachedMaterials[groupIdx] = group->material;
+
+				MeshDataPtr meshData = std::shared_ptr<MeshData>(CM_NEW(MeshData, PoolAlloc) MeshData(group->numQuads * 4),
+					&MemAllocDeleter<MeshData, PoolAlloc>::deleter);
+
+				meshData->beginDesc();
+				meshData->addVertElem(VET_FLOAT2, VES_POSITION);
+				meshData->addVertElem(VET_FLOAT2, VES_TEXCOORD);
+				meshData->addSubMesh(group->numQuads * 6);
+				meshData->endDesc();
+
+				UINT8* vertices = meshData->getElementData(VES_POSITION);
+				UINT8* uvs = meshData->getElementData(VES_TEXCOORD);
+				UINT32* indices = meshData->getIndices32();
+				UINT32 vertexStride = meshData->getVertexStride();
+				UINT32 indexStride = meshData->getIndexElementSize();
+
+				UINT32 quadOffset = 0;
+				for(auto& matElement : group->elements)
+				{
+					matElement.element->fillBuffer(vertices, uvs, indices, quadOffset, group->numQuads, vertexStride, indexStride, matElement.renderElement);
+					quadOffset += matElement.element->getNumQuads(matElement.renderElement);
+				}
+
+				if(groupIdx >= (UINT32)renderData.cachedMeshes.size())
+				{
+					renderData.cachedMeshes.push_back(Mesh::create());
+				}
+
+				gMainSyncedRC().writeSubresource(renderData.cachedMeshes[groupIdx].getInternalPtr(), 0, *meshData);
+				gMainSyncedRC().submitToGpu(true); // TODO - Remove this once I make writeSubresource accept a shared_ptr for MeshData
+
+				groupIdx++;
+			}
+		}
+	}
+
+	void GUIManager::updateInput()
 	{
 #if CM_DEBUG_MODE
 		// Checks if all referenced windows actually exist
@@ -84,7 +376,7 @@ namespace BansheeEngine
 			const RenderWindow* window = widgetInFocus->getOwnerWindow();
 
 			screenPos = Cursor::getWindowPosition(*window);
-			Vector3 vecScreenPos((float)screenPos.x, (float)screenPos.y, 0.0f);
+			Vector4 vecScreenPos((float)screenPos.x, (float)screenPos.y, 0.0f, 1.0f);
 
 			GUIElement* topMostElement = nullptr;
 			INT32 topMostDepth = std::numeric_limits<INT32>::max();
@@ -94,13 +386,18 @@ namespace BansheeEngine
 				{
 					const Matrix4& worldTfrm = widget->SO()->getWorldTfrm();
 
-					Vector3 vecLocalPos = worldTfrm.inverse() * vecScreenPos;
+					Vector4 vecLocalPos = worldTfrm.inverse() * vecScreenPos;
 					Int2 localPos(Math::RoundToInt(vecLocalPos.x), Math::RoundToInt(vecLocalPos.y));
 
-					const std::vector<GUIElement*>& elements = widget->getElements();
+					std::vector<GUIElement*> sortedElements = widget->getElements();
+					std::sort(sortedElements.begin(), sortedElements.end(), 
+						[](GUIElement* a, GUIElement* b)
+					{
+						return a->getDepth() < b->getDepth();
+					});
 
 					// Elements with lowest depth (most to the front) get handled first
-					for(auto iter = elements.rbegin(); iter != elements.rend(); ++iter)
+					for(auto iter = sortedElements.begin(); iter != sortedElements.end(); ++iter)
 					{
 						GUIElement* element = *iter;
 						const Rect& bounds = element->getBounds();

+ 25 - 171
BansheeEngine/Source/BsGUIWidget.cpp

@@ -23,14 +23,13 @@ namespace BansheeEngine
 	GUISkin GUIWidget::DefaultSkin;
 
 	GUIWidget::GUIWidget(const HSceneObject& parent)
-		:Overlay(parent), mSkin(nullptr), mOwnerWindow(nullptr), mWidgetIsDirty(false)
-	{
-		GUIManager::instance().registerWidget(this);
-	}
+		:Component(parent), mSkin(nullptr), mOwnerWindow(nullptr), mWidgetIsDirty(false), mTarget(nullptr)
+	{	}
 
 	GUIWidget::~GUIWidget()
 	{
-		GUIManager::instance().unregisterWidget(this);
+		if(mTarget != nullptr)
+			GUIManager::instance().unregisterWidget(this);
 
 		for(auto& elem : mElements)
 		{
@@ -53,9 +52,11 @@ namespace BansheeEngine
 		if(mOwnerWindow != nullptr)
 			CM_EXCEPT(InvalidStateException, "Widget has already been initialized.");
 
-		Overlay::initialize(target);
-
+		mTarget = target;
 		mOwnerWindow = ownerWindow;
+
+		GUIManager::instance().registerWidget(this);
+
 		mOwnerWindow->onWindowMovedOrResized.connect(boost::bind(&GUIWidget::ownerWindowResized, this, _1));
 	}
 
@@ -116,143 +117,40 @@ namespace BansheeEngine
 			return &DefaultSkin;
 	}
 
-	void GUIWidget::updateMeshes() const
+	bool GUIWidget::isDirty(bool cleanIfDirty)
 	{
-		struct TempMeshData
+		if(cleanIfDirty)
 		{
-			TempMeshData()
-				:numQuads(0), quadOffset(0)
-			{ }
-
-			UINT32 numQuads;
-			UINT32 quadOffset;
-			HMaterial material;
-			std::shared_ptr<MeshData> meshData;
-		};
-
-		bool isDirty = mWidgetIsDirty;
-		mWidgetIsDirty = false;
+			bool dirty = mWidgetIsDirty;
+			mWidgetIsDirty = false;
 
-		if(!isDirty)
-		{
 			for(auto& elem : mElements)
 			{
 				if(elem->isDirty())
 				{
-					isDirty = true;
-					break;
+					dirty = true;
+					elem->updateRenderElements();
 				}
 			}
-		}
-
-		if(!isDirty) // Nothing to update
-			return;
-
-		std::unordered_map<UINT64, TempMeshData> meshDataPerRenderElement;
-
-		// Group meshes based on used materials
-		// Determine mesh sizes per group
-		for(auto& elem : mElements)
-		{
-			if(elem->isDirty())
-				elem->updateRenderElements();
-
-			UINT32 numRenderElems = elem->getNumRenderElements();
 
-			for(UINT32 i = 0; i < numRenderElems; i++)
-			{
-				const HMaterial& mat = elem->getMaterial(i);
-
-				UINT64 meshGroup = mat->getInternalID(); // TODO - I group based on material ID. So if two widgets used exact copies of the same material
-				// this system won't detect it. Find a better way of determining material similarity?
-
-				UINT32 numQuads = elem->getNumQuads(i);
-				meshDataPerRenderElement[meshGroup].numQuads += numQuads;
-				meshDataPerRenderElement[meshGroup].material = mat;
-			}
-		}
-
-		// Allocate buffers for each group
-		UINT32 numMeshes = 0;
-		for(auto& renderElem : meshDataPerRenderElement)
-		{
-			MeshDataPtr meshData = std::shared_ptr<MeshData>(CM_NEW(MeshData, PoolAlloc) MeshData(renderElem.second.numQuads * 4),
-				&MemAllocDeleter<MeshData, PoolAlloc>::deleter);
-
-			meshData->beginDesc();
-			meshData->addVertElem(VET_FLOAT2, VES_POSITION);
-			meshData->addVertElem(VET_FLOAT2, VES_TEXCOORD);
-			meshData->addSubMesh(renderElem.second.numQuads * 6);
-			meshData->endDesc();
-
-			renderElem.second.meshData = meshData;
-			numMeshes++;
+			updateBounds();
+			return dirty;
 		}
-
-		// TODO - Sorting from scratch every time is not optimal.
-		//  If more performance is needed, try re-sorting only modified elements
-		//  Sort so that farthest away elements get drawn first (needed due to transparency)
-		std::vector<GUIElement*> sortedElements = mElements;
-		std::sort(sortedElements.begin(), sortedElements.end(), 
-			[](GUIElement* a, GUIElement* b)
-		{
-			return a->getDepth() < b->getDepth();
-		});
-
-		// Fill buffers for each group
-		for(auto& elem : sortedElements)
+		else
 		{
-			UINT32 numRenderElems = elem->getNumRenderElements();
+			if(mWidgetIsDirty)
+				return true;
 
-			for(UINT32 i = 0; i < numRenderElems; i++)
+			for(auto& elem : mElements)
 			{
-				const HMaterial& mat = elem->getMaterial(i);
-				UINT64 meshGroup = mat->getInternalID(); 
-				MeshDataPtr meshData = meshDataPerRenderElement[meshGroup].meshData;
-
-				UINT8* vertices = meshData->getElementData(VES_POSITION);
-				UINT8* uvs = meshData->getElementData(VES_TEXCOORD);
-				UINT32* indices = meshData->getIndices32();
-				UINT32 startingQuad = meshDataPerRenderElement[meshGroup].quadOffset;
-				UINT32 maxNumQuads = meshDataPerRenderElement[meshGroup].numQuads;
-				UINT32 vertexStride = meshData->getVertexStride();
-				UINT32 indexStride = meshData->getIndexElementSize();
-				
-				elem->fillBuffer(vertices, uvs, indices, startingQuad, maxNumQuads, vertexStride, indexStride, i);
-
-				UINT32 numQuads = elem->getNumQuads(i);
-				meshDataPerRenderElement[meshGroup].quadOffset += numQuads;
+				if(elem->isDirty())
+				{
+					return true;
+				}
 			}
-		}
-
-		// Update meshes
-		for(UINT32 i = (UINT32)mCachedMeshes.size(); i < numMeshes; i++)
-		{
-			HMesh newMesh = Mesh::create();
-			mCachedMeshes.push_back(newMesh);
-			newMesh.waitUntilLoaded();
-		}
-
-		while((UINT32)mCachedMeshes.size() > numMeshes && (UINT32)mCachedMeshes.size() > 0)
-		{
-			mCachedMeshes.erase(mCachedMeshes.end() - 1); // TODO: Destroying meshes as soon as they're not used might be a perf. penalty?
-			//  Maybe instead pool the meshes and only actually remove them when a certain number of unused ones exists.
-		}
 
-		mCachedMaterials.resize(numMeshes);
-
-		UINT32 meshIdx = 0;
-		for(auto& renderElem : meshDataPerRenderElement)
-		{
-			gMainSyncedRC().writeSubresource(mCachedMeshes[meshIdx].getInternalPtr(), 0, *renderElem.second.meshData);
-			gMainSyncedRC().submitToGpu(true); // TODO - Possibly we can avoid this. I don't see a reason we need to wait for the update to complete.
-
-			mCachedMaterials[meshIdx] = renderElem.second.material;
-
-			meshIdx++;
+			return false;
 		}
-
-		updateBounds();
 	}
 
 	bool GUIWidget::inBounds(const Int2& position) const
@@ -271,8 +169,6 @@ namespace BansheeEngine
 
 	void GUIWidget::updateBounds() const
 	{
-		const Matrix4& worldTfrm = SO()->getWorldTfrm();
-
 		if(mElements.size() > 0)
 			mBounds = mElements[0]->getBounds();
 
@@ -290,46 +186,4 @@ namespace BansheeEngine
 			area->notifyWindowResized(window->getWidth(), window->getHeight());
 		}
 	}
-
-	void GUIWidget::render(RenderContext& renderContext) const
-	{
-		// Mesh is re-created every frame. There might be a better approach that only recreates it upon change,
-		// but for now it seems like too much hassle for something like GUI that is pretty dynamic anyway.
-		updateMeshes();
-
-		// Render the meshes
-		UINT32 meshIdx = 0;
-		for(auto& mesh : mCachedMeshes)
-		{
-			HMaterial material = mCachedMaterials[meshIdx];
-
-			// TODO - Possible optimization. I currently divide by width/height inside the shader, while it
-			// might be more optimal to just scale the mesh as the resolution changes?
-			float invViewportWidth = 1.0f / (getTarget()->getWidth() * 0.5f);
-			float invViewportHeight = 1.0f / (getTarget()->getHeight() * 0.5f);
-
-			material->setFloat("invViewportWidth", invViewportWidth);
-			material->setFloat("invViewportHeight", invViewportHeight);
-			material->setMat4("worldTransform", SO()->getWorldTfrm());
-
-			if(material == nullptr || !material.isLoaded())
-				continue;
-
-			if(mesh == nullptr || !mesh.isLoaded())
-				continue;
-
-			for(UINT32 i = 0; i < material->getNumPasses(); i++)
-			{
-				PassPtr pass = material->getPass(i);
-				pass->activate(renderContext);
-
-				PassParametersPtr paramsPtr = material->getPassParameters(i);
-				pass->bindParameters(renderContext, paramsPtr);
-
-				renderContext.render(mesh->getRenderOperation());
-			}
-
-			meshIdx++;
-		}
-	}
 }

+ 2 - 2
BansheeEngine/Source/BsImageSprite.cpp

@@ -82,8 +82,8 @@ namespace BansheeEngine
 			UINT32 topBorder = desc.borderTop;
 			UINT32 bottomBorder = desc.borderBottom;
 
-			float centerWidth = (float)std::max((UINT32)0, desc.width - leftBorder - rightBorder);
-			float centerHeight = (float)std::max((UINT32)0, desc.height - topBorder - bottomBorder);
+			float centerWidth = (float)std::max((INT32)0, (INT32)desc.width - (INT32)leftBorder - (INT32)rightBorder);
+			float centerHeight = (float)std::max((INT32)0, (INT32)desc.height - (INT32)topBorder - (INT32)bottomBorder);
 
 			float topCenterStart = (float)(offset.x + leftBorder);
 			float topRightStart = (float)(topCenterStart + centerWidth);

+ 7 - 0
BansheeForwardRenderer/Source/BsForwardRenderer.cpp

@@ -12,6 +12,7 @@
 #include "CmViewport.h"
 #include "CmRenderTarget.h"
 #include "BsOverlayManager.h"
+#include "BsGUIManager.h"
 
 using namespace CamelotFramework;
 
@@ -54,6 +55,8 @@ namespace BansheeEngine
 
 		renderContext.beginFrame();
 
+		// TODO - Attempt to render all different elements in such a way that there is only 1 render target switch per render target
+
 		// Render all cameras
 		for(auto& camera : allCameras)
 			render(camera);
@@ -62,6 +65,10 @@ namespace BansheeEngine
 		for(auto& camera : allCameras)
 			OverlayManager::instance().render(camera->getViewport(), renderContext);
 
+		// Render all GUI elements
+		for(auto& camera : allCameras)
+			GUIManager::instance().render(camera->getViewport(), renderContext);
+
 		renderContext.endFrame();
 
 		// Swap all targets

+ 2 - 2
CamelotCore/Include/CmApplication.h

@@ -114,8 +114,8 @@ namespace CamelotFramework
 	 * 			from all threads except the render thread. All operations from this context will be executed after
 	 * 			non-synchronized primary context has finished executing.
 	 * 			
-	 * @note	It is more efficient to create your own non-synchronized render context if you plan on using the render context from
-	 * 			threads other than main often.
+	 * @note	It is more efficient to create your own non-synchronized render context if you plan on often using the render context from
+	 * 			threads other than main.
 	 */
 	CM_EXPORT SyncedRenderContext& gMainSyncedRC();
 }

+ 1 - 1
CamelotCore/Source/CmResourceHandle.cpp

@@ -17,7 +17,7 @@ namespace CamelotFramework
 
 	bool ResourceHandleBase::isLoaded() const 
 	{ 
-		return (mData->mIsCreated && mData->mPtr != nullptr && mData->mPtr->isInitialized()); 
+		return (mData->mIsCreated && mData->mPtr != nullptr); 
 	}
 
 	void ResourceHandleBase::waitUntilLoaded() const

+ 1 - 0
CamelotUtility/CamelotUtility.vcxproj

@@ -166,6 +166,7 @@
     <ClCompile Include="Source\CmInt2.cpp" />
     <ClCompile Include="Source\CmManagedDataBlock.cpp" />
     <ClCompile Include="Source\CmORect.cpp" />
+    <ClCompile Include="Source\CmRect.cpp" />
     <ClCompile Include="Source\CmTexAtlasGenerator.cpp" />
     <ClCompile Include="Source\CmUUID.cpp" />
     <ClCompile Include="Source\CmWorkQueue.cpp" />

+ 3 - 0
CamelotUtility/CamelotUtility.vcxproj.filters

@@ -320,5 +320,8 @@
     <ClCompile Include="Source\CmAsyncOp.cpp">
       <Filter>Source Files\Threading</Filter>
     </ClCompile>
+    <ClCompile Include="Source\CmRect.cpp">
+      <Filter>Source Files\Math</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 16 - 49
CamelotUtility/Include/CmRect.h

@@ -1,60 +1,27 @@
 #pragma once
 
 #include "CmPrerequisitesUtil.h"
-#include "CmInt2.h"
 
 namespace CamelotFramework
 {
-	/**
-	 * @brief	A rectangle. Although you may use any coordinate system, Camelot assumes
-	 * 			that X, Y values represent its bottom left corner, where X increases to the right,
-	 * 			and Y increases upwards.
-	 */
-	class Rect
+	class CM_UTILITY_EXPORT Rect
 	{
 	public:
-		Rect()
-			:x(0), y(0), width(0), height(0)
-		{ }
-
-		Rect(int _x, int _y, int _width, int _height)
-			:x(_x), y(_y), width(_width), height(_height)
-		{ }
-
-		bool contains(Int2 point) const
-		{
-			if(point.x >= x && point.x <= (x + width))
-			{
-				if(point.y >= y && point.y <= (y + height))
-					return true;
-			}
-
-			return false;
-		}
-
-		void encapsulate(const Rect& other)
-		{
-			int myRight = x + width;
-			int myBottom = y + height;
-			int otherRight = other.x + other.width;
-			int otherBottom = other.y + other.height;
-
-			if(other.x < x)
-				x = other.x;
-
-			if(other.y < y)
-				y = other.y;
-
-			if(otherRight > myRight)
-				width = otherRight - x;
-			else
-				width = myRight - x;
-
-			if(otherBottom > myBottom)
-				height = otherBottom - y;
-			else
-				height = myBottom - y;
-		}
+		Rect();
+		Rect(int _x, int _y, int _width, int _height);
+
+		bool contains(const Int2& point) const;
+		bool overlaps(const Rect& other) const;
+		void encapsulate(const Rect& other);
+
+		/**
+		 * @brief	Transforms the bounds by the given matrix.
+		 * 			Resulting value is an axis aligned rectangle encompassing the transformed points.
+		 * 			
+		 * @note	Since the resulting value is an AA rectangle of the original transformed rectangle, the bounds
+		 * 			will be larger than needed. Oriented rectangle would provide a much tighter fit.
+		 */
+		void transform(const Matrix4& matrix);
 
 		int x, y, width, height;
 	};

+ 102 - 0
CamelotUtility/Source/CmRect.cpp

@@ -0,0 +1,102 @@
+#include "CmRect.h"
+#include "CmInt2.h"
+#include "CmMatrix4.h"
+#include "CmMath.h"
+
+namespace CamelotFramework
+{
+	Rect::Rect()
+		:x(0), y(0), width(0), height(0)
+	{ }
+
+	Rect::Rect(int _x, int _y, int _width, int _height)
+		:x(_x), y(_y), width(_width), height(_height)
+	{ }
+
+	bool Rect::contains(const Int2& point) const
+	{
+		if(point.x >= x && point.x <= (x + width))
+		{
+			if(point.y >= y && point.y <= (y + height))
+				return true;
+		}
+
+		return false;
+	}
+
+	bool Rect::overlaps(const Rect& other) const
+	{
+		INT32 otherRight = other.x + other.width;
+		INT32 myRight = x + width;
+
+		INT32 otherBottom = other.y + other.height;
+		INT32 myBottom = y + height;
+
+		if(x < otherRight && myRight > other.x &&
+			y < otherBottom && myBottom > other.y)
+			return true;
+
+		return false;
+	}
+
+	void Rect::encapsulate(const Rect& other)
+	{
+		int myRight = x + width;
+		int myBottom = y + height;
+		int otherRight = other.x + other.width;
+		int otherBottom = other.y + other.height;
+
+		if(other.x < x)
+			x = other.x;
+
+		if(other.y < y)
+			y = other.y;
+
+		if(otherRight > myRight)
+			width = otherRight - x;
+		else
+			width = myRight - x;
+
+		if(otherBottom > myBottom)
+			height = otherBottom - y;
+		else
+			height = myBottom - y;
+	}
+
+	void Rect::transform(const Matrix4& matrix)
+	{
+		Vector4 verts[4];
+		verts[0] = Vector4((float)x, (float)y, 0.0f, 1.0f);
+		verts[1] = Vector4((float)x + width, (float)y, 0.0f, 1.0f);
+		verts[2] = Vector4((float)x, (float)y + height, 0.0f, 1.0f);
+		verts[3] = Vector4((float)x + width, (float)y + height, 0.0f, 1.0f);
+
+		for(UINT32 i = 0; i < 4; i++)
+			verts[i] = matrix * verts[i];
+
+		float minX = std::numeric_limits<float>::max();
+		float maxX = std::numeric_limits<float>::min();
+		float minY = std::numeric_limits<float>::max();
+		float maxY = std::numeric_limits<float>::min();
+
+		for(UINT32 i = 0; i < 4; i++)
+		{
+			if(verts[i].x < minX)
+				minX = verts[i].x;
+
+			if(verts[i].y < minY)
+				minY = verts[i].y;
+
+			if(verts[i].x > maxX)
+				maxX = verts[i].x;
+
+			if(verts[i].y > maxY)
+				maxY = verts[i].y;
+		}
+
+		x = Math::FloorToInt(minX);
+		y = Math::FloorToInt(minY);
+		width = Math::CeilToInt(maxX) - x;
+		height = Math::CeilToInt(maxY) - y;
+	}
+}