Просмотр исходного кода

Profiler Overlay code (untested)

Marko Pintera 12 лет назад
Родитель
Сommit
ff64948b20

+ 2 - 0
BansheeEngine/BansheeEngine.vcxproj

@@ -272,6 +272,7 @@
     <ClInclude Include="Include\BsGUISkin.h" />
     <ClInclude Include="Include\BsGUIWidget.h" />
     <ClInclude Include="Include\BsImageSprite.h" />
+    <ClInclude Include="Include\BsProfilerOverlay.h" />
     <ClInclude Include="Include\BsSceneManager.h" />
     <ClInclude Include="Include\BsGUIScrollArea.h" />
     <ClInclude Include="Include\BsSprite.h" />
@@ -328,6 +329,7 @@
     <ClCompile Include="Source\BsGUIToggleGroup.cpp" />
     <ClCompile Include="Source\BsGUIWidget.cpp" />
     <ClCompile Include="Source\BsImageSprite.cpp" />
+    <ClCompile Include="Source\BsProfilerOverlay.cpp" />
     <ClCompile Include="Source\BsSceneManager.cpp" />
     <ClCompile Include="Source\BsGUIScrollArea.cpp" />
     <ClCompile Include="Source\BsSprite.cpp" />

+ 6 - 0
BansheeEngine/BansheeEngine.vcxproj.filters

@@ -222,6 +222,9 @@
     <ClInclude Include="Include\BsGUIMouseEvent.h">
       <Filter>Header Files\GUI</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsProfilerOverlay.h">
+      <Filter>Header Files\GUI</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsGUIElement.cpp">
@@ -389,5 +392,8 @@
     <ClCompile Include="Source\BsGUITextInputEvent.cpp">
       <Filter>Source Files\GUI</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsProfilerOverlay.cpp">
+      <Filter>Source Files\GUI</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 91 - 0
BansheeEngine/Include/BsProfilerOverlay.h

@@ -0,0 +1,91 @@
+#pragma once
+
+#include "BsPrerequisites.h"
+#include "CmModule.h"
+#include "boost/signals/connection.hpp"
+
+namespace BansheeEngine
+{
+	class BS_EXPORT ProfilerOverlay : public CM::Module<ProfilerOverlay>
+	{
+	public:
+		struct BasicRow
+		{
+			GUILayout* labelLayout;
+			GUILayout* contentLayout;
+
+			CM::Vector<GUIElement*>::type elements;
+
+			CM::HString name;
+			CM::HString pctOfParent;
+			CM::HString numCalls;
+			CM::HString avgTime;
+			CM::HString totalTime;
+			CM::HString maxTime;
+			CM::HString avgTimeSelf;
+			CM::HString totalTimeSelf;
+			CM::HString estOverhead;
+			CM::HString estOverheadSelf;
+		};
+
+		struct PreciseRow
+		{
+			GUILayout* labelLayout;
+			GUILayout* contentLayout;
+
+			CM::Vector<GUIElement*>::type elements;
+
+			CM::HString name;
+			CM::HString pctOfParent;
+			CM::HString numCalls;
+			CM::HString avgCycles;
+			CM::HString totalCycles;
+			CM::HString maxCycles;
+			CM::HString avgCyclesSelf;
+			CM::HString totalCyclesSelf;
+			CM::HString estOverhead;
+			CM::HString estOverheadSelf;
+		};
+
+	public:
+		ProfilerOverlay(const CM::ViewportPtr& target, const CM::RenderWindowPtr& ownerWindow);
+		~ProfilerOverlay();
+
+		void setTarget(const CM::ViewportPtr& target, const CM::RenderWindowPtr& ownerWindow);
+
+		void show();
+		void hide();
+
+		/**
+		 * @brief	Called every frame. Internal method.
+		 */
+		void update();
+	private:
+		static const CM::UINT32 MAX_DEPTH;
+
+		CM::ViewportPtr mTarget;
+		CM::RenderWindowPtr mOwnerWindow;
+
+		CM::HSceneObject mWidgetSO;
+		CM::GameObjectHandle<GUIWidget> mWidget;
+		GUIArea* mBasicAreaLabels;
+		GUIArea* mPreciseAreaLabels;
+		GUIArea* mBasicAreaContents;
+		GUIArea* mPreciseAreaContents;
+
+		GUILayout* mBasicLayoutLabels;
+		GUILayout* mPreciseLayoutLabels;
+		GUILayout* mBasicLayoutContents;
+		GUILayout* mPreciseLayoutContents;
+
+		CM::Vector<BasicRow>::type mBasicRows;
+		CM::Vector<PreciseRow>::type mPreciseRows;
+
+		boost::signals::connection mWindowMovedOrResized;
+		bool mIsShown;
+
+		void windowMovedOrResized(CM::RenderWindow& window);
+		void updateAreaSizes();
+		void updateContents(const CM::ProfilerReport& report);
+	};
+}

+ 4 - 1
BansheeEngine/Source/BsApplication.cpp

@@ -10,6 +10,7 @@
 #include "BsGLBuiltinMaterialFactory.h"
 #include "BsEngineGUI.h"
 #include "CmApplication.h"
+#include "CmProfiler.h"
 
 using namespace CamelotFramework;
 
