Browse Source

Added CrashHandler (untested)

BearishSun 10 years ago
parent
commit
baaa09abee

+ 0 - 10
BansheeCore/Source/BsCoreApplication.cpp

@@ -46,18 +46,10 @@
 #include "BsShader.h"
 #include "BsTechnique.h"
 #include "BsPass.h"
-
 #include "BsRendererManager.h"
 
-#include <csignal>
-
 namespace BansheeEngine
 {
-	void handleAbort(int error)
-	{
-		BS_EXCEPT(InternalErrorException, "abort() called.");
-	}
-
 	CoreApplication::CoreApplication(START_UP_DESC& desc)
 		:mPrimaryWindow(nullptr), mIsFrameRenderingFinished(true), mRunMainLoop(false), 
 		mRendererPlugin(nullptr), mSimThreadId(BS_THREAD_CURRENT_ID), mStartUpDesc(desc)
@@ -117,8 +109,6 @@ namespace BansheeEngine
 
 	void CoreApplication::onStartUp()
 	{
-		signal(SIGABRT, handleAbort);
-
 		UINT32 numWorkerThreads = BS_THREAD_HARDWARE_CONCURRENCY - 1; // Number of cores while excluding current thread.
 
 		Platform::_startUp();

+ 1 - 13
BansheeD3D9RenderAPI/Source/BsD3D9RenderAPI.cpp

@@ -185,19 +185,7 @@ namespace BansheeEngine
 		String msg;
 
 		mResourceManager->lockDeviceAccess();
-
-		try
-		{
-			mDeviceManager->linkRenderWindow(d3d9renderWindow);
-		}
-		catch (const BansheeEngine::RenderingAPIException&)
-		{
-			// after catching the exception, clean up
-			mResourceManager->unlockDeviceAccess();
-
-			// re-throw
-			throw;
-		}
+		mDeviceManager->linkRenderWindow(d3d9renderWindow);
 
 		mResourceManager->unlockDeviceAccess();
 	}	

+ 14 - 9
BansheeEditorExec/BsEditorExec.cpp

@@ -1,13 +1,13 @@
-#include <windows.h>
 #include <iostream>
 #include <stdio.h>
 #include <fcntl.h>
 #include <io.h>
-
 #include "BsEditorApplication.h"
-
-// DEBUG ONLY
 #include "BsDebug.h"
+#include "BsCrashHandler.h"
+
+#if BS_PLATFORM == BS_PLATFORM_WIN32
+#include <windows.h>
 
 using namespace BansheeEngine;
 
@@ -55,7 +55,7 @@ void ShutdownDebugConsole()
 	DWORD CharsRead;
 	ReadConsole(ConsoleInput, &InputBuffer, 1, &CharsRead, 0);
 }
-#endif
+#endif // End BS_DEBUG_MODE
 
 int CALLBACK WinMain(
 	_In_  HINSTANCE hInstance,
@@ -68,20 +68,25 @@ int CALLBACK WinMain(
 	InitializeDebugConsole();
 #endif
 
-	try
+	CrashHandler::startUp();
+
+	__try
 	{
 		EditorApplication::startUp(RenderAPIPlugin::DX11);
 		EditorApplication::instance().runMainLoop();
 		EditorApplication::shutDown();
 	}
-	catch(Exception& e)
+	__except (gCrashHandler().reportCrash(GetExceptionInformation()))
 	{
-		LOGERR(e.getFullDescription());
+
 	}
 
 #if BS_DEBUG_MODE
 	ShutdownDebugConsole();
 #endif
 
+	CrashHandler::shutDown();
+
 	return 0;
-}
+}
+#endif // End BS_PLATFORM

+ 4 - 2
BansheeUtility/BansheeUtility.vcxproj

@@ -294,13 +294,15 @@
     <ClCompile Include="Source\BsSphere.cpp" />
     <ClCompile Include="Source\BsTexAtlasGenerator.cpp" />
     <ClCompile Include="Source\ThirdParty\md5.cpp" />
-    <ClCompile Include="Source\Win32\BsFileSystem.cpp" />
-    <ClCompile Include="Source\Win32\BsTimer.cpp" />
+    <ClCompile Include="Source\Win32\BsWin32CrashHandler.cpp" />
+    <ClCompile Include="Source\Win32\BsWin32FileSystem.cpp" />
+    <ClCompile Include="Source\Win32\BsWin32Timer.cpp" />
     <ClInclude Include="Include\BsAny.h" />
     <ClInclude Include="Include\BsBinaryCloner.h" />
     <ClInclude Include="Include\BsBounds.h" />
     <ClInclude Include="Include\BsCapsule.h" />
     <ClInclude Include="Include\BsConvexVolume.h" />
+    <ClInclude Include="Include\BsCrashHandler.h" />
     <ClInclude Include="Include\BsEvent.h" />
     <ClInclude Include="Include\BsLineSegment3.h" />
     <ClInclude Include="Include\BsMatrixNxM.h" />

+ 15 - 6
BansheeUtility/BansheeUtility.vcxproj.filters

@@ -51,6 +51,9 @@
     <Filter Include="Header Files\RTTI">
       <UniqueIdentifier>{57091de5-56e6-4b9a-9b76-8ecaeee60c3f}</UniqueIdentifier>
     </Filter>
+    <Filter Include="Source Files\Win32">
+      <UniqueIdentifier>{e56d4454-f68c-4190-88cf-98a5cc24f6b4}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="Include\BsThreadPool.h">
@@ -302,6 +305,9 @@
     <ClInclude Include="Include\BsStaticAlloc.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsCrashHandler.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsThreadPool.cpp">