@@ -49,7 +50,8 @@ namespace BansheeEngine
 		DrawHelper2D::startUp(cm_new<DrawHelper2D>());
 		DrawHelper3D::startUp(cm_new<DrawHelper3D>());
 
-		EngineGUI::startUp(new EngineGUI());
+		EngineGUI::startUp(cm_new<EngineGUI>());
+		Profiler::startUp(cm_new<Profiler>());
 
 		updateCallbackConn = CM::gApplication().mainLoopCallback.connect(boost::bind(&Application::update, this));
 	}
@@ -63,6 +65,7 @@ namespace BansheeEngine
 	{
 		CM::gApplication().mainLoopCallback.disconnect(updateCallbackConn);
 
+		Profiler::shutDown();
 		EngineGUI::shutDown();
 
 		DrawHelper3D::shutDown();

+ 416 - 0
BansheeEngine/Source/BsProfilerOverlay.cpp

@@ -0,0 +1,416 @@
+#include "BsProfilerOverlay.h"
+#include "CmSceneObject.h"
+#include "CmRenderWindowManager.h"
+#include "BsGUIWidget.h"
+#include "BsGUIArea.h"
+#include "BsGUILayout.h"
+#include "BsGUIElement.h"
+#include "BsGUILabel.h"
+#include "BsGUISpace.h"
+#include "CmProfiler.h"
+
+using namespace CamelotFramework;
+
+namespace BansheeEngine
+{
+	class BasicRowFiller
+	{
+	public:
+		UINT32 curIdx;
+		GUILayout& labelLayout;
+		GUILayout& contentLayout;
+		GUIWidget& widget;
+		Vector<ProfilerOverlay::BasicRow>::type& rows;
+
+		BasicRowFiller(Vector<ProfilerOverlay::BasicRow>::type& _rows, GUILayout& _labelLayout, GUILayout& _contentLayout, GUIWidget& _widget)
+			:rows(_rows), curIdx(0), labelLayout(_labelLayout), contentLayout(_contentLayout), widget(_widget)
+		{ }
+
+		~BasicRowFiller()
+		{
+			for(INT32 i = (INT32)rows.size() - 1; i >= (INT32)curIdx; i--)
+			{
+				labelLayout.removeChildAt(i);
+				contentLayout.removeChildAt(i);
+
+				ProfilerOverlay::BasicRow& row = rows[i];
+
+				for(auto& element : row.elements)
+					GUIElement::destroy(element);
+			}
+
+			rows.resize(curIdx);
+		}
+
+		void addData(UINT32 depth, const String& name, float pctOfParent, UINT32 numCalls, double avgTime, double totalTime, 
+			double maxTime, double avgSelfTime, double totalSelfTime, double estOverhead, double estOverheadSelf)
+		{
+			if(curIdx >= rows.size())
+			{
+				rows.push_back(ProfilerOverlay::BasicRow());
+
+				ProfilerOverlay::BasicRow& newRow = rows.back();
+
+				newRow.name = HString(L"{0}");
+				newRow.pctOfParent = HString(L"{0}");
+				newRow.numCalls = HString(L"{0}");
+				newRow.avgTime = HString(L"{0}");
+				newRow.totalTime = HString(L"{0}");
+				newRow.maxTime = HString(L"{0}");
+				newRow.avgTimeSelf = HString(L"{0}");
+				newRow.totalTimeSelf = HString(L"{0}");
+				newRow.estOverhead = HString(L"{0}");
+				newRow.estOverheadSelf = HString(L"{0}");
+
+				newRow.labelLayout = &labelLayout.addLayoutX();
+				newRow.contentLayout = &contentLayout.addLayoutX();
+
+				GUILabel* name = GUILabel::create(widget, newRow.name);
+				GUILabel* pctOfParent = GUILabel::create(widget, newRow.pctOfParent);
+				GUILabel* numCalls = GUILabel::create(widget, newRow.numCalls);
+				GUILabel* avgTime = GUILabel::create(widget, newRow.avgTime);
+				GUILabel* totalTime = GUILabel::create(widget, newRow.totalTime);
+				GUILabel* maxTime = GUILabel::create(widget, newRow.maxTime);
+				GUILabel* avgTimeSelf = GUILabel::create(widget, newRow.avgTimeSelf);
+				GUILabel* totalTimeSelf = GUILabel::create(widget, newRow.totalTimeSelf);
+				GUILabel* estOverhead = GUILabel::create(widget, newRow.estOverhead);
+				GUILabel* estOverheadSelf = GUILabel::create(widget, newRow.estOverheadSelf);
+
+				newRow.labelLayout->addSpace(0);
+				newRow.labelLayout->addElement(name);
+
+				newRow.contentLayout->addElement(pctOfParent);
+				newRow.contentLayout->addElement(numCalls);
+				newRow.contentLayout->addElement(avgTime);
+				newRow.contentLayout->addElement(totalTime);
+				newRow.contentLayout->addElement(maxTime);
+				newRow.contentLayout->addElement(avgTimeSelf);
+				newRow.contentLayout->addElement(totalTimeSelf);
+				newRow.contentLayout->addElement(estOverhead);
+				newRow.contentLayout->addElement(estOverheadSelf);
+
+				newRow.elements.push_back(name);
+				newRow.elements.push_back(pctOfParent);
+				newRow.elements.push_back(numCalls);
+				newRow.elements.push_back(avgTime);
+				newRow.elements.push_back(totalTime);
+				newRow.elements.push_back(maxTime);
+				newRow.elements.push_back(avgTimeSelf);
+				newRow.elements.push_back(totalTimeSelf);
+				newRow.elements.push_back(estOverhead);
+				newRow.elements.push_back(estOverheadSelf);
+			}
+			
+			ProfilerOverlay::BasicRow& row = rows[curIdx];
+
+			row.labelLayout->removeChildAt(0);
+			row.labelLayout->insertSpace(0, depth * 20);
+
+			row.name.setParameter(0, toWString(name));
+			row.pctOfParent.setParameter(0, toWString(pctOfParent));
+			row.numCalls.setParameter(0, toWString(numCalls));
+			row.avgTime.setParameter(0, toWString(avgTime));
+			row.totalTime.setParameter(0, toWString(totalTime));
+			row.maxTime.setParameter(0, toWString(maxTime));
+			row.avgTimeSelf.setParameter(0, toWString(avgSelfTime));
+			row.totalTimeSelf.setParameter(0, toWString(totalSelfTime));
+			row.estOverhead.setParameter(0, toWString(estOverhead));
+			row.estOverheadSelf.setParameter(0, toWString(estOverheadSelf));
+
+			curIdx++;
+		}
+	};
+
+	class PreciseRowFiller
+	{
+	public:
+		UINT32 curIdx;
+		GUILayout& labelLayout;
+		GUILayout& contentLayout;
+		GUIWidget& widget;
+		Vector<ProfilerOverlay::PreciseRow>::type& rows;
+
+		PreciseRowFiller(Vector<ProfilerOverlay::PreciseRow>::type& _rows, GUILayout& _labelLayout, GUILayout& _contentLayout, GUIWidget& _widget)
+			:rows(_rows), curIdx(0), labelLayout(_labelLayout), contentLayout(_contentLayout), widget(_widget)
+		{ }
+
+		~PreciseRowFiller()
+		{
+			for(INT32 i = (INT32)rows.size() - 1; i >= (INT32)curIdx; i--)
+			{
+				labelLayout.removeChildAt(i);
+				contentLayout.removeChildAt(i);
+
+				ProfilerOverlay::PreciseRow& row = rows[i];
+
+				for(auto& element : row.elements)
+					GUIElement::destroy(element);
+			}
+
+			rows.resize(curIdx);
+		}
+
+		void addData(UINT32 depth, const String& name, float pctOfParent, UINT32 numCalls, UINT64 avgCycles, UINT64 totalCycles, 
+			UINT64 maxCycles, UINT64 avgSelfCycles, UINT64 totalSelfCycles, UINT64 estOverhead, UINT64 estOverheadSelf)
+		{
+			if(curIdx >= rows.size())
+			{
+				rows.push_back(ProfilerOverlay::PreciseRow());
+
+				ProfilerOverlay::PreciseRow& newRow = rows.back();
+
+				newRow.name = HString(L"{0}");
+				newRow.pctOfParent = HString(L"{0}");
+				newRow.numCalls = HString(L"{0}");
+				newRow.avgCycles = HString(L"{0}");
+				newRow.totalCycles = HString(L"{0}");
+				newRow.maxCycles = HString(L"{0}");
+				newRow.avgCyclesSelf = HString(L"{0}");
+				newRow.totalCyclesSelf = HString(L"{0}");
+				newRow.estOverhead = HString(L"{0}");
+				newRow.estOverheadSelf = HString(L"{0}");
+
+				newRow.labelLayout = &labelLayout.addLayoutX();
+				newRow.contentLayout = &contentLayout.addLayoutX();
+
+				GUILabel* name = GUILabel::create(widget, newRow.name);
+				GUILabel* pctOfParent = GUILabel::create(widget, newRow.pctOfParent);
+				GUILabel* numCalls = GUILabel::create(widget, newRow.numCalls);
+				GUILabel* avgCycles = GUILabel::create(widget, newRow.avgCycles);
+				GUILabel* totalCycles = GUILabel::create(widget, newRow.totalCycles);
+				GUILabel* maxCycles = GUILabel::create(widget, newRow.maxCycles);
+				GUILabel* avgCyclesSelf = GUILabel::create(widget, newRow.avgCyclesSelf);
+				GUILabel* totalCyclesSelf = GUILabel::create(widget, newRow.totalCyclesSelf);
+				GUILabel* estOverhead = GUILabel::create(widget, newRow.estOverhead);
+				GUILabel* estOverheadSelf = GUILabel::create(widget, newRow.estOverheadSelf);
+
+				newRow.labelLayout->addSpace(0);
+				newRow.labelLayout->addElement(name);
+
+				newRow.contentLayout->addElement(pctOfParent);
+				newRow.contentLayout->addElement(numCalls);
+				newRow.contentLayout->addElement(avgCycles);
+				newRow.contentLayout->addElement(totalCycles);
+				newRow.contentLayout->addElement(maxCycles);
+				newRow.contentLayout->addElement(avgCyclesSelf);
+				newRow.contentLayout->addElement(totalCyclesSelf);
+				newRow.contentLayout->addElement(estOverhead);
+				newRow.contentLayout->addElement(estOverheadSelf);
+
+				newRow.elements.push_back(name);
+				newRow.elements.push_back(pctOfParent);
+				newRow.elements.push_back(numCalls);
+				newRow.elements.push_back(avgCycles);
+				newRow.elements.push_back(totalCycles);
+				newRow.elements.push_back(maxCycles);
+				newRow.elements.push_back(avgCyclesSelf);
+				newRow.elements.push_back(totalCyclesSelf);
+				newRow.elements.push_back(estOverhead);
+				newRow.elements.push_back(estOverheadSelf);
+			}
+
+			ProfilerOverlay::PreciseRow& row = rows[curIdx];
+
+			row.labelLayout->removeChildAt(0);
+			row.labelLayout->insertSpace(0, depth * 20);
+
+			row.name.setParameter(0, toWString(name));
+			row.pctOfParent.setParameter(0, toWString(pctOfParent));
+			row.numCalls.setParameter(0, toWString(numCalls));
+			row.avgCycles.setParameter(0, toWString(avgCycles));
+			row.totalCycles.setParameter(0, toWString(totalCycles));
+			row.maxCycles.setParameter(0, toWString(maxCycles));
+			row.avgCyclesSelf.setParameter(0, toWString(avgSelfCycles));
+			row.totalCyclesSelf.setParameter(0, toWString(totalSelfCycles));
+			row.estOverhead.setParameter(0, toWString(estOverhead));
+			row.estOverheadSelf.setParameter(0, toWString(estOverheadSelf));
+
+			curIdx++;
+		}
+	};
+
+	const UINT32 ProfilerOverlay::MAX_DEPTH = 2;
+
+	ProfilerOverlay::ProfilerOverlay(const CM::ViewportPtr& target, const CM::RenderWindowPtr& ownerWindow)
+		:mIsShown(false), mBasicAreaLabels(nullptr), mPreciseAreaLabels(nullptr), mBasicAreaContents(nullptr), mPreciseAreaContents(nullptr),
+		mBasicLayoutLabels(nullptr), mPreciseLayoutLabels(nullptr), mBasicLayoutContents(nullptr), mPreciseLayoutContents(nullptr)
+	{
+		setTarget(target, ownerWindow);
+
+		mWindowMovedOrResized = RenderWindowManager::instance().onMovedOrResized.connect(boost::bind(&ProfilerOverlay::windowMovedOrResized, this, _1));
+	}
+
+	ProfilerOverlay::~ProfilerOverlay()
+	{
+		if(mIsShown)
+			hide();
+
+		if(mOwnerWindow != nullptr)
+			mWindowMovedOrResized.disconnect();
+
+		if(mWidgetSO)
+			mWidgetSO->destroy();
+	}
+
+	void ProfilerOverlay::setTarget(const CM::ViewportPtr& target, const CM::RenderWindowPtr& ownerWindow)
+	{
+		if(mOwnerWindow != nullptr)
+			mWindowMovedOrResized.disconnect();
+
+		mTarget = target;
+		mOwnerWindow = ownerWindow;
+
+		if(mWidgetSO)
+			mWidgetSO->destroy();
+
+		mWidgetSO = SceneObject::create("ProfilerOverlay");
+		mWidget = mWidgetSO->addComponent<GUIWidget>(mTarget.get(), mOwnerWindow.get());
+
+		mBasicAreaLabels = GUIArea::create(*mWidget, 0, 0);
+		mPreciseAreaLabels = GUIArea::create(*mWidget, 0, 0);
+		mBasicAreaContents = GUIArea::create(*mWidget, 0, 0);
+		mPreciseAreaContents = GUIArea::create(*mWidget, 0, 0);
+
+		mBasicLayoutLabels = &mBasicAreaLabels->getLayout().addLayoutY();
+		mPreciseLayoutLabels = &mPreciseAreaLabels->getLayout().addLayoutY();
+		mBasicLayoutContents = &mBasicAreaContents->getLayout().addLayoutY();
+		mPreciseLayoutContents = &mPreciseAreaContents->getLayout().addLayoutY();
+
+		updateAreaSizes();
+	}
+
+	void ProfilerOverlay::show()
+	{
+		if(mIsShown)
+			return;
+
+		mBasicAreaLabels->enable();
+		mPreciseAreaLabels->enable();
+		mBasicAreaContents->enable();
+		mPreciseAreaContents->enable();
+		mIsShown = true;
+	}
+
+	void ProfilerOverlay::hide()
+	{
+		if(!mIsShown)
+			return;
+
+		mBasicAreaLabels->disable();
+		mPreciseAreaLabels->disable();
+		mBasicAreaContents->disable();
+		mPreciseAreaContents->disable();
+		mIsShown = false;
+	}
+
+	void ProfilerOverlay::update()
+	{
+		const ProfilerReport& latestReport = Profiler::instance().getReport();
+
+		updateContents(latestReport);
+	}
+
+	void ProfilerOverlay::windowMovedOrResized(RenderWindow& window)
+	{
+		if(&window != mOwnerWindow.get())
+		{
+			updateAreaSizes();
+		}
+	}
+
+	void ProfilerOverlay::updateAreaSizes()
+	{
+		static const INT32 PADDING = 10;
+		static const float LABELS_CONTENT_RATIO = 0.3f;
+
+		UINT32 width = (UINT32)std::max(0, (INT32)mOwnerWindow->getWidth() - PADDING * 2);
+		UINT32 height = (UINT32)std::max(0, (INT32)(mOwnerWindow->getHeight() - PADDING * 3)/2);
+
+		UINT32 labelsWidth = Math::CeilToInt(width * LABELS_CONTENT_RATIO);
+		UINT32 contentWidth = width - labelsWidth;
+
+		mBasicAreaLabels->setPosition(PADDING, PADDING);
+		mBasicAreaLabels->setSize(labelsWidth, height);
+
+		mPreciseAreaLabels->setPosition(PADDING, height + PADDING * 2);
+		mPreciseAreaLabels->setSize(labelsWidth, height);
+
+		mBasicAreaContents->setPosition(PADDING + labelsWidth, PADDING);
+		mBasicAreaContents->setSize(contentWidth, height);
+
+		mPreciseAreaContents->setPosition(PADDING + labelsWidth, height + PADDING * 2);
+		mPreciseAreaContents->setSize(contentWidth, height);
+	}
+
+	void ProfilerOverlay::updateContents(const ProfilerReport& report)
+	{
+		const CPUProfilerBasicSamplingEntry& basicRootEntry = report.cpuReport.getBasicSamplingData();
+		const CPUProfilerPreciseSamplingEntry& preciseRootEntry = report.cpuReport.getPreciseSamplingData();
+
+		struct TodoBasic
+		{
+			TodoBasic(const CPUProfilerBasicSamplingEntry& _entry, UINT32 _depth)
+				:entry(_entry), depth(_depth)
+			{ }
+
+			const CPUProfilerBasicSamplingEntry& entry;
+			UINT32 depth;
+		};
+
+		struct TodoPrecise
+		{
+			TodoPrecise(const CPUProfilerPreciseSamplingEntry& _entry, UINT32 _depth)
+				:entry(_entry), depth(_depth)
+			{ }
+
+			const CPUProfilerPreciseSamplingEntry& entry;
+			UINT32 depth;
+		};
+
+		BasicRowFiller basicRowFiller(mBasicRows, *mBasicLayoutLabels, *mBasicLayoutContents, *mWidget);
+
+		Stack<TodoBasic>::type todoBasic;
+		todoBasic.push(TodoBasic(basicRootEntry, 0));
+
+		while(!todoBasic.empty())
+		{
+			TodoBasic curEntry = todoBasic.top();
+			todoBasic.pop();
+
+			const CPUProfilerBasicSamplingEntry::Data& data = curEntry.entry.data;
+			basicRowFiller.addData(curEntry.depth, data.name, data.pctOfParent, data.numCalls, data.avgTimeMs, data.totalTimeMs, 
+				data.maxTimeMs, data.avgSelfTimeMs, data.totalSelfTimeMs, data.estimatedOverheadMs, data.estimatedSelfOverheadMs);
+
+			if(curEntry.depth <= MAX_DEPTH)
+			{
+				for(auto& child : curEntry.entry.childEntries)
+				{
+					todoBasic.push(TodoBasic(child, curEntry.depth + 1));
+				}
+			}
+		}
+
+		PreciseRowFiller preciseRowFiller(mPreciseRows, *mBasicLayoutLabels, *mBasicLayoutContents, *mWidget);
+
+		Stack<TodoPrecise>::type todoPrecise;
+		todoPrecise.push(TodoPrecise(preciseRootEntry, 0));
+
+		while(!todoBasic.empty())
+		{
+			TodoPrecise curEntry = todoPrecise.top();
+			todoPrecise.pop();
+
+			const CPUProfilerPreciseSamplingEntry::Data& data = curEntry.entry.data;
+			preciseRowFiller.addData(curEntry.depth, data.name, data.pctOfParent, data.numCalls, data.avgCycles, data.totalCycles, 
+				data.maxCycles, data.avgSelfCycles, data.totalSelfCycles, data.estimatedOverhead, data.estimatedSelfOverhead);
+
+			if(curEntry.depth <= MAX_DEPTH)
+			{
+				for(auto& child : curEntry.entry.childEntries)
+				{
+					todoPrecise.push(TodoPrecise(child, curEntry.depth + 1));
+				}
+			}
+		}
+	}
+}

+ 5 - 1
CamelotClient/Source/BsMainEditorWindow.cpp

@@ -12,6 +12,7 @@
 #include "BsDrawHelper2D.h"
 #include "BsDrawHelper3D.h"
 #include "CmFRect.h"
+#include "BsProfilerOverlay.h"
 
 using namespace CamelotFramework;
 using namespace BansheeEngine;
@@ -63,11 +64,14 @@ namespace BansheeEditor
 		AABox dbgBox(Vector3(-300, -200, 1000), Vector3(300, 300, 1500));
 		DrawHelper3D::instance().drawAABox(sceneCamera, dbgBox, Color::Green, 250.0f);
 
-		CPUProfiler profiler;
+		ProfilerOverlay::startUp(cm_new<ProfilerOverlay>(mCamera->getViewport(), renderWindow));
+		ProfilerOverlay::instance().show();
 	}
 
 	MainEditorWindow::~MainEditorWindow()
 	{
+		ProfilerOverlay::shutDown();
+
 		cm_delete(mDockManager);
 		cm_delete(mMenuBar);
 	}

+ 2 - 0
CamelotCore/CamelotCore.vcxproj

@@ -289,6 +289,7 @@
     <ClInclude Include="Include\CmPixelDataRTTI.h" />
     <ClInclude Include="Include\CmPixelUtil.h" />
     <ClInclude Include="Include\CmPlatformWndProc.h" />
+    <ClInclude Include="Include\CmProfiler.h" />
     <ClInclude Include="Include\CmRenderOperation.h" />
     <ClInclude Include="Include\CmRenderQueue.h" />
     <ClInclude Include="Include\CmSceneObjectRTTI.h" />
@@ -443,6 +444,7 @@
     <ClCompile Include="Source\CmPixelData.cpp" />
     <ClCompile Include="Source\CmPixelUtil.cpp" />
     <ClCompile Include="Source\CmPlatformWndProc.cpp" />
+    <ClCompile Include="Source\CmProfiler.cpp" />
     <ClCompile Include="Source\CmRenderer.cpp" />
     <ClCompile Include="Source\CmRenderQueue.cpp" />
     <ClCompile Include="Source\CmTextureView.cpp" />

+ 6 - 0
CamelotCore/CamelotCore.vcxproj.filters

@@ -471,6 +471,9 @@
     <ClInclude Include="Include\CmWin32Defs.h">
       <Filter>Header Files\Win32</Filter>
     </ClInclude>