@@ -310,9 +316,6 @@
     <ClCompile Include="Source\BsTaskScheduler.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="Source\Win32\BsTimer.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="Source\BsTime.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -340,9 +343,6 @@
     <ClCompile Include="Source\BsFrameAlloc.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="Source\Win32\BsFileSystem.cpp">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="Source\BsException.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -481,5 +481,14 @@
     <ClCompile Include="Source\BsGlobalFrameAlloc.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="Source\Win32\BsWin32FileSystem.cpp">
+      <Filter>Source Files\Win32</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\Win32\BsWin32CrashHandler.cpp">
+      <Filter>Source Files\Win32</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\Win32\BsWin32Timer.cpp">
+      <Filter>Source Files\Win32</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 73 - 0
BansheeUtility/Include/BsCrashHandler.h

@@ -0,0 +1,73 @@
+#pragma once
+
+#include "BsPrerequisitesUtil.h"
+#include "BsModule.h"
+
+#if BS_PLATFORM == BS_PLATFORM_WIN32
+#include "windows.h"
+#endif
+
+#define BS_MAX_STACKTRACE_DEPTH 200
+#define BS_MAX_STACKTRACE_NAME_BYTES 1024
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Saves crash data and notifies the user when a crash occurs.
+	 */
+	// TODO - Crashes are reported in the same process as the main application. This can be a problem if the crash was caused
+	// by heap. Any further use of the heap by the reporting methods will cause a silent crash, failing to log it. A more appropriate
+	// way of doing it should be to resume another process to actually handle the crash.
+	class BS_UTILITY_EXPORT CrashHandler : public Module<CrashHandler>
+	{
+	public:
+		CrashHandler();
+		~CrashHandler();
+
+		/**
+		 * @brief	Records a crash with a custom error message.
+		 * 			
+		 * @param	type		Type of the crash that occurred. e.g. "InvalidParameter".
+		 * @param	description	More detailed description of the issue that caused the crash.
+		 * @param	function	Optional name of the function where the error occurred.
+		 * @param	file		Optional name of the source code file in which the code that crashed the program exists.
+		 * @param	line		Optional source code line at which the crash was triggered at.
+		 */
+		void reportCrash(const char* type, const char* description, const char* function = nullptr, const char* file = nullptr, UINT32 line = 0);
+
+#if BS_PLATFORM == BS_PLATFORM_WIN32
+		/**
+		 * @brief	Records a crash resulting from a Windows-specific SEH exception. 
+		 * 			
+		 * @param	exceptionData	Exception data returned from GetExceptionInformation()
+		 * 							
+		 * @returns	Code that signals the __except exception handler on how to proceed.
+		 */
+		int reportCrash(EXCEPTION_POINTERS* exceptionData);
+#endif
+
+		/**
+		 * @brief	Returns a string containing a current stack trace. If function can be found in the symbol
+		 * 			table its readable name will be present in the stack trace, otherwise just its address.
+		 * 						
+		 * @returns	String containing the call stack with each function on its own line.
+		 */
+		String getStackTrace();
+	private:
+		/**
+		 * @brief	Returns path to the folder into which to store the crash reports.
+		 */
+		Path getCrashFolder();
+
+		static const wchar_t* CrashReportFolder;
+		static const wchar_t* CrashLogName;
+
+		struct Data;
+		Data* m;
+	};
+
+	/**
+	 * @brief	Returns an instance of the CrashHandler.
+	 */
+	BS_UTILITY_EXPORT CrashHandler& gCrashHandler();
+}

+ 4 - 3
BansheeUtility/Include/BsDynLib.h

@@ -32,8 +32,11 @@ namespace BansheeEngine
 	 */
 	class BS_UTILITY_EXPORT DynLib
     {
-
     public:
+		/**
+		 * @brief	Constructs the dynamic library object and loads the library with the specified name.
+		 */
+		DynLib(const String& name);
         ~DynLib();
 
 		/** 
@@ -64,8 +67,6 @@ namespace BansheeEngine
 	protected:
 		friend class DynLibManager;
 
-		DynLib(const String& name);
-
 		/** 
 		 * @brief	Gets the last loading error.
 		 */

+ 1 - 1
BansheeUtility/Include/BsLog.h

@@ -50,7 +50,7 @@ namespace BansheeEngine
 		/**
 		 * @brief	Saves the log file to disk.
 		 */
-		void saveToFile(const WString& path);
+		void saveToFile(const Path& path);
 
 	private:
 		Vector<LogEntry*> mEntries;

+ 57 - 3
BansheeUtility/Include/BsTimer.h

@@ -2,6 +2,60 @@
 
 #include "BsPrerequisitesUtil.h"
 
-#if BS_PLATFORM == BS_PLATFORM_WIN32
-#include "Win32/BsTimerImp.h"
-#endif
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Timer class used for querying high precision timers.
+	 *
+	 * @note	Not thread safe.
+	 */
+	class BS_UTILITY_EXPORT Timer
+    {
+    public:
+		/**
+		 * @brief	Construct the timer and start timing.
+		 */
+		Timer();
+		~Timer();
+
+		/**
+		 * @brief	Reset the timer to zero.
+		 */
+		void reset();
+
+		/**
+		 * @brief	Returns time in milliseconds since timer was initialized or
+		 * 			last reset.
+		 */
+		unsigned long getMilliseconds();
+
+		/**
+		 * @brief	Returns time in microseconds since timer was initialized or
+		 * 			last reset.
+		 */
+		unsigned long getMicroseconds();
+
+		/**
+		 * @brief	Returns time in milliseconds since timer was initialized or
+		 * 			last reset. Only CPU timer measured.
+		 */
+		unsigned long getMillisecondsCPU();
+
+		/**
+		 * @brief	Returns time in microseconds since timer was initialized or
+		 * 			last reset. Only CPU timer measured.
+		 */
+		unsigned long getMicrosecondsCPU();
+
+		/**
+		 * @brief	Returns the time at which the timer was initialized, in milliseconds.
+		 *
+		 * @return	Time in milliseconds.
+		 */
+		unsigned long getStartMs() const;
+
+	private:
+		struct Data;
+		Data* m;
+    };
+}