+    <ClInclude Include="Include\CmProfiler.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\CmApplication.cpp">
@@ -728,5 +731,8 @@
     <ClCompile Include="Source\CmPlatformWndProc.cpp">
       <Filter>Source Files\Win32</Filter>
     </ClCompile>
+    <ClCompile Include="Source\CmProfiler.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 4 - 32
CamelotCore/Include/CmPrerequisites.h

@@ -1,35 +1,10 @@
-/*-------------------------------------------------------------------------
-This source file is a part of OGRE
-(Object-oriented Graphics Rendering Engine)
-
-For the latest info, see http://www.ogre3d.org/
-
-Copyright (c) 2000-2011 Torus Knot Software Ltd
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE
--------------------------------------------------------------------------*/
-#ifndef __OgrePrerequisites_H__
-#define __OgrePrerequisites_H__
+#pragma once
 
 #include "CmPrerequisitesUtil.h"
 
 #define CM_MAX_MULTIPLE_RENDER_TARGETS 8
 #define CM_FORCE_SINGLETHREADED_RENDERING 0
+#define CM_PROFILING_ENABLED 1
 
 //----------------------------------------------------------------------------
 // Windows Settings
@@ -158,6 +133,7 @@ namespace CamelotFramework {
 	class BindableGpuParams;
 	struct RenderOperation;
 	class RenderQueue;
+	struct ProfilerReport;
 	// Asset import
 	class SpecificImporter;
 	class Importer;
@@ -341,8 +317,4 @@ namespace CamelotFramework
 	 * @param	callback	The callback.
 	 */
 	void CM_EXPORT deferredCall(std::function<void()> callback);
-}
-
-#endif
-
-
+}

+ 104 - 0
CamelotCore/Include/CmProfiler.h

@@ -0,0 +1,104 @@
+#pragma once
+
+#include "CmPrerequisites.h"
+#include "CmModule.h"
+#include "CmCPUProfiler.h"
+
+namespace CamelotFramework
+{
+	struct ProfilerReport
+	{
+		CPUProfilerReport cpuReport;
+	};
+
+	class CM_EXPORT Profiler : public Module<Profiler>
+	{
+	public:
+		Profiler();
+		~Profiler();
+
+		/**
+		 * @copydoc CPUProfiler::beginThread
+		 */
+		void beginThread(const String& name) 
+		{ 
+#if CM_PROFILING_ENABLED
+			mCPUProfiler->beginThread(name); 
+#endif
+		}
+
+		/**
+		 * @copydoc CPUProfiler::endThread
+		 */
+		void endThread() 
+		{ 
+#if CM_PROFILING_ENABLED
+			mCPUProfiler->endThread(); 
+#endif
+		}
+
+		/**
+		 * @copydoc CPUProfiler::beginSample
+		 */
+		void beginSample(const String& name) 
+		{ 
+#if CM_PROFILING_ENABLED
+			mCPUProfiler->beginSample(name); 
+#endif
+		}
+
+		/**
+		 * @copydoc CPUProfiler::endSample
+		 */
+		void endSample(const String& name) 
+		{ 
+#if CM_PROFILING_ENABLED
+			mCPUProfiler->endSample(name); 
+#endif
+		}
+
+		/**
+		 * @copydoc CPUProfiler::beginSamplePrecise
+		 */
+		void beginSamplePrecise(const String& name) 
+		{ 
+#if CM_PROFILING_ENABLED
+			mCPUProfiler->beginSamplePrecise(name); 
+#endif
+		}
+
+		/**
+		 * @copydoc CPUProfiler::endSamplePrecise
+		 */
+		void endSamplePrecise(const String& name) 
+		{ 
+#if CM_PROFILING_ENABLED
+			mCPUProfiler->endSamplePrecise(name); 
+#endif
+		}
+
+		/**
+		 * @brief	Called every frame. Internal method.
+		 */
+		void update();
+
+		/**
+		 * @brief	Returns a profiler report for the specified frame. 
+		 *
+		 * @param	Profiler report index, ranging [0, NUM_SAVED_FRAMES]. 0 always returns the latest
+		 * 					 report. Increasing indexes return reports for older and older frames. Out of range
+		 * 					 indexes will be clamped.
+		 *
+		 * @note	Profiler reports get updated every frame. Oldest reports that no longer fit in the saved reports buffer
+		 * 			are discarded.
+		 */
+		const ProfilerReport& getReport(UINT32 idx = 0) const;
+
+	private:
+		static const UINT32 NUM_SAVED_FRAMES;
+		ProfilerReport* mSavedReports;
+		UINT32 mNextReportIdx;
+
+		CPUProfiler* mCPUProfiler;
+	};
+}