+ 0 - 71
BansheeUtility/Include/Win32/BsTimerImp.h

@@ -1,71 +0,0 @@
-#pragma once
-
-#include "../BsPrerequisitesUtil.h"
-
-#ifndef WIN32_LEAN_AND_MEAN
-#	define WIN32_LEAN_AND_MEAN
-#endif
-#if !defined(NOMINMAX) && defined(_MSC_VER)
-#	define NOMINMAX // Required to stop windows.h messing up std::min
-#endif
-#include "windows.h"
-
-namespace BansheeEngine
-{
-	/**
-	 * @brief	Timer class used for querying high precision timers.
-	 *
-	 * @note	Not thread safe.
-	 */
-	class BS_UTILITY_EXPORT Timer
-    {
-    public:
-		/**
-		 * @brief	Construct the timer and start timing.
-		 */
-		Timer();
-		~Timer();
-
-		/**
-		 * @brief	Reset the timer to zero.
-		 */
-		void reset();
-
-		/**
-		 * @brief	Returns time in milliseconds since timer was initialized or
-		 * 			last reset.
-		 */
-		unsigned long getMilliseconds();
-
-		/**
-		 * @brief	Returns time in microseconds since timer was initialized or
-		 * 			last reset.
-		 */
-		unsigned long getMicroseconds();
-
-		/**
-		 * @brief	Returns time in milliseconds since timer was initialized or
-		 * 			last reset. Only CPU timer measured.
-		 */
-		unsigned long getMillisecondsCPU();
-
-		/**
-		 * @brief	Returns time in microseconds since timer was initialized or
-		 * 			last reset. Only CPU timer measured.
-		 */
-		unsigned long getMicrosecondsCPU();
-
-		/**
-		 * @brief	Returns the time at which the timer was initialized, in milliseconds.
-		 *
-		 * @return	Time in milliseconds.
-		 */
-		unsigned long getStartMs() const;
-
-	private:
-		clock_t mZeroClock;
-
-		LARGE_INTEGER mStartTime;
-		LARGE_INTEGER mFrequency;
-    };
-}

+ 0 - 69
BansheeUtility/Include/Win32/CmTimerImp.h

@@ -1,69 +0,0 @@
-#pragma once
-
-#include "../BsPrerequisitesUtil.h"
-
-#ifndef WIN32_LEAN_AND_MEAN
-#	define WIN32_LEAN_AND_MEAN
-#endif
-#if !defined(NOMINMAX) && defined(_MSC_VER)
-#	define NOMINMAX // Required to stop windows.h messing up std::min
-#endif
-#include "windows.h"
-
-namespace BansheeEngine
-{
-	/**
-	 * @brief	Timer class used for querying high precision timers.
-	 */
-	class BS_UTILITY_EXPORT Timer
-    {
-    public:
-		/**
-		 * @brief	Construct the timer and start timing.
-		 */
-		Timer();
-		~Timer();
-
-		/**
-		 * @brief	Reset the timer to zero.
-		 */
-		void reset();
-
-		/**
-		 * @brief	Returns time in milliseconds since timer was initialized or
-		 * 			last reset.
-		 */
-		unsigned long getMilliseconds();
-
-		/**
-		 * @brief	Returns time in microseconds since timer was initialized or
-		 * 			last reset.
-		 */
-		unsigned long getMicroseconds();
-
-		/**
-		 * @brief	Returns time in milliseconds since timer was initialized or
-		 * 			last reset. Only CPU timer measured.
-		 */
-		unsigned long getMillisecondsCPU();
-
-		/**
-		 * @brief	Returns time in microseconds since timer was initialized or
-		 * 			last reset. Only CPU timer measured.
-		 */
-		unsigned long getMicrosecondsCPU();
-
-		/**
-		 * @brief	Returns the time at which the timer was initialized, in milliseconds.
-		 *
-		 * @return	Time in milliseconds.
-		 */
-		unsigned long getStartMs() const;
-
-	private:
-		clock_t mZeroClock;
-
-		LARGE_INTEGER mStartTime;
-		LARGE_INTEGER mFrequency;
-    };
-}

+ 3 - 1
BansheeUtility/Source/BsDynLib.cpp

@@ -19,7 +19,9 @@ namespace BansheeEngine
     DynLib::DynLib(const String& name)
     {
         mName = name;
-        m_hInst = NULL;
+        m_hInst = nullptr;
+
+		load();
     }
 
     DynLib::~DynLib()

+ 1 - 3
BansheeUtility/Source/BsDynLibManager.cpp

@@ -7,7 +7,7 @@ namespace BansheeEngine
 	{
 	}
 