+ 44 - 0
CamelotCore/Source/CmProfiler.cpp

@@ -0,0 +1,44 @@
+#include "CmProfiler.h"
+#include "CmMath.h"
+
+namespace CamelotFramework
+{
+	const UINT32 Profiler::NUM_SAVED_FRAMES = 200;
+
+	Profiler::Profiler()
+		:mSavedReports(nullptr), mCPUProfiler(nullptr), mNextReportIdx(0)
+	{
+#if CM_PROFILING_ENABLED
+		mCPUProfiler = cm_new<CPUProfiler>();
+#endif
+
+		mSavedReports = cm_newN<ProfilerReport>(NUM_SAVED_FRAMES);
+	}
+
+	Profiler::~Profiler()
+	{
+		if(mCPUProfiler != nullptr)
+			cm_delete(mCPUProfiler);
+
+		if(mSavedReports != nullptr)
+			cm_deleteN(mSavedReports, NUM_SAVED_FRAMES);
+	}
+
+	void Profiler::update()
+	{
+#if CM_PROFILING_ENABLED
+		mSavedReports[mNextReportIdx].cpuReport = mCPUProfiler->generateReport();
+
+		mCPUProfiler->reset();
+
+		mNextReportIdx = (mNextReportIdx + 1) % NUM_SAVED_FRAMES;
+#endif
+	}
+
+	const ProfilerReport& Profiler::getReport(UINT32 idx) const
+	{
+		idx = Math::Clamp(idx, 0U, (UINT32)(NUM_SAVED_FRAMES - 1));
+
+		return mSavedReports[idx];
+	}
+}

+ 2 - 2
CamelotUtility/Include/CmCPUProfiler.h

@@ -283,14 +283,14 @@ namespace CamelotFramework
 	class CM_UTILITY_EXPORT CPUProfilerReport
 	{
 	public:
+		CPUProfilerReport();
+
 		const CPUProfilerBasicSamplingEntry& getBasicSamplingData() const { return mBasicSamplingRootEntry; }
 		const CPUProfilerPreciseSamplingEntry& getPreciseSamplingData() const { return mPreciseSamplingRootEntry; }
 
 	private:
 		friend class CPUProfiler;
 
-		CPUProfilerReport();
-
 		CPUProfilerBasicSamplingEntry mBasicSamplingRootEntry;
 		CPUProfilerPreciseSamplingEntry mPreciseSamplingRootEntry;
 	};

+ 30 - 0
CamelotUtility/Include/CmString.h

@@ -475,6 +475,11 @@ namespace CamelotFramework
         unsigned short width = 0, char fill = ' ', 
         std::ios::fmtflags flags = std::ios::fmtflags(0) );
 
+	/** Converts a double to a WString. */
+	CM_UTILITY_EXPORT WString toWString(double val, unsigned short precision = 6, 
+		unsigned short width = 0, char fill = ' ', 
+		std::ios::fmtflags flags = std::ios::fmtflags(0) );
+
     /** Converts a Radian to a WString. */
     CM_UTILITY_EXPORT WString toWString(Radian val, unsigned short precision = 6, 
         unsigned short width = 0, char fill = ' ', 
@@ -505,6 +510,16 @@ namespace CamelotFramework
         unsigned short width = 0, char fill = ' ', 
         std::ios::fmtflags flags = std::ios::fmtflags(0) );
 
+	/** Converts an INT64 to a WString. */
+	CM_UTILITY_EXPORT WString toWString(INT64 val, 
+		unsigned short width = 0, char fill = ' ', 
+		std::ios::fmtflags flags = std::ios::fmtflags(0) );
+
+	/** Converts an UINT64 to a WString. */
+	CM_UTILITY_EXPORT WString toWString(UINT64 val, 
+		unsigned short width = 0, char fill = ' ', 
+		std::ios::fmtflags flags = std::ios::fmtflags(0) );
+
     /** Converts a boolean to a WString. 
     @param yesNo If set to true, result is 'yes' or 'no' instead of 'true' or 'false'
     */
@@ -570,6 +585,11 @@ namespace CamelotFramework
         unsigned short width = 0, char fill = ' ', 
         std::ios::fmtflags flags = std::ios::fmtflags(0) );
 