-    DynLib* DynLibManager::load( const String& filename)
+    DynLib* DynLibManager::load(const String& filename)
     {
 		auto iterFind = mLoadedLibraries.find(filename);
 		if (iterFind != mLoadedLibraries.end())
@@ -17,8 +17,6 @@ namespace BansheeEngine
 		else
 		{
 	        DynLib* newLib = new (bs_alloc<DynLib>()) DynLib(filename);
-
-			newLib->load();       
         	mLoadedLibraries[filename] = newLib;
 
 	        return newLib;

+ 1 - 1
BansheeUtility/Source/BsLog.cpp

@@ -39,7 +39,7 @@ namespace BansheeEngine
 		mEntries.clear();
 	}
 
-	void Log::saveToFile(const WString& path)
+	void Log::saveToFile(const Path& path)
 	{
 		// TODO - Save the log as HTML
 		BS_EXCEPT(NotImplementedException, "Log save to file not yet implemented.");

+ 0 - 68
BansheeUtility/Source/Win32/BsTimer.cpp

@@ -1,68 +0,0 @@
-#include "BsTimer.h"
-#include "BsBitwise.h"
-
-namespace BansheeEngine
-{
-	Timer::Timer()
-	{
-		reset();
-	}
-
-	Timer::~Timer()
-	{
-	}
-
-	void Timer::reset()
-	{
-		QueryPerformanceFrequency(&mFrequency);
-		QueryPerformanceCounter(&mStartTime);
-
-		mZeroClock = clock();
-	}
-
-	unsigned long Timer::getMilliseconds()
-	{
-		LARGE_INTEGER curTime;
-		QueryPerformanceCounter(&curTime);
-
-		LONGLONG newTime = curTime.QuadPart - mStartTime.QuadPart;
-    
-		// Scale by 1000 for milliseconds
-		unsigned long newTicks = (unsigned long) (1000 * newTime / mFrequency.QuadPart);
-
-		return newTicks;
-	}
-
-	unsigned long Timer::getStartMs() const
-	{
-		unsigned long newTicks = (unsigned long) (1000 * mStartTime.QuadPart / mFrequency.QuadPart);
-
-		return newTicks;
-	}
-
-	unsigned long Timer::getMicroseconds()
-	{
-		LARGE_INTEGER curTime;
-		QueryPerformanceCounter(&curTime);
-
-		LONGLONG newTime = curTime.QuadPart - mStartTime.QuadPart;
-    
-		// Scale by 1000000 for microseconds
-		unsigned long newMicro = (unsigned long) (1000000 * newTime / mFrequency.QuadPart);
-
-		return newMicro;
-	}
-
-	unsigned long Timer::getMillisecondsCPU()
-	{
-		clock_t newClock = clock();
-		return (unsigned long)((float)(newClock - mZeroClock) / ((float)CLOCKS_PER_SEC / 1000.0f));
-	}
-
-
-	unsigned long Timer::getMicrosecondsCPU()
-	{
-		clock_t newClock = clock();
-		return (unsigned long)((float)(newClock - mZeroClock) / ((float)CLOCKS_PER_SEC / 1000000.0f));
-	}
-}

+ 484 - 0
BansheeUtility/Source/Win32/BsWin32CrashHandler.cpp

@@ -0,0 +1,484 @@
+#include "BsCrashHandler.h"
+#include "BsDebug.h"
+#include "BsDynLib.h"
+#include "BsFileSystem.h"
+#include "DbgHelp.h"
+#include <psapi.h>
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Returns the raw stack trace using the provided context. Raw stack trace contains only
+	 * 			function addresses.
+	 * 			
+	 * @param	context		Processor context from which to start the stack trace. 
+	 * @param	stackTrace	Output parameter that will contain the function addresses. First address is the deepest
+	 * 						called function and following address is its caller and so on.
+	 * 						
+	 * @returns	Number of functions in the call stack.
+	 */
+	UINT32 win32_getRawStackTrace(CONTEXT context, UINT64 stackTrace[BS_MAX_STACKTRACE_DEPTH])
+	{
+		HANDLE hProcess = GetCurrentProcess();
+		HANDLE hThread = GetCurrentThread();
+		UINT32 machineType;
+
+		STACKFRAME64 stackFrame;
+		memset(&stackFrame, 0, sizeof(stackFrame));
+
+		stackFrame.AddrPC.Mode = AddrModeFlat;
+		stackFrame.AddrStack.Mode = AddrModeFlat;
+		stackFrame.AddrFrame.Mode = AddrModeFlat;
+
+#if BS_ARCH_TYPE == BS_ARCHITECTURE_x86_64
+		stackFrame.AddrPC.Offset = context.Rip;
+		stackFrame.AddrStack.Offset = context.Rsp;
+		stackFrame.AddrFrame.Offset = context.Rbp;
+
+		machineType = IMAGE_FILE_MACHINE_AMD64;
+#else
+		stackFrame.AddrPC.Offset = context.Eip;
+		stackFrame.AddrStack.Offset = context.Esp;
+		stackFrame.AddrFrame.Offset = context.Ebp;
+
+		machineType = IMAGE_FILE_MACHINE_I386;
+#endif
+
+		UINT32 numEntries = 0;
+		while (true)
+		{
+			if (!StackWalk64(machineType, hProcess, hThread, &stackFrame, &context, nullptr,
+				SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
+			{
+				LOGERR("Stack walk failed.");
+				break;
+			}
+
+			if (numEntries < BS_MAX_STACKTRACE_DEPTH)
+				stackTrace[numEntries] = stackFrame.AddrPC.Offset;
+
+			numEntries++;
+
+			if (stackFrame.AddrPC.Offset == 0 || stackFrame.AddrFrame.Offset == 0)
+				break;
+		}
+
+		return numEntries;
+	}
+
+	/**
+	 * @brief	Returns a string containing a stack trace using the provided context. If function can be found in the symbol
+	 * 			table its readable name will be present in the stack trace, otherwise just its address.
+	 * 			
+	 * @param	context		Processor context from which to start the stack trace. 
+	 * @param	skip		Number of bottom-most call stack entries to skip.
+	 * 						
+	 * @returns	String containing the call stack with each function on its own line.
+	 */
+	String win32_getStackTrace(CONTEXT context, int skip = 0)
+	{
+		UINT64 rawStackTrace[BS_MAX_STACKTRACE_DEPTH];
+		UINT32 numEntries = win32_getRawStackTrace(context, rawStackTrace);
+
+		numEntries = std::min((UINT32)BS_MAX_STACKTRACE_DEPTH, numEntries);
+
+		UINT32 bufferSize = sizeof(PIMAGEHLP_SYMBOL64) + BS_MAX_STACKTRACE_NAME_BYTES;
+		UINT8* buffer = (UINT8*)bs_alloc(bufferSize);
+
+		PIMAGEHLP_SYMBOL64 symbol = (PIMAGEHLP_SYMBOL64)buffer;
+		symbol->SizeOfStruct = bufferSize;
+		symbol->MaxNameLength = BS_MAX_STACKTRACE_NAME_BYTES;
+
+		HANDLE hProcess = GetCurrentProcess();
+
+		StringStream outputStream;
+		for (UINT32 i = skip; i < numEntries; i++)
+		{
+			if (i > skip)
+				outputStream << std::endl;
+
+			DWORD64 funcAddress = rawStackTrace[i];
+
+			// Output function name
+			DWORD64 dummy;
+			if (SymGetSymFromAddr64(hProcess, funcAddress, &dummy, symbol))
+				outputStream << StringUtil::format("{0}() - ", symbol->Name);
+
+			// Output file name and line
+			IMAGEHLP_LINE64	lineData;
+			lineData.SizeOfStruct = sizeof(lineData);
+
+			String addressString = toString(funcAddress, 0, ' ', std::ios::hex);
+
+			DWORD column;
+			if (SymGetLineFromAddr64(hProcess, funcAddress, &column, &lineData))
+			{
+				outputStream << StringUtil::format("0x{0} File[{1}:{2} ({3})]", addressString, 
+					lineData.FileName, lineData.LineNumber, column);
+			}
+			else
+			{
+				outputStream << StringUtil::format("0x{0}", addressString);
+			}
+
+			// Output module name
+			IMAGEHLP_MODULE64 moduleData;
+			moduleData.SizeOfStruct = sizeof(moduleData);
+
+			if (SymGetModuleInfo64(hProcess, funcAddress, &moduleData))
+				outputStream << StringUtil::format(" Module[{0}]", moduleData.ImageName);
+		}
+
+		bs_free(buffer);
+
+		return outputStream.str();
+	}
+
+	typedef bool(WINAPI *EnumProcessModulesType)(HANDLE hProcess, HMODULE* lphModule, DWORD cb, LPDWORD lpcbNeeded);
+	typedef DWORD(WINAPI *GetModuleBaseNameType)(HANDLE hProcess, HMODULE hModule, LPSTR lpBaseName, DWORD nSize);
+	typedef DWORD(WINAPI *GetModuleFileNameExType)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize);
+	typedef bool(WINAPI *GetModuleInformationType)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO lpmodinfo, DWORD cb);
+
+	static DynLib* gPSAPILib = nullptr;
+
+	static EnumProcessModulesType gEnumProcessModules;
+	static GetModuleBaseNameType gGetModuleBaseName;
+	static GetModuleFileNameExType gGetModuleFileNameEx;
+	static GetModuleInformationType gGetModuleInformation;
+
+	/**
+	 * @brief	Dynamically load the PSAPI.dll and the required symbols, if not already loaded.
+	 */
+	void win32_initPSAPI()
+	{
+		if (gPSAPILib != nullptr)
+			return;
+
+		gPSAPILib = bs_new<DynLib>("PSAPI.dll");
+		gEnumProcessModules = (EnumProcessModulesType)gPSAPILib->getSymbol("EnumProcessModules");
+		gGetModuleBaseName = (GetModuleBaseNameType)gPSAPILib->getSymbol("GetModuleFileNameExA");
+		gGetModuleFileNameEx = (GetModuleFileNameExType)gPSAPILib->getSymbol("GetModuleBaseNameA");
+		gGetModuleInformation = (GetModuleInformationType)gPSAPILib->getSymbol("GetModuleInformation");
+	}
+
+	/**
+	 * @brief	Unloads the PSAPI.dll if is loaded.
+	 */
+	void win32_unloadPSAPI()
+	{
+		if (gPSAPILib == nullptr)
+			return;
+
+		gPSAPILib->unload();
+		bs_delete(gPSAPILib);
+		gPSAPILib = nullptr;
+	}
+
+	static bool gSymbolsLoaded = false;
+
+	/**
+	 * @brief	Loads symbols for all modules in the current process. Loaded symbols allow the stack walker to retrieve
+	 * 			human readable method, file, module names and other information.
+	 */
+	void win32_loadSymbols()
+	{
+		if (gSymbolsLoaded)
+			return;
+
+		HANDLE hProcess = GetCurrentProcess();
+		UINT32 options = SymGetOptions();
+
+		options |= SYMOPT_LOAD_LINES;
+		options |= SYMOPT_EXACT_SYMBOLS;
+		options |= SYMOPT_UNDNAME;
+		options |= SYMOPT_FAIL_CRITICAL_ERRORS;
+		options |= SYMOPT_NO_PROMPTS;
+		options |= SYMOPT_DEFERRED_LOADS;
+
+		SymSetOptions(options);
+		SymInitialize(hProcess, nullptr, true);
+
+		DWORD bufferSize;
+		gEnumProcessModules(hProcess, nullptr, 0, &bufferSize);
+
+		HMODULE* modules = (HMODULE*)bs_alloc(bufferSize);
+		gEnumProcessModules(hProcess, modules, bufferSize, &bufferSize);
+
+		UINT32 numModules = bufferSize / sizeof(HMODULE);
+		for (UINT32 i = 0; i < numModules; i++)
+		{
+			MODULEINFO moduleInfo;
+
+			char moduleName[BS_MAX_STACKTRACE_NAME_BYTES];
+			char imageName[BS_MAX_STACKTRACE_NAME_BYTES];
+
+			gGetModuleInformation(hProcess, modules[i], &moduleInfo, sizeof(moduleInfo));
+			gGetModuleFileNameEx(hProcess, modules[i], imageName, BS_MAX_STACKTRACE_NAME_BYTES);
+			gGetModuleBaseName(hProcess, modules[i], moduleName, BS_MAX_STACKTRACE_NAME_BYTES);
+
+			char pdbSearchPath[BS_MAX_STACKTRACE_NAME_BYTES];
+			GetFullPathNameA(imageName, BS_MAX_STACKTRACE_NAME_BYTES, pdbSearchPath, nullptr);
+			SymSetSearchPath(GetCurrentProcess(), pdbSearchPath);
+
+			SymLoadModule64(hProcess, modules[i], imageName, moduleName, (DWORD64)moduleInfo.lpBaseOfDll,
+				(DWORD)moduleInfo.SizeOfImage);
+		}
+
+		bs_free(modules);
+		gSymbolsLoaded = true;
+	}
+
+	/**
+	 * @brief	Converts an exception record into a human readable error message.
+	 */
+	String win32_getExceptionMessage(EXCEPTION_RECORD* record)
+	{
+		String exceptionAddress = toString((UINT64)record->ExceptionAddress, 0, ' ', std::ios::hex);
+
+		String format;
+		switch (record->ExceptionCode)
+		{
+		case EXCEPTION_ACCESS_VIOLATION:
+			{
+				DWORD_PTR violatedAddress = 0;
+				if (record->NumberParameters == 2)
+				{
+					if (record->ExceptionInformation[0] == 0)
+						format = "Unhandled exception at 0x{0}. Access violation reading 0x{1}.";
+					else if (record->ExceptionInformation[0] == 8)
+						format = "Unhandled exception at 0x{0}. Access violation DEP 0x{1}.";
+					else
+						format = "Unhandled exception at 0x{0}. Access violation writing 0x{1}.";
+
+					violatedAddress = record->ExceptionInformation[1];
+				}
+				else
+					format = "Unhandled exception at 0x{0}. Access violation.";
+
+				String violatedAddressStr = toString(violatedAddress, 0, ' ', std::ios::hex);
+				return StringUtil::format(format, exceptionAddress, violatedAddressStr);
+			}
+		case EXCEPTION_IN_PAGE_ERROR:
+		{
+			DWORD_PTR violatedAddress = 0;
+			DWORD_PTR code = 0;
+			if (record->NumberParameters == 3)
+			{
+				if (record->ExceptionInformation[0] == 0)
+					format = "Unhandled exception at 0x{0}. Page fault reading 0x{1} with code 0x{2}.";
+				else if (record->ExceptionInformation[0] == 8)
+					format = "Unhandled exception at 0x{0}. Page fault DEP 0x{1} with code 0x{2}.";
+				else
+					format = "Unhandled exception at 0x{0}. Page fault writing 0x{1} with code 0x{2}.";
+
+				violatedAddress = record->ExceptionInformation[1];
+				code = record->ExceptionInformation[3];
+			}
+			else
+				format = "Unhandled exception at 0x{0}. Page fault.";
+
+			String violatedAddressStr = toString(violatedAddress, 0, ' ', std::ios::hex);
+			String codeStr = toString(code, 0, ' ', std::ios::hex);
+			return StringUtil::format(format, exceptionAddress, violatedAddressStr, codeStr);
+		}
+		case STATUS_ARRAY_BOUNDS_EXCEEDED:
+		{
+			format = "Unhandled exception at 0x{0}. Attempting to access an out of range array element.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_DATATYPE_MISALIGNMENT:
+		{
+			format = "Unhandled exception at 0x{0}. Attempting to access missaligned data.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_FLT_DENORMAL_OPERAND:
+		{
+			format = "Unhandled exception at 0x{0}. Floating point operand too small.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_FLT_DIVIDE_BY_ZERO:
+		{
+			format = "Unhandled exception at 0x{0}. Floating point operation attempted to divide by zero.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_FLT_INVALID_OPERATION:
+		{
+			format = "Unhandled exception at 0x{0}. Floating point invalid operation.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_FLT_OVERFLOW:
+		{
+			format = "Unhandled exception at 0x{0}. Floating point overflow.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_FLT_UNDERFLOW:
+		{
+			format = "Unhandled exception at 0x{0}. Floating point underflow.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_FLT_STACK_CHECK:
+		{
+			format = "Unhandled exception at 0x{0}. Floating point stack overflow/underflow.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_ILLEGAL_INSTRUCTION:
+		{
+			format = "Unhandled exception at 0x{0}. Attempting to execute an illegal instruction.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_PRIV_INSTRUCTION:
+		{
+			format = "Unhandled exception at 0x{0}. Attempting to execute a private instruction.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_INT_DIVIDE_BY_ZERO:
+		{
+			format = "Unhandled exception at 0x{0}. Integer operation attempted to divide by zero.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_INT_OVERFLOW:
+		{
+			format = "Unhandled exception at 0x{0}. Integer operation result has overflown.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		case EXCEPTION_STACK_OVERFLOW:
+		{
+			format = "Unhandled exception at 0x{0}. Stack overflow.";
+			return StringUtil::format(format, exceptionAddress);
+		}
+		default:
+		{
+			format = "Unhandled exception at 0x{0}. Code 0x{1}.";
+
+			String exceptionCode = toString((UINT32)record->ExceptionCode, 0, ' ', std::ios::hex);
+			return StringUtil::format(format, exceptionAddress, exceptionCode);
+		}
+		}
+	}
+
+	void win32_writeMiniDump(const Path& filePath, EXCEPTION_POINTERS* exceptionData)
+	{
+		HANDLE hFile = CreateFileW(filePath.toWString().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 
+			FILE_ATTRIBUTE_NORMAL, nullptr);
+
+		if (hFile != INVALID_HANDLE_VALUE)
+		{
+			MINIDUMP_EXCEPTION_INFORMATION DumpExceptionInfo;
+
+			DumpExceptionInfo.ThreadId = GetCurrentThreadId();
+			DumpExceptionInfo.ExceptionPointers = exceptionData;
+			DumpExceptionInfo.ClientPointers = false;
+
+			MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, 
+				&DumpExceptionInfo, nullptr, nullptr);
+			CloseHandle(hFile);
+		}
+	}
+
+	static const wchar_t* gMiniDumpName = L"minidump.dmp";
+	const wchar_t* CrashHandler::CrashReportFolder = L"CrashReport-{0}/";
+	const wchar_t* CrashHandler::CrashLogName = L"log.html";
+
+	struct CrashHandler::Data
+	{
+		Mutex mutex;
+	};
+
+	CrashHandler::CrashHandler()
+	{
+		m = bs_new<Data>();
+	}
+
+	CrashHandler::~CrashHandler()
+	{
+		win32_unloadPSAPI();
+
+		bs_delete(m);
+	}
+
+	void CrashHandler::reportCrash(const char* type, const char* description, const char* function,
+		const char* file, UINT32 line)
+	{
+		// Win32 debug methods are not thread safe
+		Lock<>(m->mutex);
+
+		String stackTrace = getStackTrace();
+
+		StringStream errorMessageStream;
+		errorMessageStream << "Fatal error occurred in Banshee Engine and the program has to terminate!" << std::endl;
+		errorMessageStream << "\t\t" << type << " - " << description << std::endl;
+		errorMessageStream << "\t\t in " << function << " [" << file << ":" << line << "]" << std::endl;
+		errorMessageStream << std::endl;
+		errorMessageStream << "Stack trace: " << std::endl;
+		errorMessageStream << stackTrace;
+
+		String errorMessage = errorMessageStream.str();
+		gDebug().logError(errorMessage);
+
+		Path crashFolder = getCrashFolder();
+		FileSystem::createDir(crashFolder);
+
+		gDebug().getLog().saveToFile(crashFolder + toWString(CrashLogName));
+		win32_writeMiniDump(crashFolder + toWString(gMiniDumpName), nullptr);
+
+		MessageBoxA(nullptr, errorMessage.c_str(), "Banshee fatal error!", MB_OK);
+
+		// Note: Potentially also log Windows Error Report and/or send crash data to server
+	}
+
+	int CrashHandler::reportCrash(EXCEPTION_POINTERS* exceptionData)
+	{
+		// Win32 debug methods are not thread safe
+		Lock<>(m->mutex);
+
+		win32_initPSAPI();
+		win32_loadSymbols();
+		String stackTrace = win32_getStackTrace(*exceptionData->ContextRecord, 0);
+
+		StringStream errorMessageStream;
+		errorMessageStream << "Fatal error occurred in Banshee Engine and the program has to terminate!" << std::endl;
+		errorMessageStream << "\t\t" << win32_getExceptionMessage(exceptionData->ExceptionRecord);
+		errorMessageStream << std::endl;
+		errorMessageStream << "Stack trace: " << std::endl;
+		errorMessageStream << stackTrace;
+
+		String errorMessage = errorMessageStream.str();
+		gDebug().logError(errorMessage);
+
+		Path crashFolder = getCrashFolder();
+		FileSystem::createDir(crashFolder);
+
+		gDebug().getLog().saveToFile(crashFolder + toWString(CrashLogName));
+		win32_writeMiniDump(crashFolder + toWString(gMiniDumpName), exceptionData);
+
+		MessageBoxA(nullptr, errorMessage.c_str(), "Banshee fatal error!", MB_OK);
+
+		// Note: Potentially also log Windows Error Report and/or send crash data to server
+
+		return EXCEPTION_EXECUTE_HANDLER;
+	}
+
+	Path CrashHandler::getCrashFolder()
+	{
+		SYSTEMTIME systemTime;
+		GetLocalTime(&systemTime);
+
+		WString timeStamp = L"{0}-{1}-{2}_{3}:{4}";
+		timeStamp = StringUtil::format(timeStamp, systemTime.wYear, systemTime.wMonth, systemTime.wDay, 
+			systemTime.wHour, systemTime.wMinute);
+
+		WString folderName = StringUtil::format(CrashReportFolder, timeStamp);
+
+		return FileSystem::getWorkingDirectoryPath() + folderName;
+	}
+
+	String CrashHandler::getStackTrace()
+	{
+		CONTEXT context;
+		RtlCaptureContext(&context);
+
+		win32_initPSAPI();
+		win32_loadSymbols();
+		return win32_getStackTrace(context, 0);
+	}
+}

+ 0 - 0
BansheeUtility/Source/Win32/BsFileSystem.cpp → BansheeUtility/Source/Win32/BsWin32FileSystem.cpp


+ 86 - 0
BansheeUtility/Source/Win32/BsWin32Timer.cpp

@@ -0,0 +1,86 @@
+#include "BsTimer.h"
+#include "BsBitwise.h"
+
+#ifndef WIN32_LEAN_AND_MEAN
+#	define WIN32_LEAN_AND_MEAN
+#endif
+#if !defined(NOMINMAX) && defined(_MSC_VER)
+#	define NOMINMAX // Required to stop windows.h messing up std::min
+#endif
+#include "windows.h"
+
+namespace BansheeEngine
+{
+	struct Timer::Data
+	{
+		clock_t zeroClock;
+
+		LARGE_INTEGER startTime;
+		LARGE_INTEGER frequency;
+	};
+
+	Timer::Timer()
+	{
+		m = bs_new<Data>();
+		reset();
+	}
+
+	Timer::~Timer()
+	{
+		bs_delete(m);
+	}
+
+	void Timer::reset()
+	{
+		QueryPerformanceFrequency(&m->frequency);
+		QueryPerformanceCounter(&m->startTime);
+
+		m->zeroClock = clock();
+	}
+
+	unsigned long Timer::getMilliseconds()
+	{
+		LARGE_INTEGER curTime;
+		QueryPerformanceCounter(&curTime);
+
+		LONGLONG newTime = curTime.QuadPart - m->startTime.QuadPart;
+    
+		// Scale by 1000 for milliseconds
+		unsigned long newTicks = (unsigned long)(1000 * newTime / m->frequency.QuadPart);
+
+		return newTicks;
+	}
+
+	unsigned long Timer::getStartMs() const
+	{
+		unsigned long newTicks = (unsigned long)(1000 * m->startTime.QuadPart / m->frequency.QuadPart);
+
+		return newTicks;
+	}
+
+	unsigned long Timer::getMicroseconds()
+	{
+		LARGE_INTEGER curTime;
+		QueryPerformanceCounter(&curTime);
+
+		LONGLONG newTime = curTime.QuadPart - m->startTime.QuadPart;
+    
+		// Scale by 1000000 for microseconds
+		unsigned long newMicro = (unsigned long)(1000000 * newTime / m->frequency.QuadPart);
+
+		return newMicro;
+	}
+
+	unsigned long Timer::getMillisecondsCPU()
+	{
+		clock_t newClock = clock();
+		return (unsigned long)((float)(newClock - m->zeroClock) / ((float)CLOCKS_PER_SEC / 1000.0f));
+	}
+
+
+	unsigned long Timer::getMicrosecondsCPU()
+	{
+		clock_t newClock = clock();
+		return (unsigned long)((float)(newClock - m->zeroClock) / ((float)CLOCKS_PER_SEC / 1000000.0f));
+	}
+}

+ 11 - 0
RenderBeast/Include/BsRenderBeast.h

@@ -226,6 +226,17 @@ namespace BansheeEngine
 		 */
 		void refreshSamplerOverrides(bool force = false);
 
+		/**
+		 * @brief	Extracts the necessary values from the projection matrix that allow you to transform
+		 * 			device Z value into world Z value.
+		 * 			
+		 * @param	projMatrix	Projection matrix that was used to create the device Z value to transform.
+		 * 						
+		 * @returns	Returns two values that can be used to transform device z to world z using this formula:
+		 * 			z = 1 / (deviceZ * x - y)
+		 */
+		static Vector2 getDeviceZTransform(const Matrix4& projMatrix);
+
 		/**
 		 * @brief	Activates the specified pass on the pipeline.
 		 *

+ 9 - 0
RenderBeast/Source/BsRenderBeast.cpp

@@ -741,6 +741,15 @@ namespace BansheeEngine
 		cameraData.transparentQueue->sort();
 	}
 
+	Vector2 RenderBeast::getDeviceZTransform(const Matrix4& projMatrix)
+	{
+		Vector2 output;
+		output.x = 1.0f / projMatrix[2][2];
+		output.y = projMatrix[2][3] / projMatrix[2][2];
+
+		return output;
+	}
+
 	void RenderBeast::refreshSamplerOverrides(bool force)
 	{
 		for (auto& entry : mSamplerOverrides)

+ 12 - 25
TODOExperimentation.txt

@@ -7,33 +7,20 @@ Determine how is light bleeding handled (if at all)
 ---------------------- IMPLEMENTATION ---------------------------
 
 Next week:
- - Store render targets per camera (create and update them as needed)
- - Create shaders that write to gbuffer (DX11/OpenGL only)
- - Create shaders that render lights for deferred rendering
+ - Finish up DefferedPointLightPass by generating cone geometry in shader
+ - Ensure all shaders compile
+ - Generate C++ code for populating cbuffers for deferred shaders (2+ days)
+ - Modify Light so it generated adequate number of vertices required for cone geometry, without actually creating the cone
  - Think about how to handle post-processing shaders (HDR tone mapping)
  - Add cube and 3D support for render texture pool
+ - Load up and set up a test-bed with Ribek's scene
 
-When doing viewport clear in DX11 it will only clear the first render target
-RenderTexturePool needs support for cube and 3D textures
-Load up and set up a test-bed with Ribek's scene
-Quantize buffer sizes so they're divideable by 8 when requesting them from RenderTexturePool
-
-R11G11B10 and R10G10B10A2 formats haven't been tested
-
-Rendering procedure should be:
- - For each camera go over each renderable and determine if their layers match
- - If they match do frustum culling
- - Add renderables to opaque or transparent queue depending on material
- - This should happen in renderAllCore before actual rendering starts
- - When rendering bind gbuffer render targets and render opaque queue
- - Unbind gbuffer and resolve to scene color
- - Bind scene color and render transparent queue
- - Resolve scene color to frame buffer (they could be the same target if no MSAA is used, and gamma correction is properly set)
- - After rendering is done make sure to clear the queues so I don't hold invalid references
-
-Store RenderTargets per camera
- - Only create it if camera is rendering some renderables
- - If none are rendered clear the reference to free the targets
+Notes:
+ - When doing viewport clear in DX11 it will only clear the first render target
+ - Quantize buffer sizes so they're divideable by 8 when requesting them from RenderTexturePool
+ - R11G11B10 and R10G10B10A2 formats haven't been tested
+ - Will need to ensure the code works in OpenGL (means porting shaders or building the cross compiler). I cannot delay 
+   this as later it will be hard to debug when the pipeline is more complex.
 
 Generate different RenderableController for each set of elements
  - Will likely want to rename current LitTexRenderableController to OpaqueSomething
@@ -101,7 +88,7 @@ Later:
  - Proper PBR materials with reflection
  - Post-processing system - FXAA, SSAO, Color correction, Depth of field (Bokeh)
  - Forward rendering for transparent objects
- - Occlusion
+ - Occlusion culling
  - GI
  - Volumetric lighting
  - SSR