+	/** Converts a double to a String. */
+	CM_UTILITY_EXPORT String toString(double val, unsigned short precision = 6, 
+		unsigned short width = 0, char fill = ' ', 
+		std::ios::fmtflags flags = std::ios::fmtflags(0) );
+
     /** Converts a Radian to a String. */
     CM_UTILITY_EXPORT String toString(Radian val, unsigned short precision = 6, 
         unsigned short width = 0, char fill = ' ', 
@@ -600,6 +620,16 @@ namespace CamelotFramework
         unsigned short width = 0, char fill = ' ', 
         std::ios::fmtflags flags = std::ios::fmtflags(0) );
 
+	/** Converts an unsigned long to a String. */
+	CM_UTILITY_EXPORT String toString(INT64 val, 
+		unsigned short width = 0, char fill = ' ', 
+		std::ios::fmtflags flags = std::ios::fmtflags(0) );
+
+	/** Converts an unsigned long to a String. */
+	CM_UTILITY_EXPORT String toString(UINT64 val, 
+		unsigned short width = 0, char fill = ' ', 
+		std::ios::fmtflags flags = std::ios::fmtflags(0) );
+
     /** Converts a boolean to a String. 
     @param yesNo If set to true, result is 'yes' or 'no' instead of 'true' or 'false'
     */

+ 71 - 0
CamelotUtility/Source/CmString.cpp

@@ -162,6 +162,19 @@ namespace CamelotFramework
 		return stream.str();
 	}
 
+	WString toWString(double val, unsigned short precision, 
+		unsigned short width, char fill, std::ios::fmtflags flags)
+	{
+		WStringStream stream;
+		stream.precision(precision);
+		stream.width(width);
+		stream.fill(fill);
+		if (flags)
+			stream.setf(flags);
+		stream << val;
+		return stream.str();
+	}
+
 	WString toWString(Radian val, unsigned short precision, 
 		unsigned short width, char fill, std::ios::fmtflags flags)
 	{
@@ -220,6 +233,28 @@ namespace CamelotFramework
 		return stream.str();
 	}
 
+	WString toWString(INT64 val, unsigned short width, char fill, std::ios::fmtflags flags)
+	{
+		WStringStream stream;
+		stream.width(width);
+		stream.fill(fill);
+		if (flags)
+			stream.setf(flags);
+		stream << val;
+		return stream.str();
+	}
+
+	WString toWString(UINT64 val, unsigned short width, char fill, std::ios::fmtflags flags)
+	{
+		WStringStream stream;
+		stream.width(width);
+		stream.fill(fill);
+		if (flags)
+			stream.setf(flags);
+		stream << val;
+		return stream.str();
+	}
+
 	WString toWString(const Vector2& val)
 	{
 		WStringStream stream;
@@ -350,6 +385,19 @@ namespace CamelotFramework
 		return stream.str();
 	}
 
+	String toString(double val, unsigned short precision, 
+		unsigned short width, char fill, std::ios::fmtflags flags)
+	{
+		StringStream stream;
+		stream.precision(precision);
+		stream.width(width);
+		stream.fill(fill);
+		if (flags)
+			stream.setf(flags);
+		stream << val;
+		return stream.str();
+	}
+
 	String toString(Radian val, unsigned short precision, 
 		unsigned short width, char fill, std::ios::fmtflags flags)
 	{
@@ -408,6 +456,29 @@ namespace CamelotFramework
 		return stream.str();
 	}
 
+	String toString(INT64 val, 
+		unsigned short width, char fill, std::ios::fmtflags flags)
+	{
+		StringStream stream;
+		stream.width(width);
+		stream.fill(fill);
+		if (flags)
+			stream.setf(flags);
+		stream << val;
+		return stream.str();
+	}
+
+	String toString(UINT64 val, unsigned short width, char fill, std::ios::fmtflags flags)
+	{
+		StringStream stream;
+		stream.width(width);
+		stream.fill(fill);
+		if (flags)
+			stream.setf(flags);
+		stream << val;
+		return stream.str();
+	}
+
 	String toString(const Vector2& val)
 	{
 		StringStream stream;

+ 1 - 0
Notes.txt

@@ -51,6 +51,7 @@ Reminders:
    likely use on GUIWidget per element. However currently I only perform batching within a single widget which 
    doesn't help in the mentioned case.
   - Later add InputMap class in which you can bind certain actions (like move left, fire, etc.) to Keyboard, Joystick or Mouse buttons.
+    - Also ensure button combinations are possible. e.g. on keyboard I might want to press F1 to open debug menu, but on joystick it might be A+B+X
   - Add a field that tracks % of resource deserialization in BinarySerializer
   - Add GL Texture buffers (They're equivalent to DX11 buffers) - http://www.opengl.org/wiki/Buffer_Texture
   - Instead of doing setThisPtr on every CoreGpuObject, use intrusive shared_ptr instead?

+ 2 - 2
TODO.txt

@@ -11,8 +11,8 @@ LONGTERM TODO:
 PROFILER:
  TODO: Profiler is right now including windows.h. I need to work around that but don't feel like bothering with it atm
   - Easy way would be to move CPUProfiler outside of Utility and into Core
- BIG ISSUE: Current profiler has two different active stacks. Which means calling beginSample and beginSamplePrecise nested will not set proper parents...
- TODO: Remove debug code from CPUProfiler.h
+ProfilerOverlay::update isn't being called
+ - Does editor need a main loop as well?
 
 I still re-create GUIWidget mesh every frame instead of just updating it.