summaryrefslogtreecommitdiff
path: root/Runtime/Profiler
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Profiler')
-rw-r--r--Runtime/Profiler/CollectProfilerStats.cpp193
-rw-r--r--Runtime/Profiler/CollectProfilerStats.h18
-rw-r--r--Runtime/Profiler/DeprecatedFrameStatsProfiler.cpp23
-rw-r--r--Runtime/Profiler/DeprecatedFrameStatsProfiler.h28
-rw-r--r--Runtime/Profiler/ExternalGraphicsProfiler.h20
-rw-r--r--Runtime/Profiler/ExtractLoadedObjectInfo.cpp76
-rw-r--r--Runtime/Profiler/ExtractLoadedObjectInfo.h26
-rw-r--r--Runtime/Profiler/GPUProfiler.cpp100
-rw-r--r--Runtime/Profiler/GPUProfiler.h24
-rw-r--r--Runtime/Profiler/IntelGPAProfiler.cpp14
-rw-r--r--Runtime/Profiler/IntelGPAProfiler.h25
-rw-r--r--Runtime/Profiler/MemoryProfiler.cpp1003
-rw-r--r--Runtime/Profiler/MemoryProfiler.h237
-rw-r--r--Runtime/Profiler/MemoryProfilerStats.cpp158
-rw-r--r--Runtime/Profiler/MemoryProfilerStats.h75
-rw-r--r--Runtime/Profiler/MemoryProfilerTests.cpp73
-rw-r--r--Runtime/Profiler/ObjectMemoryProfiler.cpp245
-rw-r--r--Runtime/Profiler/ObjectMemoryProfiler.h19
-rw-r--r--Runtime/Profiler/Profiler.h187
-rw-r--r--Runtime/Profiler/ProfilerConnection.cpp353
-rw-r--r--Runtime/Profiler/ProfilerConnection.h91
-rw-r--r--Runtime/Profiler/ProfilerFrameData.cpp230
-rw-r--r--Runtime/Profiler/ProfilerFrameData.h134
-rw-r--r--Runtime/Profiler/ProfilerHistory.cpp560
-rw-r--r--Runtime/Profiler/ProfilerHistory.h113
-rw-r--r--Runtime/Profiler/ProfilerImpl.cpp1682
-rw-r--r--Runtime/Profiler/ProfilerImpl.h304
-rw-r--r--Runtime/Profiler/ProfilerProperty.cpp789
-rw-r--r--Runtime/Profiler/ProfilerProperty.h162
-rw-r--r--Runtime/Profiler/ProfilerStats.cpp432
-rw-r--r--Runtime/Profiler/ProfilerStats.h323
-rw-r--r--Runtime/Profiler/SerializationUtility.cpp24
-rw-r--r--Runtime/Profiler/SerializationUtility.h41
-rw-r--r--Runtime/Profiler/SharkProfiler.cpp93
-rw-r--r--Runtime/Profiler/SharkProfiler.h7
-rw-r--r--Runtime/Profiler/TimeHelper.cpp190
-rw-r--r--Runtime/Profiler/TimeHelper.h121
37 files changed, 8193 insertions, 0 deletions
diff --git a/Runtime/Profiler/CollectProfilerStats.cpp b/Runtime/Profiler/CollectProfilerStats.cpp
new file mode 100644
index 0000000..2efe7c3
--- /dev/null
+++ b/Runtime/Profiler/CollectProfilerStats.cpp
@@ -0,0 +1,193 @@
+#include "UnityPrefix.h"
+#include "CollectProfilerStats.h"
+#include "Runtime/Allocator/MemoryManager.h"
+
+#if ENABLE_PROFILER
+
+#if UNITY_OSX
+#include <mach/mach.h>
+#elif UNITY_WIN && !UNITY_WP8
+#include "Psapi.h"
+#elif UNITY_XENON
+#include "PlatformDependent/Xbox360/Source/XenonMemory.h"
+#endif
+
+#include "Runtime/Profiler/ProfilerStats.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/BaseClasses/BaseObject.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Graphics/RenderTexture.h"
+#include "Runtime/Shaders/VBO.h"
+#include "Runtime/Filters/Deformation/SkinnedMeshFilter.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Runtime/Profiler/ProfilerImpl.h"
+#include "Runtime/Profiler/TimeHelper.h"
+#include "Runtime/Audio/AudioManager.h"
+#include "Runtime/Profiler/MemoryProfilerStats.h"
+#include "MemoryProfiler.h"
+#include "Runtime/Misc/SystemInfo.h"
+#include "Runtime/Dynamics/PhysicsModule.h"
+#include "Runtime/Interfaces/IPhysics.h"
+#include "Runtime/Interfaces/IPhysics2D.h"
+#include "Runtime/Interfaces/IAudio.h"
+
+void CollectDrawStats (DrawStats& drawStats)
+{
+ // Read GFX Device and populate rendering statistics with obtained information
+ drawStats.drawCalls = GetGfxDevice().GetFrameStats().GetDrawStats().calls;
+ drawStats.triangles = GetGfxDevice().GetFrameStats().GetDrawStats().tris;
+ drawStats.vertices = GetGfxDevice().GetFrameStats().GetDrawStats().verts;
+
+ drawStats.batchedDrawCalls = GetGfxDevice().GetFrameStats().GetDrawStats().batchedCalls;
+ drawStats.batchedTriangles = GetGfxDevice().GetFrameStats().GetDrawStats().batchedTris;
+ drawStats.batchedVertices = GetGfxDevice().GetFrameStats().GetDrawStats().batchedVerts;
+
+ drawStats.shadowCasters = GetGfxDevice().GetFrameStats().GetClientStats().shadowCasters;
+
+ GetGfxDevice().GetFrameStats().AccumulateUsedTextureUsage();
+ drawStats.usedTextureCount = GetGfxDevice().GetFrameStats().GetDrawStats().usedTextureCount;
+ drawStats.usedTextureBytes = GetGfxDevice().GetFrameStats().GetDrawStats().usedTextureBytes;
+
+ drawStats.renderTextureCount = RenderTexture::GetCreatedRenderTextureCount();
+ drawStats.renderTextureBytes = RenderTexture::GetCreatedRenderTextureBytes();
+ drawStats.renderTextureStateChanges = GetGfxDevice().GetFrameStats().GetStateChanges().renderTexture;
+
+ drawStats.screenWidth = GetGfxDevice().GetFrameStats().GetMemoryStats().screenWidth;
+ drawStats.screenHeight = GetGfxDevice().GetFrameStats().GetMemoryStats().screenHeight;
+ drawStats.screenFSAA = GetGfxDevice().GetFrameStats().GetMemoryStats().screenFSAA;
+ drawStats.screenBytes = GetGfxDevice().GetFrameStats().GetMemoryStats().screenBytes;
+
+ drawStats.vboTotal = GetGfxDevice().GetTotalVBOCount();
+ drawStats.vboTotalBytes = GetGfxDevice().GetTotalVBOBytes();
+ drawStats.vboUploads = GetGfxDevice().GetFrameStats().GetStateChanges().vboUploads;
+ drawStats.vboUploadBytes = GetGfxDevice().GetFrameStats().GetStateChanges().vboUploadBytes;
+ drawStats.ibUploads = GetGfxDevice().GetFrameStats().GetStateChanges().ibUploads;
+ drawStats.ibUploadBytes = GetGfxDevice().GetFrameStats().GetStateChanges().ibUploadBytes;
+ drawStats.totalAvailableVRamMBytes = RoundfToInt(gGraphicsCaps.videoMemoryMB);
+
+ drawStats.visibleSkinnedMeshes = SkinnedMeshRenderer::GetVisibleSkinnedMeshRendererCount();
+}
+
+
+
+static void GatherObjectAllocationInformation(const dynamic_array<Object*>& objs, int& nbObjects, int& bytes)
+{
+ nbObjects = objs.size();
+ bytes = 0;
+ for (int i=0; i<nbObjects; i++)
+ bytes += objs[i]->GetRuntimeMemorySize();
+}
+
+void CollectMemoryAllocationStats(MemoryStats& memoryStats)
+{
+ GatherObjectAllocationInformation( GetMemoryProfilerStats().GetTextures(), memoryStats.textureCount, memoryStats.textureBytes);
+ GatherObjectAllocationInformation( GetMemoryProfilerStats().GetMeshes(), memoryStats.meshCount, memoryStats.meshBytes);
+ GatherObjectAllocationInformation( GetMemoryProfilerStats().GetMaterials(), memoryStats.materialCount, memoryStats.materialBytes);
+ GatherObjectAllocationInformation( GetMemoryProfilerStats().GetAnimationClips(), memoryStats.animationClipCount, memoryStats.animationClipBytes);
+ GatherObjectAllocationInformation( GetMemoryProfilerStats().GetAudioClips(), memoryStats.audioCount, memoryStats.audioBytes);
+ memoryStats.totalObjectsCount = Object::GetLoadedObjectCount();
+
+#if ENABLE_MEMORY_MANAGER
+
+ #if UNITY_XENON
+ size_t additionalUsedMemoryUnity = xenon::GetOtherMemoryAllocated();
+ size_t additionalUsedMemorySystem = 32 * 1024 * 1024; // OS reserved
+ #else
+ size_t additionalUsedMemoryUnity = 0;
+ size_t additionalUsedMemorySystem = 0;
+ #endif
+
+ memoryStats.bytesUsedProfiler = GetMemoryManager().GetAllocator(kMemProfiler)->GetAllocatedMemorySize();
+ memoryStats.bytesUsedFMOD = GetMemoryManager().GetAllocatedMemory(kMemFMOD);
+ memoryStats.bytesUsedUnity = GetUsedHeapSize() - memoryStats.bytesUsedProfiler - memoryStats.bytesUsedFMOD + additionalUsedMemoryUnity;
+ #if ENABLE_MONO
+ memoryStats.bytesUsedMono = mono_gc_get_used_size();
+ #else
+ memoryStats.bytesUsedMono = 0;
+ #endif
+ memoryStats.bytesUsedGFX = GetMemoryManager().GetRegisteredGFXDriverMemory();
+ memoryStats.bytesUsedTotal = memoryStats.bytesUsedUnity + memoryStats.bytesUsedMono + memoryStats.bytesUsedGFX + memoryStats.bytesUsedProfiler + additionalUsedMemorySystem;
+
+ memoryStats.bytesReservedProfiler = GetMemoryManager().GetAllocator(kMemProfiler)->GetReservedSizeTotal();
+ memoryStats.bytesReservedFMOD = GetMemoryManager().GetAllocatedMemory(kMemFMOD);
+ memoryStats.bytesReservedUnity = GetMemoryManager().GetTotalReservedMemory() - memoryStats.bytesReservedProfiler - memoryStats.bytesReservedFMOD + additionalUsedMemoryUnity;
+ #if ENABLE_MONO
+ memoryStats.bytesReservedMono = mono_gc_get_heap_size();
+ #else
+ memoryStats.bytesReservedMono = 0;
+ #endif
+ memoryStats.bytesReservedGFX = GetMemoryManager().GetRegisteredGFXDriverMemory();
+ memoryStats.bytesReservedTotal = memoryStats.bytesReservedUnity + memoryStats.bytesReservedMono + memoryStats.bytesReservedGFX + memoryStats.bytesReservedProfiler + additionalUsedMemorySystem;
+
+ memoryStats.assetCount = GetMemoryProfilerStats().GetAssetCount();
+ memoryStats.sceneObjectCount = GetMemoryProfilerStats().GetSceneObjectCount();
+ memoryStats.gameObjectCount = GetMemoryProfilerStats().GetGameObjectCount();
+ memoryStats.classCount = GetMemoryProfilerStats().GetClassCount();
+
+ memoryStats.bytesVirtual = systeminfo::GetUsedVirtualMemoryMB() * 1024*1024;
+
+ #if UNITY_WP8
+ memoryStats.bytesCommitedLimit = systeminfo::GetCommitedMemoryLimitMB() * 1024 * 1024;
+ memoryStats.bytesCommitedTotal = systeminfo::GetCommitedMemoryMB() * 1024 * 1024;
+ #else
+ memoryStats.bytesCommitedLimit = 0;
+ memoryStats.bytesCommitedTotal = 0;
+ #endif
+
+#if ENABLE_MEM_PROFILER
+ //memoryStats.memoryOverview = GetMemoryProfiler()->GetOverview();
+ #endif
+
+#endif // #if ENABLE_MEMORY_MANAGER
+}
+
+void CollectProfilerStats (AllProfilerStats& stats)
+{
+ CollectMemoryAllocationStats(stats.memoryStats);
+ CollectDrawStats(stats.drawStats);
+
+ UnityProfiler::Get().GetDebugStats(stats.debugStats);
+
+ IAudio* audioModule = GetIAudio();
+ if (audioModule)
+ audioModule->GetProfilerStats(stats.audioStats);
+
+ IPhysics* physicsModule = GetIPhysics();
+ if (physicsModule)
+ physicsModule->GetProfilerStats(stats.physicsStats);
+
+ IPhysics2D* physics2DModule = GetIPhysics2D ();
+ if (physics2DModule)
+ physics2DModule->GetProfilerStats (stats.physics2DStats);
+}
+
+ProfilerString GetMiniMemoryOverview()
+{
+#if ENABLE_MEMORY_MANAGER
+ return ("Allocated: " + FormatBytes(GetUsedHeapSize()) + " Objects: " + IntToString(Object::GetLoadedObjectCount ())).c_str();
+#else
+ return "";
+#endif
+}
+
+#endif
+
+unsigned GetUsedHeapSize()
+{
+#if ENABLE_MEMORY_MANAGER
+
+#if (UNITY_OSX && UNITY_EDITOR)
+ UInt32 osxversion = 0;
+ Gestalt(gestaltSystemVersion, (MacSInt32 *) &osxversion);
+ if(osxversion >= 0x01060)
+ return MemoryManager::m_LowLevelAllocated - GetMemoryManager().GetTotalUnusedReservedMemory();
+ else
+ return 0;
+#else
+ return GetMemoryManager().GetTotalAllocatedMemory();
+#endif
+
+#else
+ return 0;
+#endif
+}
diff --git a/Runtime/Profiler/CollectProfilerStats.h b/Runtime/Profiler/CollectProfilerStats.h
new file mode 100644
index 0000000..cf446a9
--- /dev/null
+++ b/Runtime/Profiler/CollectProfilerStats.h
@@ -0,0 +1,18 @@
+#ifndef _COLLECT_PROFILER_STATS_H_
+#define _COLLECT_PROFILER_STATS_H_
+
+#include "Configuration/UnityConfigure.h"
+
+struct AllProfilerStats;
+struct MemoryStats;
+
+#if ENABLE_PROFILER
+
+void CollectProfilerStats (AllProfilerStats& stats);
+ProfilerString GetMiniMemoryOverview();
+
+#endif
+
+unsigned GetUsedHeapSize();
+
+#endif \ No newline at end of file
diff --git a/Runtime/Profiler/DeprecatedFrameStatsProfiler.cpp b/Runtime/Profiler/DeprecatedFrameStatsProfiler.cpp
new file mode 100644
index 0000000..be04bcb
--- /dev/null
+++ b/Runtime/Profiler/DeprecatedFrameStatsProfiler.cpp
@@ -0,0 +1,23 @@
+#include "Runtime/Profiler/DeprecatedFrameStatsProfiler.h"
+#include "Runtime/Profiler/TimeHelper.h"
+
+#if UNITY_OSX || UNITY_IPHONE
+#include <mach/mach_time.h>
+#endif
+
+FrameStats::Timestamp GetTimestamp()
+{
+#if UNITY_OSX || UNITY_IPHONE
+ return mach_absolute_time();
+#elif UNITY_ANDROID
+ return START_TIME;
+#else
+ return (signed long long)(START_TIME);//GetTimeSinceStartup ()*1000000000.0);
+#endif
+}
+static FrameStats gFrameStats;
+
+FrameStats const& GetFrameStats()
+{
+ return gFrameStats;
+} \ No newline at end of file
diff --git a/Runtime/Profiler/DeprecatedFrameStatsProfiler.h b/Runtime/Profiler/DeprecatedFrameStatsProfiler.h
new file mode 100644
index 0000000..24eb11b
--- /dev/null
+++ b/Runtime/Profiler/DeprecatedFrameStatsProfiler.h
@@ -0,0 +1,28 @@
+#pragma once
+
+struct FrameStats
+{
+ typedef signed long long Timestamp;
+
+ void reset() {
+ fixedBehaviourManagerDt = 0;
+ fixedPhysicsManagerDt = 0;
+ dynamicBehaviourManagerDt = 0;
+ coroutineDt = 0;
+ skinMeshUpdateDt = 0;
+ animationUpdateDt = 0;
+ renderDt = 0;
+
+ fixedUpdateCount = 0;
+ }
+
+ Timestamp fixedBehaviourManagerDt;
+ Timestamp fixedPhysicsManagerDt;
+ Timestamp dynamicBehaviourManagerDt;
+ Timestamp coroutineDt;
+ Timestamp skinMeshUpdateDt;
+ Timestamp animationUpdateDt;
+ Timestamp renderDt;
+ int fixedUpdateCount;
+};
+FrameStats const& GetFrameStats(); \ No newline at end of file
diff --git a/Runtime/Profiler/ExternalGraphicsProfiler.h b/Runtime/Profiler/ExternalGraphicsProfiler.h
new file mode 100644
index 0000000..89b82cc
--- /dev/null
+++ b/Runtime/Profiler/ExternalGraphicsProfiler.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "Profiler.h"
+
+#if ENABLE_PROFILER
+
+#include "Runtime/GfxDevice/GfxDevice.h"
+
+struct AutoGfxEventMainThread {
+ AutoGfxEventMainThread(const char* name) { GetGfxDevice().BeginProfileEvent(name); }
+ ~AutoGfxEventMainThread() { GetGfxDevice().EndProfileEvent(); }
+};
+#define PROFILER_AUTO_GFX(INFO, OBJECT_PTR) PROFILER_AUTO(INFO,OBJECT_PTR); AutoGfxEventMainThread _gfx_event(INFO.name);
+
+
+#else // no profiling compiled in
+
+# define PROFILER_AUTO_GFX(INFO, OBJECT_PTR)
+
+#endif
diff --git a/Runtime/Profiler/ExtractLoadedObjectInfo.cpp b/Runtime/Profiler/ExtractLoadedObjectInfo.cpp
new file mode 100644
index 0000000..aede649
--- /dev/null
+++ b/Runtime/Profiler/ExtractLoadedObjectInfo.cpp
@@ -0,0 +1,76 @@
+#include "UnityPrefix.h"
+#if ENABLE_PROFILER
+#include "ExtractLoadedObjectInfo.h"
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Serialize/PersistentManager.h"
+#include "Runtime/Misc/GarbageCollectSharedAssets.h"
+
+LoadedObjectMemoryType GetLoadedObjectReason (Object* object)
+{
+ bool referencedOnlyByNativeData = false;
+
+ if(!object->GetCachedScriptingObject ())
+ referencedOnlyByNativeData = true;
+
+ bool isPersistent = object->IsPersistent();
+
+ // Builtin resource (Treated as root)
+ if (isPersistent)
+ {
+ string path = GetPersistentManager().GetPathName(object->GetInstanceID());
+ if (path == "library/unity editor resources" || path == "library/unity default resources")
+ {
+ return kBuiltinResource;
+ }
+ }
+
+ // Objects marked DontSave (Treated as root)
+ if (object->TestHideFlag(Object::kDontSave))
+ {
+ return kMarkedDontSave;
+ }
+
+ // Asset marked dirty in the editor (Treated as root)
+#if UNITY_EDITOR
+ if (isPersistent && object->IsPersistentDirty() && ShouldPersistentDirtyObjectBeKeptAlive(object->GetInstanceID()))
+ {
+ return kAssetMarkedDirtyInEditor;
+ }
+#endif
+
+ // gameobjects and components in the scene (treated as roots)
+ if (!isPersistent)
+ {
+ int classID = object->GetClassID();
+ if (classID == ClassID (GameObject))
+ {
+ return kSceneObject;
+ }
+ else if (Object::IsDerivedFromClassID(classID, ClassID(Component)))
+ {
+ Unity::Component* com = static_cast<Unity::Component*> (object);
+ if (com->GetGameObjectPtr())
+ return kSceneObject;
+ }
+ }
+
+ // Asset is being referenced from something, specify what
+ if (!isPersistent)
+ {
+ if (referencedOnlyByNativeData)
+ return kSceneAssetReferencedByNativeCodeOnly;
+ else
+ return kSceneAssetReferenced;
+ }
+ else
+ {
+ if (referencedOnlyByNativeData)
+ return kAssetReferencedByNativeCodeOnly;
+ else
+ return kAssetReferenced;
+ }
+
+ return kNotApplicable;
+}
+
+#endif
diff --git a/Runtime/Profiler/ExtractLoadedObjectInfo.h b/Runtime/Profiler/ExtractLoadedObjectInfo.h
new file mode 100644
index 0000000..50cd643
--- /dev/null
+++ b/Runtime/Profiler/ExtractLoadedObjectInfo.h
@@ -0,0 +1,26 @@
+#ifndef EXTRACT_LOADED_OBJECT_INFO_H
+#define EXTRACT_LOADED_OBJECT_INFO_H
+
+#if ENABLE_PROFILER
+
+// Enum is in sync with ProfilerAPI.txt GarbageCollectReason
+enum LoadedObjectMemoryType
+{
+ kSceneObject = 0,
+ kBuiltinResource = 1,
+ kMarkedDontSave = 2,
+ kAssetMarkedDirtyInEditor = 3,
+
+ kSceneAssetReferencedByNativeCodeOnly = 5,
+ kSceneAssetReferenced = 6,
+
+ kAssetReferencedByNativeCodeOnly = 8,
+ kAssetReferenced = 9,
+ kNotApplicable = 10
+};
+
+LoadedObjectMemoryType GetLoadedObjectReason (Object* object);
+
+#endif
+
+#endif
diff --git a/Runtime/Profiler/GPUProfiler.cpp b/Runtime/Profiler/GPUProfiler.cpp
new file mode 100644
index 0000000..c452853
--- /dev/null
+++ b/Runtime/Profiler/GPUProfiler.cpp
@@ -0,0 +1,100 @@
+#include "UnityPrefix.h"
+#if ENABLE_PROFILER
+
+#include "GPUProfiler.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/GfxDevice/GfxTimerQuery.h"
+#include "ProfilerImpl.h"
+#include "ProfilerFrameData.h"
+
+PROFILER_INFORMATION(gBeginQueriesProf, "GPUProfiler.BeginQueries", kProfilerOverhead)
+PROFILER_INFORMATION(gEndQueriesProf, "GPUProfiler.EndQueries", kProfilerOverhead)
+
+
+void GPUProfiler::GPUTimeSample()
+{
+ // GPU samples should only be added on the main thread.
+ DebugAssert (Thread::CurrentThreadIsMainThread ());
+
+ UnityProfilerPerThread* prof = UnityProfilerPerThread::ms_InstanceTLS;
+ DebugAssert(prof);
+ if (!prof->GetIsActive() || !gGraphicsCaps.hasTimerQuery || gGraphicsCaps.buggyTimerQuery)
+ return;
+
+ GfxTimerQuery* timer = ProfilerFrameData::AllocTimerQuery();
+ timer->Measure();
+ ProfilerData::GPUTime sample = {prof->GetActiveSampleIndex(), timer, 0xFFFFFFFF, g_CurrentGPUSection};
+ prof->AddGPUSample(sample);
+}
+
+void GPUProfiler::BeginFrame()
+{
+ PROFILER_AUTO(gBeginQueriesProf, NULL);
+ GetGfxDevice().BeginTimerQueries();
+}
+
+void GPUProfiler::EndFrame()
+{
+ GPU_TIMESTAMP();
+ PROFILER_AUTO(gEndQueriesProf, NULL);
+ GetGfxDevice().EndTimerQueries();
+}
+
+bool GPUProfiler::CollectGPUTime( dynamic_array<ProfilerData::GPUTime>& gpuSamples, bool wait )
+{
+ if(!gGraphicsCaps.hasTimerQuery)
+ return false;
+
+ UInt32 flags = wait ? GfxTimerQuery::kWaitAll : GfxTimerQuery::kWaitRenderThread;
+
+ // Gather query times
+ for(int i = 0; i < gpuSamples.size(); i++)
+ {
+ ProfilerData::GPUTime& sample = gpuSamples[i];
+ if (sample.timerQuery != NULL)
+ {
+ ProfileTimeFormat elapsed = sample.timerQuery->GetElapsed(flags);
+ sample.gpuTimeInMicroSec = elapsed == kInvalidProfileTime? 0xFFFFFFFF: elapsed/1000;
+ if (wait || sample.gpuTimeInMicroSec != 0xFFFFFFFF)
+ {
+ // Recycle query object
+ ProfilerFrameData::ReleaseTimerQuery(sample.timerQuery);
+ sample.timerQuery = NULL;
+ }
+ }
+ }
+ return true;
+}
+
+int GPUProfiler::ComputeGPUTime( dynamic_array<ProfilerData::GPUTime>& gpuSamples )
+{
+ if (!CollectGPUTime(gpuSamples, true))
+ return 0;
+
+ // Why is the first sample invalid?
+ if (!gpuSamples.empty())
+ gpuSamples[0].gpuTimeInMicroSec = 0;
+
+ int totalTimeMicroSec = 0;
+ for(int i = 0; i < gpuSamples.size(); i++)
+ {
+ totalTimeMicroSec += gpuSamples[i].gpuTimeInMicroSec;
+ }
+ return totalTimeMicroSec;
+}
+
+void GPUProfiler::ClearTimerQueries ( dynamic_array<ProfilerData::GPUTime>& gpuSamples )
+{
+ for(int i = 0; i < gpuSamples.size(); i++)
+ {
+ ProfilerData::GPUTime& sample = gpuSamples[i];
+ if (sample.timerQuery != NULL)
+ {
+ // Recycle query object
+ ProfilerFrameData::ReleaseTimerQuery(sample.timerQuery);
+ sample.timerQuery = NULL;
+ }
+ }
+}
+
+#endif // ENABLE_PROFILER
diff --git a/Runtime/Profiler/GPUProfiler.h b/Runtime/Profiler/GPUProfiler.h
new file mode 100644
index 0000000..7d2bf17
--- /dev/null
+++ b/Runtime/Profiler/GPUProfiler.h
@@ -0,0 +1,24 @@
+#ifndef _GPUPROFILER_H_
+#define _GPUPROFILER_H_
+
+#if ENABLE_PROFILER
+
+struct ProfilerSample;
+#include "ProfilerImpl.h"
+#include "Runtime/Utilities/dynamic_array.h"
+
+class GPUProfiler
+{
+public:
+ static void BeginFrame();
+ static void EndFrame();
+
+ static void GPUTimeSample ( );
+
+ static bool CollectGPUTime ( dynamic_array<ProfilerData::GPUTime>& gpuSamples, bool wait );
+ static int ComputeGPUTime ( dynamic_array<ProfilerData::GPUTime>& gpuSamples);
+ static void ClearTimerQueries ( dynamic_array<ProfilerData::GPUTime>& gpuSamples );
+};
+
+#endif
+#endif
diff --git a/Runtime/Profiler/IntelGPAProfiler.cpp b/Runtime/Profiler/IntelGPAProfiler.cpp
new file mode 100644
index 0000000..8400d0b
--- /dev/null
+++ b/Runtime/Profiler/IntelGPAProfiler.cpp
@@ -0,0 +1,14 @@
+#include "UnityPrefix.h"
+#include "IntelGPAProfiler.h"
+
+#if INTEL_GPA_PROFILER_AVAILABLE
+
+__itt_domain* g_IntelGPADomain;
+
+void InitializeIntelGPAProfiler()
+{
+ g_IntelGPADomain = __itt_domain_createA("Unity.Player");
+}
+
+
+#endif
diff --git a/Runtime/Profiler/IntelGPAProfiler.h b/Runtime/Profiler/IntelGPAProfiler.h
new file mode 100644
index 0000000..bd538d0
--- /dev/null
+++ b/Runtime/Profiler/IntelGPAProfiler.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#define INTEL_GPA_PROFILER_AVAILABLE (ENABLE_PROFILER && UNITY_WIN && !UNITY_EDITOR && !WEBPLUG && !UNITY_64 && !UNITY_WINRT)
+
+#if INTEL_GPA_PROFILER_AVAILABLE
+#include "External/IntelGPASDK/include/ittnotify.h"
+
+void InitializeIntelGPAProfiler();
+extern __itt_domain* g_IntelGPADomain;
+
+#define INTEL_GPA_INFORMATION_DATA __itt_string_handle* intelGPAData;
+#define INTEL_GPA_INFORMATION_INITIALIZE() intelGPAData = __itt_string_handle_create(functionName)
+#define INTEL_GPA_SAMPLE_BEGIN(info) __itt_task_begin(g_IntelGPADomain, __itt_null, __itt_null, (__itt_string_handle*) info->intelGPAData)
+#define INTEL_GPA_SAMPLE_END() __itt_task_end(g_IntelGPADomain)
+
+#else
+
+#define INTEL_GPA_INFORMATION_DATA
+#define INTEL_GPA_INFORMATION_INITIALIZE()
+#define INTEL_GPA_SAMPLE_BEGIN(info)
+#define INTEL_GPA_SAMPLE_END()
+
+#endif
diff --git a/Runtime/Profiler/MemoryProfiler.cpp b/Runtime/Profiler/MemoryProfiler.cpp
new file mode 100644
index 0000000..485712d
--- /dev/null
+++ b/Runtime/Profiler/MemoryProfiler.cpp
@@ -0,0 +1,1003 @@
+#include "UnityPrefix.h"
+
+#include "Runtime/Profiler/MemoryProfiler.h"
+#include "Runtime/Allocator/MemoryManager.h"
+
+#if ENABLE_MEM_PROFILER
+
+#include "Runtime/Allocator/MemoryMacros.h"
+#include "Runtime/Utilities/BitUtility.h"
+
+#if RECORD_ALLOCATION_SITES
+#include "Runtime/Mono/MonoManager.h"
+#include "Runtime/Mono/MonoUtility.h"
+#endif
+
+#if !UNITY_EDITOR && ENABLE_STACKS_ON_ALL_ALLOCS
+#include "Runtime/Utilities/Stacktrace.cpp"
+#endif
+
+#include "Runtime/Threads/AtomicOps.h"
+
+#define ROOT_UNRELATED_ALLOCATIONS 0
+
+MemoryProfiler* MemoryProfiler::s_MemoryProfiler = NULL;
+UNITY_TLS_VALUE(ProfilerAllocationHeader**) MemoryProfiler::m_RootStack;
+UNITY_TLS_VALUE(UInt32) MemoryProfiler::m_RootStackSize;
+UNITY_TLS_VALUE(ProfilerAllocationHeader**) MemoryProfiler::m_CurrentRootHeader;
+UNITY_TLS_VALUE(bool) MemoryProfiler::m_RecordingAllocation;
+
+struct ProfilerAllocationHeader
+{
+ enum RootStatusMask
+ {
+ kIsRootAlloc = 0x1,
+ kRegisteredRoot = 0x2
+ };
+
+#if MAINTAIN_RELATED_ALLOCATION_LIST
+ // pointer to next allocation in root chain. Root is first element in this list
+ ProfilerAllocationHeader* next;
+ union
+ {
+ // pointer to prev allocation in root chain. Needed for removal
+ ProfilerAllocationHeader* prev;
+ // for Roots, the rootReference points to the AllocationRootReference,
+ AllocationRootReference* rootReference;
+ };
+
+#endif
+
+ volatile int accumulatedSize; // accumulated size of every allocation that relates to this one - just size if child alloc
+ ProfilerAllocationHeader* GetRootPtr() { return (ProfilerAllocationHeader*)((size_t)relatesTo&~0x3); }
+ void SetRootPtr (ProfilerAllocationHeader* ptr) { relatesTo = ptr; }
+ bool IsRoot () { return ((size_t)relatesTo&kIsRootAlloc)!=0; }
+ bool IsRegisteredRoot () { return ((size_t)relatesTo&kRegisteredRoot)!=0; }
+
+ void SetAsRegisteredRoot () { relatesTo = (ProfilerAllocationHeader*)(kIsRootAlloc|kRegisteredRoot); }
+
+ void SetAsRoot () { relatesTo = (ProfilerAllocationHeader*)(kIsRootAlloc); }
+
+ ProfilerAllocationHeader* relatesTo;
+
+#if RECORD_ALLOCATION_SITES
+ const MemoryProfiler::AllocationSite* site; // Site of where the allocation was done
+#endif
+};
+
+
+void MemoryProfiler::StaticInitialize()
+{
+ // This delayed initialization is necessary to avoid recursion, when TLS variables that are used
+ // by the profiler, are themselves managed by the memory manager.
+ MemoryProfiler* temp = new (MemoryManager::LowLevelAllocate(sizeof(MemoryProfiler))) MemoryProfiler();
+ temp->AllocateStructs();
+
+ s_MemoryProfiler = temp;
+}
+
+void MemoryProfiler::StaticDestroy()
+{
+ s_MemoryProfiler->~MemoryProfiler();
+ MemoryManager::LowLevelFree(s_MemoryProfiler);
+ s_MemoryProfiler = NULL;
+}
+
+#include "Runtime/Allocator/BaseAllocator.h"
+#include "Runtime/Utilities/Word.h"
+#include "Runtime/Utilities/Stacktrace.h"
+
+MemoryProfiler::MemoryProfiler()
+: m_DefaultRootHeader (NULL)
+, m_SizeUsed (0)
+, m_NumAllocations (0)
+, m_AccSizeUsed (0)
+, m_AccNumAllocations (0)
+{
+ memset (m_SizeDistribution, 0, sizeof(m_SizeDistribution));
+}
+
+MemoryProfiler::~MemoryProfiler()
+{
+ m_RecordingAllocation = true;
+#if RECORD_ALLOCATION_SITES
+ UNITY_DELETE(m_AllocationSites,kMemMemoryProfiler);
+ UNITY_DELETE(m_AllocationSizes,kMemMemoryProfiler);
+#endif
+ UNITY_DELETE(m_ReferenceIDSizes,kMemMemoryProfiler);
+ UNITY_DELETE(m_RootAllocationTypes,kMemMemoryProfiler);
+
+ // using allocator directly to avoid allocation registration
+ BaseAllocator* alloc = GetMemoryManager().GetAllocator(kMemProfiler);
+ alloc->Deallocate(m_RootStack);
+ m_RecordingAllocation = false;
+}
+
+
+void MemoryProfiler::SetupAllocationHeader(ProfilerAllocationHeader* header, ProfilerAllocationHeader* root, int size)
+{
+ header->SetRootPtr(root);
+ header->accumulatedSize = size;
+#if MAINTAIN_RELATED_ALLOCATION_LIST
+ header->next = NULL;
+ header->prev = NULL;
+#endif
+#if RECORD_ALLOCATION_SITES
+ header->site = NULL;
+#endif
+}
+
+void MemoryProfiler::AllocateStructs()
+{
+ m_RecordingAllocation = true;
+ m_RootStackSize = 20;
+ // using allocator directly to avoid allocation registration
+ BaseAllocator* alloc = GetMemoryManager().GetAllocator(kMemProfiler);
+ m_RootStack = (ProfilerAllocationHeader**)alloc->Allocate(m_RootStackSize*sizeof(ProfilerAllocationHeader*), kDefaultMemoryAlignment);
+ m_CurrentRootHeader = &m_RootStack[0];
+ *m_CurrentRootHeader = NULL;
+
+#if RECORD_ALLOCATION_SITES
+ m_AllocationSites = UNITY_NEW(AllocationSites, kMemMemoryProfiler)();
+ m_AllocationSizes = UNITY_NEW(AllocationSizes,kMemMemoryProfiler)();
+#endif
+ m_ReferenceIDSizes = UNITY_NEW(ReferenceID,kMemMemoryProfiler)();
+ m_RootAllocationTypes = UNITY_NEW(RootAllocationTypes, kMemMemoryProfiler)();
+
+#if ROOT_UNRELATED_ALLOCATIONS
+ int* defaultRootContainer = UNITY_NEW(int, kMemProfiler);
+ m_DefaultRootHeader = GetMemoryManager().GetAllocator(kMemProfiler)->GetProfilerHeader(defaultRootContainer);
+ SetupAllocationHeader(m_DefaultRootHeader, NULL, sizeof(int));
+ RegisterRootAllocation(defaultRootContainer, GetMemoryManager().GetAllocator(kMemProfiler), "UnrootedAllocations", "");
+ *m_CurrentRootHeader = m_DefaultRootHeader;
+#endif
+
+ m_RecordingAllocation = false;
+}
+
+void MemoryProfiler::ThreadCleanup()
+{
+ m_RecordingAllocation = true;
+ if(m_RootStack)
+ {
+ // using allocator directly to avoid allocation registration
+ BaseAllocator* alloc = GetMemoryManager().GetAllocator(kMemProfiler);
+ alloc->Deallocate(m_RootStack);
+ }
+ m_RecordingAllocation = false;
+}
+
+void* g_LastAllocations[2];
+int g_LastAllocationIndex = 0;
+
+void MemoryProfiler::InitAllocation(void* ptr, BaseAllocator* alloc)
+{
+ Assert(!s_MemoryProfiler);
+ if (alloc)
+ {
+ ProfilerAllocationHeader* const header = alloc->GetProfilerHeader(ptr);
+ if (header)
+ {
+ size_t const size = alloc->GetPtrSize(ptr);
+ SetupAllocationHeader(header, NULL, size);
+ }
+ }
+}
+
+void MemoryProfiler::RegisterAllocation(void* ptr, MemLabelRef label, const char* file, int line, size_t allocsize)
+{
+ BaseAllocator* alloc = GetMemoryManager().GetAllocator(label);
+ size_t size = alloc ? alloc->GetPtrSize(ptr) : allocsize;
+
+#if RECORD_ALLOCATION_SITES
+ Mutex::AutoLock lock(m_Mutex);
+#endif
+ if (m_RecordingAllocation)
+ {
+ //mircea@ due to stupid init order, the gConsolePath std::string is not initialized when the assert triggers.
+ // we really really really really need to find a good solution to avoid shit like that!
+ //Assert(label.label == kMemMemoryProfilerId);
+ ProfilerAllocationHeader* header = alloc ? alloc->GetProfilerHeader(ptr) : NULL;
+ if(header)
+ {
+ SetupAllocationHeader(header, NULL, size);
+ }
+ return;
+ }
+ m_RecordingAllocation = true;
+
+ ProfilerAllocationHeader* root = label.GetRootHeader();
+
+ if(label.UseAutoRoot())
+ {
+ if(m_CurrentRootHeader != NULL)
+ root = *m_CurrentRootHeader;
+ else
+ root = NULL;
+ }
+
+#if RECORD_ALLOCATION_SITES
+ m_SizeUsed += size;
+ m_NumAllocations++;
+ m_AccSizeUsed += size;
+ m_AccNumAllocations++;
+ m_SizeDistribution[HighestBit(size)]++;
+
+ AllocationSite site;
+ site.label = label.label;
+ site.file = file;
+ site.line = line;
+ site.allocated = 0;
+ site.alloccount = 0;
+ site.ownedAllocated = 0;
+ site.ownedCount = 0;
+ site.cummulativeAllocated = 0;
+ site.cummulativeAlloccount = 0;
+#if ENABLE_STACKS_ON_ALL_ALLOCS
+ site.stack[0] = 0;
+ static volatile bool track = false;
+ //if(root && track)
+ {
+ // if(Thread::CurrentThreadIsMainThread())
+ {
+ //static int counter = 0;
+ //if(((counter++) & 0x100) == 0 || file == NULL)
+ {
+ SET_ALLOC_OWNER(NULL);
+ site.stackHash = GetStacktrace(site.stack, 20, 3);
+ }
+ }
+ }
+#endif
+ AllocationSites::iterator it = m_AllocationSites->find(site); // std::set will allocate on insertion (very rare)
+ if(it == m_AllocationSites->end())
+ it = m_AllocationSites->insert(site).first;
+ AllocationSite* mutablesite = const_cast<AllocationSite*>(&(*it));
+ mutablesite->allocated += size;
+ mutablesite->alloccount++;
+ mutablesite->cummulativeAllocated += size;
+ mutablesite->cummulativeAlloccount++;
+ if(root)
+ {
+ mutablesite->ownedAllocated += size;
+ mutablesite->ownedCount++;
+ }
+#endif
+
+ ProfilerAllocationHeader* header = alloc ? alloc->GetProfilerHeader(ptr) : NULL;
+ if(header)
+ {
+ SetupAllocationHeader(header, root, size);
+
+#if RECORD_ALLOCATION_SITES
+ header->site = &(*it);
+#endif
+
+ if(root != NULL)
+ {
+ // Find the root owner and add this to the allocation list and accumulated size
+ if(root)
+ {
+ AtomicAdd(&root->accumulatedSize, size);
+#if MAINTAIN_RELATED_ALLOCATION_LIST
+ InsertAfterRoot(root, header);
+#endif
+ }
+ }
+
+ }
+ else if(alloc) // no alloc present for stray mallocs
+ {
+#if RECORD_ALLOCATION_SITES
+ LocalHeaderInfo info = {size, &(*it)};
+ m_AllocationSizes->insert(std::make_pair(ptr, info)); // Will allocate
+#endif
+ }
+
+ g_LastAllocations[g_LastAllocationIndex]= ptr;
+ g_LastAllocationIndex ^= 1;
+
+ m_RecordingAllocation = false;
+}
+
+void MemoryProfiler::UnregisterAllocation(void* ptr, BaseAllocator* alloc, size_t freesize, ProfilerAllocationHeader** outputRootHeader, MemLabelRef label)
+{
+ if(ptr == NULL)
+ return;
+
+#if RECORD_ALLOCATION_SITES
+ Mutex::AutoLock lock(m_Mutex);
+#endif
+ if (m_RecordingAllocation)
+ return;
+
+ size_t size = alloc ? alloc->GetPtrSize(ptr) : freesize;
+ ProfilerAllocationHeader* header = alloc ? alloc->GetProfilerHeader(ptr) : NULL;
+#if RECORD_ALLOCATION_SITES
+ if(header && header->site == NULL)
+ return;
+#endif
+
+ ProfilerAllocationHeader* root = NULL;
+ m_RecordingAllocation = true;
+
+// Assert(!header || header->IsRoot() || header->GetRootPtr() == label.GetRootHeader());
+
+#if RECORD_ALLOCATION_SITES
+ const AllocationSite* site = NULL;
+ if(!alloc)
+ {
+ #if ENABLE_STACKS_ON_ALL_ALLOCS
+ AllocationSite tmpsite = {kMemLabelCount, {0}, 0, NULL, 0, 0, 0, 0, 0, 0, 0};
+ #else
+ AllocationSite tmpsite = {kMemLabelCount, NULL, 0, 0, 0, 0, 0, 0, 0};
+ #endif
+ AllocationSites::iterator it = m_AllocationSites->insert(tmpsite).first;
+ site = &(*it);
+ }
+ else if ( !header )
+ {
+ AllocationSizes::iterator itPtrSize = m_AllocationSizes->find(ptr);
+
+ #if UNITY_IPHONE
+ // oh hello osam apple. When we override global new - we override it for everything.
+ // BUT some libraries calls delete on pointers that were new-ed with system operator new
+ // effectively crashing here. So play safe, yes
+ if( itPtrSize == m_AllocationSizes->end() )
+ {
+ m_RecordingAllocation = false;
+ return kMemNewDeleteId;
+ }
+ #endif
+
+ Assert(itPtrSize != m_AllocationSizes->end());
+
+ size = (*itPtrSize).second.size;
+ site = (*itPtrSize).second.site;
+ m_AllocationSizes->erase(itPtrSize);
+ }
+#endif
+ if(header)
+ {
+ root = header->GetRootPtr();
+ if (header->GetRootPtr() || header->IsRoot())
+ {
+ if(header->IsRoot())
+ root = header;
+
+ int memoryleft = AtomicAdd(&root->accumulatedSize, -(int)size);
+ if (root == header)
+ {
+ // This is the root object. Check that all linked allocations are deleted. If not, unlink all.
+ if (memoryleft != 0)
+ {
+#if MAINTAIN_RELATED_ALLOCATION_LIST
+ Mutex::AutoLock lock(m_Mutex);
+ UnlinkAllAllocations(root);
+#else
+ ErrorString("Not all allocations related to a root has been deleted - might cause unity to crash later on!!");
+#endif
+ }
+ root->rootReference->root = NULL;
+ root->rootReference->Release();
+ root->rootReference = NULL;
+ }
+#if MAINTAIN_RELATED_ALLOCATION_LIST
+ UnlinkHeader(header);
+#endif
+ }
+ else
+ AtomicAdd(&header->accumulatedSize, -(int)size);
+#if RECORD_ALLOCATION_SITES
+ site = header->site;
+#endif
+ }
+
+#if RECORD_ALLOCATION_SITES
+ AllocationSite* mutablesite = const_cast<AllocationSite*>(site);
+ mutablesite->allocated -= size;
+ mutablesite->alloccount--;
+ if(root)
+ {
+ mutablesite->ownedAllocated -= size;
+ mutablesite->ownedCount--;
+ }
+ m_SizeUsed -= size;
+ m_NumAllocations--;
+ m_SizeDistribution[HighestBit(size)]--;
+#endif
+ // Roots are registered with their related data pointing to themselves.
+ if(header && header->IsRegisteredRoot() )
+ UnregisterRootAllocation (ptr);
+
+ if (outputRootHeader != NULL)
+ *outputRootHeader = root;
+
+ m_RecordingAllocation = false;
+ return;
+}
+
+void MemoryProfiler::TransferOwnership(void* ptr, BaseAllocator* alloc, ProfilerAllocationHeader* newRootHeader)
+{
+ Assert(alloc);
+ size_t size = alloc->GetPtrSize(ptr);
+ ProfilerAllocationHeader* header = alloc->GetProfilerHeader(ptr);
+
+ if(header)
+ {
+ ProfilerAllocationHeader* root = header->GetRootPtr();
+ if(root != NULL)
+ {
+#if MAINTAIN_RELATED_ALLOCATION_LIST
+ // Unlink from currentRoot
+ UnlinkHeader(header);
+#endif
+ AtomicAdd(&root->accumulatedSize, -(int)size);
+ header->SetRootPtr(NULL);
+#if RECORD_ALLOCATION_SITES
+ AllocationSite* mutablesite = const_cast<AllocationSite*>(header->site);
+ mutablesite->ownedAllocated -= size;
+ mutablesite->ownedCount--;
+#endif
+ }
+
+ if(newRootHeader == NULL)
+ newRootHeader = m_DefaultRootHeader;
+
+ // we have a new root. Relink
+ if(newRootHeader)
+ {
+ DebugAssert(newRootHeader->IsRoot());
+
+ AtomicAdd(&newRootHeader->accumulatedSize, size);
+#if MAINTAIN_RELATED_ALLOCATION_LIST
+ InsertAfterRoot(newRootHeader, header);
+#endif
+ header->SetRootPtr(newRootHeader);
+#if RECORD_ALLOCATION_SITES
+ AllocationSite* mutablesite = const_cast<AllocationSite*>(header->site);
+ mutablesite->ownedAllocated += size;
+ mutablesite->ownedCount++;
+#endif
+ }
+ }
+}
+
+#if MAINTAIN_RELATED_ALLOCATION_LIST
+void MemoryProfiler::UnlinkAllAllocations(ProfilerAllocationHeader* root)
+{
+#if ENABLE_STACKS_ON_ALL_ALLOCS
+ // Print stack for root and stacks for all unallocated child allocations
+ {
+ char buffer[4048];
+ void*const* s = root->site->stack;
+ GetReadableStackTrace(buffer, 4048, (void**)(s), 20);
+ printf_console(FormatString<TEMP_STRING>("Root still has %d allocated memory\nRoot allocated from\n%s\n",root->accumulatedSize, buffer).c_str());
+
+ ProfilerAllocationHeader* header = root->next;
+ while(header)
+ {
+ // for each allocation: print stack and decrease sites ownedcount
+ char buffer[4048];
+ void*const* s = header->site->stack;
+ GetReadableStackTrace(buffer, 4048, (void**)(s), 20);
+ printf_console(FormatString<TEMP_STRING>("%s\n", buffer).c_str());
+ AllocationSite* mutablesite = const_cast<AllocationSite*>(header->site);
+
+ mutablesite->ownedAllocated -= header->accumulatedSize;
+ mutablesite->ownedCount--;
+ header = header->next;
+ }
+ }
+#else
+// ErrorString("Not all allocations related to a root has been deleted - might cause unity to crash later on!!");
+#endif
+
+ // unlink all allocations
+ ProfilerAllocationHeader* header = root->next;
+ while(header)
+ {
+ ProfilerAllocationHeader* nextHeader = header->next;
+ UnlinkHeader(header);
+#if ROOT_UNRELATED_ALLOCATIONS
+ header->SetRootPtr(m_DefaultRootHeader); // set root owner
+ InsertAfterRoot(m_DefaultRootHeader, header);
+#else
+ header->SetRootPtr(NULL); // clear root owner
+#endif
+ header = nextHeader;
+ }
+ root->next = NULL;
+}
+
+void MemoryProfiler::InsertAfterRoot( ProfilerAllocationHeader* root, ProfilerAllocationHeader* header )
+{
+ DebugAssert(root->IsRoot());
+ Mutex::AutoLock lock(m_Mutex);
+ if(root->next)
+ root->next->prev = header;
+ header->next = root->next;
+ header->prev = root;
+ root->next = header;
+}
+
+void MemoryProfiler::UnlinkHeader(ProfilerAllocationHeader* header )
+{
+ Mutex::AutoLock lock(m_Mutex);
+
+ DebugAssert(header->prev == NULL || header->prev->next == header);
+ DebugAssert(header->next == NULL || header->next->prev == header);
+
+ if(header->prev)
+ header->prev->next = header->next;
+ if(header->next)
+ header->next->prev = header->prev;
+ header->prev = NULL;
+ header->next = NULL;
+}
+
+
+AllocationRootReference* MemoryProfiler::GetRootReferenceFromHeader(ProfilerAllocationHeader* root)
+{
+ if (root == NULL || root->rootReference == NULL || GetMemoryProfiler()->IsRecording())
+ return NULL;
+ Assert(root->IsRoot());
+ root->rootReference->Retain();
+ return root->rootReference;
+}
+
+#endif
+
+void MemoryProfiler::ValidateRoot(ProfilerAllocationHeader* root)
+{
+ if(!root)
+ return;
+ size_t accSize = 0;
+ Assert(root->IsRoot());
+ Assert(root->next == NULL || root->next->prev == root);
+ Assert(root->rootReference != NULL && root->rootReference->root != root);
+ ProfilerAllocationHeader* header = root->next;
+ while(header)
+ {
+ Assert (header->next == NULL || header->next->prev == header);
+ Assert (header->relatesTo == root);
+ accSize += header->accumulatedSize;
+ header = header->next;
+ }
+ Assert(root->accumulatedSize >= accSize);
+}
+
+bool MemoryProfiler::PushAllocationRoot(void* root, bool forcePush)
+{
+ ProfilerAllocationHeader** current_root_header = m_CurrentRootHeader;
+ if(current_root_header == NULL)
+ {
+ if (root == NULL)
+ return false;
+ m_RecordingAllocation = true;
+ m_RootStackSize = 10;
+ // using allocator directly to avoid allocation registration
+ BaseAllocator* alloc = GetMemoryManager().GetAllocator(kMemProfiler);
+ m_RootStack = (ProfilerAllocationHeader**)alloc->Allocate(m_RootStackSize*sizeof(ProfilerAllocationHeader*), kDefaultMemoryAlignment);
+ m_CurrentRootHeader = &m_RootStack[0];
+ *m_CurrentRootHeader = 0;
+ current_root_header = m_CurrentRootHeader;
+ m_RecordingAllocation = false;
+ }
+
+ ProfilerAllocationHeader* rootHeader = NULL;
+ if(root != NULL)
+ {
+ BaseAllocator* rootAlloc = GetMemoryManager().GetAllocatorContainingPtr(root);
+ Assert(rootAlloc);
+ rootHeader = (ProfilerAllocationHeader*)rootAlloc->GetProfilerHeader(root);
+ Assert(rootHeader == NULL || rootHeader->IsRoot());
+ }
+
+ if(!forcePush && rootHeader == *current_root_header)
+ return false;
+
+ UInt32 offset = m_CurrentRootHeader-m_RootStack;
+ if(offset == m_RootStackSize-1)
+ {
+ m_RecordingAllocation = true;
+ m_RootStackSize = m_RootStackSize*2;
+ // using allocator directly to avoid allocation registration
+ BaseAllocator* alloc = GetMemoryManager().GetAllocator(kMemProfiler);
+ m_RootStack = (ProfilerAllocationHeader**)alloc->Reallocate(&m_RootStack[0],m_RootStackSize*sizeof(ProfilerAllocationHeader*), kDefaultMemoryAlignment);
+ m_CurrentRootHeader = &m_RootStack[offset];
+ current_root_header = m_CurrentRootHeader;
+ m_RecordingAllocation = false;
+ }
+
+ *(++current_root_header) = rootHeader;
+ m_CurrentRootHeader = current_root_header;
+ return true;
+}
+
+void MemoryProfiler::PopAllocationRoot()
+{
+ --m_CurrentRootHeader;
+}
+
+ProfilerAllocationHeader* MemoryProfiler::GetCurrentRootHeader()
+{
+ ProfilerAllocationHeader* root = m_CurrentRootHeader ? *m_CurrentRootHeader : NULL;
+ return root;
+}
+
+int MemoryProfiler::GetHeaderSize()
+{
+ return sizeof(ProfilerAllocationHeader);
+}
+
+size_t MemoryProfiler::GetRelatedMemorySize(const void* ptr){
+ // this could use a linked list of related allocations, and thereby this info could be viewed as a hierarchy
+ BaseAllocator* alloc = GetMemoryManager().GetAllocatorContainingPtr(ptr);
+ ProfilerAllocationHeader* header = alloc ? alloc->GetProfilerHeader(ptr) : NULL;
+ return header ? header->accumulatedSize : 0;
+}
+
+void MemoryProfiler::RegisterMemoryToID( size_t id, size_t size )
+{
+ Mutex::AutoLock lock(m_Mutex);
+ m_RecordingAllocation = true;
+ ReferenceID::iterator itIDSize = m_ReferenceIDSizes->find(id);
+ if(itIDSize == m_ReferenceIDSizes->end())
+ m_ReferenceIDSizes->insert(std::make_pair(id,size));
+ else
+ itIDSize->second += size;
+ m_RecordingAllocation = false;
+}
+
+void MemoryProfiler::UnregisterMemoryToID( size_t id, size_t size )
+{
+ Mutex::AutoLock lock(m_Mutex);
+ m_RecordingAllocation = true;
+ ReferenceID::iterator itIDSize = m_ReferenceIDSizes->find(id);
+ if (itIDSize != m_ReferenceIDSizes->end())
+ {
+ itIDSize->second -= size;
+ if(itIDSize->second == 0)
+ m_ReferenceIDSizes->erase(itIDSize);
+ }
+ else
+ ErrorString("Id not found in map");
+ m_RecordingAllocation = false;
+}
+
+size_t MemoryProfiler::GetRelatedIDMemorySize(size_t id)
+{
+ Mutex::AutoLock lock(m_Mutex);
+ ReferenceID::iterator itIDSize = m_ReferenceIDSizes->find(id);
+ if(itIDSize == m_ReferenceIDSizes->end())
+ return 0;
+ return itIDSize->second;
+}
+
+
+ProfilerString MemoryProfiler::GetOverview() const
+{
+#if RECORD_ALLOCATION_SITES
+ struct MemCatInfo
+ {
+ UInt64 totalMem;
+ UInt64 totalCount;
+ UInt64 cummulativeMem;
+ UInt64 cummulativeCount;
+ };
+ MemCatInfo info[kMemLabelCount+1];
+ MemCatInfo allocatorMemUsage[16];
+ MemCatInfo totalMemUsage;
+ BaseAllocator* allocators[16];
+
+ memset (&info,0,sizeof(info));
+ memset (&allocatorMemUsage,0,sizeof(allocatorMemUsage));
+ memset (&totalMemUsage,0,sizeof(totalMemUsage));
+ memset (&allocators,0,sizeof(allocators));
+
+
+ AllocationSites::iterator it = m_AllocationSites->begin();
+ for( ;it != m_AllocationSites->end(); ++it)
+ {
+ MemLabelIdentifier label = (*it).label;
+ if (label >= kMemLabelCount)
+ label = kMemLabelCount;
+ info[label].totalMem += (*it).allocated;
+ info[label].totalCount += (*it).alloccount;
+ info[label].cummulativeMem += (*it).cummulativeAllocated;
+ info[label].cummulativeCount += (*it).cummulativeAlloccount;
+
+ MemLabelId memLabel(label, NULL);
+ BaseAllocator* alloc = GetMemoryManager().GetAllocator(memLabel);
+ int allocatorindex = GetMemoryManager().GetAllocatorIndex(alloc);
+ allocatorMemUsage[allocatorindex].totalMem += (*it).allocated;
+ allocatorMemUsage[allocatorindex].totalCount += (*it).alloccount;
+ allocatorMemUsage[allocatorindex].cummulativeMem += (*it).cummulativeAllocated;
+ allocatorMemUsage[allocatorindex].cummulativeCount += (*it).cummulativeAlloccount;
+ allocators[allocatorindex] = alloc;
+ totalMemUsage.totalMem += (*it).allocated;
+ totalMemUsage.totalCount += (*it).alloccount;
+ totalMemUsage.cummulativeMem += (*it).cummulativeAllocated;
+ totalMemUsage.cummulativeCount += (*it).cummulativeAlloccount;
+ }
+
+ const int kStringSize = 400*1024;
+ TEMP_STRING str;
+ str.reserve(kStringSize);
+ // Total memory registered in all allocators
+ str += FormatString<TEMP_STRING>("[ Total Memory ] : %0.2fMB ( %d ) [%0.2fMB ( %d )]\n\n", (float)(totalMemUsage.totalMem)/(1024.f*1024.f), totalMemUsage.totalCount,
+ (float)(totalMemUsage.cummulativeMem)/(1024.f*1024.f), totalMemUsage.cummulativeCount);
+
+ // Memory registered by allocators
+ for(int i = 0; i < 16; i++)
+ {
+ if (allocatorMemUsage[i].cummulativeCount == 0)
+ continue;
+ BaseAllocator* alloc = allocators[i];
+ str += FormatString<TEMP_STRING>("[ %s ] : %0.2fKB ( %d ) [acc: %0.2fMB ( %d )] (Requested:%0.2fKB, Overhead:%0.2fKB, Reserved:%0.2fMB)\n", (alloc?alloc->GetName():"Custom"), (float)(allocatorMemUsage[i].totalMem)/1024.f, allocatorMemUsage[i].totalCount,
+ (float)(allocatorMemUsage[i].cummulativeMem)/(1024.f*1024.f), allocatorMemUsage[i].cummulativeCount,(float) (alloc?alloc->GetAllocatedMemorySize():0)/1024.f, (float)((alloc?alloc->GetAllocatorSizeTotalUsed():0) - (alloc?alloc->GetAllocatedMemorySize():0))/1024.f, (alloc?alloc->GetReservedSizeTotal():0)/(1024.f*1024.f));
+ }
+
+ // Memory registered on labels
+ for(int i = 0; i <= kMemLabelCount; i++)
+ {
+ if (info[i].cummulativeCount == 0)
+ continue;
+ MemLabelId label((MemLabelIdentifier)i, NULL);
+ BaseAllocator* alloc = GetMemoryManager().GetAllocator(label);
+ str += FormatString<TEMP_STRING>("\n[ %s : %s ]\n", GetMemoryManager().GetMemcatName(label), alloc?alloc->GetName():"Custom");
+ str += FormatString<TEMP_STRING>(" TotalAllocated : %0.2fKB ( %d ) [%0.2fMB ( %d )]\n", (float)(info[i].totalMem)/1024.f, info[i].totalCount,
+ (float)(info[i].cummulativeMem)/(1024.f*1024.f), info[i].cummulativeCount);
+ }
+
+#if ENABLE_STACKS_ON_ALL_ALLOCS
+ const int kBufferSize = 8*1024;
+ char buffer[kBufferSize];
+ UNITY_VECTOR(kMemMemoryProfiler,const AllocationSite*) sortedVector;
+ sortedVector.reserve(m_AllocationSites->size());
+ it = m_AllocationSites->begin();
+ for( ;it != m_AllocationSites->end(); ++it)
+ sortedVector.push_back(&(*it));
+ std::sort(sortedVector.begin(), sortedVector.end(), AllocationSite::Sorter());
+ {
+ UNITY_VECTOR(kMemMemoryProfiler, const AllocationSite*)::iterator it = sortedVector.begin();
+ for( ;it != sortedVector.end(); ++it)
+ {
+ if(str.length()>kStringSize-kBufferSize)
+ break;
+ if( (*it)->alloccount == 0)
+ break;
+ str += FormatString<TEMP_STRING>("\n[ %s ]\n", GetMemoryManager().GetMemcatName(MemLabelId((*it)->label, NULL)));
+ if((*it)->stack[0] != 0)
+ {
+ GetReadableStackTrace(buffer, kBufferSize, (void**)((*it)->stack), 20);
+ str += FormatString<TEMP_STRING>("%s\n", buffer);
+ }
+ else
+ str += FormatString<TEMP_STRING>("[ %s:%d ]\n", (*it)->file, (*it)->line);
+ //, (*it).file);
+ str += FormatString<TEMP_STRING>(" TotalAllocated : %0.2fKB ( %d ) [%0.2fMB ( %d )]\n", (float)((*it)->allocated)/1024.f, (*it)->alloccount,
+ (float)((*it)->cummulativeAllocated)/(1024.f*1024.f), (*it)->cummulativeAlloccount);
+ }
+ }
+#endif
+ return ProfilerString(str.c_str());
+#else
+ return ProfilerString();
+#endif
+}
+
+#if RECORD_ALLOCATION_SITES
+struct MonoObjectMemoryStackInfo
+{
+ bool expanded;
+ bool sorted;
+ int allocated;
+ int ownedAllocated;
+ MonoArray* callerSites;
+ ScriptingStringPtr name;
+};
+
+MonoObject* MemoryProfiler::MemoryStackEntry::Deserialize()
+{
+ MonoClass* klass = GetMonoManager().GetMonoClass ("ObjectMemoryStackInfo", "UnityEditorInternal");
+ MonoObject* obj = mono_object_new (mono_domain_get(), klass);
+
+ MonoObjectMemoryStackInfo& memInfo = ExtractMonoObjectData<MonoObjectMemoryStackInfo> (obj);
+ memInfo.expanded = false;
+ memInfo.sorted = false;
+ memInfo.allocated = totalMemory;
+ memInfo.ownedAllocated = ownedMemory;
+ string tempname (name.c_str(),name.size()-2);
+ memInfo.name = MonoStringNew(tempname);
+ memInfo.callerSites = mono_array_new(mono_domain_get(), klass, callerSites.size());
+
+ std::map<void*,MemoryStackEntry>::iterator it = callerSites.begin();
+ int index = 0;
+ while(it != callerSites.end())
+ {
+ MonoObject* child = (*it).second.Deserialize();
+ GetMonoArrayElement<MonoObject*> (memInfo.callerSites,index++) = child;
+ ++it;
+ }
+ return obj;
+}
+
+MemoryProfiler::MemoryStackEntry* MemoryProfiler::GetStackOverview() const
+{
+ m_RecordingAllocation = true;
+ MemoryStackEntry* topLevel = UNITY_NEW(MemoryStackEntry,kMemProfiler);
+ topLevel->name = "Allocated unity memory ";
+ AllocationSites::iterator it = m_AllocationSites->begin();
+ for( ;it != m_AllocationSites->end(); ++it)
+ {
+ int size = (*it).allocated;
+ if(size == 0)
+ continue;
+ int ownedsize = (*it).ownedAllocated;
+ void*const* stack = &(*it).stack[0];
+ MemoryStackEntry* currentLevel = topLevel;
+ currentLevel->totalMemory += size;
+ currentLevel->ownedMemory += ownedsize;
+ while(*stack)
+ {
+ currentLevel = &(currentLevel->callerSites[*stack]);
+ if(currentLevel->totalMemory == 0)
+ {
+ char buffer[2048];
+ void* temp[2];
+ temp[0] = *stack;
+ temp[1] = 0;
+ GetReadableStackTrace(buffer, 2048, (void**)temp, 1);
+ currentLevel->name = std::string(buffer,40);
+ }
+ currentLevel->totalMemory += size;
+ currentLevel->ownedMemory += ownedsize;
+ ++stack;
+ }
+ }
+ m_RecordingAllocation = false;
+ return topLevel;
+}
+
+void MemoryProfiler::ClearStackOverview(MemoryProfiler::MemoryStackEntry* entry) const
+{
+ m_RecordingAllocation = true;
+ UNITY_DELETE(entry, kMemProfiler);
+ m_RecordingAllocation = false;
+}
+#endif
+
+void MemoryProfiler::RegisterRootAllocation (void* root, BaseAllocator* allocator, const char* areaName, const char* objectName)
+{
+ bool result = true;
+ if(areaName != NULL)
+ {
+ m_Mutex.Lock();
+ m_RecordingAllocation = true;
+
+ RootAllocationType typeData;
+ typeData.areaName = areaName;
+ typeData.objectName = objectName;
+ result = m_RootAllocationTypes->insert(std::make_pair (root, typeData)).second;
+
+ m_RecordingAllocation = false;
+ m_Mutex.Unlock();
+ }
+ if (result)
+ {
+ ProfilerAllocationHeader* header = allocator->GetProfilerHeader(root);
+ if(header)
+ {
+ if (!header->rootReference)
+ {
+ m_RecordingAllocation = true;
+ header->rootReference = UNITY_NEW(AllocationRootReference, kMemProfiler) (header);
+ m_RecordingAllocation = false;
+ }
+ if(areaName)
+ header->SetAsRegisteredRoot();
+ else
+ header->SetAsRoot();
+ }
+ }
+ else
+ {
+ ErrorString("Registered allocation root already exists");
+ }
+}
+
+void MemoryProfiler::UnregisterRootAllocation (void* root)
+{
+ m_Mutex.Lock();
+ m_RecordingAllocation = true;
+ bool result = m_RootAllocationTypes->erase(root);
+ m_RecordingAllocation = false;
+ m_Mutex.Unlock();
+
+ if (!result)
+ {
+ ErrorString("Allocation root has already been deleted");
+ }
+}
+
+void MemoryProfiler::SetRootAllocationObjectName (void* root, const char* objectName)
+{
+ Mutex::AutoLock lock (m_Mutex);
+ m_RecordingAllocation = true;
+ RootAllocationTypes::iterator it = m_RootAllocationTypes->find(root);
+ Assert(it != m_RootAllocationTypes->end());
+ (*it).second.objectName = objectName;
+ m_RecordingAllocation = false;
+}
+
+void MemoryProfiler::GetRootAllocationInfos (RootAllocationInfos& infos)
+{
+ Mutex::AutoLock lock (m_Mutex);
+ m_RecordingAllocation = true;
+
+ int index = infos.size();
+ infos.resize_uninitialized(infos.size() + m_RootAllocationTypes->size());
+ for (RootAllocationTypes::const_iterator i=m_RootAllocationTypes->begin();i != m_RootAllocationTypes->end();++i)
+ {
+ RootAllocationInfo& info = infos[index++];
+ info.memorySize = GetRelatedMemorySize(i->first);
+ info.areaName = i->second.areaName;
+ info.objectName = i->second.objectName.c_str();
+ }
+ m_RecordingAllocation = false;
+}
+
+ProfilerAllocationHeader* MemoryProfiler::GetAllocationRootHeader(void* ptr, MemLabelRef label) const
+{
+ BaseAllocator* alloc = GetMemoryManager().GetAllocator(label);
+ ProfilerAllocationHeader* header = alloc?alloc->GetProfilerHeader(ptr):NULL;
+ return header?header->GetRootPtr(): NULL;
+}
+
+
+#if RECORD_ALLOCATION_SITES
+ProfilerString MemoryProfiler::GetUnrootedAllocationsOverview()
+{
+ ProfilerString result;
+#if ROOT_UNRELATED_ALLOCATIONS
+ m_RecordingAllocation = true;
+ std::map<const AllocationSite*,size_t> allocations;
+ ProfilerAllocationHeader* next = m_DefaultRootHeader->next;
+ while(next)
+ {
+ if(!next->IsRoot())
+ {
+ Assert(next->relatesTo == m_DefaultRootHeader);
+ allocations[next->site]+=next->accumulatedSize;
+ }
+ next = next->next;
+ }
+ std::vector<std::pair<const AllocationSite*,size_t> > sorted;
+ std::map<const AllocationSite*,size_t>::iterator it = allocations.begin();
+ for( ;it != allocations.end(); ++it)
+ {
+ sorted.push_back(*it);
+ }
+ std::sort(sorted.begin(), sorted.end(), AllocationSiteSizeSorter());
+ for(int i = 0;i < sorted.size(); ++i)
+ {
+ int size = sorted[i].second;
+ printf_console("%db\n", size);
+ char buffer[2048];
+ GetReadableStackTrace(buffer, 2048, (void**)sorted[i].first->stack, 10);
+ printf_console("%s\n", buffer);
+ if(i > 20)
+ break;
+ }
+ m_RecordingAllocation = false;
+#endif
+ return result;
+}
+
+#endif
+
+
+#endif
+
diff --git a/Runtime/Profiler/MemoryProfiler.h b/Runtime/Profiler/MemoryProfiler.h
new file mode 100644
index 0000000..a7c0727
--- /dev/null
+++ b/Runtime/Profiler/MemoryProfiler.h
@@ -0,0 +1,237 @@
+#pragma once
+
+#include "Runtime/Allocator/MemoryMacros.h"
+
+#if ENABLE_MEM_PROFILER
+
+#define RECORD_ALLOCATION_SITES 0
+#define ENABLE_STACKS_ON_ALL_ALLOCS 0
+#define MAINTAIN_RELATED_ALLOCATION_LIST 1
+
+#if ENABLE_STACKS_ON_ALL_ALLOCS
+ #undef RECORD_ALLOCATION_SITES
+ #define RECORD_ALLOCATION_SITES 1
+#endif
+
+
+#include "Runtime/Threads/Mutex.h"
+#include "Runtime/Threads/ThreadSpecificValue.h"
+#include "Runtime/Threads/AtomicRefCounter.h"
+#include <map>
+#include "Runtime/Utilities/MemoryPool.h"
+
+struct MonoObject;
+// header for all allocations
+
+struct AllocationRootReference
+{
+ AllocationRootReference(ProfilerAllocationHeader* root): root(root) {}
+
+ void Retain() { m_Counter.Retain(); }
+ void Release()
+ {
+ if(m_Counter.Release())
+ delete_internal(this, kMemProfiler);
+ }
+
+ AtomicRefCounter m_Counter;
+ ProfilerAllocationHeader* root;
+};
+
+
+class MemoryProfiler
+{
+public:
+ MemoryProfiler();
+ ~MemoryProfiler();
+
+ static void StaticInitialize();
+ static void StaticDestroy();
+
+ void ThreadCleanup();
+
+ static void InitAllocation(void* ptr, BaseAllocator* alloc);
+ void RegisterAllocation(void* ptr, MemLabelRef label, const char* file, int line, size_t size = 0);
+ void UnregisterAllocation(void* ptr, BaseAllocator* alloc, size_t size, ProfilerAllocationHeader** rootHeader, MemLabelRef label);
+
+ void TransferOwnership(void* ptr, BaseAllocator* alloc, ProfilerAllocationHeader* newRootHeader);
+
+ static int GetHeaderSize();
+
+ ProfilerString GetOverview() const;
+
+ ProfilerString GetUnrootedAllocationsOverview();
+ ProfilerAllocationHeader* GetAllocationRootHeader(void* ptr, MemLabelRef label) const;
+
+ // Roots allocations can be created using UNITY_NEW_AS_ROOT.
+ // They automatically show up in the memory profiler as a seperate section.
+ struct RootAllocationInfo
+ {
+ const char* areaName;
+ const char* objectName;
+ size_t memorySize;
+ };
+ typedef dynamic_array<RootAllocationInfo> RootAllocationInfos;
+
+ void RegisterRootAllocation (void* root, BaseAllocator* allocator, const char* areaName, const char* objectName);
+ void UnregisterRootAllocation (void* root);
+ void GetRootAllocationInfos (RootAllocationInfos& infos);
+ void SetRootAllocationObjectName (void* root, const char* objectName);
+
+ static AllocationRootReference* GetRootReferenceFromHeader(ProfilerAllocationHeader* root);
+
+ bool PushAllocationRoot(void* root, bool forcePush);
+ void PopAllocationRoot();
+ ProfilerAllocationHeader* GetCurrentRootHeader();
+
+ void RegisterMemoryToID(size_t id, size_t size);
+ void UnregisterMemoryToID( size_t id, size_t size );
+ size_t GetRelatedIDMemorySize(size_t id);
+
+ size_t GetRelatedMemorySize(const void* ptr);
+
+ static bool IsRecording() {return m_RecordingAllocation;}
+
+
+ struct MemoryStackEntry
+ {
+ MemoryStackEntry():totalMemory(0),ownedMemory(0){}
+ MonoObject* Deserialize();
+ int totalMemory;
+ int ownedMemory;
+ std::map<void*, MemoryStackEntry> callerSites;
+ std::string name;
+ };
+ MemoryStackEntry* GetStackOverview() const;
+ void ClearStackOverview(MemoryStackEntry* entry) const;
+
+ static MemoryProfiler* s_MemoryProfiler;
+
+ void AllocateStructs();
+ struct AllocationSite;
+private:
+
+ static void SetupAllocationHeader(ProfilerAllocationHeader* header, ProfilerAllocationHeader* root, int size);
+ void ValidateRoot(ProfilerAllocationHeader* root);
+
+#if MAINTAIN_RELATED_ALLOCATION_LIST
+ void UnlinkAllAllocations(ProfilerAllocationHeader* root);
+ void InsertAfterRoot( ProfilerAllocationHeader* root, ProfilerAllocationHeader* header );
+ void UnlinkHeader(ProfilerAllocationHeader* header);
+#endif
+
+ static UNITY_TLS_VALUE(bool) m_RecordingAllocation;
+
+ Mutex m_Mutex;
+
+ size_t m_SizeUsed;
+ UInt32 m_NumAllocations;
+
+ size_t m_AccSizeUsed;
+ UInt64 m_AccNumAllocations;
+
+ UInt32 m_SizeDistribution[32];
+
+ size_t m_InternalMemoryUsage;
+
+ typedef std::pair< size_t, size_t> ReferenceIDPair;
+ typedef STL_ALLOCATOR( kMemMemoryProfiler, ReferenceIDPair ) ReferenceIDAllocator;
+ typedef std::map< size_t, size_t, std::less<size_t>, ReferenceIDAllocator > ReferenceID;
+ ReferenceID* m_ReferenceIDSizes;
+
+ struct RootAllocationType
+ {
+ const char* areaName;
+ ProfilerString objectName;
+ };
+
+ typedef std::pair< void*, RootAllocationType> RootAllocationTypePair;
+ typedef STL_ALLOCATOR( kMemMemoryProfiler, RootAllocationTypePair ) AllocationRootTypeAllocator;
+
+ typedef std::map< void*, RootAllocationType, std::less<void*>, AllocationRootTypeAllocator > RootAllocationTypes;
+ RootAllocationTypes* m_RootAllocationTypes;
+
+ static UNITY_TLS_VALUE(ProfilerAllocationHeader**) m_RootStack;
+ static UNITY_TLS_VALUE(UInt32) m_RootStackSize;
+ static UNITY_TLS_VALUE(ProfilerAllocationHeader**) m_CurrentRootHeader;
+
+ ProfilerAllocationHeader* m_DefaultRootHeader;
+
+#if RECORD_ALLOCATION_SITES
+public:
+ struct AllocationSite
+ {
+ MemLabelIdentifier label;
+#if ENABLE_STACKS_ON_ALL_ALLOCS
+ void* stack[20];
+ UInt32 stackHash;
+#endif
+ const char* file;
+ int line;
+ int allocated;
+ int alloccount;
+ int ownedAllocated;
+ int ownedCount;
+ size_t cummulativeAllocated;
+ size_t cummulativeAlloccount;
+
+ bool operator()(const AllocationSite& s1, const AllocationSite& s2) const
+ {
+ return s1.line != s2.line ? s1.line < s2.line :
+ s1.label != s2.label ? s1.label < s2.label:
+#if ENABLE_STACKS_ON_ALL_ALLOCS
+ s1.stackHash != s2.stackHash? s1.stackHash< s2.stackHash:
+#endif
+ s1.file < s2.file ;
+ }
+
+ struct Sorter
+ {
+ bool operator()( const AllocationSite* a, const AllocationSite* b ) const
+ {
+ return a->allocated > b->allocated;
+ }
+ };
+ };
+ typedef std::set<AllocationSite, AllocationSite, STL_ALLOCATOR(kMemMemoryProfiler, AllocationSite) > AllocationSites;
+ AllocationSites* m_AllocationSites;
+private:
+ struct LocalHeaderInfo
+ {
+ size_t size;
+ const AllocationSite* site;
+ };
+
+ // map used for headers for allocations that don't support profileheaders
+ typedef std::pair< void* const, LocalHeaderInfo> SiteHeaderPair;
+ typedef STL_ALLOCATOR( kMemMemoryProfiler, SiteHeaderPair ) MapAllocator;
+ typedef std::map< void*, LocalHeaderInfo, std::less<void*>, MapAllocator > AllocationSizes;
+ AllocationSizes* m_AllocationSizes;
+#endif
+
+ struct AllocationSiteSizeSorter {
+ bool operator()( const std::pair<const AllocationSite*,size_t>& a, const std::pair<const AllocationSite*,size_t>& b ) const
+ {
+ return a.second > b.second;
+ }
+ };
+};
+
+inline MemoryProfiler* GetMemoryProfiler()
+{
+ return MemoryProfiler::s_MemoryProfiler;
+}
+
+#else
+
+class MemoryProfiler
+{
+public:
+ static int GetHeaderSize() { return 0; }
+ static void SetInLLAllocator (bool inLowLevelAllocator) { }
+ size_t GetRelatedMemorySize(const void* ptr) { return 0;}
+ static bool IsRecording() {return false;}
+};
+
+#endif
+
diff --git a/Runtime/Profiler/MemoryProfilerStats.cpp b/Runtime/Profiler/MemoryProfilerStats.cpp
new file mode 100644
index 0000000..9e7e7e9
--- /dev/null
+++ b/Runtime/Profiler/MemoryProfilerStats.cpp
@@ -0,0 +1,158 @@
+#if ENABLE_PROFILER
+
+#include "UnityPrefix.h"
+#include "MemoryProfilerStats.h"
+#include "Runtime/BaseClasses/BaseObject.h"
+#include "Runtime/Threads/AtomicOps.h"
+#include "Runtime/Threads/Thread.h"
+#include "Runtime/Serialize/PersistentManager.h"
+
+void profiler_register_object(Object* obj)
+{
+ GetMemoryProfilerStats().RegisterObject(obj);
+}
+
+void profiler_unregister_object(Object* obj)
+{
+ GetMemoryProfilerStats().UnregisterObject(obj);
+}
+
+void profiler_change_persistancy(int instanceID, bool oldvalue, bool newvalue)
+{
+ GetMemoryProfilerStats().ChangePersistancyflag(instanceID, oldvalue, newvalue);
+}
+
+
+void TestAndInsertObject(Object* obj, int objClassID, int classID, dynamic_array<Object*>& objs)
+{
+ if (objClassID == classID)
+ objs.push_back(obj);
+}
+
+void TestAndRemoveObject(Object* obj, int objClassID, int classID, dynamic_array<Object*>& objs)
+{
+ if (objClassID == classID)
+ {
+ // run from the end - last created objects are most likely to be destoyed
+ dynamic_array<Object*>::iterator it = objs.end();
+ while(it != objs.begin())
+ {
+ --it;
+ if(*it == obj){
+ objs.erase(it, it+1);
+ return;
+ }
+ }
+ ErrorString(Format("An object that was removed, was not found in the object list for that type (%s)", Object::ClassIDToString(classID).c_str()));
+ }
+}
+
+void MemoryProfilerStats::ChangePersistancyflag(int instanceID, bool oldvalue, bool newvalue)
+{
+ if(oldvalue == newvalue)
+ return;
+#if SUPPORT_THREADS
+ if(!Thread::EqualsCurrentThreadID(GetPersistentManager().GetMainThreadID()))
+ return;
+#endif
+ Object* obj = Object::IDToPointer(instanceID);
+ if(obj == NULL)
+ return;
+
+ if(oldvalue == true)
+ {
+ AtomicDecrement(&assetCount);
+ AddDynamicObjectCount(obj, obj->GetClassID());
+ }
+ else
+ {
+ AtomicIncrement(&assetCount);
+ RemoveDynamicObjectCount(obj, obj->GetClassID());
+ }
+}
+
+void MemoryProfilerStats::AddDynamicObjectCount(Object* obj, int classID)
+{
+ AtomicIncrement(&sceneObjectCount);
+ if( classID == ClassID(GameObject) )
+ AtomicIncrement(&gameObjectCount);
+}
+
+void MemoryProfilerStats::RemoveDynamicObjectCount(Object* obj, int classID)
+{
+ AtomicDecrement(&sceneObjectCount);
+ if( classID == ClassID(GameObject) )
+ AtomicDecrement(&gameObjectCount);
+}
+
+void MemoryProfilerStats::RegisterObject ( Object* obj )
+{
+ int classID = obj->GetClassID();
+
+ TestAndInsertObject(obj, classID, ClassID(Texture2D), textures);
+ TestAndInsertObject(obj, classID, ClassID(Mesh), meshes);
+ TestAndInsertObject(obj, classID, ClassID(Material), materials);
+ TestAndInsertObject(obj, classID, ClassID(AnimationClip), animations);
+ TestAndInsertObject(obj, classID, ClassID(AudioClip), audioclips);
+
+ if(classCount.size() <= classID)
+ classCount.resize_initialized(classID+1,0);
+ ++classCount[classID];
+
+ if(obj->IsPersistent())
+ AtomicIncrement(&assetCount);
+ else
+ AddDynamicObjectCount(obj, classID);
+}
+
+void MemoryProfilerStats::UnregisterObject ( Object* obj )
+{
+ int classID = obj->GetClassID();
+ TestAndRemoveObject(obj, classID, ClassID(Texture2D), textures);
+ TestAndRemoveObject(obj, classID, ClassID(Mesh), meshes);
+ TestAndRemoveObject(obj, classID, ClassID(Material), materials);
+ TestAndRemoveObject(obj, classID, ClassID(AnimationClip), animations);
+ TestAndRemoveObject(obj, classID, ClassID(AudioClip), audioclips);
+
+ Assert (classCount.size() > classID);
+ --classCount[classID];
+
+ if(obj->IsPersistent())
+ AtomicDecrement(&assetCount);
+ else
+ RemoveDynamicObjectCount(obj, classID);
+}
+
+MemoryProfilerStats::MemoryProfilerStats()
+: assetCount(0)
+, sceneObjectCount(0)
+, gameObjectCount(0)
+{
+}
+
+MemoryProfilerStats::~MemoryProfilerStats()
+{
+}
+
+
+MemoryProfilerStats* gMemoryProfilerStats = NULL;
+MemoryProfilerStats& GetMemoryProfilerStats()
+{
+ Assert(gMemoryProfilerStats != NULL);
+ return *gMemoryProfilerStats;
+}
+
+void InitializeMemoryProfilerStats()
+{
+ Assert(gMemoryProfilerStats == NULL);
+ gMemoryProfilerStats = new MemoryProfilerStats();
+}
+
+void CleanupMemoryProfilerStats()
+{
+ Assert(gMemoryProfilerStats != NULL);
+ delete gMemoryProfilerStats;
+ gMemoryProfilerStats = NULL;
+}
+
+#endif \ No newline at end of file
diff --git a/Runtime/Profiler/MemoryProfilerStats.h b/Runtime/Profiler/MemoryProfilerStats.h
new file mode 100644
index 0000000..a148484
--- /dev/null
+++ b/Runtime/Profiler/MemoryProfilerStats.h
@@ -0,0 +1,75 @@
+#ifndef _MEMORY_PROFILER_STATS_H_
+#define _MEMORY_PROFILER_STATS_H_
+
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/Utilities/dynamic_array.h"
+
+
+#if ENABLE_PROFILER
+
+
+class Object;
+
+class MemoryProfilerStats
+{
+public:
+ MemoryProfilerStats();
+ ~MemoryProfilerStats();
+
+ void RegisterObject ( Object* obj );
+ void UnregisterObject ( Object* obj );
+ void ChangePersistancyflag (int instanceID, bool oldvalue, bool newvalue);
+
+ typedef dynamic_array<Object*> ObjectVector;
+ const ObjectVector& GetTextures() const {return textures;}
+ const ObjectVector& GetMeshes() const {return meshes;}
+ const ObjectVector& GetMaterials() const {return materials;}
+ const ObjectVector& GetAnimationClips() const {return animations;}
+ const ObjectVector& GetAudioClips() const {return audioclips;}
+
+ const dynamic_array<int>& GetClassCount() const {return classCount;}
+
+ int GetAssetCount() const {return assetCount;}
+ int GetSceneObjectCount() const {return sceneObjectCount;}
+ int GetGameObjectCount() const {return gameObjectCount;}
+private:
+
+ ObjectVector textures;
+ ObjectVector meshes;
+ ObjectVector materials;
+ ObjectVector animations;
+ ObjectVector audioclips;
+
+ volatile int assetCount;
+ volatile int sceneObjectCount;
+ volatile int gameObjectCount;
+
+ dynamic_array<int> classCount;
+
+ void AddDynamicObjectCount(Object* obj, int classID);
+ void RemoveDynamicObjectCount(Object* obj, int classID);
+
+};
+
+MemoryProfilerStats& GetMemoryProfilerStats();
+void InitializeMemoryProfilerStats();
+void CleanupMemoryProfilerStats();
+
+void profiler_register_object(Object* obj);
+void profiler_unregister_object(Object* obj);
+void profiler_change_persistancy(int instanceID, bool oldvalue, bool newvalue);
+
+#define PROFILER_REGISTER_OBJECT(obj) profiler_register_object(obj);
+#define PROFILER_UNREGISTER_OBJECT(obj) profiler_unregister_object(obj);
+#define PROFILER_CHANGE_PERSISTANCY(id,oldvalue,newvalue) profiler_change_persistancy(id,oldvalue,newvalue);
+
+#else
+
+#define PROFILER_REGISTER_OBJECT(obj)
+#define PROFILER_UNREGISTER_OBJECT(obj)
+#define PROFILER_CHANGE_PERSISTANCY(id,oldvalue,newvalue)
+
+#endif
+
+
+#endif \ No newline at end of file
diff --git a/Runtime/Profiler/MemoryProfilerTests.cpp b/Runtime/Profiler/MemoryProfilerTests.cpp
new file mode 100644
index 0000000..4c02f54
--- /dev/null
+++ b/Runtime/Profiler/MemoryProfilerTests.cpp
@@ -0,0 +1,73 @@
+#include "UnityPrefix.h"
+#include "MemoryProfiler.h"
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+
+#if ENABLE_PROFILER
+#include "Runtime/Profiler/MemoryProfiler.h"
+
+SUITE (MemoryProfilerTests)
+{
+ struct MemoryProfilerFixture
+ {
+ MemoryProfilerFixture()
+ {
+ // string allocates length + 1 rounded up to mod 16
+ stringlength = 32;
+#if UNITY_OSX
+ // on osx, there is allocated room for refcounting as well
+ stringlength += 2;
+#endif
+ }
+ ~MemoryProfilerFixture()
+ {
+ }
+ int stringlength;
+ };
+
+ struct VectorOfStrings
+ {
+ UNITY_VECTOR(kMemDefault, UnityStr) vec;
+ };
+
+ struct StructWithStrings
+ {
+ UnityStr str1;
+ UnityStr str2;
+ };
+
+ TEST_FIXTURE(MemoryProfilerFixture, GetRelatedMemorySize_StringVector_MatchesExpectedSize)
+ {
+ VectorOfStrings* vec = UNITY_NEW_AS_ROOT(VectorOfStrings, kMemDefault, "", "");
+ vec->vec.reserve(10);
+ {
+ UnityStr str("HelloWorld HelloWorld");
+ SET_ALLOC_OWNER(vec);
+ vec->vec.push_back(str);
+ }
+
+ CHECK_EQUAL(GetMemoryProfiler()->GetRelatedMemorySize(vec), sizeof(UnityStr)*10 + sizeof(UNITY_VECTOR(kMemDefault, UnityStr)) + stringlength);
+ UNITY_DELETE(vec, kMemDefault);
+ }
+
+ TEST_FIXTURE(MemoryProfilerFixture, GetRelatedMemorySize_StringLosingOwner_MatchesExpectedSize)
+ {
+ StructWithStrings* strings;
+ {
+ VectorOfStrings* vec = UNITY_NEW_AS_ROOT(VectorOfStrings,kMemDefault, "", "");
+ SET_ALLOC_OWNER(vec);
+ strings = UNITY_NEW(StructWithStrings, kMemDefault);
+ strings->str1 = "HelloWorld HelloWorld";
+ strings->str2 = "HelloWorld HelloWorld";
+ CHECK_EQUAL(GetMemoryProfiler()->GetRelatedMemorySize(vec), sizeof(UNITY_VECTOR(kMemDefault, UnityStr)) + 2 * stringlength + sizeof(StructWithStrings) );
+ UNITY_DELETE(vec, kMemDefault);
+ }
+ UNITY_DELETE(strings, kMemDefault);
+ }
+
+}
+
+#endif
+#endif \ No newline at end of file
diff --git a/Runtime/Profiler/ObjectMemoryProfiler.cpp b/Runtime/Profiler/ObjectMemoryProfiler.cpp
new file mode 100644
index 0000000..31350c8
--- /dev/null
+++ b/Runtime/Profiler/ObjectMemoryProfiler.cpp
@@ -0,0 +1,245 @@
+#include "UnityPrefix.h"
+#include "ObjectMemoryProfiler.h"
+#include "MemoryProfiler.h"
+#include "SerializationUtility.h"
+#include "Runtime/Misc/GarbageCollectSharedAssets.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Misc/SystemInfo.h"
+#include "Runtime/Mono/MonoBehaviour.h"
+#include "ExtractLoadedObjectInfo.h"
+
+#if ENABLE_MEM_PROFILER
+
+#if UNITY_EDITOR
+#include "Editor/Src/Prefabs/Prefab.h"
+#endif
+
+namespace ObjectMemoryProfiler
+{
+
+#if ENABLE_MEM_PROFILER && ENABLE_PLAYERCONNECTION
+#define USE_MONO_LIVENESS (ENABLE_MONO && !UNITY_NACL)
+#endif
+
+
+typedef std::vector<Object*> ObjectList;
+static const UInt32 OBJECT_MEMORY_STREAM_VERSION = 0x00000002;
+static const UInt32 OBJECT_MEMORY_STREAM_TAIL = 0xAFAFAFAF;
+
+
+#if UNITY_EDITOR
+struct MonoObjectMemoryInfo
+{
+ int instanceId;
+ UInt32 memorySize;
+ int count;
+ int reason;
+ ScriptingStringPtr name;
+ ScriptingStringPtr className;
+};
+#endif
+
+static void Serialize(dynamic_array<int>& stream, const char* customAreaName, const char* objectName, size_t memorySize)
+{
+ stream.push_back(0);
+ stream.push_back(memorySize);
+ stream.push_back(0);
+ stream.push_back(kNotApplicable);
+ WriteString(stream, objectName);
+ WriteString(stream, customAreaName);
+}
+
+static void Serialize(dynamic_array<int>& stream, const char* objectName, int count )
+{
+ stream.push_back(0);
+ stream.push_back(0);
+ stream.push_back(count);
+ stream.push_back(kNotApplicable);
+ WriteString(stream, objectName);
+ WriteString(stream, "");
+}
+
+static void Serialize(dynamic_array<int>& stream, Object* object, int count )
+{
+ const char* objectName = object->GetName();
+ const std::string& className = object->GetClassName();
+
+ stream.push_back(object->GetInstanceID());
+ stream.push_back(object->GetRuntimeMemorySize());
+ stream.push_back(count);
+ stream.push_back(GetLoadedObjectReason(object));
+ if(object->GetClassID() == ClassID (MonoBehaviour))
+ WriteString(stream, ((MonoBehaviour*)(object))->GetScriptFullClassName().c_str());
+#if UNITY_EDITOR
+ else if(object->GetClassID() == ClassID (Prefab))
+ WriteString(stream, ((Prefab*)(object))->GetRootGameObject()->GetName());
+#endif
+ else
+ WriteString(stream, objectName);
+ WriteString(stream, className.c_str());
+}
+
+static void SerializeHeader(dynamic_array<int>& stream)
+{
+ stream.push_back(UNITY_LITTLE_ENDIAN);
+ int version = OBJECT_MEMORY_STREAM_VERSION;
+#if ENABLE_STACKS_ON_ALL_ALLOCS
+ version += 0x10000000;
+#endif
+ stream.push_back(version);
+}
+
+void TakeMemorySnapshot(dynamic_array<int>& stream)
+{
+ dynamic_array<Object*> loadedObjects;
+ dynamic_array<const char*> additionalCategories;
+ dynamic_array<UInt32> indexCounts;
+ dynamic_array<UInt32> referencedObjectIndices;
+
+ CalculateAllObjectReferences (loadedObjects, additionalCategories, indexCounts, referencedObjectIndices);
+
+ // MemoryProfiler Roots
+ MemoryProfiler::RootAllocationInfos rootInfos (kMemProfiler);
+ GetMemoryProfiler ()->GetRootAllocationInfos(rootInfos);
+
+ // loaded objects contain all loaded game objects
+ // this is followed by additional strings that has references as well
+ // indexCounts contain the count for each of the previous object references
+ // the indices into the object array for the references
+
+ SerializeHeader(stream);
+
+ // serialize the referenceIndices
+ stream.push_back(referencedObjectIndices.size());
+ WriteIntArray(stream, (int*)&referencedObjectIndices[0],referencedObjectIndices.size());
+
+ // serialize loaded objects followed by additionalCats
+ int totalObjects = loadedObjects.size() + additionalCategories.size() + rootInfos.size() + 1
+ #if ENABLE_MONO
+ + 2
+ #endif
+ ;
+ stream.push_back(totalObjects);
+
+ for(int i = 0 ; i < loadedObjects.size() ; i++)
+ {
+ Serialize(stream, loadedObjects[i], indexCounts[i]);
+ }
+
+ for(int i=0;i<additionalCategories.size();i++)
+ {
+ Serialize(stream, additionalCategories[i], indexCounts[i+loadedObjects.size()]);
+ }
+
+ for(int i=0;i<rootInfos.size();i++)
+ Serialize(stream, rootInfos[i].areaName, rootInfos[i].objectName, rootInfos[i].memorySize);
+
+ Serialize(stream, "System.ExecutableAndDlls", "", systeminfo::GetExecutableSizeMB()*1024*1024);
+
+ #if ENABLE_MONO
+ Serialize(stream, "ManagedHeap.UsedSize", "", mono_gc_get_used_size ());
+ Serialize(stream, "ManagedHeap.ReservedUnusedSize", "", mono_gc_get_heap_size () - mono_gc_get_used_size ());
+
+ ///@TOOD: Other mono metadata
+ #endif
+ stream.push_back(OBJECT_MEMORY_STREAM_TAIL);
+}
+
+
+#if UNITY_EDITOR
+
+static void Deserialize(const void* data, size_t size, MonoArray ** objectArray, MonoArray ** referenceArray)
+{
+ int* current_offset = (int*)data;
+ int wordsize = size/sizeof(int);
+ int* endBuffer = current_offset + wordsize;
+
+ int dataIsLittleEndian = *current_offset++;
+ bool swapdata = UNITY_LITTLE_ENDIAN ? dataIsLittleEndian == 0 : dataIsLittleEndian != 0;
+ if(swapdata)
+ {
+ int* ptr = current_offset;
+ while(ptr < endBuffer)
+ SwapEndianBytes(*(ptr++));
+ }
+
+ // header
+
+ int version = OBJECT_MEMORY_STREAM_VERSION;
+#if ENABLE_STACKS_ON_ALL_ALLOCS
+ version += 0x10000000;
+#endif
+ if(*current_offset!=version)
+ return;
+ current_offset++;
+
+ // deserialize the referenceIndices
+ int numberOfReferences = *current_offset++;
+ MonoArray* reference_array = mono_array_new(mono_domain_get(), MONO_COMMON.int_32, numberOfReferences);
+ ReadIntArray(&current_offset, &Scripting::GetScriptingArrayElement<int> (reference_array, 0), numberOfReferences);
+
+ // objectCount
+ int numberOfObjects = *current_offset++;
+ MonoClass* klass = GetMonoManager().GetMonoClass ("ObjectMemoryInfo", "UnityEditorInternal");
+ MonoArray* object_array = mono_array_new(mono_domain_get(), klass, numberOfObjects);
+
+ for (int i = 0; i < numberOfObjects;i++)
+ {
+ MonoObject* obj = mono_object_new (mono_domain_get(), klass);
+ GetMonoArrayElement<MonoObject*> (object_array,i) = obj;
+ }
+
+ for (int i = 0; i < numberOfObjects;i++)
+ {
+ MonoObjectMemoryInfo& memInfo = ExtractMonoObjectData<MonoObjectMemoryInfo> (GetMonoArrayElement<MonoObject*> (object_array,i));
+ memInfo.instanceId = *current_offset++;
+ memInfo.memorySize = *current_offset++;
+ memInfo.count = *current_offset++;
+ memInfo.reason = *current_offset++;
+ std::string name;
+ std::string className;
+ ReadString(&current_offset, name, swapdata);
+ ReadString(&current_offset, className, swapdata);
+ memInfo.name = MonoStringNew(name);
+ memInfo.className = MonoStringNew(className);
+ }
+
+ Assert(*current_offset==OBJECT_MEMORY_STREAM_TAIL);
+
+ *objectArray = object_array;
+ *referenceArray = reference_array;
+}
+
+void DeserializeAndApply (const void* data, size_t size)
+{
+ MonoArray* objectArray;
+ MonoArray* indexArray;
+ Deserialize (data, size, &objectArray, &indexArray);
+
+ ScriptingInvocation invocation ("UnityEditor", "ProfilerWindow", "SetMemoryProfilerInfo");
+ invocation.AddArray(objectArray);
+ invocation.AddArray(indexArray);
+ invocation.Invoke();
+}
+
+void SetDataFromEditor ()
+{
+ dynamic_array<int> data;
+ TakeMemorySnapshot(data);
+ DeserializeAndApply(data.begin(), data.size()*sizeof(int));
+
+#if RECORD_ALLOCATION_SITES
+ MemoryProfiler::MemoryStackEntry* stack = GetMemoryProfiler()->GetStackOverview();
+ MonoObject* obj = stack->Deserialize ();
+ void* arguments[] = { obj };
+ CallStaticMonoMethod("MemoryProfiler", "SetMemoryProfilerStackInfo", arguments);
+ GetMemoryProfiler()->ClearStackOverview(stack);
+
+ ProfilerString unrooted = GetMemoryProfiler()->GetUnrootedAllocationsOverview();
+#endif
+}
+
+#endif
+}
+
+#endif
diff --git a/Runtime/Profiler/ObjectMemoryProfiler.h b/Runtime/Profiler/ObjectMemoryProfiler.h
new file mode 100644
index 0000000..95c94b3
--- /dev/null
+++ b/Runtime/Profiler/ObjectMemoryProfiler.h
@@ -0,0 +1,19 @@
+#ifndef _OBJECT_MEMORY_PROFILER
+#define _OBJECT_MEMORY_PROFILER
+
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/Utilities/dynamic_array.h"
+
+#if ENABLE_MEM_PROFILER
+
+namespace ObjectMemoryProfiler
+{
+ void TakeMemorySnapshot (dynamic_array<int>& stream);
+
+#if UNITY_EDITOR
+ void SetDataFromEditor ();
+ void DeserializeAndApply (const void* data, size_t size);
+#endif
+};
+#endif
+#endif
diff --git a/Runtime/Profiler/Profiler.h b/Runtime/Profiler/Profiler.h
new file mode 100644
index 0000000..ee47e6c
--- /dev/null
+++ b/Runtime/Profiler/Profiler.h
@@ -0,0 +1,187 @@
+#ifndef _PROFILER_H_
+#define _PROFILER_H_
+
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_PROFILER
+
+#include "Runtime/Utilities/EnumFlags.h"
+#include "Runtime/Scripting/Backend/ScriptingTypes.h"
+
+/*
+ // Example of profiling a code block:
+ // Define PROFILER_INFORMATION outside of a function
+
+ PROFILER_INFORMATION (gMyReallyFastFunctionProfile, "MyClass.MyFunction", kProfilerRender)
+
+ void MyFunction ()
+ {
+ PROFILER_AUTO (gMyReallyFastFunctionProfile, this or NULL);
+ // or
+ PROFILER_BEGIN (gMyReallyFastFunctionProfile, this or NULL);
+ PROFILER_END
+ }
+
+ // PROFILER_AUTO_THREAD_SAFE etc. can be used if you are not sure if the code might be called from another thread
+ // PROFILER_AUTO_INTERNAL etc. can be used if you do not want the profiler blocks to be in released
+ // unity builds only in internal developer builds
+
+ */
+
+enum ProfilerMode
+{
+ kProfilerEnabled = 1 << 0,
+ kProfilerGame = 1 << 1,
+ kProfilerDeepScripts = 1 << 2,
+ kProfilerEditor = 1 << 3,
+};
+ENUM_FLAGS(ProfilerMode);
+
+class Object;
+struct ProfilerSample;
+
+// ProfilerHistory uses AddToChart to sum the different groups into different charts.
+// Only kProfilerRender, kProfilerScripts, kProfilerPhysics, kProfilerGC, kProfilerVSync currently make any impact in the UI.
+enum ProfilerGroup
+{
+ kProfilerRender,
+ kProfilerScripts,
+ kProfilerGUI,
+ kProfilerPhysics,
+ kProfilerAnimation,
+ kProfilerAI,
+ kProfilerAudio,
+ kProfilerParticles,
+ kProfilerNetwork,
+ kProfilerLoading,
+ kProfilerOther,
+ kProfilerGC,
+ kProfilerVSync,
+ kProfilerOverhead,
+ kProfilerPlayerLoop,
+ kProfilerGroupCount
+};
+
+enum GpuSection
+{
+ kGPUSectionOther,
+ kGPUSectionOpaquePass,
+ kGPUSectionTransparentPass,
+ kGPUSectionShadowPass,
+ kGPUSectionDeferedPrePass,
+ kGPUSectionDeferedLighting,
+ kGPUSectionPostProcess
+};
+
+struct EXPORT_COREMODULE ProfilerInformation
+{
+ ProfilerInformation (const char* const functionName, ProfilerGroup grp, bool warn = false );
+
+ const char* name; // function
+ UInt16 group; // ProfilerGroup
+ enum { kDefault = 0, kScriptMonoRuntimeInvoke = 1, kScriptEnterLeave = 2 };
+ UInt8 flags;
+ UInt8 isWarning;
+
+ void* intelGPAData;
+};
+
+void EXPORT_COREMODULE profiler_begin(ProfilerInformation* info, const Object* obj);
+void EXPORT_COREMODULE profiler_end();
+
+void EXPORT_COREMODULE profiler_begin_thread_safe(ProfilerInformation* info, const Object* obj);
+void EXPORT_COREMODULE profiler_end_thread_safe();
+
+ProfilerSample* EXPORT_COREMODULE mono_profiler_begin(ScriptingMethodPtr method, ScriptingClassPtr profileKlass, ScriptingObjectPtr instance);
+void EXPORT_COREMODULE mono_profiler_end(ProfilerSample* beginsample);
+
+void EXPORT_COREMODULE gpu_time_sample();
+
+void profiler_begin_frame();
+void profiler_end_frame();
+void profiler_start_mode(ProfilerMode flags);
+void profiler_end_mode(ProfilerMode flags);
+
+// Create&destroy a profiler for a specific thread (Used by worker threads & GPU thread)
+void profiler_initialize_thread (const char* name, bool separateBeginEnd);
+void profiler_cleanup_thread ();
+
+// API for worker threads & GfxThread
+void profiler_set_active_seperate_thread (bool enabled);
+void profiler_begin_frame_seperate_thread (ProfilerMode mode);
+void profiler_end_frame_seperate_thread (int frameIDAndValid);
+void profiler_disable_sampling_seperate_thread ();
+
+// Profiler interface macros
+#define PROFILER_INFORMATION(VAR_NAME, NAME, GROUP) static ProfilerInformation VAR_NAME(NAME, GROUP);
+#define PROFILER_WARNING(VAR_NAME, NAME, GROUP) static ProfilerInformation VAR_NAME(NAME, GROUP, true);
+#define PROFILER_AUTO(INFO, OBJECT_PTR) ProfilerAutoObject _PROFILER_AUTO_OBJECT_(&INFO, OBJECT_PTR);
+#define PROFILER_BEGIN(INFO, OBJECT_PTR) profiler_begin (&INFO, OBJECT_PTR);
+#define PROFILER_END profiler_end();
+
+#define MONO_PROFILER_BEGIN(monomethod,monoclass,obj) ProfilerSample* beginsample = mono_profiler_begin (monomethod, monoclass,obj);
+#define MONO_PROFILER_END mono_profiler_end(beginsample);
+
+#define PROFILER_AUTO_THREAD_SAFE(INFO, OBJECT_PTR) ProfilerAutoObjectThreadSafe _PROFILER_AUTO_OBJECT_(&INFO, OBJECT_PTR);
+
+#define GPU_TIMESTAMP() gpu_time_sample();
+#define GPU_AUTO_SECTION(section) AutoGpuSection autoGpuSection(section);
+
+struct ProfilerAutoObject
+{
+ ProfilerAutoObject (ProfilerInformation* info, const Object* obj) { profiler_begin(info, obj); }
+ ~ProfilerAutoObject() { profiler_end(); }
+};
+
+struct ProfilerAutoObjectThreadSafe
+{
+ ProfilerAutoObjectThreadSafe (ProfilerInformation* info, const Object* obj) { profiler_begin_thread_safe (info, obj); }
+ ~ProfilerAutoObjectThreadSafe() { profiler_end_thread_safe(); }
+};
+
+extern GpuSection g_CurrentGPUSection;
+
+class AutoGpuSection
+{
+public:
+ AutoGpuSection(GpuSection section) { oldGPUSection = g_CurrentGPUSection; g_CurrentGPUSection = section; }
+ ~AutoGpuSection() { g_CurrentGPUSection = oldGPUSection; }
+private:
+ GpuSection oldGPUSection;
+};
+
+
+#else
+
+#define PROFILER_INFORMATION(VAR_NAME, NAME, GROUP)
+#define PROFILER_WARNING(VAR_NAME, NAME, GROUP)
+#define PROFILER_AUTO(INFO, OBJECT_PTR)
+#define PROFILER_BEGIN(INFO, OBJECT_PTR)
+#define PROFILER_END
+#define MONO_PROFILER_BEGIN(monomethod,monoclass,obj)
+#define MONO_PROFILER_END
+
+#define PROFILER_AUTO_THREAD_SAFE(INFO, OBJECT_PTR)
+
+#define GPU_TIMESTAMP()
+#define GPU_AUTO_SECTION(section)
+
+
+#endif
+
+
+#if ENABLE_PROFILER_INTERNAL_CALLS
+#define PROFILER_AUTO_INTERNAL(INFO, OBJECT_PTR) PROFILER_AUTO(INFO, OBJECT_PTR)
+#define PROFILER_BEGIN_INTERNAL(INFO, OBJECT_PTR) PROFILER_BEGIN(INFO, OBJECT_PTR)
+#define PROFILER_END_INTERNAL PROFILER_END
+
+#define PROFILER_AUTO_THREAD_SAFE_INTERNAL(INFO, OBJECT_PTR) PROFILER_AUTO_THREAD_SAFE(INFO, OBJECT_PTR)
+#else
+#define PROFILER_AUTO_INTERNAL(INFO, OBJECT_PTR)
+#define PROFILER_BEGIN_INTERNAL(INFO, OBJECT_PTR)
+#define PROFILER_END_INTERNAL
+
+#define PROFILER_AUTO_THREAD_SAFE_INTERNAL(INFO, OBJECT_PTR)
+#endif
+
+#endif /*_PROFILER_H_*/
diff --git a/Runtime/Profiler/ProfilerConnection.cpp b/Runtime/Profiler/ProfilerConnection.cpp
new file mode 100644
index 0000000..7d299dc
--- /dev/null
+++ b/Runtime/Profiler/ProfilerConnection.cpp
@@ -0,0 +1,353 @@
+#include "UnityPrefix.h"
+#include "ProfilerConnection.h"
+
+#if ENABLE_PROFILER
+
+#if ENABLE_PLAYERCONNECTION
+#include "ProfilerImpl.h"
+#include "ProfilerFrameData.h"
+#include "ProfilerStats.h"
+#include "Runtime/Mono/MonoHeapShot.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Network/NetworkUtility.h"
+#include "Runtime/Network/PlayerCommunicator/PlayerConnection.h"
+#include "Runtime/Network/PlayerCommunicator/EditorConnection.h"
+#include "ProfilerHistory.h"
+#include "ObjectMemoryProfiler.h"
+#endif
+
+#if UNITY_EDITOR
+#include "Editor/Src/ProjectWizardUtility.h"
+#include "Runtime/Utilities/FileUtilities.h"
+#include "Runtime/Utilities/PathNameUtility.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Mono/MonoManager.h"
+#include <ctime>
+#endif
+
+UInt32 ProfilerConnection::ms_EditorGuid = 0xFFFFFFFF;
+UInt32 ProfilerConnection::ms_CustomIPGuid = 0xFFFFFFFE;
+ProfilerConnection* ProfilerConnection::ms_Instance = NULL;
+
+void ProfilerConnection::Initialize()
+{
+ Assert(ms_Instance == NULL);
+ ms_Instance = new ProfilerConnection();
+#if ENABLE_PLAYERCONNECTION
+ ms_Instance->PrepareConnections();
+#endif
+}
+
+void ProfilerConnection::Cleanup()
+{
+ Assert(ms_Instance != NULL);
+#if ENABLE_PLAYERCONNECTION
+ ms_Instance->RemoveConnections();
+#endif
+ delete ms_Instance;
+ ms_Instance = NULL;
+}
+
+ProfilerConnection::ProfilerConnection()
+: m_ConnectedProfiler(ms_EditorGuid)
+, m_CurrentBuildTarget(kBuildNoTargetPlatform)
+{
+}
+
+#if UNITY_EDITOR
+
+void ProfilerConnection::DirectIPConnect(const std::string& IP)
+{
+ m_ConnectedProfiler = EditorConnection::Get().ConnectPlayerDirectIP(IP);
+ if(m_ConnectedProfiler == PLAYER_DIRECT_IP_CONNECT_GUID)
+ EnableConnectedProfiler(true);
+}
+
+void ProfilerConnection::GetAvailableProfilers ( std::vector<UInt32>& values )
+{
+ values.clear();
+ values.push_back(ms_EditorGuid);
+ EditorConnection::Get().GetAvailablePlayers(values);
+}
+
+void ProfilerConnection::EnableConnectedProfiler ( bool enable )
+{
+ int enabled = enable;
+ EditorConnection::Get().SendMessage(m_ConnectedProfiler, GeneralConnection::kProfileStartupInformation, &enabled, 4);
+}
+
+std::string ProfilerConnection::GetConnectionIdentification(UInt32 guid)
+{
+ if (guid != ms_EditorGuid)
+ return EditorConnection::Get().GetConnectionIdentifier(guid);
+ return "Editor";
+}
+
+bool ProfilerConnection::IsIdentifierConnectable(UInt32 guid)
+{
+ if (guid != ms_EditorGuid)
+ return EditorConnection::Get().IsIdentifierConnectable(guid);
+ return true;
+}
+
+bool ProfilerConnection::IsIdentifierOnLocalhost(UInt32 guid)
+{
+ if (guid != ms_EditorGuid)
+ return EditorConnection::Get().IsIdentifierOnLocalhost(guid);
+ return true;
+}
+
+bool ProfilerConnection::IsConnectionEditor()
+{
+ return m_ConnectedProfiler == ms_EditorGuid;
+}
+
+void ProfilerConnection::SetConnectedProfiler( UInt32 guid, bool sendDisable)
+{
+ if (guid == ms_EditorGuid){
+ if(m_ConnectedProfiler != guid && sendDisable)
+ EnableConnectedProfiler(false);
+ m_ConnectedProfiler = ms_EditorGuid;
+ return;
+ }
+
+ m_ConnectedProfiler = EditorConnection::Get().ConnectPlayer(guid);
+ if(m_ConnectedProfiler == guid)
+ EnableConnectedProfiler(true);
+}
+
+void ProfilerConnection::SetupTargetSpecificConnection(BuildTargetPlatform targetPlatform)
+{
+ if (m_CurrentBuildTarget == targetPlatform)
+ return;
+ m_CurrentBuildTarget = targetPlatform;
+ EditorConnection::Get().RemovePlayer(PLAYER_DIRECTCONNECT_GUID);
+ switch (targetPlatform)
+ {
+ case kBuild_Android:
+ {
+ std::string localhost = "127.0.0.1";
+ std::string hostName = Format("AndroidPlayer(ADB@%s:%i)", localhost.c_str(), PLAYER_DIRECTCONNECT_PORT);
+ UInt32 guid = EditorConnection::Get().AddPlayer(hostName, localhost, PLAYER_DIRECTCONNECT_PORT, PLAYER_DIRECTCONNECT_GUID, GeneralConnection::kSupportsProfile);
+ AssertMsg(guid == PLAYER_DIRECTCONNECT_GUID, "Unable to add Android direct profiler connection");
+ }
+ }
+}
+
+UInt32 ProfilerConnection::GetConnectedProfiler()
+{
+ return m_ConnectedProfiler;
+}
+
+UInt32 ProfilerConnection::GetEditorGuid()
+{
+ return ms_EditorGuid;
+}
+
+void ProfilerConnection::HandleDisconnectionMessage (UInt32 guid)
+{
+ Assert (UNITY_EDITOR);
+ ProfilerConnection::Get().SetConnectedProfiler(ms_EditorGuid, false);
+}
+
+#endif
+
+
+
+
+// Network communication and serialization
+
+#if ENABLE_PLAYERCONNECTION
+
+void ProfilerConnection::PrepareConnections()
+{
+#if UNITY_EDITOR
+ EditorConnection::Get().RegisterConnectionHandler(&ProfilerConnection::HandleConnectionMessage);
+ EditorConnection::Get().RegisterDisconnectionHandler(&ProfilerConnection::HandleDisconnectionMessage);
+ EditorConnection::Get().RegisterMessageHandler(GeneralConnection::kProfileDataMessage, &ProfilerConnection::HandleProfilerDataMessage);
+ EditorConnection::Get().RegisterMessageHandler(GeneralConnection::kObjectMemoryProfileDataMessage, &ProfilerConnection::HandleObjectMemoryProfileDataMessage);
+ EditorConnection::Get().RegisterMessageHandler(GeneralConnection::kFileTransferMessage, &ProfilerConnection::HandleFileDataMessage);
+#else
+ PlayerConnection::Get().RegisterConnectionHandler(&ProfilerConnection::HandleConnectionMessage);
+ PlayerConnection::Get().RegisterMessageHandler(GeneralConnection::kProfileStartupInformation, &ProfilerConnection::EnableProfilerMessage);
+ PlayerConnection::Get().RegisterMessageHandler(GeneralConnection::kObjectMemoryProfileSnapshot, &ProfilerConnection::GetObjectMemoryProfile);
+ PlayerConnection::Get().RegisterMessageHandler(GeneralConnection::kCaptureHeaphshotMessage, &ProfilerConnection::HandleCaptureHeapshotMessage);
+#endif
+}
+
+void ProfilerConnection::RemoveConnections()
+{
+#if UNITY_EDITOR
+ EditorConnection::Get().UnregisterConnectionHandler(&ProfilerConnection::HandleConnectionMessage);
+ EditorConnection::Get().UnregisterDisconnectionHandler(&ProfilerConnection::HandleDisconnectionMessage);
+ EditorConnection::Get().UnregisterMessageHandler(GeneralConnection::kProfileDataMessage, &ProfilerConnection::HandleProfilerDataMessage);
+ EditorConnection::Get().UnregisterMessageHandler(GeneralConnection::kObjectMemoryProfileDataMessage, &ProfilerConnection::HandleObjectMemoryProfileDataMessage);
+ EditorConnection::Get().UnregisterMessageHandler(GeneralConnection::kFileTransferMessage, &ProfilerConnection::HandleFileDataMessage);
+#else
+ PlayerConnection::Get().UnregisterConnectionHandler(&ProfilerConnection::HandleConnectionMessage);
+ PlayerConnection::Get().UnregisterMessageHandler(GeneralConnection::kProfileStartupInformation, &ProfilerConnection::EnableProfilerMessage);
+ PlayerConnection::Get().UnregisterMessageHandler(GeneralConnection::kObjectMemoryProfileSnapshot, &ProfilerConnection::GetObjectMemoryProfile);
+ PlayerConnection::Get().UnregisterMessageHandler(GeneralConnection::kCaptureHeaphshotMessage, &ProfilerConnection::HandleCaptureHeapshotMessage);
+#endif
+}
+
+void ProfilerConnection::HandleConnectionMessage (UInt32 guid)
+{
+ ProfilerConnection::Get().m_ConnectedProfiler = guid;
+#if UNITY_EDITOR
+ ProfilerConnection::Get().EnableConnectedProfiler(true);
+#endif
+}
+
+
+void ProfilerConnection::EnableProfilerMessage ( const void* data, UInt32 size, UInt32 guid)
+{
+ // message sent from Editor to Player, to start and stop the profiler
+ Assert(!UNITY_EDITOR);
+ if(GetBuildSettingsPtr() && !GetBuildSettingsPtr()->hasAdvancedVersion)
+ {
+ ErrorString("Profiler is only supported with Pro License");
+ return;
+ }
+ bool enable = *(int*)data != 0;
+ if (enable)
+ ProfilerConnection::Get().m_ConnectedProfiler = guid;
+ if ( ProfilerConnection::Get().m_ConnectedProfiler == guid )
+ UnityProfiler::Get().SetEnabled(enable);
+}
+
+void ProfilerConnection::GetObjectMemoryProfile( const void* data, UInt32 size, UInt32 guid)
+{
+ // message recieved on the player from Editor
+ Assert(!UNITY_EDITOR);
+ if ( ProfilerConnection::Get().m_ConnectedProfiler != guid )
+ return;
+
+ if(GetBuildSettingsPtr() && !GetBuildSettingsPtr()->hasAdvancedVersion)
+ {
+ ErrorString("Profiler is only supported with Pro License");
+ return;
+ }
+
+#if ENABLE_MEM_PROFILER
+ dynamic_array<int> buffer;
+ ObjectMemoryProfiler::TakeMemorySnapshot(buffer);
+ PlayerConnection::Get().SendMessage(ProfilerConnection::Get().m_ConnectedProfiler,PlayerConnection::kObjectMemoryProfileDataMessage, &buffer[0], buffer.size()*sizeof(int));
+#endif
+}
+
+
+void ProfilerConnection::HandleCaptureHeapshotMessage (const void* data, UInt32 size, UInt32 guid)
+{
+#if ENABLE_MONO_HEAPSHOT
+ printf_console("Capturing heapshot\n");
+ HeapShotData heapShotData;
+ HeapShotDumpObjectMap(heapShotData);
+ //bool WriteBytesToFile (const void *data, int byteLength, const string& pathName);
+ //WriteBytesToFile(&data[0], data.size(), "game:\\test.dump");
+ if (heapShotData.size() > 0)
+ TransferFileOverPlayerConnection("testplayer.heapshot", &heapShotData[0], heapShotData.size());
+#endif
+}
+
+
+void ProfilerConnection::SendFrameDataToEditor ( ProfilerFrameData& data )
+{
+ // TODO: Send partial data ( right now we double buffer the frame on the profiler side, to wait for GPU data)
+ dynamic_array<int> buffer;
+
+ UnityProfiler::SerializeFrameData(data, buffer);
+
+ if(buffer.size()<128*1024)
+ PlayerConnection::Get().SendMessage(m_ConnectedProfiler,PlayerConnection::kProfileDataMessage, &buffer[0], buffer.size()*sizeof(int));
+}
+
+
+
+#if UNITY_EDITOR
+
+void ProfilerConnection::SendCaptureHeapshotMessage()
+{
+ int enabled = 1;
+ //printf_console("Sending capture heapshot cmd");
+ EditorConnection::Get().SendMessage(m_ConnectedProfiler, GeneralConnection::kCaptureHeaphshotMessage, &enabled, sizeof(int));
+}
+
+void ProfilerConnection::HandleFileDataMessage (const void* data, UInt32 size, UInt32 guid)
+{
+ UInt8* uData = (UInt8*) data;
+ //printf_console("HandleFileDataMessage");
+
+ UInt32 fileNameLength = *(UInt32*)uData;
+ uData += sizeof(UInt32);
+
+ char rawFileName[255];
+ memcpy(rawFileName, uData, fileNameLength);
+ rawFileName[fileNameLength] = '\0';
+ uData += fileNameLength;
+
+ UInt32 contentLength = *(UInt32*)uData;
+ uData += sizeof(UInt32);
+
+ //printf_console("Name: %s Content length: %d\n", rawFileName, contentLength);
+
+ time_t now;
+ time(&now);
+ struct tm nowTime;
+ nowTime = *localtime(&now);
+
+ std::string fileName = Format("%04d-%02d-%02d_%02dh%02dm%02ds.heapshot", nowTime.tm_year + 1900, nowTime.tm_mon + 1, nowTime.tm_mday,
+ nowTime.tm_hour, nowTime.tm_min, nowTime.tm_sec);
+ //printf_console(fileName.c_str());
+ // ToDo: figure it out what we're saving, for now think always that it's a heapshot file
+ std::string heapShotDirectory = AppendPathName (GetProjectPath (), "Heapshots");
+ if (CreateDirectorySafe(heapShotDirectory))
+ {
+ std::string fullPath = AppendPathName (heapShotDirectory, fileName);
+ WriteBytesToFile(uData, contentLength, fullPath);
+
+ void* params[] = {scripting_string_new(fileName)};
+ CallStaticMonoMethod ("HeapshotWindow", "EventHeapShotReceived", params);
+ }
+}
+void ProfilerConnection::HandleProfilerDataMessage ( const void* data, UInt32 size, UInt32 guid )
+{
+ if (ProfilerConnection::Get().GetConnectedProfiler() != guid)
+ return;
+
+ if (!UnityProfiler::Get().GetEnabled())
+ return;
+
+ ProfilerFrameData* frame = UNITY_NEW(ProfilerFrameData, kMemProfiler) (1, 0);
+ if( UnityProfiler::DeserializeFrameData(frame, data, size) )
+ ProfilerHistory::Get().AddFrameDataAndTransferOwnership(frame, guid);
+ else
+ UNITY_DELETE(frame, kMemProfiler);
+}
+
+void ProfilerConnection::SendGetObjectMemoryProfile()
+{
+#if ENABLE_MEM_PROFILER
+ if(m_ConnectedProfiler == ms_EditorGuid)
+ ObjectMemoryProfiler::SetDataFromEditor();
+ else
+ EditorConnection::Get().SendMessage(m_ConnectedProfiler, GeneralConnection::kObjectMemoryProfileSnapshot, NULL, 0);
+#endif
+}
+
+void ProfilerConnection::HandleObjectMemoryProfileDataMessage ( const void* data, UInt32 size, UInt32 guid )
+{
+#if ENABLE_MEM_PROFILER
+ if (ProfilerConnection::Get().GetConnectedProfiler() != guid)
+ return;
+
+ ObjectMemoryProfiler::DeserializeAndApply(data,size);
+#endif
+}
+
+#endif
+
+
+
+#endif
+#endif
diff --git a/Runtime/Profiler/ProfilerConnection.h b/Runtime/Profiler/ProfilerConnection.h
new file mode 100644
index 0000000..ffde5b3
--- /dev/null
+++ b/Runtime/Profiler/ProfilerConnection.h
@@ -0,0 +1,91 @@
+#ifndef _PROFILERCONNECTION_H_
+#define _PROFILERCONNECTION_H_
+
+#if ENABLE_PROFILER
+
+#include "Configuration/UnityConfigure.h"
+
+#include "Runtime/Threads/Thread.h"
+#include "ProfilerImpl.h"
+#include "Runtime/Misc/SystemInfo.h"
+#include "Runtime/Serialize/SerializationMetaFlags.h"
+
+class ProfilerFrameData;
+
+enum ConnectedProfiler
+{
+ kConnectedProfilerEditor,
+ kConnectedProfilerStandalone,
+ kConnectedProfilerWeb,
+ kConnectedProfileriPhone,
+ kConnectedProfilerAndroid,
+ kConnectedProfilerXenon,
+ kConnectedProfilerPS3,
+ kConnectedProfilerWII,
+ kConnectedProfilerPepper,
+ kConnectedProfilerCount
+};
+
+class ProfilerConnection
+{
+public:
+ static void Initialize();
+ static void Cleanup();
+ // Singleton accessor for ProfilerConnection
+ static ProfilerConnection& Get() { return *ms_Instance; }
+
+#if UNITY_EDITOR
+ void GetAvailableProfilers (std::vector<UInt32>& values);
+
+ void EnableConnectedProfiler ( bool enable );
+ void SetConnectedProfiler (UInt32 guid, bool sendDisable = true);
+ UInt32 GetConnectedProfiler ();
+ static UInt32 GetEditorGuid();
+ void DirectIPConnect(const std::string& IP);
+
+ std::string GetConnectionIdentification(UInt32 guid);
+ bool IsIdentifierConnectable(UInt32 guid);
+ bool IsIdentifierOnLocalhost(UInt32 guid);
+ bool IsConnectionEditor();
+ void SetupTargetSpecificConnection(BuildTargetPlatform targetPlatform);
+
+ static void HandleFileDataMessage (const void* data, UInt32 size, UInt32 guid);
+ void SendCaptureHeapshotMessage();
+
+ void SendGetObjectMemoryProfile();
+#endif
+
+ void SendFrameDataToEditor( ProfilerFrameData& data );
+
+
+private:
+ ProfilerConnection();
+
+#if ENABLE_PLAYERCONNECTION
+
+ void PrepareConnections();
+ void RemoveConnections();
+
+ static void HandleProfilerDataMessage (const void* data, UInt32 size, UInt32 guid);
+ static void HandleObjectMemoryProfileDataMessage (const void* data, UInt32 size, UInt32 guid);
+ static void HandlePlayerConnectionMessage (const void* data, UInt32 size, UInt32 guid);
+ static void HandleConnectionMessage (UInt32 guid);
+ static void HandleDisconnectionMessage (UInt32 guid);
+ static void EnableProfilerMessage ( const void* data, UInt32 size, UInt32 guid);
+ static void GetObjectMemoryProfile( const void* data, UInt32 size, UInt32 guid);
+ static void HandleCaptureHeapshotMessage ( const void* data, UInt32 size, UInt32 guid);
+#endif
+
+private:
+ UInt32 m_ConnectedProfiler;
+ int m_CurrentBuildTarget;
+
+ // ProfilerConnection instance to use with singleton pattern
+ static ProfilerConnection* ms_Instance;
+ static UInt32 ms_EditorGuid;
+ static UInt32 ms_CustomIPGuid;
+};
+
+#endif
+
+#endif
diff --git a/Runtime/Profiler/ProfilerFrameData.cpp b/Runtime/Profiler/ProfilerFrameData.cpp
new file mode 100644
index 0000000..d56c2fe
--- /dev/null
+++ b/Runtime/Profiler/ProfilerFrameData.cpp
@@ -0,0 +1,230 @@
+#include "UnityPrefix.h"
+#include "ProfilerFrameData.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+
+#if ENABLE_PROFILER
+
+
+
+// -------------------------------------------------------------------
+
+
+dynamic_array<GfxTimerQuery*> ProfilerFrameData::m_UnusedQueries;
+
+ProfilerFrameData::ProfilerFrameData(int threadCount, int frameID)
+: m_FrameID(frameID)
+{
+ Assert(threadCount > 0);
+ m_ThreadData = new ThreadData[threadCount];
+ m_ThreadCount = threadCount;
+}
+
+
+ProfilerFrameData::~ProfilerFrameData()
+{
+ for (int t = 0; t < m_ThreadCount; ++t)
+ {
+ ThreadData& td = m_ThreadData[t];
+ for (int i = 0; i < td.m_GPUTimeSamples.size(); i++)
+ {
+ GfxTimerQuery* query = td.m_GPUTimeSamples[i].timerQuery;
+ if (query)
+ m_UnusedQueries.push_back(query);
+ }
+ }
+ delete[] m_ThreadData;
+}
+
+GfxTimerQuery* ProfilerFrameData::AllocTimerQuery()
+{
+ if (!m_UnusedQueries.empty())
+ {
+ GfxTimerQuery* query = m_UnusedQueries.back();
+ m_UnusedQueries.pop_back();
+ return query;
+ }
+ else
+ return GetGfxDevice().CreateTimerQuery();
+}
+
+void ProfilerFrameData::ReleaseTimerQuery(GfxTimerQuery* query)
+{
+ m_UnusedQueries.push_back(query);
+}
+
+void ProfilerFrameData::FreeAllTimerQueries()
+{
+ for (int i = 0; i < m_UnusedQueries.size(); i++)
+ GetGfxDevice().DeleteTimerQuery(m_UnusedQueries[i]);
+ m_UnusedQueries.clear();
+}
+
+void ProfilerFrameData::ThreadData::ExtractAllChildSamples (UInt32 index, dynamic_array<UInt32>& allChildren) const
+{
+ const ProfilerSample* sample = &m_AllSamples[index];
+ const ProfilerSample* currentSample = sample + 1;
+ for (int i=0;i<sample->nbChildren;i++)
+ {
+ UInt32 currentIndex = currentSample - m_AllSamples.begin();
+ allChildren.push_back(currentIndex);
+ currentSample = SkipSampleRecurse(currentSample);
+ }
+}
+
+
+
+// -------------------------------------------------------------------
+
+
+
+#if UNITY_EDITOR
+
+#include "ProfilerHistory.h"
+
+ProfilerFrameDataIterator::ProfilerFrameDataIterator()
+: m_ThreadIdx(0)
+, m_FrameData(NULL)
+, m_CurrIndex(0)
+{
+}
+
+const ProfilerSample& ProfilerFrameDataIterator::GetSample(UInt32 index) const
+{
+ DebugAssert(m_FrameData);
+ const ProfilerFrameData::ThreadData& tdata = m_FrameData->m_ThreadData[m_ThreadIdx];
+ Assert(index < tdata.m_AllSamples.size());
+
+ return tdata.m_AllSamples[index];
+}
+
+int ProfilerFrameDataIterator::GetGroup() const
+{
+ const ProfilerSample& s = GetSample(m_CurrIndex);
+ return s.information ? s.information->group : kProfilerOther;
+}
+
+float ProfilerFrameDataIterator::GetStartTimeMS () const
+{
+ const ProfilerSample& s = GetSample(m_CurrIndex);
+ return (s.startTimeUS - m_FrameData->m_StartTimeUS) / 1000.0;
+}
+
+float ProfilerFrameDataIterator::GetDurationMS () const
+{
+ const ProfilerSample& s = GetSample(m_CurrIndex);
+ return s.timeUS / 1000.0;
+}
+
+
+int ProfilerFrameDataIterator::GetThreadCount(int frame) const
+{
+ ProfilerFrameData* frameData = ProfilerHistory::Get().GetFrameData(frame);
+ if (frameData == NULL)
+ return 0;
+ return frameData->m_ThreadCount;
+}
+
+double ProfilerFrameDataIterator::GetFrameStartS(int frame) const
+{
+ ProfilerFrameData* frameData = ProfilerHistory::Get().GetFrameData(frame);
+ if (frameData == NULL)
+ return 0.0;
+ return frameData->m_StartTimeUS / 1000000.0;
+}
+
+const std::string* ProfilerFrameDataIterator::GetThreadName () const
+{
+ if (!m_FrameData)
+ return NULL;
+ const ProfilerFrameData::ThreadData& tdata = m_FrameData->m_ThreadData[m_ThreadIdx];
+ return &tdata.m_ThreadName;
+}
+
+
+void ProfilerFrameDataIterator::SetRoot(int frame, int threadIdx)
+{
+ m_Stack.clear();
+ m_CurrIndex = 0;
+ m_FrameData = NULL;
+
+ ProfilerFrameData* frameData = ProfilerHistory::Get().GetFrameData(frame);
+ if (frameData == NULL)
+ return;
+
+ if (threadIdx < 0 || threadIdx >= frameData->m_ThreadCount)
+ return;
+
+ m_FrameData = frameData;
+ m_ThreadIdx = threadIdx;
+
+ m_CurrIndex = 0;
+}
+
+float ProfilerFrameDataIterator::GetFrameTimeMS() const
+{
+ if (m_FrameData)
+ return m_FrameData->m_TotalCPUTimeInMicroSec/1000.0;
+ else
+ return 0.0f;
+}
+
+bool ProfilerFrameDataIterator::GetNext(bool expanded)
+{
+ if (m_FrameData == NULL)
+ return false;
+
+ const ProfilerFrameData::ThreadData& tdata = m_FrameData->m_ThreadData[m_ThreadIdx];
+ const UInt32 nSamples = tdata.m_AllSamples.size();
+ if (m_CurrIndex >= nSamples)
+ {
+ DebugAssert(nSamples == 0);
+ return false;
+ }
+
+ const ProfilerSample* s = tdata.GetSample (m_CurrIndex);
+ const ProfilerSample* nextSameLevel = SkipSampleRecurse (s);
+ const UInt32 idxNextSameLevel = nextSameLevel - tdata.m_AllSamples.data();
+
+ const bool hasChildren = s->nbChildren != 0;
+ if (hasChildren && expanded)
+ {
+ // entering into children, push our range into stack
+ StackInfo info;
+ if (!m_Stack.empty())
+ info.path = m_Stack.back().path;
+ const ProfilerSample* ps = tdata.GetSample(m_CurrIndex);
+ if (ps && ps->information)
+ {
+ info.path += (ps && ps->information) ? ps->information->name : "?";
+ info.path += '/';
+ }
+
+ info.sampleBegin = m_CurrIndex;
+ info.sampleEnd = idxNextSameLevel;
+ m_Stack.push_back(info);
+ ++m_CurrIndex;
+ }
+ else
+ {
+ // We'll go to sample idxNextSameLevel. But if we've reached end of our level,
+ // that sample might be at parent level already. Pop scope until we're at
+ // the right level.
+ m_CurrIndex = idxNextSameLevel;
+ while (!m_Stack.empty() && idxNextSameLevel >= m_Stack.back().sampleEnd)
+ m_Stack.pop_back();
+ if (m_Stack.empty())
+ return false; // reached very end
+ }
+
+ s = tdata.GetSample (m_CurrIndex);
+
+ m_FunctionName = s->information ? s->information->name : "?";
+ m_FunctionPath = m_Stack.back().path + m_FunctionName;
+
+ return true;
+}
+
+
+#endif // #if UNITY_EDITOR
+
+#endif // #if ENABLE_PROFILER
diff --git a/Runtime/Profiler/ProfilerFrameData.h b/Runtime/Profiler/ProfilerFrameData.h
new file mode 100644
index 0000000..97bb273
--- /dev/null
+++ b/Runtime/Profiler/ProfilerFrameData.h
@@ -0,0 +1,134 @@
+#ifndef _PROFILERFRAMEDATA_H_
+#define _PROFILERFRAMEDATA_H_
+
+
+#include "ProfilerImpl.h"
+
+#if ENABLE_PROFILER
+
+
+// -------------------------------------------------------------------
+
+
+// Profiling data stored for one frame
+class ProfilerFrameData
+{
+public:
+ struct ThreadData
+ {
+ ThreadData()
+ : m_AllSamples(kMemProfiler)
+ , m_GPUTimeSamples(kMemProfiler)
+ , m_InstanceIDSamples(kMemProfiler)
+ , m_AllocatedGCMemorySamples(kMemProfiler)
+ , m_WarningSamples(kMemProfiler)
+ , m_ThreadName("<no data this frame>")
+ {
+ }
+
+ const ProfilerSample* GetRoot () const { return &m_AllSamples[0]; }
+ ProfilerSample* GetRoot () { return &m_AllSamples[0]; }
+ const ProfilerSample* GetSample (int sample) const { return &m_AllSamples[sample]; }
+
+ void ExtractAllChildSamples (UInt32 index, dynamic_array<UInt32>& allChildren) const;
+
+ dynamic_array<ProfilerSample> m_AllSamples;
+ dynamic_array<ProfilerData::GPUTime> m_GPUTimeSamples;
+ dynamic_array<ProfilerData::InstanceID> m_InstanceIDSamples;
+ dynamic_array<ProfilerData::AllocatedGCMemory> m_AllocatedGCMemorySamples;
+ dynamic_array<UInt32> m_WarningSamples;
+
+ std::string m_ThreadName;
+ };
+
+ ProfilerFrameData(int threadCount, int frameID);
+ ~ProfilerFrameData();
+
+ int m_FrameID;
+ int frameIndex;
+ int realFrame;
+
+ // Automatic statistic value extraction can extract any int value from here
+ AllProfilerStats allStats;
+ int selectedTime;
+ // Until here
+
+ ThreadData* m_ThreadData;
+ int m_ThreadCount;
+
+ ProfileTimeFormat m_StartTimeUS;
+ int m_TotalCPUTimeInMicroSec;
+ int m_TotalGPUTimeInMicroSec;
+
+#if ENABLE_PLAYERCONNECTION
+ void Serialize(dynamic_array<int>& bs);
+ void Deserialize(int** bs, bool swapdata);
+
+ static ProfilerInformation* DeserializeProfilerInformation( int** bitstream, bool swapdata );
+ static void SerializeProfilerInformation( const ProfilerInformation& info, dynamic_array<int>& bitstream );
+
+#endif
+
+ static GfxTimerQuery* AllocTimerQuery();
+ static void ReleaseTimerQuery(GfxTimerQuery* query);
+ static void FreeAllTimerQueries();
+
+private:
+ static dynamic_array<GfxTimerQuery*> m_UnusedQueries;
+};
+
+
+// -------------------------------------------------------------------
+
+
+#if UNITY_EDITOR
+
+// Hierarchical iterator over raw samples in one profiler frame
+class ProfilerFrameDataIterator
+{
+public:
+ ProfilerFrameDataIterator();
+
+ int GetThreadCount(int frame) const;
+ double GetFrameStartS(int frame) const;
+ const std::string* GetThreadName () const;
+ float GetFrameTimeMS() const;
+ float GetStartTimeMS() const;
+
+ float GetDurationMS() const;
+ int GetGroup() const;
+ int GetID() const { return m_CurrIndex; }
+ int GetDepth() const { return m_Stack.size(); }
+ const std::string& GetFunctionName() const { return m_FunctionName; }
+ const std::string& GetFunctionPath() const { return m_FunctionPath; }
+
+ void SetRoot(int frame, int threadIdx);
+ bool GetNext(bool expanded);
+ bool IsFrameValid() const { return m_FrameData != NULL; }
+
+private:
+ const ProfilerSample& GetSample(UInt32 index) const;
+
+private:
+ struct StackInfo
+ {
+ std::string path;
+ UInt32 sampleBegin;
+ UInt32 sampleEnd;
+ };
+
+ ProfilerFrameData* m_FrameData;
+ int m_ThreadIdx;
+
+ UNITY_VECTOR(kMemProfiler,StackInfo) m_Stack;
+ UInt32 m_CurrIndex;
+
+ std::string m_FunctionName; // name (e.g. Camera.Render)
+ std::string m_FunctionPath; // hierarchical name, e.g. PlayerLoop/RenderCameras/Camera.Render
+};
+
+#endif // #if UNITY_EDITOR
+
+#endif // #if ENABLE_PROFILER
+
+#endif
diff --git a/Runtime/Profiler/ProfilerHistory.cpp b/Runtime/Profiler/ProfilerHistory.cpp
new file mode 100644
index 0000000..d0f9056
--- /dev/null
+++ b/Runtime/Profiler/ProfilerHistory.cpp
@@ -0,0 +1,560 @@
+#include "UnityPrefix.h"
+#include "ProfilerHistory.h"
+
+#if ENABLE_PROFILER && UNITY_EDITOR
+
+#include "Runtime/Network/PlayerCommunicator/PlayerConnection.h"
+#include "Runtime/Network/PlayerCommunicator/EditorConnection.h"
+#include "ProfilerStats.h"
+#include "Profiler.h"
+#include "GPUProfiler.h"
+#include "Runtime/Utilities/Word.h"
+#include "ProfilerImpl.h"
+#include "ProfilerProperty.h"
+#include "ProfilerConnection.h"
+#include "Runtime/Utilities/PathNameUtility.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+
+#include <fstream>
+#include <sstream>
+#include "Runtime/Misc/SystemInfo.h"
+
+static const ProfilerSample* CalculateSampleAtPath (const ProfilerSample* sample, const char* path, dynamic_array<ProfilerSample*>& outputSamples);
+
+ProfilerHistory* ProfilerHistory::ms_Instance = NULL;
+
+// ProfilerHistory class implementation
+//
+
+void ProfilerHistory::Initialize()
+{
+ Assert(ms_Instance == NULL);
+ ms_Instance = new ProfilerHistory();
+}
+
+void ProfilerHistory::Cleanup()
+{
+ Assert(ms_Instance != NULL);
+ delete ms_Instance;
+ ms_Instance = NULL;
+}
+
+ProfilerHistory::ProfilerHistory()
+ : m_MaxFrameHistoryLength(300)
+ , m_FrameCounter(0)
+ , m_BytesUsedLastFrame(0u)
+ , m_HistoryPosition(-1)
+ , m_Frames(kMemProfiler)
+ , m_Properties(kMemProfiler)
+{
+ InitializeStatisticsProperties(m_Properties);
+
+ m_FramesWithGPUData = 0;
+#if SUPPORT_THREADS
+ m_MainThreadID = Thread::GetCurrentThreadID();
+#endif
+}
+
+ProfilerHistory::~ProfilerHistory()
+{
+ CleanupFrameHistory();
+}
+
+
+static inline int FindSeperator (const char* in)
+{
+ const char* c = in;
+ while (*c != '/' && *c != '\0')
+ c++;
+ return c - in;
+}
+
+static const ProfilerSample* CalculateSampleAtPath (const ProfilerSample* sample, const char* path, dynamic_array<ProfilerSample*>& outputSamples)
+{
+ int seperatorIndex = FindSeperator(path);
+
+ const ProfilerSample* child = sample + 1;
+ for (int i=0;i<sample->nbChildren;i++)
+ {
+ if (strncmp(child->information->name, path, seperatorIndex) == 0)
+ {
+ if (seperatorIndex == strlen (path))
+ {
+ outputSamples.push_back(const_cast<ProfilerSample*>(child));
+ child = SkipSampleRecurse(child);
+ }
+ else
+ {
+ child = CalculateSampleAtPath(child, path + seperatorIndex + 1, outputSamples);
+ }
+ }
+ else
+ {
+ child = SkipSampleRecurse(child);
+ }
+ }
+
+ return child;
+}
+
+static void AddToChart(ChartSample& data, int group, int sampleTime, int direction)
+{
+ // Add times to their groups
+ if (group == kProfilerRender)
+ data.rendering += sampleTime * direction;
+ else if (group == kProfilerScripts)
+ data.scripts += sampleTime * direction;
+ else if (group == kProfilerPhysics)
+ data.physics += sampleTime * direction;
+ else if (group == kProfilerGC)
+ data.gc += sampleTime * direction;
+ else if (group == kProfilerVSync)
+ data.vsync += sampleTime * direction;
+ else
+ data.others += sampleTime * direction;
+}
+
+
+
+
+// Adds the self time of each sample into the group.
+// Expects that ChartSample has all elements set to zero, except the other time which needs to be the root time
+// of the root sample.
+static const ProfilerSample* RecursiveAdjustChartForGroupChange(ChartSample& data, const ProfilerSample* root, int direction)
+{
+ Assert(root != NULL);
+
+ const ProfilerSample* childSample = root + 1;
+
+ // Walk through the children and collect chart information recursively
+ for (int i=0;i<root->nbChildren;i++)
+ {
+ //@TODO: Make this cleaner
+ if (root->information == NULL || root->information->group != childSample->information->group)
+ {
+ int childSampleTime = (int) childSample->timeUS*1000;
+
+ // Add times to their groups
+ AddToChart(data, childSample->information->group, childSampleTime, direction);
+
+ // Remove this sample time from the group the parent is in, thus we end up with only the self time of the parent
+ if (root->information != NULL)
+ AddToChart(data, root->information->group, childSampleTime, -direction);
+ }
+
+ childSample = RecursiveAdjustChartForGroupChange(data, childSample, direction);
+ }
+
+ return childSample;
+}
+
+static void GatherChartInformation (ChartSample& data, const ProfilerSample* sample, int size)
+{
+ const ProfilerSample* end = RecursiveAdjustChartForGroupChange(data, sample, 1);
+ Assert(end - sample == size || size == -1);
+}
+
+static void GatherGPUChartInformation (ChartSample& data, const ProfilerData::GPUTime* sample, int size)
+{
+ data.hasGPUProfiler = size != 0;
+ for(int i = 0; i < size; i++)
+ {
+ int sampletime = sample[i].gpuTimeInMicroSec*1000;
+ switch(sample[i].gpuSection)
+ {
+ case kGPUSectionShadowPass:
+ data.gpuShadows += sampletime;
+ break;
+ case kGPUSectionOpaquePass:
+ data.gpuOpaque += sampletime;
+ break;
+ case kGPUSectionTransparentPass:
+ data.gpuTransparent += sampletime;
+ break;
+ case kGPUSectionPostProcess:
+ data.gpuPostProcess += sampletime;
+ break;
+ case kGPUSectionDeferedPrePass:
+ data.gpuDeferredPrePass += sampletime;
+ break;
+ case kGPUSectionDeferedLighting:
+ data.gpuDeferredLighting += sampletime;
+ break;
+ default:
+ data.gpuOther += sampletime;
+ break;
+ };
+ }
+}
+
+void ProfilerHistory::AddFrameDataAndTransferOwnership (ProfilerFrameData* frame, int guid)
+{
+ Assert(UNITY_EDITOR);
+
+ if(!AddFrame(frame, guid))
+ UNITY_DELETE(frame, kMemProfiler);
+}
+
+bool ProfilerHistory::AddFrame (ProfilerFrameData* frame, int identifier)
+{
+ if (ProfilerConnection::Get().GetConnectedProfiler() != identifier)
+ return false;
+
+ // Fill in group timing information for the charts
+ ChartSample& activeChartSample = frame->allStats.chartSample;
+ activeChartSample = ChartSample();
+
+ const ProfilerFrameData::ThreadData& tdata = frame->m_ThreadData[0];
+ GatherChartInformation(activeChartSample, tdata.m_AllSamples.begin(), tdata.m_AllSamples.size());
+ GatherGPUChartInformation(activeChartSample, tdata.m_GPUTimeSamples.begin(), tdata.m_GPUTimeSamples.size());
+
+ CalculateSelectedTimeAndChart(*frame);
+
+#if SUPPORT_THREADS
+ Assert (Thread::EqualsCurrentThreadID(m_MainThreadID));
+#endif
+
+ if (m_Frames.size() >= m_MaxFrameHistoryLength)
+ KillOneFrame();
+
+ frame->frameIndex = m_FrameCounter++;
+
+ // We count frames with GPU data to detect if there is any GPU profiling data
+ if (frame->allStats.chartSample.hasGPUProfiler)
+ m_FramesWithGPUData++;
+
+ m_Frames.push_back(frame);
+ return true;
+}
+
+
+void ProfilerHistory::CalculateSelectedTimeAndChart (ProfilerFrameData& frame)
+{
+ frame.selectedTime = 0;
+ frame.allStats.chartSampleSelected = ChartSample();
+ if (!m_SelectedPropertyPath.empty())
+ {
+ const ProfilerFrameData::ThreadData& tdata = frame.m_ThreadData[0];
+ dynamic_array<ProfilerSample*> selectedSamples;
+ const ProfilerSample* endSample = CalculateSampleAtPath(tdata.GetRoot(), m_SelectedPropertyPath.c_str(), selectedSamples);
+ Assert(endSample - tdata.GetRoot() == tdata.m_AllSamples.size());
+
+ for (int i=0;i<selectedSamples.size();i++)
+ {
+ ProfilerSample* sample = selectedSamples[i];
+
+ AddToChart(frame.allStats.chartSampleSelected, sample->information->group, (int)sample->timeUS*1000, 1);
+ GatherChartInformation(frame.allStats.chartSampleSelected, sample, -1);
+ frame.selectedTime += sample->timeUS*1000;
+ }
+ }
+}
+
+void ProfilerHistory::SetSelectedPropertyPath (const std::string& samplePath)
+{
+ if (m_SelectedPropertyPath != samplePath)
+ {
+ m_SelectedPropertyPath = samplePath;
+
+ // Recompute cached selected frame time from new path
+ for (int i=0;i<m_Frames.size();i++)
+ CalculateSelectedTimeAndChart(*m_Frames[i]);
+ }
+}
+
+bool ProfilerHistory::IsGPUProfilerBuggyOnDriver ()
+{
+ return gGraphicsCaps.buggyTimerQuery;
+}
+
+bool ProfilerHistory::IsGPUProfilerSupportedByOS ()
+{
+#if UNITY_OSX
+ return systeminfo::GetOperatingSystemNumeric() >= 1070;
+#else
+ return true;
+#endif
+}
+
+bool ProfilerHistory::IsGPUProfilerSupported ()
+{
+ if (m_Frames.empty())
+ return true;
+ else
+ return m_FramesWithGPUData != 0;
+}
+
+
+void ProfilerHistory::KillOneFrame()
+{
+ if (m_Frames.size() < 3)
+ return;
+
+ // Kill first or second frame. But always keep the frame the user is currently viewing (m_HistoryPosition)
+ int killIndex = 0;
+ if (m_Frames[0]->frameIndex == m_HistoryPosition)
+ killIndex = 1;
+
+ // We count frames with GPU data to detect if there is any GPU profiling data
+ if (m_Frames[killIndex]->allStats.chartSample.hasGPUProfiler)
+ m_FramesWithGPUData--;
+
+ UNITY_DELETE(m_Frames[killIndex], kMemProfiler);
+ m_Frames.erase(&m_Frames[killIndex] , &m_Frames[killIndex+1]);
+
+}
+
+const ProfilerFrameData* ProfilerHistory::GetLastFrameData() const
+{
+ if (!m_Frames.empty())
+ return m_Frames.back();
+ else
+ return NULL;
+}
+
+ProfilerFrameData* ProfilerHistory::GetLastFrameData()
+{
+ if (!m_Frames.empty())
+ return m_Frames.back();
+ else
+ return NULL;
+}
+
+const ProfilerFrameData* ProfilerHistory::GetFrameData(int frame) const
+{
+ if (frame == -1)
+ return GetLastFrameData();
+ else
+ {
+ if (m_Frames.size () > 1)
+ {
+ int index = m_Frames.size() - (m_Frames.back()->frameIndex - frame) - 1;
+ if (index >= 0 && index < m_Frames.size() && m_Frames[index]->frameIndex == frame)
+ return m_Frames[index];
+ }
+
+ int size = m_Frames.size();
+ for (int i=0;i<size;i++)
+ {
+ if (frame == m_Frames[i]->frameIndex)
+ return m_Frames[i];
+ }
+ return NULL;
+ }
+}
+
+ProfilerFrameData* ProfilerHistory::GetFrameData(int frame)
+{
+ if (frame == -1)
+ return GetLastFrameData();
+ else
+ {
+ if (m_Frames.size () > 1)
+ {
+ int index = m_Frames.size() - (m_Frames.back()->frameIndex - frame) - 1;
+ if (index >= 0 && index < m_Frames.size() && m_Frames[index]->frameIndex == frame)
+ return m_Frames[index];
+ }
+
+ int size = m_Frames.size();
+ for (int i=0;i<size;i++)
+ {
+ if (frame == m_Frames[i]->frameIndex)
+ return m_Frames[i];
+ }
+ return NULL;
+ }
+}
+
+int ProfilerHistory::GetFirstFrameIndex ()
+{
+ if (m_Frames.size() >= 1)
+ return m_Frames[0]->frameIndex;
+ else
+ return -1;
+}
+
+int ProfilerHistory::GetLastFrameIndex ()
+{
+ if (!m_Frames.empty())
+ return m_Frames.back()->frameIndex;
+ else
+ return -1;
+}
+
+int ProfilerHistory::GetNextFrameIndex (int frame)
+{
+ if (frame == -1)
+ return -1;
+
+ int size = m_Frames.size();
+ for (int i=0;i<size;i++)
+ {
+ if (m_Frames[i]->frameIndex > frame)
+ return m_Frames[i]->frameIndex;
+ }
+ return -1;
+}
+
+int ProfilerHistory::GetPreviousFrameIndex (int frame)
+{
+ if (frame == -1)
+ frame = std::numeric_limits<int>::max();
+
+ int size = m_Frames.size();
+ for (int i=size-1;i>=0;i--)
+ {
+ if (m_Frames[i]->frameIndex < frame)
+ return m_Frames[i]->frameIndex;
+ }
+ return -1;
+}
+
+void ProfilerHistory::CleanupFrameHistory()
+{
+ for (int i = 0; i < m_Frames.size(); ++i)
+ UNITY_DELETE(m_Frames[i], kMemProfiler);
+ m_Frames.clear();
+
+ m_FrameCounter = 0;
+ m_HistoryPosition = -1;
+ m_BytesUsedLastFrame = 0u;
+
+ UnityProfiler::Get().ClearPendingFrames();
+}
+
+static std::string FormatCount (int b)
+{
+ if (b < 1000)
+ return Format ("%i", b);
+ if (b < 1000000)
+ return Format ("%01.1fk", b/1000.0);
+ b /= 1000;
+ return Format ("%01.1fM", b/1000.0);
+}
+
+std::string ProfilerHistory::GetFormattedStatisticsValue(ProfilerFrameData& frameData, int identifier)
+{
+ Assert (identifier >= 0 && identifier < m_Properties.size());
+
+ int val = GetStatisticsValue(frameData,identifier);
+ if (val == -1)
+ return "";
+
+ switch(m_Properties[identifier].format) {
+ case kFormatTime: return Format("%i.%ims", val/1000000, val%1000000/100000);
+ case kFormatCount: return FormatCount (val);
+ case kFormatBytes: return FormatBytes(val);
+ case kFormatPercentage: return Format("%i.%i %%", val / 10, val % 10);
+ default: AssertString("unknown format"); return "";
+ }
+}
+
+std::string ProfilerHistory::GetFormattedStatisticsValue(int frame, int identifier)
+{
+ Assert (identifier >= 0 && identifier < m_Properties.size());
+
+ ProfilerFrameData* frameData = GetFrameData(frame);
+ if (frameData == NULL)
+ return "";
+ return GetFormattedStatisticsValue (*frameData, identifier);
+}
+
+int ProfilerHistory::GetStatisticsValue(ProfilerFrameData& data, int identifier)
+{
+ if (identifier < 0 || identifier >= m_Properties.size())
+ return -1;
+
+ return ::GetStatisticsValue(m_Properties[identifier].offset, data.allStats);
+}
+
+
+void ProfilerHistory::GetStatisticsValuesBatch(int identifier, int firstValue, float scale, float* buffer, int size, float* outMaxValue)
+{
+ int offset = -1;
+ if (identifier >= 0 && identifier < m_Properties.size())
+ offset = m_Properties[identifier].offset;
+
+ *outMaxValue = 0;
+
+ for (int i=0;i<size;i++)
+ {
+ int frame = firstValue + i;
+ int value = -1;
+
+ ///@TODO: Optimize GetFrameData
+ if (frame >= 0 && offset != -1)
+ {
+ ProfilerFrameData* data = GetFrameData(frame);
+ if (data)
+ value = ::GetStatisticsValue(offset, data->allStats);
+ }
+ if (value >= 0)
+ {
+ float scaledValue = double(value) * double(scale);
+ buffer[i] = scaledValue;
+ *outMaxValue = std::max(*outMaxValue, scaledValue);
+ }
+ else
+ {
+ buffer[i] = -1;
+ }
+ }
+}
+
+int ProfilerHistory::GetStatisticsIdentifier(const std::string& statName)
+{
+ for (size_t i = 0; i < m_Properties.size(); ++i)
+ if (statName == m_Properties[i].name)
+ return i;
+ return -1;
+}
+
+void ProfilerHistory::GetAllStatisticsProperties(std::vector<std::string>& all)
+{
+ all.reserve(m_Properties.size());
+ for (size_t i = 0; i < m_Properties.size(); ++i)
+ all.push_back(m_Properties[i].name);
+}
+
+void ProfilerHistory::GetGraphStatisticsPropertiesForArea(ProfilerArea area, std::vector<std::string>& all)
+{
+ all.reserve(m_Properties.size());
+ for (size_t i = 0; i < m_Properties.size(); ++i)
+ {
+ if (area == m_Properties[i].area && m_Properties[i].showGraph)
+ all.push_back(m_Properties[i].name);
+ }
+}
+
+ProfilerString ProfilerHistory::GetOverviewTextForProfilerArea (int frame, ProfilerArea profilerArea)
+{
+ ProfilerFrameData* frameData = GetFrameData(frame);
+ if (frameData == NULL)
+ return "";
+
+ /////@TODO: USE ONLY GENERIC CODE BELOW> STOP HARDCODING SHIT
+ if (profilerArea == kProfilerAreaCPU)
+ {
+
+ }
+ else if (profilerArea == kProfilerAreaRendering)
+ return frameData->allStats.drawStats.ToString();
+ else if (profilerArea == kProfilerAreaMemory)
+ return frameData->allStats.memoryStats.ToString();
+
+ TEMP_STRING overview;
+ for (int i=0;i<m_Properties.size();i++)
+ {
+ StatisticsProperty& prop = m_Properties[i];
+ if (prop.area != profilerArea)
+ continue;
+
+ overview += FormatString<TEMP_STRING>("%s: %s\n", prop.name.c_str(), GetFormattedStatisticsValue(*frameData, i).c_str());
+ }
+
+ return overview.c_str();
+}
+
+#endif // ENABLE_PROFILER
diff --git a/Runtime/Profiler/ProfilerHistory.h b/Runtime/Profiler/ProfilerHistory.h
new file mode 100644
index 0000000..e5bd04c
--- /dev/null
+++ b/Runtime/Profiler/ProfilerHistory.h
@@ -0,0 +1,113 @@
+#ifndef _PROFILERHISTORY_H_
+#define _PROFILERHISTORY_H_
+
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_PROFILER && UNITY_EDITOR
+
+#include "Runtime/Threads/Thread.h"
+#include "TimeHelper.h"
+#include "ProfilerImpl.h"
+#include "ProfilerFrameData.h"
+#include "ProfilerStats.h"
+#include "Runtime/Misc/SystemInfo.h"
+
+struct ProfilerSample;
+
+class ProfilerHistory
+{
+public:
+
+ typedef dynamic_array<ProfilerFrameData*> ProfileFrameVector;
+
+ // Memory overview
+ //void* memoryOverview;
+
+ ~ProfilerHistory();
+
+ static void Initialize();
+ static void Cleanup();
+
+ // Singleton accessor for profiler
+ static ProfilerHistory& Get() { return *ms_Instance; }
+
+ void AddFrameDataAndTransferOwnership(ProfilerFrameData* frame, int guid);
+
+ // Deletes one frame from the history if we have too many in the history
+ void KillOneFrame();
+
+ // Release memory allocated for all frame history
+ void CleanupFrameHistory();
+
+ const ProfilerFrameData* GetFrameData(int frame) const;
+ ProfilerFrameData* GetFrameData(int frame);
+ const ProfilerFrameData* GetLastFrameData() const;
+ ProfilerFrameData* GetLastFrameData();
+
+ // Frame index access functions
+ int GetFirstFrameIndex ();
+ int GetLastFrameIndex ();
+ int GetNextFrameIndex (int frame);
+ int GetPreviousFrameIndex (int frame);
+
+ // The maximum amount of history samples. (-1 because there is one sample reserved for the currently selected history position, which is not shown in graphs)
+ int GetMaxFrameHistoryLength () { return m_MaxFrameHistoryLength - 1; }
+
+ void GetStatisticsValuesBatch(int identifier, int firstValue, float scale, float* buffer, int size, float* outMaxValue);
+ std::string GetFormattedStatisticsValue(ProfilerFrameData& frame, int identifier);
+ std::string GetFormattedStatisticsValue(int frame, int identifier);
+
+ ProfilerString GetOverviewTextForProfilerArea (int frame, ProfilerArea profilerArea);
+
+ int GetStatisticsValue(ProfilerFrameData& frameData, int identifier);
+ int GetStatisticsIdentifier(const std::string& statName);
+ void GetAllStatisticsProperties(std::vector<std::string>& all);
+ void GetGraphStatisticsPropertiesForArea(ProfilerArea area, std::vector<std::string>& all);
+
+ void SetHistoryPosition (int pos) { m_HistoryPosition = pos; }
+ int GetHistoryPosition () const { return m_HistoryPosition; }
+ int GetFrameCounter () { return m_FrameCounter; }
+
+ bool IsGPUProfilerBuggyOnDriver ();
+ bool IsGPUProfilerSupportedByOS ();
+ bool IsGPUProfilerSupported ();
+
+ void SetSelectedPropertyPath (const std::string& samplePath);
+ const std::string& GetSelectedPropertyPath () { return m_SelectedPropertyPath; }
+
+private:
+ ProfilerHistory();
+
+ void CalculateSelectedTimeAndChart (ProfilerFrameData& frameData);
+ void CleanupActiveFrame();
+
+ bool AddFrame (ProfilerFrameData* frame, int source);
+
+private:
+ dynamic_array<StatisticsProperty> m_Properties;
+
+ // Profile history parameters
+ int m_MaxFrameHistoryLength;
+ int m_FrameCounter;
+ int m_HistoryPosition;
+
+ int m_FramesWithGPUData;
+
+ UInt32 m_BytesUsedLastFrame;
+
+ // STL vector to store lists of profile sample objects of all functions being called within one frame
+ ProfileFrameVector m_Frames;
+
+#if SUPPORT_THREADS
+ Thread::ThreadID m_MainThreadID;
+#endif
+
+ std::string m_SelectedPropertyPath;
+
+ // Profiler instance to use with singleton pattern
+ static ProfilerHistory* ms_Instance;
+};
+
+#endif
+
+#endif
diff --git a/Runtime/Profiler/ProfilerImpl.cpp b/Runtime/Profiler/ProfilerImpl.cpp
new file mode 100644
index 0000000..2abafa5
--- /dev/null
+++ b/Runtime/Profiler/ProfilerImpl.cpp
@@ -0,0 +1,1682 @@
+#include "UnityPrefix.h"
+#include "Profiler.h"
+#include "Runtime/BaseClasses/BaseObject.h"
+
+#include <string.h>
+#if ENABLE_PROFILER
+
+#include "ProfilerImpl.h"
+#include "GPUProfiler.h"
+#include "ProfilerHistory.h"
+#include "ProfilerFrameData.h"
+#include "CollectProfilerStats.h"
+#include "ProfilerConnection.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Utilities/File.h"
+#include "Runtime/Serialize/SwapEndianBytes.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Threads/JobScheduler.h"
+#include "IntelGPAProfiler.h"
+#include "Runtime/Scripting/ScriptingManager.h"
+
+#if UNITY_PS3
+#include "External/libsntuner.h"
+#define PS3_TUNER_SAMPLE_BEGIN(s) snPushMarker((s)->name)
+#define PS3_TUNER_SAMPLE_END() snPopMarker()
+#else
+#define PS3_TUNER_SAMPLE_BEGIN(s)
+#define PS3_TUNER_SAMPLE_END()
+#endif
+
+
+#define DEBUG_AUTO_PROFILER_LOG 0
+#if DEBUG_AUTO_PROFILER_LOG && !UNITY_BUILD_COPY_PROTECTED
+#error "Must disable DEBUG_AUTO_PROFILER_LOG in deployed build"
+#endif
+
+#if UNITY_EDITOR
+#include "Editor/Src/EditorHelper.h"
+#endif
+
+
+#if ENABLE_MONO
+#include "Runtime/Scripting/CommonScriptingClasses.h"
+const int kMonoProfilerDefaultFlags = MONO_PROFILE_GC | MONO_PROFILE_ALLOCATIONS | MONO_PROFILE_EXCEPTIONS;
+#endif // #if ENABLE_MONO
+
+const int kProfilerDataStreamVersion = 0x20122123;
+
+void profiler_begin_frame()
+{
+ if (UnityProfiler::GetPtr())
+ UnityProfiler::Get().BeginFrame();
+}
+
+void profiler_end_frame()
+{
+ if (UnityProfiler::GetPtr())
+ UnityProfiler::Get().EndFrame();
+}
+
+void profiler_start_mode(ProfilerMode flags)
+{
+ if (UnityProfiler::GetPtr())
+ UnityProfiler::Get().StartProfilingMode(flags);
+}
+
+void profiler_end_mode(ProfilerMode flags)
+{
+ if (UnityProfiler::GetPtr())
+ UnityProfiler::Get().EndProfilingMode(flags);
+}
+
+void profiler_begin_thread_safe(ProfilerInformation* info, const Object* obj)
+{
+ PS3_TUNER_SAMPLE_BEGIN(info);
+
+ UnityProfilerPerThread* prof = UnityProfilerPerThread::ms_InstanceTLS;
+ if (prof && prof->m_ProfilerAllowSampling)
+ {
+ SET_ALLOC_OWNER(NULL);
+ prof->BeginSample(info, obj);
+ }
+}
+
+void profiler_end_thread_safe()
+{
+ PS3_TUNER_SAMPLE_END();
+
+ UnityProfilerPerThread* prof = UnityProfilerPerThread::ms_InstanceTLS;
+ if (prof && prof->m_ProfilerAllowSampling)
+ prof->EndSample(START_TIME);
+}
+
+
+void profiler_begin(ProfilerInformation* info, const Object* obj)
+{
+ INTEL_GPA_SAMPLE_BEGIN(info);
+ PS3_TUNER_SAMPLE_BEGIN(info);
+
+ UnityProfilerPerThread* prof = UnityProfilerPerThread::ms_InstanceTLS;
+ if (prof && prof->m_ProfilerAllowSampling)
+ {
+ SET_ALLOC_OWNER(NULL);
+ prof->BeginSample(info, obj);
+ }
+}
+
+void profiler_end()
+{
+ INTEL_GPA_SAMPLE_END();
+ PS3_TUNER_SAMPLE_END();
+
+ UnityProfilerPerThread* prof = UnityProfilerPerThread::ms_InstanceTLS;
+ if (prof && prof->m_ProfilerAllowSampling)
+ prof->EndSample(START_TIME);
+}
+
+GpuSection g_CurrentGPUSection = kGPUSectionOther;
+
+void gpu_time_sample()
+{
+ GPUProfiler::GPUTimeSample();
+}
+
+void profiler_initialize_thread (const char* name, bool separateBeginEnd)
+{
+#if UNITY_EDITOR
+ if(IsDeveloperBuild())
+ UnityProfilerPerThread::Initialize(name, true);
+#endif
+}
+
+void profiler_cleanup_thread ()
+{
+ UnityProfilerPerThread::Cleanup();
+}
+
+void profiler_set_active_seperate_thread (bool enabled)
+{
+ UnityProfiler* prof = UnityProfiler::GetPtr();
+ if (prof)
+ prof->SetActiveSeparateThread(enabled);
+}
+
+void profiler_begin_frame_seperate_thread (ProfilerMode mode)
+{
+ UnityProfiler* prof = UnityProfiler::GetPtr();
+ if (prof)
+ prof->BeginFrameSeparateThread(mode);
+
+}
+
+void profiler_disable_sampling_seperate_thread ()
+{
+ UnityProfiler* prof = UnityProfiler::GetPtr();
+ if (prof)
+ prof->DisableSamplingSeparateThread();
+
+}
+
+void profiler_end_frame_seperate_thread (int frameIDAndValid)
+{
+ UnityProfiler* prof = UnityProfiler::GetPtr();
+ if (prof)
+ prof->EndFrameSeparateThread(frameIDAndValid);
+}
+
+
+// -------------------------------------------------------------------------
+
+
+
+ProfilerInformation::ProfilerInformation (const char* const functionName, ProfilerGroup grp, bool warn)
+: name(functionName)
+, group(grp)
+, flags(kDefault)
+, isWarning(warn)
+{
+ INTEL_GPA_INFORMATION_INITIALIZE();
+}
+
+
+
+// -------------------------------------------------------------------------
+// Per-thread profiler class
+
+const int kDeepProfilingMaxSamples = 1024 * 1024 * 4; // 80 MB on 32bit
+const int kNormalProfilingMaxSamples = 1024 * 512; // 10 MB on 32bit
+
+
+UNITY_TLS_VALUE(UnityProfilerPerThread*) UnityProfilerPerThread::ms_InstanceTLS;
+
+void UnityProfilerPerThread::Initialize(const char* threadName, bool separateBeginEnd)
+{
+ SET_ALLOC_OWNER(UnityProfiler::GetPtr());
+ Assert(ms_InstanceTLS == NULL);
+ ms_InstanceTLS = UNITY_NEW(UnityProfilerPerThread, kMemProfiler)(threadName, separateBeginEnd);
+
+ UnityProfiler::ms_Instance->AddPerThreadProfiler(ms_InstanceTLS);
+}
+
+void UnityProfilerPerThread::Cleanup()
+{
+ if(ms_InstanceTLS == NULL)
+ return;
+
+ UnityProfiler::ms_Instance->RemovePerThreadProfiler(ms_InstanceTLS);
+
+ UnityProfilerPerThread* instance = ms_InstanceTLS;
+ UNITY_DELETE(instance, kMemProfiler);
+ ms_InstanceTLS = NULL;
+}
+
+UnityProfilerPerThread::UnityProfilerPerThread(const char* threadName, bool separateBeginEnd)
+: m_ProfilerAllowSampling(false)
+, m_ActiveGlobalAllocator(1024 * 128, kMemProfiler)
+, m_OutOfSampleMemory(false)
+, m_ErrorDurringFrame(false)
+, m_ActiveSamples(kMemProfiler)
+, m_SampleStack(kMemProfiler)
+, m_SampleTimeBeginStack(kMemProfiler)
+, m_GPUTimeSamples(kMemProfiler)
+, m_InstanceIDSamples(kMemProfiler)
+, m_AllocatedGCMemorySamples(kMemProfiler)
+, m_WarningSamples(kMemProfiler)
+, m_ProfilersListNode(this)
+, m_GCCollectTime(0)
+, m_ThreadName(threadName)
+, m_ThreadIndex(0)
+, m_SeparateBeginEnd(separateBeginEnd)
+{
+ #if ENABLE_THREAD_CHECK_IN_ALLOCS
+ Thread::ThreadID tid = Thread::GetCurrentThreadID();
+ m_ActiveGlobalAllocator.SetThreadIDs(tid, tid);
+ #endif
+}
+
+UnityProfilerPerThread::~UnityProfilerPerThread()
+{
+ m_ActiveGlobalAllocator.purge (true);
+ m_ActiveMethodCache.clear();
+ m_DynamicMethodCache.clear();
+}
+
+void UnityProfilerPerThread::BeginFrame(ProfilerMode mode)
+{
+ if (mode & kProfilerDeepScripts)
+ m_ActiveSamples.resize_uninitialized(kDeepProfilingMaxSamples);
+ else
+ m_ActiveSamples.resize_uninitialized(kNormalProfilingMaxSamples);
+
+ m_ActiveSamples[0] = ProfilerSample();
+ m_ActiveSamples[0].startTimeUS = GetProfileTime(START_TIME)/1000;
+ m_NextSampleIndex = 1;
+
+ m_AllocatedGCMemorySamples.resize_uninitialized(0);
+ m_GPUTimeSamples.resize_uninitialized(0);
+ m_InstanceIDSamples.resize_uninitialized(0);
+ m_WarningSamples.resize_uninitialized(0);
+
+ m_SampleStack.resize_uninitialized(0);
+ m_SampleStack.push_back(0);
+
+ m_SampleTimeBeginStack.push_back(START_TIME);
+
+ m_GCCollectTime = 0;
+}
+
+
+bool UnityProfilerPerThread::EndFrame()
+{
+ Assert (!GetIsActive());
+
+ ProfilerSample* rootSample = GetRoot();
+ if (m_SampleStack.size()>1)
+ {
+ if (!m_ErrorDurringFrame)
+ ErrorString("Too many Profiler.BeginSample (BeginSample and EndSample count must match)");
+ m_ErrorDurringFrame = true;
+ }
+
+ bool ok = (rootSample->nbChildren != 0 && !m_OutOfSampleMemory && !m_ErrorDurringFrame);
+ return ok;
+}
+
+
+void UnityProfilerPerThread::ClearFrame ()
+{
+ m_NextSampleIndex = 0;
+
+ m_SampleStack.resize_uninitialized(0);
+ m_SampleTimeBeginStack.resize_uninitialized(0);
+ m_GPUTimeSamples.resize_uninitialized(0);
+ m_InstanceIDSamples.resize_uninitialized(0);
+ m_AllocatedGCMemorySamples.resize_uninitialized(0);
+ m_WarningSamples.resize_uninitialized(0);
+
+ m_ProfilerAllowSampling = false;
+ m_OutOfSampleMemory = false;
+ m_ErrorDurringFrame = false;
+}
+
+
+void UnityProfilerPerThread::SetIsActive (bool enabled)
+{
+ if (!enabled && m_ProfilerAllowSampling)
+ {
+ if (m_GCCollectTime != 0)
+ InjectGCCollectSample();
+ }
+ m_ProfilerAllowSampling = enabled && !m_SampleStack.empty();
+}
+
+void UnityProfilerPerThread::AddMiscSamplesAfterFrame(ProfileTimeFormat frameDuration, bool addOverhead)
+{
+ // Insert GC.Collect sample if we had one
+ if (m_GCCollectTime != 0)
+ InjectGCCollectSample();
+
+ ProfilerSample* rootSample = GetRoot();
+ if (rootSample)
+ rootSample->timeUS = frameDuration / 1000;
+
+ if (addOverhead)
+ CreateOverheadSample();
+}
+
+
+ProfilerSample* UnityProfilerPerThread::BeginSample(ProfilerInformation* info, const Object* obj)
+{
+ DebugAssert(this);
+
+ DebugAssert(m_ProfilerAllowSampling);
+ DebugAssert(!m_SampleStack.empty() == m_ProfilerAllowSampling);
+
+ // Insert GC.Collect sample if we had one
+ if (m_GCCollectTime != 0)
+ InjectGCCollectSample();
+
+ // Add child to children
+ m_ActiveSamples[m_SampleStack.back()].nbChildren++;
+
+ // Add new sample and insert into stack
+ m_SampleStack.push_back(m_NextSampleIndex);
+ ProfilerSample* sample = &m_ActiveSamples[m_NextSampleIndex];
+ m_NextSampleIndex++;
+ if(m_NextSampleIndex > ((int)m_ActiveSamples.size()-1))
+ {
+ m_NextSampleIndex = m_ActiveSamples.size()-1;
+ if(!m_OutOfSampleMemory)
+ {
+ m_OutOfSampleMemory = true;
+ ErrorString("The profiler has run out of samples for this frame. This frame will be skipped.");
+ }
+ }
+
+ // Initialize sample object
+ sample->information = info;
+
+ if(info->isWarning)
+ m_WarningSamples.push_back(GetActiveSampleIndex ());
+
+ sample->nbChildren = 0;
+ sample->startTimeUS = 0;
+
+ if(obj != NULL)
+ {
+ ProfilerData::InstanceID instanceSample = {GetActiveSampleIndex(), obj->GetInstanceID()};
+ m_InstanceIDSamples.push_back(instanceSample);
+ }
+
+ // Get current processor time and store it in the sample
+ m_SampleTimeBeginStack.push_back(START_TIME);
+ return sample;
+}
+
+
+void UnityProfilerPerThread::EndSample(ABSOLUTE_TIME time)
+{
+ DebugAssert(this);
+
+ DebugAssert(m_ProfilerAllowSampling);
+ if(m_SampleStack.size() <= 1)
+ {
+ if(!m_ErrorDurringFrame)
+ ErrorString("Non matching Profiler.EndSample (BeginSample and EndSample count must match)");
+ m_ErrorDurringFrame = true;
+ return;
+ }
+ DebugAssert(m_SampleStack.empty() != m_ProfilerAllowSampling);
+
+ ProfilerSample* sample = &m_ActiveSamples[m_SampleStack.back()];
+ // Set duration and start time
+ sample->startTimeUS = GetProfileTime(m_SampleTimeBeginStack.back())/1000;
+ sample->timeUS = GetProfileTime(SUBTRACTED_TIME(time, m_SampleTimeBeginStack.back()))/1000;
+
+ // Insert GC.Collect sample if we had one
+ if (m_GCCollectTime != 0)
+ InjectGCCollectSample();
+
+ m_SampleStack.pop_back();
+ m_SampleTimeBeginStack.pop_back();
+}
+
+
+void UnityProfilerPerThread::BeginSampleDynamic(const std::string& name, const Object* obj)
+{
+ DebugAssert(this);
+ if (!m_ProfilerAllowSampling)
+ return;
+
+ DynamicMethodCache::iterator found = m_DynamicMethodCache.find(name);
+ if (found != m_DynamicMethodCache.end())
+ {
+ BeginSample(&found->second, obj);
+ }
+ else
+ {
+ found = m_DynamicMethodCache.insert(make_pair(name, ProfilerInformation(NULL, kProfilerScripts))).first;
+ found->second.name = found->first.c_str();
+ found->second.group = kProfilerScripts;
+ BeginSample(&found->second, obj);
+ }
+}
+
+
+void UnityProfilerPerThread::SaveToFrameData (ProfilerFrameData& dst) const
+{
+ ProfilerFrameData::ThreadData& tdata = dst.m_ThreadData[m_ThreadIndex];
+ tdata.m_ThreadName = m_ThreadName;
+ tdata.m_AllSamples.assign(m_ActiveSamples.begin(), &m_ActiveSamples[m_NextSampleIndex]);
+ tdata.m_GPUTimeSamples.assign(m_GPUTimeSamples.begin(), m_GPUTimeSamples.end());
+ tdata.m_InstanceIDSamples.assign(m_InstanceIDSamples.begin(), m_InstanceIDSamples.end());
+ tdata.m_AllocatedGCMemorySamples.assign(m_AllocatedGCMemorySamples.begin(), m_AllocatedGCMemorySamples.end());
+ tdata.m_WarningSamples.assign(m_WarningSamples.begin(), m_WarningSamples.end());
+}
+
+
+
+// -------------------------------------------------------------------------
+// Global Profiler class
+
+
+
+UnityProfiler* UnityProfiler::ms_Instance = NULL;
+
+PROFILER_INFORMATION(gGCCollect, "GC.Collect", kProfilerGC)
+PROFILER_INFORMATION(gOverheadProfile, "Overhead", kProfilerOverhead)
+
+
+#if SUPPORT_THREADS
+#define IS_MAIN_THREAD(p) Thread::EqualsCurrentThreadID(p->m_MainThreadID)
+#else
+#define IS_MAIN_THREAD(p) (true)
+#endif
+
+
+void UnityProfiler::Initialize ()
+{
+ Assert(ms_Instance == NULL);
+ ms_Instance = UNITY_NEW_AS_ROOT(UnityProfiler,kMemProfiler, "Profiler", "");
+ UnityProfilerPerThread::Initialize("Main Thread");
+}
+
+void UnityProfiler::CleanupGfx ()
+{
+ Assert(ms_Instance != NULL);
+ for (int i = 0; i < kFrameCount; i++)
+ {
+ if (ms_Instance->m_PreviousFrames[i] != NULL)
+ GPUProfiler::ClearTimerQueries(ms_Instance->m_PreviousFrames[i]->m_ThreadData[0].m_GPUTimeSamples);
+ }
+ ProfilerFrameData::FreeAllTimerQueries();
+}
+
+void UnityProfiler::Cleanup ()
+{
+ UnityProfilerPerThread::Cleanup();
+
+ Assert(ms_Instance != NULL);
+ UNITY_DELETE(ms_Instance, kMemProfiler);
+ ms_Instance = NULL;
+}
+
+
+
+UnityProfiler::UnityProfiler()
+: m_ProfilerEnabledThisFrame(false)
+, m_ProfilerEnabledLastFrame(false)
+, m_ProfilerAllowSamplingGlobal(false)
+, m_EnabledCount(0)
+, m_FramesLogged(0)
+, m_TextFile(NULL)
+, m_DataFile(NULL)
+, m_BinaryLogEnabled(false)
+, m_ProfilerCount(0)
+, m_FrameIDCounter(1)
+{
+#if SUPPORT_THREADS
+ m_MainThreadID = Thread::GetCurrentThreadID();
+#endif
+
+ m_PendingProfilerMode = kProfilerGame;
+ m_ProfilerMode = m_PendingProfilerMode;
+ ABSOLUTE_TIME_INIT(m_LastEnabledTime);
+
+ memset(m_PreviousFrames, 0, sizeof(m_PreviousFrames));
+
+ #if DEBUG_AUTO_PROFILER_LOG
+#if UNITY_OSX
+ SetEnabled(true);
+ string result = getenv ("HOME");
+ SetLogPath(AppendPathName( result, "Library/Logs/Unity/Profiler.log"));
+#elif UNITY_WIN
+ SetEnabled(true);
+ const char* tempPath = ::getenv("TEMP");
+ if (!tempPath)
+ tempPath = "C:";
+ SetLogPath(AppendPathName (tempPath, "UnityProfiler.log"));
+#endif
+ #endif // #if DEBUG_AUTO_PROFILER_LOG
+}
+
+
+UnityProfiler::~UnityProfiler()
+{
+ SetLogPath("");
+ UNITY_DELETE(m_TextFile, kMemProfiler);
+ UNITY_DELETE(m_DataFile, kMemProfiler);
+}
+
+void UnityProfiler::AddPerThreadProfiler (UnityProfilerPerThread* prof)
+{
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ m_Profilers.push_back(prof->GetProfilersListNode());
+ ++m_ProfilerCount;
+}
+
+void UnityProfiler::RemovePerThreadProfiler (UnityProfilerPerThread* prof)
+{
+ if (!this)
+ return;
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ prof->GetProfilersListNode().RemoveFromList();
+ --m_ProfilerCount;
+}
+
+
+void UnityProfiler::CheckPro()
+{
+ if (GetEnabled())
+ {
+ BuildSettings* buildSettings = GetBuildSettingsPtr();
+#if UNITY_EDITOR
+ if (buildSettings && !buildSettings->hasPROVersion)
+#else
+ if (buildSettings && !buildSettings->hasAdvancedVersion)
+#endif
+ {
+ ErrorString("Profiler is only supported in Unity Pro.");
+ SetEnabled(false);
+ }
+ }
+}
+
+void UnityProfiler::SetEnabled (bool val)
+{
+ BuildSettings* buildSettings = GetBuildSettingsPtr();
+#if UNITY_EDITOR
+ if (buildSettings && !buildSettings->hasPROVersion)
+ return;
+#else
+ if (buildSettings && !buildSettings->hasAdvancedVersion)
+ return;
+#endif
+
+ if(val)
+ m_PendingProfilerMode |= kProfilerEnabled;
+ else
+ m_PendingProfilerMode &= ~kProfilerEnabled;
+}
+
+void UnityProfiler::RecordPreviousFrame(ProfilerMode mode)
+{
+ UnityProfiler* profiler = UnityProfiler::GetPtr();
+ if(!profiler)
+ return;
+
+ if(profiler->m_ProfilerEnabledLastFrame)
+ {
+ GPUProfiler::EndFrame();
+ profiler->EndProfilingMode(mode);
+ profiler->EndFrame();
+ profiler->m_ProfilerEnabledLastFrame = false;
+ }
+}
+
+bool UnityProfiler::StartNewFrame(ProfilerMode mode)
+{
+ UnityProfiler* profiler = UnityProfiler::GetPtr();
+ if(!profiler)
+ return false;
+
+ UnityProfiler::Get().UpdateEnabled();
+
+ if (UnityProfiler::Get().GetEnabled())
+ {
+ profiler->BeginFrame();
+ profiler->StartProfilingMode(mode);
+ GPUProfiler::BeginFrame();
+ profiler->m_ProfilerEnabledLastFrame = true;
+ }
+
+ return profiler->m_ProfilerEnabledLastFrame;
+}
+
+void UnityProfiler::BeginFrame ()
+{
+ DebugAssert(IS_MAIN_THREAD(this));
+
+ CheckPro();
+
+ GfxDevice& device = GetGfxDevice();
+ device.ProfileControl(GfxDevice::kGfxProfDisableSampling, 0);
+
+ m_ProfilerMode = m_PendingProfilerMode;
+ m_ProfilerEnabledThisFrame = m_ProfilerMode & kProfilerEnabled;
+
+ {
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ for(ProfilersList::iterator it = m_Profilers.begin(); it != m_Profilers.end(); ++it)
+ {
+ UnityProfilerPerThread& prof = **it;
+ if (!prof.IsSeparateBeginEnd())
+ prof.m_ProfilerAllowSampling = false;
+ }
+ }
+ m_ProfilerAllowSamplingGlobal = false;
+
+ if(!m_ProfilerEnabledThisFrame)
+ return;
+
+ device.ProfileControl(GfxDevice::kGfxProfBeginFrame, m_ProfilerMode);
+ {
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ for(ProfilersList::iterator it = m_Profilers.begin(); it != m_Profilers.end(); ++it)
+ {
+ UnityProfilerPerThread& prof = **it;
+ if (!prof.IsSeparateBeginEnd())
+ prof.BeginFrame(m_ProfilerMode);
+ }
+ }
+
+
+ m_TotalProfilerFrameDuration = START_TIME;
+ // accumulates all time from startFrame(N) to startFrame(N+1)
+ m_ProfilerEnabledDuration = 0;
+}
+
+
+void UnityProfiler::BeginFrameSeparateThread(ProfilerMode mode)
+{
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ UnityProfilerPerThread* profTLS = UnityProfilerPerThread::ms_InstanceTLS;
+ if (!profTLS)
+ return;
+ Assert(profTLS->IsSeparateBeginEnd());
+
+ profTLS->BeginFrame(mode);
+}
+
+void UnityProfiler::DisableSamplingSeparateThread()
+{
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ UnityProfilerPerThread* profTLS = UnityProfilerPerThread::ms_InstanceTLS;
+ if (!profTLS)
+ return;
+ Assert(profTLS->IsSeparateBeginEnd());
+
+ profTLS->m_ProfilerAllowSampling = false;
+}
+
+void UnityProfiler::SetActiveSeparateThread(bool enabled)
+{
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ UnityProfilerPerThread* profTLS = UnityProfilerPerThread::ms_InstanceTLS;
+ if (!profTLS)
+ return;
+ Assert(profTLS->IsSeparateBeginEnd());
+
+ profTLS->SetIsActive(enabled);
+}
+
+
+void UnityProfiler::EndFrame ()
+{
+ Assert (m_EnabledCount == 0);
+
+ if (!m_ProfilerEnabledThisFrame)
+ return;
+
+ bool profileFrameValid = true;
+ int threadIdx = 0;
+ {
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ for(ProfilersList::iterator it = m_Profilers.begin(); it != m_Profilers.end(); ++it)
+ {
+ UnityProfilerPerThread& prof = **it;
+ prof.SetThreadIndex(threadIdx);
+ if (!prof.IsSeparateBeginEnd())
+ {
+ bool threadValid = prof.EndFrame();
+ if (threadIdx == 0 && !threadValid)
+ profileFrameValid = false;
+ }
+ ++threadIdx;
+ }
+ }
+
+ int curFrameID = 0;
+ if (profileFrameValid)
+ {
+ StartProfilingMode(kProfilerGame);
+
+ threadIdx = 0;
+ {
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ for(ProfilersList::iterator it = m_Profilers.begin(); it != m_Profilers.end(); ++it)
+ {
+ UnityProfilerPerThread& prof = **it;
+ if (!prof.IsSeparateBeginEnd())
+ prof.AddMiscSamplesAfterFrame(m_ProfilerEnabledDuration, threadIdx==0);
+ ++threadIdx;
+ }
+ }
+
+ EndProfilingMode(kProfilerGame);
+
+
+ // Collect GPU timing for recent frames (non blocking)
+ for (int i = kFrameCount - 2; i >= 0; i--)
+ {
+ if (m_PreviousFrames[i] != NULL)
+ GPUProfiler::CollectGPUTime(m_PreviousFrames[i]->m_ThreadData[0].m_GPUTimeSamples, false);
+ }
+
+ // Compute GPU timing for oldest frames (blocking)
+ ProfilerFrameData* oldestFrame = m_PreviousFrames[kFrameCount-1];
+ if(oldestFrame)
+ {
+ oldestFrame->m_TotalGPUTimeInMicroSec = GPUProfiler::ComputeGPUTime(oldestFrame->m_ThreadData[0].m_GPUTimeSamples);
+
+ Mutex::AutoLock prevFramesLock(m_PrevFramesMutex);
+
+ LogFrame(oldestFrame);
+#if UNITY_EDITOR
+ // profilerhistory takes ownership and pushes pointer on a vector;
+ ProfilerHistory::Get().AddFrameDataAndTransferOwnership (oldestFrame, ProfilerConnection::GetEditorGuid());
+ // allocate new frame
+ oldestFrame = UNITY_NEW(ProfilerFrameData, kMemProfiler) (m_ProfilerCount, ++m_FrameIDCounter);
+#elif ENABLE_PLAYERCONNECTION
+ ProfilerConnection::Get().SendFrameDataToEditor (*oldestFrame); // reuse allocated memory
+ // GPUProfiler::ExtractGPUTime(m_PreviousGPUTimeSamples); // TODO use this sceme instead to reduce memory
+ // ProfilerConnection::Get().TransferPartialData(...)
+ // m_PreviousGPUTimeSamples.swap(m_GPUTimeSamples);
+#endif
+ }
+
+ ProfilerFrameData* curFrame = NULL;
+ {
+ Mutex::AutoLock prevFramesLock(m_PrevFramesMutex);
+ for (int i = 1; i < kFrameCount; i++)
+ m_PreviousFrames[i] = m_PreviousFrames[i - 1];
+
+ m_PreviousFrames[0] = oldestFrame;
+
+ if (m_PreviousFrames[0] == NULL)
+ {
+ m_PreviousFrames[0] = UNITY_NEW(ProfilerFrameData,kMemProfiler) (m_ProfilerCount, ++m_FrameIDCounter);
+ }
+
+ curFrame = m_PreviousFrames[0];
+ }
+
+ curFrameID = curFrame->m_FrameID;
+ {
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ for(ProfilersList::iterator it = m_Profilers.begin(); it != m_Profilers.end(); ++it)
+ {
+ UnityProfilerPerThread& prof = **it;
+ if (!prof.IsSeparateBeginEnd())
+ prof.SaveToFrameData(*curFrame);
+ }
+ }
+
+ CollectProfilerStats(curFrame->allStats);
+ }
+
+ const unsigned frameIDAndValidFlag = curFrameID | (profileFrameValid ? 0x80000000 : 0);
+ GetGfxDevice().ProfileControl(GfxDevice::kGfxProfEndFrame, frameIDAndValidFlag);
+ #if ENABLE_JOB_SCHEDULER
+ GetJobScheduler().EndProfilerFrame (frameIDAndValidFlag);
+ #endif
+
+ ABSOLUTE_TIME frameStartTime = m_TotalProfilerFrameDuration;
+ m_TotalProfilerFrameDuration = SUBTRACTED_TIME(START_TIME, frameStartTime);
+ if (profileFrameValid)
+ {
+ m_PreviousFrames[0]->m_StartTimeUS = GetProfileTime(frameStartTime) / 1000;
+ m_PreviousFrames[0]->m_TotalCPUTimeInMicroSec = GetProfileTime(m_TotalProfilerFrameDuration) / 1000;
+ }
+
+ {
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ for(ProfilersList::iterator it = m_Profilers.begin(); it != m_Profilers.end(); ++it)
+ {
+ UnityProfilerPerThread& prof = **it;
+ if (!prof.IsSeparateBeginEnd())
+ prof.ClearFrame();
+ }
+ }
+ m_ProfilerEnabledThisFrame = false;
+ m_ProfilerAllowSamplingGlobal = false;
+}
+
+
+
+void UnityProfiler::EndFrameSeparateThread(unsigned frameIDAndValid)
+{
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ UnityProfilerPerThread* profTLS = UnityProfilerPerThread::ms_InstanceTLS;
+ if (!profTLS)
+ return;
+ Assert(profTLS->IsSeparateBeginEnd());
+
+ profTLS->EndFrame();
+
+ const bool frameValid = (frameIDAndValid & 0x80000000);
+ if (frameValid)
+ {
+ Mutex::AutoLock prevFramesLock(m_PrevFramesMutex);
+ int frameID = frameIDAndValid & 0x7fffffff;
+ for (int i = 0; i < kFrameCount; ++i)
+ {
+ ProfilerFrameData* frame = m_PreviousFrames[i];
+ if (!frame)
+ continue;
+ if (frame->m_FrameID != frameID)
+ continue;
+ profTLS->SaveToFrameData(*frame);
+ }
+ }
+
+ profTLS->ClearFrame();
+}
+
+
+void UnityProfiler::ClearPendingFrames()
+{
+ Mutex::AutoLock prevFramesLock(m_PrevFramesMutex);
+ for (int i = 0; i < kFrameCount; i++)
+ {
+ UNITY_DELETE(m_PreviousFrames[i],kMemProfiler);
+ m_PreviousFrames[i] = NULL;
+ }
+}
+
+
+const ProfilerSample* UnityProfilerPerThread::GetActiveSample(int parentLevel) const
+{
+ const int indexInStack = m_SampleStack.size()-1 - parentLevel;
+ if (indexInStack < 0 || indexInStack >= m_SampleStack.size())
+ return NULL;
+ const int sampleIndex = m_SampleStack[indexInStack];
+ const ProfilerSample* sample = &m_ActiveSamples[sampleIndex];
+ return sample;
+}
+
+
+void UnityProfilerPerThread::InjectGCCollectSample()
+{
+ ProfileTimeFormat collectTime = m_GCCollectTime;
+ m_GCCollectTime = 0;
+
+ BeginSample(&gGCCollect, NULL);
+ ProfilerSample* gcSample = GetActiveSample();
+ EndSample(START_TIME);
+ gcSample->timeUS = collectTime/1000;
+}
+
+
+void UnityProfilerPerThread::CreateOverheadSample()
+ {
+ BeginSample(&gOverheadProfile, NULL);
+ ProfilerSample* overheadSample = GetActiveSample();
+ EndSample(START_TIME);
+
+ // Calculate overhead time be taking the root time and subtracting all children.
+ ProfilerSample* root = GetRoot();
+ ProfileTimeFormat overheadTime = root->timeUS * 1000;
+ const ProfilerSample* sample = root + 1;
+ for (int i=0;i<root->nbChildren;i++)
+ {
+ overheadTime -= sample->timeUS*1000;
+ sample = SkipSampleRecurse(sample);
+ }
+ overheadSample->timeUS += overheadTime/1000;
+}
+
+
+const ProfilerSample* SkipSampleRecurse (const ProfilerSample* sample)
+{
+ const ProfilerSample* child = sample + 1;
+ for (int i=0;i<sample->nbChildren;i++)
+ child = SkipSampleRecurse(child);
+
+ return child;
+}
+
+
+static void UpdateWithSmallestTime (ABSOLUTE_TIME& val, ABSOLUTE_TIME newval, int iteration)
+{
+ if (iteration == 0 || IsSmallerAbsoluteTime (newval, val))
+ val = newval;
+}
+
+
+void UnityProfiler::StartProfilingMode (ProfilerMode mode)
+{
+ if(m_ProfilerMode & mode)
+ {
+ SetIsActive(true);
+ }
+}
+
+void UnityProfiler::EndProfilingMode (ProfilerMode mode)
+{
+ if(m_ProfilerMode & mode)
+ SetIsActive(false);
+}
+
+
+void UnityProfiler::SetIsActive (bool enabled)
+{
+ if (enabled)
+ {
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ for(ProfilersList::iterator it = m_Profilers.begin(); it != m_Profilers.end(); ++it)
+ {
+ UnityProfilerPerThread& prof = **it;
+ if (!prof.IsSeparateBeginEnd())
+ prof.ClearGCCollectTime();
+ }
+ }
+
+ if(!m_ProfilerEnabledThisFrame)
+ return;
+ // m_EnabledCount can go below 0 (double disable), but will not start before enableCount is 1
+ m_EnabledCount += (enabled?1:-1);
+ if ( enabled && (m_EnabledCount != 1))
+ return;
+
+ if (!enabled && (m_EnabledCount != 0))
+ return;
+
+
+ if (!enabled && m_ProfilerAllowSamplingGlobal)
+ {
+ m_ProfilerEnabledDuration += GetProfileTime(SUBTRACTED_TIME(START_TIME, m_LastEnabledTime));
+ ABSOLUTE_TIME_INIT(m_LastEnabledTime);
+ }
+
+ m_ProfilerAllowSamplingGlobal = enabled;
+ {
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ for(ProfilersList::iterator it = m_Profilers.begin(); it != m_Profilers.end(); ++it)
+ {
+ UnityProfilerPerThread& prof = **it;
+ if (!prof.IsSeparateBeginEnd())
+ prof.SetIsActive (enabled);
+ }
+ }
+ GetGfxDevice().ProfileControl(GfxDevice::kGfxProfSetActive, enabled ? 1 : 0);
+
+ if (enabled && m_ProfilerAllowSamplingGlobal)
+ {
+ Assert(GetProfileTime (m_LastEnabledTime) == 0);
+ m_LastEnabledTime = START_TIME;
+ }
+}
+
+
+void UnityProfiler::GetDebugStats (DebugStats& debugStats)
+{
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ debugStats.m_ProfilerMemoryUsage = 0;
+ debugStats.m_AllocatedProfileSamples = 0;
+
+ debugStats.m_ProfilerMemoryUsageOthers = 0;
+ for(ProfilersList::iterator it = m_Profilers.begin(); it != m_Profilers.end(); ++it)
+ {
+ debugStats.m_ProfilerMemoryUsageOthers += (*it)->GetAllocatedBytes();
+ }
+}
+
+void UnityProfiler::CleanupMonoMethodCaches()
+{
+ Mutex::AutoLock lock(m_ProfilersMutex);
+ for(ProfilersList::iterator it = m_Profilers.begin(); it != m_Profilers.end(); ++it)
+ {
+ (*it)->CleanupMonoMethodCache();
+ }
+}
+
+
+#if ENABLE_MONO || UNITY_WINRT
+
+ProfilerSample* mono_profiler_begin(ScriptingMethodPtr method, ScriptingClassPtr profileKlass, ScriptingObjectPtr instance)
+{
+ UnityProfilerPerThread* profTLS = UnityProfilerPerThread::ms_InstanceTLS;
+ if (!profTLS || !profTLS->m_ProfilerAllowSampling)
+ return NULL;
+ UnityProfiler* profiler = UnityProfiler::ms_Instance;
+
+ if (!IS_MAIN_THREAD(profiler))
+ return NULL;
+
+ // If deep profiling, we return the current sample, which is the one we have to get back to when this call exits
+ if ((profiler->m_ProfilerMode & kProfilerDeepScripts) != 0)
+ return profTLS->GetActiveSample();
+
+ DebugAssert(profTLS->m_SampleStack.empty() != profTLS->m_ProfilerAllowSampling);
+
+ // Extract Object ptr for profiler
+ Object* objPtr = NULL;
+
+#if ENABLE_MONO
+ if (instance)
+ {
+ MonoClass* instanceClass = mono_object_get_class(instance);
+ if (mono_class_is_subclass_of(instanceClass, MONO_COMMON.unityEngineObject, false))
+ objPtr = ScriptingObjectOfType<Object>(instance).GetPtr();
+ }
+#endif
+
+ // Do we have this method's info in the cache?
+ ProfilerInformation* information;
+ UnityProfilerPerThread::MethodInfoCache::iterator it = profTLS->m_ActiveMethodCache.find(method);
+ if (it != profTLS->m_ActiveMethodCache.end())
+ information = it->second;
+ else
+ information = profTLS->CreateProfilerInformationForMethod(instance, method, scripting_method_get_name(method), profileKlass, ProfilerInformation::kScriptMonoRuntimeInvoke);
+
+ // Begin Sample
+ return profTLS->BeginSample(information, objPtr);
+}
+
+void mono_profiler_end(ProfilerSample* beginsample)
+{
+ UnityProfilerPerThread* profTLS = UnityProfilerPerThread::ms_InstanceTLS;
+ if (!profTLS || !profTLS->m_ProfilerAllowSampling)
+ return;
+ UnityProfiler* profiler = UnityProfiler::ms_Instance;
+
+ if (!IS_MAIN_THREAD(profiler))
+ return;
+
+ // roll back the stack if there has been an exception, and some end samples have been skipped
+ while(profTLS->GetActiveSample() != beginsample)
+ profTLS->EndSample(START_TIME);
+
+ // if not deep profiling, end the current sample
+ if ((profiler->m_ProfilerMode & kProfilerDeepScripts) == 0)
+ profTLS->EndSample(START_TIME);
+}
+
+#endif // #if ENABLE_MONO || UNITY_WINRT
+
+
+ProfilerInformation* UnityProfilerPerThread::CreateProfilerInformationForMethod(ScriptingObjectPtr object, ScriptingMethodPtr method, const char* methodName, ScriptingTypePtr profileKlass, int flags)
+{
+#if !ENABLE_MONO && !UNITY_WINRT
+ return 0;
+#else // ENABLE_MONO || UNITY_WINRT
+
+ ProfilerInformation* information = static_cast<ProfilerInformation*> (m_ActiveGlobalAllocator.allocate(sizeof(ProfilerInformation)));
+ information->group = kProfilerScripts;
+ information->flags = flags;
+ information->isWarning = false;
+
+#if ENABLE_MONO
+ const char* klassName = mono_class_get_name(mono_method_get_class(method->monoMethod));
+#else // UNITY_WINRT
+ const char* klassName = "";
+ if (object != SCRIPTING_NULL)
+ {
+ ScriptingTypePtr klass = scripting_object_get_class(object, GetScriptingTypeRegistry());
+ if (klass != NULL)
+ klassName = scripting_class_get_name(klass);
+ }
+#endif
+
+ if (profileKlass == NULL)
+ {
+ // Optimized snprintf(buffer, kNameBufferSize, "%s.%s()", klassName, methodName); to minimize profiler overhead
+ int size = 4;
+ for(int i=0;i<klassName[i] != 0;i++) { size++; }
+ for(int i=0;i<methodName[i] != 0;i++) { size++; }
+
+ char* allocatedBuffer = static_cast<char*> (m_ActiveGlobalAllocator.allocate(size));
+ information->name = allocatedBuffer;
+ char* c = allocatedBuffer;
+ for(int i=0;i<klassName[i] != 0;i++)
+ {
+ *c = klassName[i]; c++;
+ }
+
+ *c = '.'; c++;
+ for(int i=0;i<methodName[i] != 0;i++)
+ {
+ *c = methodName[i]; c++;
+ }
+
+ c[0] = '(';
+ c[1] = ')';
+ c[2] = 0;
+
+ // Put into method cache
+ m_ActiveMethodCache.insert (std::make_pair(method, information));
+
+ return information;
+ }
+ else
+ {
+ enum { kNameBufferSize = 256 };
+ char buffer[kNameBufferSize];
+ char coroutineMethodName[kNameBufferSize] = { 0 };
+
+ // The generated class for a coroutine is called "<Start>Iterator_1"
+ // We want to extract "Start" and use that as the method name.
+ const char* coroutineMethodNameEnd = NULL;
+ if (klassName[0] == '<')
+ coroutineMethodNameEnd = strchr(klassName, '>');
+
+ if (coroutineMethodNameEnd != NULL)
+ strncpy(coroutineMethodName, klassName + 1, std::min((int)(coroutineMethodNameEnd - (klassName + 1)), (int)kNameBufferSize));
+ else
+ strncpy(coroutineMethodName, klassName, kNameBufferSize);
+
+ snprintf(buffer, kNameBufferSize, "%s.%s() [Coroutine: %s]", scripting_class_get_name(profileKlass), coroutineMethodName, methodName);
+
+ int size = strlen(buffer)+1;
+ char* copyBuffer = static_cast<char*> (m_ActiveGlobalAllocator.allocate(size));
+ memcpy(copyBuffer, buffer, size);
+ information->name = copyBuffer;
+
+ // Put into method cache
+ m_ActiveMethodCache.insert (std::make_pair(method, information));
+
+ return information;
+ }
+#endif // ENABLE_MONO || UNITY_WINRT
+}
+
+
+
+ProfilerInformation* UnityProfilerPerThread::GetProfilerInformation (const std::string& name, UInt16 group, UInt16 flags, bool isWarning)
+{
+ DynamicMethodCache::iterator found = m_DynamicMethodCache.find(name);
+ if (found != m_DynamicMethodCache.end())
+ return &found->second;
+ else
+ {
+ // Put into method cache
+ DynamicMethodCache::iterator found = m_DynamicMethodCache.insert (std::make_pair(name, ProfilerInformation(NULL, (ProfilerGroup) group))).first;
+ found->second.name = found->first.c_str();
+ found->second.flags = flags;
+ found->second.isWarning = isWarning;
+ return &found->second;
+ }
+}
+
+
+void UnityProfilerPerThread::EnterMonoMethod(MonoMethod *method)
+{
+#if ENABLE_MONO
+ if (!m_ProfilerAllowSampling)
+ return;
+ const char* methodName = mono_method_get_name(method);
+ if (strncmp (methodName, "runtime_invoke", 14) == 0)
+ return;
+
+ // Do we have this method's info in the cache?
+ ScriptingMethodPtr scriptingMethod = GetScriptingMethodRegistry().GetMethod(method);
+ MethodInfoCache::iterator it = m_ActiveMethodCache.find(scriptingMethod);
+ if (it != m_ActiveMethodCache.end())
+ {
+ BeginSample (it->second, NULL);
+ return;
+ }
+
+ // Method info not in the cache; create and cache it
+ ProfilerInformation* information = CreateProfilerInformationForMethod(NULL, scriptingMethod, methodName, NULL, ProfilerInformation::kScriptEnterLeave);
+
+ // Begin Sample
+ BeginSample(information, NULL);
+ #endif // #if ENABLE_MONO
+}
+
+
+void UnityProfilerPerThread::LeaveMonoMethod(MonoMethod *method)
+{
+#if ENABLE_MONO
+ if (!m_ProfilerAllowSampling)
+ return;
+ const char* methodName = mono_method_get_name(method);
+ if (strncmp (methodName, "runtime_invoke", 14) == 0)
+ return;
+
+ ABSOLUTE_TIME time = START_TIME;
+ EndSample(time);
+ #endif // #if ENABLE_MONO
+}
+
+
+void UnityProfilerPerThread::SampleGCAllocation (MonoObject *obj, MonoClass *klass)
+{
+#if ENABLE_MONO
+ if (!m_ProfilerAllowSampling)
+ return;
+
+#if 0
+ // We can extract the name of the class being allocated here.
+ string info = Format("\n\t\t%s size: %d", mono_class_get_name(klass), size);
+#endif
+
+ int size = mono_object_get_size(obj);
+ ProfilerData::AllocatedGCMemory allocSample = {GetActiveSampleIndex(), size};
+ m_AllocatedGCMemorySamples.push_back(allocSample);
+ #endif // #if ENABLE_MONO
+}
+
+
+static void enter_mono_sample(void* pr, MonoMethod* method)
+{
+ UnityProfilerPerThread* prof = UnityProfilerPerThread::ms_InstanceTLS;
+ if (prof)
+ prof->EnterMonoMethod(method);
+}
+
+static void leave_mono_sample(void* pr, MonoMethod* method)
+{
+ UnityProfilerPerThread* prof = UnityProfilerPerThread::ms_InstanceTLS;
+ if (prof)
+ prof->LeaveMonoMethod(method);
+}
+
+
+#if ENABLE_MONO_MEMORY_PROFILER
+
+void UnityProfiler::SetupProfilerEvents ()
+{
+ int flags = kMonoProfilerDefaultFlags;
+ if (m_PendingProfilerMode & kProfilerDeepScripts)
+ flags |= MONO_PROFILE_ENTER_LEAVE;
+
+ mono_profiler_set_events (flags);
+}
+
+static void sample_mono_shutdown (void *prof)
+{
+}
+
+
+void UnityProfilerPerThread::SampleGCMonoCallback (void* pr, int event, int generation)
+{
+ if (event == 1)
+ {
+ UnityProfilerPerThread* prof = UnityProfilerPerThread::ms_InstanceTLS;
+ if (prof)
+ {
+ prof->m_GCStartTime = START_TIME;
+ }
+ }
+
+ if (event == 4)
+ {
+ UnityProfilerPerThread* prof = UnityProfilerPerThread::ms_InstanceTLS;
+ if (prof)
+ {
+ prof->m_GCCollectTime += GetProfileTime(ELAPSED_TIME(prof->m_GCStartTime));
+ }
+ }
+}
+
+static void sample_gc_resize (void *pr, SInt64 new_size)
+{
+ // printf_console("--- GC resize %d\n", (int)new_size);
+}
+
+static void sample_allocation (void* pr, MonoObject *obj, MonoClass *klass)
+{
+ UnityProfilerPerThread* prof = UnityProfilerPerThread::ms_InstanceTLS;
+ if (prof)
+ prof->SampleGCAllocation(obj, klass);
+}
+
+
+void mono_profiler_startup ()
+{
+ mono_profiler_install (NULL, sample_mono_shutdown);
+
+ mono_profiler_install_gc(UnityProfilerPerThread::SampleGCMonoCallback, sample_gc_resize);
+ mono_profiler_install_allocation(sample_allocation);
+#if UNITY_EDITOR
+ // Deep profiling is only be available in editor
+ mono_profiler_install_enter_leave (enter_mono_sample, leave_mono_sample);
+#endif
+ int flags = kMonoProfilerDefaultFlags;
+ mono_profiler_set_events (flags);
+}
+
+#endif // ENABLE_MONO_MEMORY_PROFILER
+
+
+
+void UnityProfiler::LogFrame(ProfilerFrameData* data)
+{
+#if !UNITY_PEPPER
+ if (m_LogFile.empty())
+ return;
+
+ float fps = 1000000.0 / data->m_ThreadData[0].GetRoot()->timeUS;
+
+#if DEBUG_AUTO_PROFILER_LOG
+ const int kMinimumFramerate = 20;
+ if (fps > kMinimumFramerate)
+ return;
+#endif
+
+ {
+ string fpsCategory;
+ if (fps < 10)
+ fpsCategory = "Very Low";
+ else if (fps < 20)
+ fpsCategory = "Low";
+ else if (fps < 25)
+ fpsCategory = "Okay";
+ else if (fps < 25)
+ fpsCategory = "Average";
+ else if (fps < 40)
+ fpsCategory = "Good";
+ else
+ fpsCategory = "Very Good";
+ std::string output = Format(" -- Frame %d Framerate: %.1f [%s Framerate]\n", ++m_FramesLogged, fps, fpsCategory.c_str());
+
+ m_TextFile->Write(output.c_str(),output.length());
+ }
+
+ if(m_BinaryLogEnabled)
+ {
+#if ENABLE_PLAYERCONNECTION
+ /// TODO: make async write. Causes stalls
+ dynamic_array<int> buffer;
+
+ SerializeFrameData(*data, buffer);
+ int size = buffer.size()*sizeof(int);
+ m_DataFile->Write(&size,sizeof(size));
+ int threadCount = data->m_ThreadCount;
+ m_DataFile->Write(&threadCount, sizeof(threadCount));
+ m_DataFile->Write(buffer.begin(), size);
+#endif
+ }
+#endif
+}
+
+void UnityProfiler::SetLogPath (std::string logPath)
+{
+#if WEBPLUG
+ if(!logPath.empty())
+ {
+ std::string name(GetConsoleLogPath());
+ ConvertSeparatorsToUnity(name);
+ logPath = DeleteLastPathNameComponent(name)+"/profile.log";
+ }
+#endif
+ if (m_LogFile != logPath)
+ {
+ m_LogFile = logPath;
+#if !UNITY_PEPPER
+ if (!logPath.empty())
+ {
+ m_FramesLogged = 0;
+ if(!m_TextFile)
+ m_TextFile = UNITY_NEW(File, kMemProfiler);
+ if(!m_DataFile)
+ m_DataFile = UNITY_NEW(File, kMemProfiler);
+ m_TextFile->Open(m_LogFile, File::kWritePermission);
+ m_DataFile->Open(m_LogFile+".data", File::kWritePermission);
+ }
+ else
+ {
+ if(m_TextFile)
+ m_TextFile->Close();
+ if(m_DataFile)
+ m_DataFile->Close();
+ }
+#endif
+ }
+}
+void UnityProfiler::AddFramesFromFile(string path)
+{
+#if UNITY_EDITOR
+ File dataFile;
+ dataFile.Open(path+".data", File::kReadPermission);
+ int size;
+ dynamic_array<int> buffer;
+ while(dataFile.Read(&size,sizeof(size)))
+ {
+ int threadCount;
+ dataFile.Read(&threadCount,sizeof(threadCount));
+ buffer.resize_uninitialized(size/sizeof(int));
+ dataFile.Read(buffer.begin(),size);
+ ProfilerFrameData* frame = UNITY_NEW(ProfilerFrameData, kMemProfiler) (threadCount, 0);
+
+ int fileguid = ProfilerConnection::Get().GetConnectedProfiler();
+ if( UnityProfiler::DeserializeFrameData(frame,buffer.begin(),size) )
+ ProfilerHistory::Get().AddFrameDataAndTransferOwnership(frame, fileguid);
+ else
+ UNITY_DELETE(frame, kMemProfiler);
+ }
+ dataFile.Close();
+#endif
+}
+
+void UnityProfiler::SerializeFrameData(ProfilerFrameData& frame, dynamic_array<int>& buffer)
+{
+#if ENABLE_PLAYERCONNECTION
+ buffer.push_back(UNITY_LITTLE_ENDIAN);
+ buffer.push_back(kProfilerDataStreamVersion);
+
+ frame.Serialize(buffer);
+
+ buffer.push_back(0xAFAFAFAF);
+#endif
+}
+
+bool UnityProfiler::DeserializeFrameData(ProfilerFrameData* frame, const void* data, int size)
+{
+#if ENABLE_PLAYERCONNECTION
+ int* buffer = (int*)data;
+ int wordsize = size/sizeof(int);
+ int* endBuffer = buffer + wordsize;
+ int** bitstream = &buffer;
+
+ int dataIsLittleEndian = *((*bitstream)++);
+ bool shouldswap = UNITY_LITTLE_ENDIAN ? dataIsLittleEndian == 0 : dataIsLittleEndian != 0;
+ if(shouldswap)
+ {
+ int* ptr = *bitstream;
+ while(ptr < endBuffer)
+ SwapEndianBytes(*(ptr++));
+ }
+
+ int version = *((*bitstream)++);
+
+ if(version != kProfilerDataStreamVersion)
+ return false;
+
+ frame->Deserialize(bitstream, shouldswap);
+ Assert(**bitstream == 0xAFAFAFAF);
+ return true;
+#else
+ return false;
+#endif
+}
+
+
+// --------------------------------------------------------------------------
+// Profiler serialization
+
+
+#if ENABLE_PLAYERCONNECTION
+template< class T >
+void WriteArray(dynamic_array<int>& bitstream, const dynamic_array<T>& array)
+{
+ bitstream.push_back(array.size());
+ if(array.size() > 0)
+ {
+ int startindex = bitstream.size();
+ bitstream.resize_uninitialized( startindex + array.size() * sizeof(T) / sizeof(int) );
+ memcpy( (char*)&bitstream[startindex], (char*)&array[0], sizeof(T) * array.size() );
+ }
+}
+
+template< class T >
+void ReadArray( int** bitstream, dynamic_array<T>& array)
+{
+ int size = *((*bitstream)++);
+ array.resize_uninitialized(size);
+ if(size > 0)
+ {
+ memcpy((char*)&array[0], (char*)*bitstream, sizeof(T) * size);
+ *bitstream += sizeof(T) * size / sizeof(int);
+ }
+}
+
+template< typename T >
+void ReadArrayFixup( int** bitstream, dynamic_array<T>& array, bool swapdata)
+{
+ ReadArray<T>(bitstream, array);
+ if (swapdata)
+ {
+ for (typename dynamic_array<T>::iterator it = array.begin(); it != array.end(); ++it)
+ {
+ (*it).Fixup();
+ }
+ }
+}
+
+void WriteConditionaly(dynamic_array<int>& bitstream, ProfilerInformation* object)
+{
+ if (object)
+ {
+ bitstream.push_back(1);
+ ProfilerFrameData::SerializeProfilerInformation(*object, bitstream);
+ }
+ else
+ bitstream.push_back(0);
+}
+
+void ReadConditionaly( int** bitstream, ProfilerInformation*& object, bool swapdata)
+{
+ int condition = *((*bitstream)++);
+ if(condition)
+ object = ProfilerFrameData::DeserializeProfilerInformation(bitstream, swapdata);
+}
+
+static void SerializeString (dynamic_array<int>& bitstream, int len, const char* str)
+{
+ int startindex = bitstream.size();
+ bitstream.resize_initialized( startindex + len/4 + 1);
+ memcpy((char*)&bitstream[startindex], str, len+1);
+}
+
+static std::string DeserializeString (int**& bitstream, bool swapdata)
+{
+ char* chars = (char*)*bitstream;
+ if (swapdata)
+ {
+ int wordcount = strlen(chars)/4 + 1;
+ for(int i = 0; i < wordcount; i++)
+ SwapEndianBytes((*bitstream)[i]);
+ }
+ std::string name((char*)*bitstream);
+ (*bitstream) += name.length()/4 + 1;
+ return name;
+}
+
+
+#define HIPART(x) ((x>>32) & 0xFFFFFFFF)
+#define LOPART(x) (x & 0xFFFFFFFF)
+
+void ProfilerFrameData::Serialize( dynamic_array<int>& bitstream )
+{
+ bitstream.push_back(frameIndex);
+ bitstream.push_back(realFrame);
+ bitstream.push_back(m_StartTimeUS);
+ bitstream.push_back(m_TotalCPUTimeInMicroSec);
+ bitstream.push_back(m_TotalGPUTimeInMicroSec);
+ allStats.Serialize(bitstream);
+
+ bitstream.push_back(m_ThreadCount);
+ for (int t = 0; t < m_ThreadCount; ++t)
+ {
+ const ThreadData& tdata = m_ThreadData[t];
+
+ SerializeString(bitstream, tdata.m_ThreadName.size(), tdata.m_ThreadName.c_str());
+ bitstream.push_back(tdata.m_AllSamples.size());
+ for(int i = 0; i < tdata.m_AllSamples.size(); i++)
+ {
+ bitstream.push_back(tdata.m_AllSamples[i].timeUS);
+ bitstream.push_back(tdata.m_AllSamples[i].startTimeUS);
+ bitstream.push_back(tdata.m_AllSamples[i].nbChildren);
+ }
+
+ bitstream.push_back(tdata.m_GPUTimeSamples.size());
+ for(int i = 0; i < tdata.m_GPUTimeSamples.size(); i++)
+ {
+ bitstream.push_back(tdata.m_GPUTimeSamples[i].gpuTimeInMicroSec);
+ bitstream.push_back(tdata.m_GPUTimeSamples[i].relatedSampleIndex);
+ bitstream.push_back(tdata.m_GPUTimeSamples[i].gpuSection);
+ }
+
+ // Don't write m_InstanceIDSamples, since the IDs are not portable
+ WriteArray(bitstream, tdata.m_AllocatedGCMemorySamples);
+ for(int i = 0; i < tdata.m_AllSamples.size(); i++)
+ WriteConditionaly(bitstream,tdata.m_AllSamples[i].information);
+
+ WriteArray(bitstream, tdata.m_WarningSamples);
+ }
+}
+
+void ProfilerFrameData::Deserialize( int** bitstream, bool swapdata )
+{
+ frameIndex = *((*bitstream)++);
+ realFrame = *((*bitstream)++);
+ m_StartTimeUS = *((*bitstream)++);
+ m_TotalCPUTimeInMicroSec = *((*bitstream)++);
+ m_TotalGPUTimeInMicroSec = *((*bitstream)++);
+ allStats.Deserialize(bitstream, swapdata);
+
+ int threadCount = *((*bitstream)++);
+ if (threadCount != m_ThreadCount)
+ {
+ delete[] m_ThreadData;
+ m_ThreadData = new ThreadData[threadCount];
+ m_ThreadCount = threadCount;
+ }
+
+ for (int t = 0; t < m_ThreadCount; ++t)
+ {
+ ThreadData& tdata = m_ThreadData[t];
+
+ tdata.m_ThreadName = DeserializeString (bitstream, swapdata);
+
+ tdata.m_AllSamples.resize_uninitialized(*((*bitstream)++));
+
+ for(int i = 0; i < tdata.m_AllSamples.size(); i++)
+ {
+ tdata.m_AllSamples[i].timeUS = *((*bitstream)++);
+ tdata.m_AllSamples[i].startTimeUS = *((*bitstream)++);
+ tdata.m_AllSamples[i].nbChildren = *((*bitstream)++);
+ tdata.m_AllSamples[i].information = NULL;
+ }
+
+ tdata.m_GPUTimeSamples.resize_uninitialized(*((*bitstream)++));
+ for(int i = 0; i < tdata.m_GPUTimeSamples.size(); i++)
+ {
+ tdata.m_GPUTimeSamples[i].gpuTimeInMicroSec = *((*bitstream)++);
+ tdata.m_GPUTimeSamples[i].relatedSampleIndex = *((*bitstream)++);
+ tdata.m_GPUTimeSamples[i].gpuSection = (GpuSection)(*((*bitstream)++));
+ tdata.m_GPUTimeSamples[i].timerQuery = NULL;
+ }
+
+ // m_InstanceIDSamples are not written, since the IDs are not portable
+ tdata.m_InstanceIDSamples.resize_uninitialized(0);
+ ReadArray(bitstream, tdata.m_AllocatedGCMemorySamples);
+ for(int i = 0; i < tdata.m_AllSamples.size(); i++)
+ ReadConditionaly(bitstream, tdata.m_AllSamples[i].information, swapdata);
+
+ ReadArray(bitstream, tdata.m_WarningSamples);
+ }
+}
+
+
+void ProfilerFrameData::SerializeProfilerInformation( const ProfilerInformation& info, dynamic_array<int>& bitstream )
+{
+ SerializeString (bitstream, strlen(info.name), info.name);
+ bitstream.push_back((info.group << 16) | (info.flags << 8) | info.isWarning);
+}
+
+ProfilerInformation* ProfilerFrameData::DeserializeProfilerInformation( int** bitstream, bool swapdata )
+{
+ std::string name = DeserializeString (bitstream, swapdata);
+
+ int groupFlags = *((*bitstream)++);
+ UInt16 group = groupFlags >> 16;
+ UInt8 flags = (groupFlags & 0xFF00) >> 8;
+ UInt8 warn = groupFlags & 0xFF;
+
+ UnityProfilerPerThread* prof = UnityProfilerPerThread::ms_InstanceTLS;
+ DebugAssert(prof);
+ return prof->GetProfilerInformation(name, group, flags, warn);
+}
+
+#endif // #if ENABLE_PLAYERCONNECTION
+
+
+
+
+#endif // #if ENABLE_PROFILER
diff --git a/Runtime/Profiler/ProfilerImpl.h b/Runtime/Profiler/ProfilerImpl.h
new file mode 100644
index 0000000..413b0ad
--- /dev/null
+++ b/Runtime/Profiler/ProfilerImpl.h
@@ -0,0 +1,304 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_PROFILER
+
+#include "Runtime/Utilities/LogAssert.h"
+#include "TimeHelper.h"
+#include "Runtime/Allocator/LinearAllocator.h"
+#include "Runtime/Threads/Mutex.h"
+#include "Runtime/Threads/Thread.h"
+#include "Runtime/Threads/ThreadSpecificValue.h"
+#include "Profiler.h"
+#include "ProfilerStats.h"
+#include "TimeHelper.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include <map>
+#include "Runtime/Scripting/ScriptingUtility.h"
+
+
+class GfxTimerQuery;
+class ProfilerFrameData;
+class File;
+
+
+// Additional data possibly "attached" to base ProfilerSamples.
+// GPU times, object IDs etc. Separated out since only a few
+// samples need it.
+namespace ProfilerData
+{
+ // GPU time per draw call or other interesting GPU event.
+ struct GPUTime
+ {
+ UInt32 relatedSampleIndex;
+ GfxTimerQuery* timerQuery;
+ int gpuTimeInMicroSec;
+ GpuSection gpuSection;
+ };
+
+ // Instance ID of object that the source of profiled event.
+ struct InstanceID
+ {
+ UInt32 relatedSampleIndex;
+ SInt32 instanceID;
+ };
+
+ // Managed GC memory allocated during the call.
+ struct AllocatedGCMemory
+ {
+ UInt32 relatedSampleIndex;
+ UInt32 allocatedGCMemory;
+ };
+};
+
+// Base profiler sample with CPU times;
+// minimal amount of actual per-sample data.
+struct ProfilerSample
+{
+ ProfilerSample()
+ : information(NULL)
+ , startTimeUS(0)
+ , timeUS(0)
+ , nbChildren(0)
+ {
+ }
+
+ UInt64 startTimeUS; // start time in microsecs
+ UInt32 timeUS; // duration in microsecs
+
+ ProfilerInformation* information; // actual information
+
+ int nbChildren;
+};
+
+const ProfilerSample* SkipSampleRecurse (const ProfilerSample* sample);
+
+
+
+class UnityProfilerPerThread
+{
+public:
+ static UNITY_TLS_VALUE(UnityProfilerPerThread*) ms_InstanceTLS;
+ bool m_ProfilerAllowSampling; // public for performance in free sampling functions
+
+public:
+ static void Initialize(const char* threadName, bool separateBeginEnd = false);
+ static void Cleanup();
+
+ ~UnityProfilerPerThread();
+
+ bool GetIsActive () const { return m_ProfilerAllowSampling; }
+ void SetIsActive (bool enabled);
+
+ void BeginFrame(ProfilerMode mode);
+ bool EndFrame();
+ void ClearFrame();
+ void AddMiscSamplesAfterFrame(ProfileTimeFormat frameDuration, bool addOverhead);
+ void SaveToFrameData (ProfilerFrameData& dst) const;
+
+ ProfilerSample* BeginSample(ProfilerInformation* info, const Object* obj);
+ void EndSample(ABSOLUTE_TIME time);
+
+ void BeginSampleDynamic(const std::string& name, const Object* obj);
+ void EndSampleDynamic() { if (m_ProfilerAllowSampling) EndSample(START_TIME); }
+
+ void AddGPUSample (const ProfilerData::GPUTime& sample) { m_GPUTimeSamples.push_back(sample); }
+
+
+ // Clear the mono method cache, but do not clear the memory used to store info/names.
+ // We still need that memory to display profiler information.
+ void CleanupMonoMethodCache() { m_ActiveMethodCache.clear(); }
+
+ ProfilerSample* GetRoot () { return m_NextSampleIndex == 0 ? NULL : &m_ActiveSamples[0]; }
+ UInt32 GetActiveSampleIndex () { return m_SampleStack.back(); }
+ ProfilerSample* GetActiveSample ()
+ {
+ // @TODO out of memory?
+ return &m_ActiveSamples[ GetActiveSampleIndex () ];
+ }
+ const ProfilerSample* GetActiveSample(int parentLevel) const;
+
+ ListNode<UnityProfilerPerThread>& GetProfilersListNode() { return m_ProfilersListNode; }
+
+ ProfilerInformation* CreateProfilerInformationForMethod(ScriptingObjectPtr object, ScriptingMethodPtr method, const char* methodName, ScriptingTypePtr profileKlass, int flags);
+ ProfilerInformation* GetProfilerInformation( const std::string& name, UInt16 group, UInt16 flags, bool isWarning);
+
+ void EnterMonoMethod (MonoMethod *method);
+ void LeaveMonoMethod (MonoMethod *method);
+ void SampleGCAllocation (MonoObject *obj, MonoClass *klass);
+ static void SampleGCMonoCallback (void *prof, int event, int generation);
+ void ClearGCCollectTime() { m_GCCollectTime = 0; }
+
+ size_t GetAllocatedBytes() const { return m_ActiveGlobalAllocator.GetAllocatedBytes(); }
+ void SetThreadIndex(int idx) { m_ThreadIndex = idx; }
+ bool IsSeparateBeginEnd() const { return m_SeparateBeginEnd; }
+
+private:
+ UnityProfilerPerThread(const char* threadName, bool separateBeginEnd); // prevent public creation
+
+ void InjectGCCollectSample();
+ void CreateOverheadSample();
+
+private:
+ int m_NextSampleIndex;
+ dynamic_array<ProfilerSample> m_ActiveSamples;
+ dynamic_array<UInt32> m_SampleStack;
+ dynamic_array<ABSOLUTE_TIME> m_SampleTimeBeginStack;
+ dynamic_array<ProfilerData::GPUTime> m_GPUTimeSamples;
+ dynamic_array<ProfilerData::InstanceID> m_InstanceIDSamples;
+ dynamic_array<ProfilerData::AllocatedGCMemory> m_AllocatedGCMemorySamples;
+ dynamic_array<UInt32> m_WarningSamples;
+ bool m_OutOfSampleMemory;
+ bool m_ErrorDurringFrame;
+
+ ProfileTimeFormat m_GCCollectTime;
+
+ typedef UNITY_MAP(kMemProfiler, ScriptingMethodPtr, ProfilerInformation*) MethodInfoCache;
+ typedef UNITY_MAP(kMemProfiler, std::string, ProfilerInformation) DynamicMethodCache;
+
+ ForwardLinearAllocator m_ActiveGlobalAllocator;
+ MethodInfoCache m_ActiveMethodCache;
+ DynamicMethodCache m_DynamicMethodCache;
+
+ ListNode<UnityProfilerPerThread> m_ProfilersListNode;
+
+ ABSOLUTE_TIME m_GCStartTime;
+
+ const char* m_ThreadName;
+ int m_ThreadIndex;
+ bool m_SeparateBeginEnd;
+
+ friend ProfilerSample* mono_profiler_begin(ScriptingMethodPtr method, ScriptingClassPtr profileKlass, ScriptingObjectPtr instance);
+ friend void mono_profiler_end(ProfilerSample* beginsample);
+};
+
+
+
+class UnityProfiler
+{
+public:
+ static UnityProfiler* ms_Instance; // singleton
+
+public:
+ // Destructor
+ ~UnityProfiler();
+
+ static void Initialize ();
+ static void CleanupGfx ();
+ static void Cleanup ();
+
+ // Singleton accessor for profiler
+ static UnityProfiler& Get() { return *ms_Instance; }
+ static UnityProfiler* GetPtr() { return ms_Instance; }
+
+ void SetEnabled (bool val);
+ void UpdateEnabled() { m_ProfilerMode = m_PendingProfilerMode; }
+ bool GetEnabled () { return (m_ProfilerMode & kProfilerEnabled) != 0; }
+
+ void SetProfileEditor (bool val) { val ? m_PendingProfilerMode |= kProfilerEditor : m_PendingProfilerMode &= ~kProfilerEditor ; }
+ bool GetProfileEditor () { return (m_PendingProfilerMode & kProfilerEditor) != 0; }
+
+ void SetDeepProfiling (bool val) { val ? m_PendingProfilerMode |= kProfilerDeepScripts : m_PendingProfilerMode &= ~kProfilerDeepScripts ; }
+ bool GetDeepProfiling () { return (m_PendingProfilerMode & kProfilerDeepScripts) != 0; }
+
+ void StartProfilingMode (ProfilerMode mode);
+ void EndProfilingMode (ProfilerMode mode);
+
+ void BeginFrame ();
+ void EndFrame ();
+ void BeginFrameSeparateThread(ProfilerMode mode);
+ void EndFrameSeparateThread(unsigned frameIDAndValid);
+ void DisableSamplingSeparateThread();
+ void SetActiveSeparateThread(bool enabled);
+
+ void ClearPendingFrames();
+
+
+ void SetupProfilerEvents ();
+ ProfilerSample* AllocateSample();
+
+ void GetDebugStats (DebugStats& debugStats);
+
+ void CleanupMonoMethodCaches();
+
+
+ void SetLogPath (std::string logPath);
+ std::string GetLogPath () { return m_LogFile; }
+
+ void EnableBinaryLog(bool val) { m_BinaryLogEnabled = val; }
+ bool BinaryLogEnabled() { return m_BinaryLogEnabled; }
+
+ static void AddFramesFromFile(std::string path);
+
+ static void SerializeFrameData(ProfilerFrameData& frame, dynamic_array<int>& buffer);
+ static bool DeserializeFrameData(ProfilerFrameData* frame, const void* data, int size);
+
+ void Serialize( dynamic_array<int>& bs );
+ static ProfilerInformation* Deserialize( int** bs, bool swapdata );
+
+ void AddPerThreadProfiler (UnityProfilerPerThread* prof);
+ void RemovePerThreadProfiler (UnityProfilerPerThread* prof);
+
+ static void RecordPreviousFrame(ProfilerMode mode);
+ static bool StartNewFrame(ProfilerMode mode);
+
+private:
+ UnityProfiler(); // prevent accidental creation
+
+ void SetIsActive (bool enabled);
+
+ void CheckPro();
+
+ void LogFrame (ProfilerFrameData* frame);
+
+private:
+ bool m_ProfilerEnabledThisFrame;
+ bool m_ProfilerEnabledLastFrame;
+ bool m_ProfilerAllowSamplingGlobal;
+ int m_EnabledCount;
+
+
+ // hold framedata for sevral frames to wait for GPU samples
+ enum { kFrameCount = 2 };
+ ProfilerFrameData* m_PreviousFrames[kFrameCount];
+
+ // indicates Disabled or what elements we are profiling
+ ProfilerMode m_ProfilerMode;
+ ProfilerMode m_PendingProfilerMode;
+
+ #if SUPPORT_THREADS
+ Thread::ThreadID m_MainThreadID;
+ #endif
+
+ // accumulates time inside profiler start/stop (multiple start/stop pair are allowed during the frame)
+ ABSOLUTE_TIME m_TotalProfilerFrameDuration;
+ // accumulates all time from startFrame(N) to startFrame(N+1)
+ ProfileTimeFormat m_ProfilerEnabledDuration;
+ ABSOLUTE_TIME m_LastEnabledTime;
+
+ typedef List< ListNode<UnityProfilerPerThread> > ProfilersList;
+ ProfilersList m_Profilers;
+ Mutex m_ProfilersMutex;
+ int m_ProfilerCount;
+
+ Mutex m_PrevFramesMutex;
+ int m_FrameIDCounter;
+
+
+ std::string m_LogFile;
+ int m_FramesLogged;
+ bool m_BinaryLogEnabled;
+ File* m_TextFile;
+ File* m_DataFile;
+
+
+ friend class ProfilerHistory;
+ friend ProfilerSample* mono_profiler_begin(ScriptingMethodPtr method, ScriptingClassPtr profileKlass, ScriptingObjectPtr instance);
+ friend void mono_profiler_end(ProfilerSample* beginsample);
+};
+
+
+
+#endif // #if ENABLE_PROFILER
diff --git a/Runtime/Profiler/ProfilerProperty.cpp b/Runtime/Profiler/ProfilerProperty.cpp
new file mode 100644
index 0000000..c854908
--- /dev/null
+++ b/Runtime/Profiler/ProfilerProperty.cpp
@@ -0,0 +1,789 @@
+#include "UnityPrefix.h"
+#include "ProfilerProperty.h"
+
+#if ENABLE_PROFILER && UNITY_EDITOR
+
+#include "ProfilerImpl.h"
+#include "Runtime/BaseClasses/BaseObject.h"
+#include <iostream>
+#include <sstream>
+#include "Runtime/Utilities/Word.h"
+#include "Runtime/Utilities/ArrayUtility.h"
+
+/*
+TODO:
+* Sync time filter does not work
+* non multiple selection in raw hierarch
+
+* select object in double click
+* Tooltip showing all names
+ * deep profiler???
+ */
+
+
+
+// Constant and static variables initialization
+static const char* const kRadixPoint = "0.";
+static const char* const kPercentFmt = "%d.%d%%";
+static const char* kNotAvailable = "N/A";
+
+
+struct ProfilerHierarchy
+{
+ ProfilerHierarchy() : oneSample(~0), sampleIdx(kMemProfiler) {}
+
+ // Can correspond to one or more individual samples. Optimize common use case (especially in timeline view)
+ // when there is exactly one sample: that is stored in 'oneSample'. If there are more samples, they
+ // are put into an array.
+ UInt32 oneSample;
+ dynamic_array<UInt32> sampleIdx;
+
+ UNITY_VECTOR(kMemProfiler,ProfilerHierarchy) children;
+ ProfilerHierarchy* parent;
+
+ ProfilerSampleData data;
+};
+
+// Implement swap to avoid massive amounts of copying during the sort function
+namespace std
+{
+ template<>
+ inline void swap(ProfilerHierarchy& __a, ProfilerHierarchy& __b)
+ {
+ std::swap(__a.oneSample, __b.oneSample);
+ __a.sampleIdx.swap(__b.sampleIdx);
+ __a.children.swap(__b.children);
+ std::swap(__a.parent, __b.parent);
+ std::swap(__a.data, __b.data);
+ }
+}
+
+
+static std::string GetFormattedPercent(ProfileTimeFormat inTime, ProfileTimeFormat totalTime)
+{
+ if (totalTime == 0)
+ return "0.0%";
+
+ // Calculate percentage, format obtained value and convert into string
+ UInt64 time = inTime * (UInt64) 1000;
+ UInt64 integer = time / totalTime;
+
+ return Format(kPercentFmt, (int) integer / 10, (int) integer % 10);
+}
+
+static std::string GetFormattedTime(ProfileTimeFormat time, int decimals = 2)
+{
+ time /= (1000000/Pow(10,decimals));
+
+ std::string value = Format("%u", time);
+
+ int length = value.length();
+
+ if (length > decimals)
+ {
+ value.insert(length - decimals, 1, '.');
+ }
+ else
+ {
+ std::string tmp;
+ tmp.assign(decimals, '0');
+
+ value.copy((char*) tmp.c_str() + (decimals - length), length);
+
+ return kRadixPoint + tmp;
+ }
+
+ return value;
+}
+
+static const char* GetObjectNameFromInstanceID (SInt32 instanceID)
+{
+ Object* obj = dynamic_instanceID_cast<Object*>(instanceID);
+ if (obj)
+ return obj->GetName();
+ else
+ return kNotAvailable;
+}
+
+static const char* GetFunctionName(const ProfilerSampleData& input)
+{
+ if (input.information)
+ return input.information->name;
+ else
+ return "Invalid";
+}
+
+static bool LargerFunctionName(const ProfilerSampleData& lhs, const ProfilerSampleData& rhs)
+{
+
+ if (!lhs.information || !rhs.information)
+ return &lhs < &rhs;
+
+ return StrICmp(lhs.information->name, rhs.information->name) > 0;
+}
+
+
+static void GetSortedInstanceIDs (const dynamic_array<AdditionalProfilerSampleData*>& additionalData, UInt32 oneSample, const dynamic_array<UInt32>& samples, dynamic_array<SInt32>& outputInstanceIDs)
+{
+ // Get sorted list of instanceID's
+ outputInstanceIDs.push_back(additionalData[oneSample]?additionalData[oneSample]->instanceID:0);
+ for (int i=0;i<samples.size();i++)
+ outputInstanceIDs.push_back(additionalData[samples[i]]?additionalData[samples[i]]->instanceID:0);
+ std::sort (outputInstanceIDs.begin(), outputInstanceIDs.end());
+}
+
+static const char* GetObjectNameSummary (UInt32 oneSample, const dynamic_array<UInt32>& samples, const dynamic_array<AdditionalProfilerSampleData*>& additionalData)
+{
+ int instanceID = additionalData[oneSample] ? additionalData[oneSample]->instanceID : 0;
+ const char* name = GetObjectNameFromInstanceID(instanceID);
+
+ for (int i=0;i<samples.size();i++)
+ {
+ int instanceID = additionalData[samples[i]]?additionalData[samples[i]]->instanceID:0;
+ const char* tempName = GetObjectNameFromInstanceID(instanceID);
+ if (name != tempName) //@TODO: this is just comparing pointers?!
+ return "multiple";
+ }
+
+ return name;
+}
+
+static std::string GetProfilerColumn (const ProfilerSampleData& data, UInt32 oneSample, const dynamic_array<UInt32>& samples, const dynamic_array<AdditionalProfilerSampleData*>& additionalData, bool supportsGPUProfiler, ProfilerColumn column)
+{
+ switch (column)
+ {
+ case kFunctionNameColumn:
+ return GetFunctionName(data);
+
+ case kTotalPercentColumn:
+ return GetFormattedPercent(data.time, data.rootTime);
+
+ case kSelfPercentColumn:
+ if (data.time > data.childrenTime)
+ return GetFormattedPercent(data.time - data.childrenTime, data.rootTime);
+ else
+ return GetFormattedPercent(0u, 1u);
+
+ case kTotalGPUPercentColumn:
+ if (!supportsGPUProfiler)
+ return kNotAvailable;
+ else if (supportsGPUProfiler)
+ return GetFormattedPercent(data.gpuTime, data.rootGPUTime);
+ else
+ return kNotAvailable;
+
+ case kSelfGPUPercentColumn:
+ if (!supportsGPUProfiler)
+ return kNotAvailable;
+ else if (data.gpuTime > data.childrenGPUTime)
+ return GetFormattedPercent(data.gpuTime - data.childrenGPUTime, data.rootGPUTime);
+ else
+ return GetFormattedPercent(0u, 1u);
+
+ case kCallsColumn:
+ return Format("%u", data.numberOfCalls);
+
+ case kWarningColumn:
+ if(data.warningCount == 0)
+ return "";
+ return Format("%u", data.warningCount);
+
+ case kGCMemory:
+ return FormatBytes(data.allocatedGCMemory);
+
+ case kTotalTimeColumn:
+ return GetFormattedTime(data.time);
+
+ case kSelfTimeColumn:
+ if (data.time > data.childrenTime)
+ return GetFormattedTime(data.time - data.childrenTime);
+ else
+ return GetFormattedTime(0u);
+
+ case kDrawCallsColumn:
+ if (supportsGPUProfiler)
+ return Format("%u", data.gpuSamplesCount);
+ else
+ return kNotAvailable;
+
+ case kTotalGPUTimeColumn:
+ if (supportsGPUProfiler)
+ return GetFormattedTime(data.gpuTime,3);
+ else
+ return kNotAvailable;
+
+ case kSelfGPUTimeColumn:
+ if (!supportsGPUProfiler)
+ return kNotAvailable;
+ else if (data.gpuTime > data.childrenGPUTime)
+ return GetFormattedTime(data.gpuTime - data.childrenGPUTime);
+ else
+ return GetFormattedTime(0u);
+
+ case kObjectNameColumn:
+
+ return GetObjectNameSummary(oneSample, samples, additionalData);
+
+ default:
+ AssertString("Unimplemented GetProfilerColumn ");
+ return "";
+ }
+}
+
+
+static bool IsSmaller (const ProfilerSampleData& lhs, const ProfilerSampleData& rhs, ProfilerColumn criteria)
+{
+ if (criteria == kTotalTimeColumn || criteria == kTotalPercentColumn)
+ {
+ if (lhs.time != rhs.time)
+ return lhs.time < rhs.time;
+ }
+ else if (criteria == kTotalGPUTimeColumn || criteria == kTotalGPUPercentColumn)
+ {
+ if (lhs.gpuTime != rhs.gpuTime)
+ return lhs.gpuTime < rhs.gpuTime;
+ }
+ else if (criteria == kSelfTimeColumn || criteria == kSelfPercentColumn)
+ {
+ if ((lhs.time - lhs.childrenTime) != (rhs.time - rhs.childrenTime))
+ return (lhs.time - lhs.childrenTime) < (rhs.time - rhs.childrenTime);
+ }
+ else if (criteria == kSelfGPUTimeColumn || criteria == kSelfGPUPercentColumn)
+ {
+ if ((lhs.gpuTime - lhs.childrenGPUTime) != (rhs.gpuTime - rhs.childrenGPUTime))
+ return (lhs.gpuTime - lhs.childrenGPUTime) < (rhs.gpuTime - rhs.childrenGPUTime);
+ }
+ else if (criteria == kGCMemory)
+ {
+ if (lhs.allocatedGCMemory != rhs.allocatedGCMemory)
+ return lhs.allocatedGCMemory < rhs.allocatedGCMemory;
+ }
+ else if (criteria == kDrawCallsColumn)
+ {
+ if (lhs.gpuSamplesCount != rhs.gpuSamplesCount)
+ return lhs.gpuSamplesCount < rhs.gpuSamplesCount;
+ }
+ else if (criteria == kFunctionNameColumn)
+ {
+ return LargerFunctionName(lhs, rhs);
+ }
+ else if (criteria == kWarningColumn)
+ {
+ return lhs.warningCount < rhs.warningCount;
+ }
+ else if (criteria == kCallsColumn)
+ {
+ if (lhs.numberOfCalls != rhs.numberOfCalls)
+ return lhs.numberOfCalls < rhs.numberOfCalls;
+ }
+ else if (criteria == kObjectNameColumn)
+ {
+ // Not supported. Screws with the data flow and not worth it...
+ return LargerFunctionName(lhs, rhs);
+ }
+ else
+ {
+ AssertString("Unsupported sortmode");
+ return false;
+ }
+
+ // As a fallback if the results are the same. Sort by name
+ return LargerFunctionName(lhs, rhs);
+}
+
+
+// ProfilerProperty class implementation
+//
+
+MemoryPool ProfilerProperty::s_AdditionalDataPool(false, "AdditionalData Pool", sizeof (AdditionalProfilerSampleData), 16 * 1024, kMemProfiler);
+
+ProfilerProperty::ProfilerProperty()
+ :m_Root(NULL)
+ ,m_FrameData(NULL)
+ ,m_ProfilerViewType(kViewHierarchy)
+ ,m_ProfilerSortColumn(kTotalTimeColumn)
+ ,m_Depth(0)
+ ,m_ActiveHierarchy(NULL)
+ ,m_OnlyShowGPUSamples(false)
+ ,m_AdditionalData(kMemProfiler)
+ ,m_ThreadIdx(0)
+{
+}
+
+ProfilerProperty::~ProfilerProperty()
+{
+ CleanupProperty();
+}
+
+struct SortByColumn
+{
+ ProfilerColumn column;
+
+ SortByColumn (ProfilerColumn c) : column (c) { }
+
+ bool operator () (const ProfilerHierarchy& lhs, const ProfilerHierarchy& rhs) const
+ {
+ return !IsSmaller(lhs.data, rhs.data, column);
+ }
+};
+
+struct SortByFunctionName
+{
+ const ProfilerFrameData& frameData;
+ int threadIdx;
+
+ SortByFunctionName (const ProfilerFrameData& f, int t) : frameData (f), threadIdx(t) { }
+
+ bool operator () (const UInt32 lhs, const UInt32 rhs) const
+ {
+ const char* lhsName = frameData.m_ThreadData[threadIdx].GetSample(lhs)->information->name;
+ const char* rhsName = frameData.m_ThreadData[threadIdx].GetSample(rhs)->information->name;
+ return strcmp(lhsName, rhsName) < 0;
+ }
+};
+
+static AdditionalProfilerSampleData* AllocateAdditionalData()
+{
+ void* ptr = ProfilerProperty::s_AdditionalDataPool.Allocate();
+ memset(ptr, 0, sizeof(AdditionalProfilerSampleData));
+ return (AdditionalProfilerSampleData*) ptr;
+}
+
+static void InitializeAdditionalProfilerData(const ProfilerFrameData& frameData, int threadIdx, dynamic_array<AdditionalProfilerSampleData*>& additionalData)
+{
+ const ProfilerFrameData::ThreadData& tdata = frameData.m_ThreadData[threadIdx];
+ UInt32 sampleCount = tdata.m_AllSamples.size();
+
+ for(int i = 0; i < additionalData.size(); i++)
+ ProfilerProperty::s_AdditionalDataPool.Deallocate(additionalData[i]);
+
+ additionalData.resize_uninitialized(sampleCount);
+ if (sampleCount > 0)
+ memset(&additionalData[0], 0, sampleCount*sizeof(AdditionalProfilerSampleData*));
+
+ dynamic_array<ProfilerData::AllocatedGCMemory>::const_iterator itGCMem = tdata.m_AllocatedGCMemorySamples.begin();
+ for(; itGCMem != tdata.m_AllocatedGCMemorySamples.end(); ++itGCMem)
+ {
+ if(additionalData[itGCMem->relatedSampleIndex] == 0)
+ additionalData[itGCMem->relatedSampleIndex] = AllocateAdditionalData();
+ additionalData[itGCMem->relatedSampleIndex]->allocatedGCMemory += itGCMem->allocatedGCMemory;
+ }
+
+ dynamic_array<ProfilerData::InstanceID>::const_iterator itID = tdata.m_InstanceIDSamples.begin();
+ for(; itID != tdata.m_InstanceIDSamples.end(); ++itID)
+ {
+ if(additionalData[itID->relatedSampleIndex] == 0)
+ additionalData[itID->relatedSampleIndex] = AllocateAdditionalData();
+ additionalData[itID->relatedSampleIndex]->instanceID = itID->instanceID;
+ }
+ dynamic_array<ProfilerData::GPUTime>::const_iterator itGPUTime = tdata.m_GPUTimeSamples.begin();
+ for(; itGPUTime != tdata.m_GPUTimeSamples.end(); ++itGPUTime)
+ {
+ if(additionalData[itGPUTime->relatedSampleIndex] == 0)
+ additionalData[itGPUTime->relatedSampleIndex] = AllocateAdditionalData();
+ additionalData[itGPUTime->relatedSampleIndex]->gpuTime += itGPUTime->gpuTimeInMicroSec*1000;
+ additionalData[itGPUTime->relatedSampleIndex]->drawCalls++;
+ additionalData[itGPUTime->relatedSampleIndex]->gpuSection = itGPUTime->gpuSection;
+ }
+
+ dynamic_array<UInt32>::const_iterator itWarningSample = tdata.m_WarningSamples.begin();
+ for(; itWarningSample != tdata.m_WarningSamples.end(); ++itWarningSample)
+ {
+ if(additionalData[*itWarningSample] == 0)
+ additionalData[*itWarningSample] = AllocateAdditionalData();
+ additionalData[*itWarningSample]->warningCount += 1;
+ }
+}
+
+
+static UInt32 FillAdditionalProfilerData(const ProfilerFrameData& frameData, int threadIdx, dynamic_array<AdditionalProfilerSampleData*>& additionalData, UInt32 index = 0)
+{
+ const ProfilerFrameData::ThreadData& tdata = frameData.m_ThreadData[threadIdx];
+ if (tdata.m_AllSamples.empty())
+ return 0;
+
+ int childIndex = index+1;
+ for(int i = 0; i < tdata.GetSample(index)->nbChildren; i++)
+ {
+ int nextChild = FillAdditionalProfilerData(frameData, threadIdx, additionalData, childIndex);
+ if(additionalData[childIndex])
+ {
+ if(additionalData[index] == NULL)
+ additionalData[index] = AllocateAdditionalData();
+ additionalData[index]->gpuTime += additionalData[childIndex]->gpuTime;
+ additionalData[index]->drawCalls += additionalData[childIndex]->drawCalls;
+ additionalData[index]->allocatedGCMemory += additionalData[childIndex]->allocatedGCMemory;
+ additionalData[index]->warningCount += additionalData[childIndex]->warningCount;
+ }
+ childIndex = nextChild;
+ }
+ return childIndex;
+}
+
+static void ProcessProfilerOneSample (const ProfilerFrameData::ThreadData& tdata, UInt32 sampleIndex, ProfilerSampleData& data, const dynamic_array<AdditionalProfilerSampleData*>& additionalData)
+{
+ const ProfilerSample* sample = tdata.GetSample(sampleIndex);
+ data.time += sample->timeUS*1000;
+ if(additionalData[sampleIndex])
+ {
+ data.allocatedGCMemory += additionalData[sampleIndex]->allocatedGCMemory;
+ data.gpuTime += additionalData[sampleIndex]->gpuTime;
+ data.gpuSamplesCount += additionalData[sampleIndex]->drawCalls;
+ data.warningCount += additionalData[sampleIndex]->warningCount;
+ }
+ ///@TODO: maybe not depend on data structure of samples here...
+ const ProfilerSample* child = sample + 1;
+ for (int c=0;c<sample->nbChildren;c++)
+ {
+ UInt32 childSampleIndex = child - tdata.GetRoot();
+ data.childrenTime += child->timeUS*1000;
+
+ if(additionalData[childSampleIndex])
+ {
+ data.childrenGPUTime += additionalData[childSampleIndex]->gpuTime;
+ data.childrenGPUSamplesCount += additionalData[childSampleIndex]->drawCalls;
+ }
+ child = SkipSampleRecurse(child);
+ }
+}
+
+static void ProcessProfilerSampleData (const ProfilerFrameData& frameData, int threadIdx, ProfilerHierarchy& hierarchy, const dynamic_array<AdditionalProfilerSampleData*>& additionalData)
+{
+ const ProfilerFrameData::ThreadData& tdata = frameData.m_ThreadData[threadIdx];
+ Assert(hierarchy.oneSample < tdata.m_AllSamples.size());
+
+ ProfilerSampleData& data = hierarchy.data;
+ data.time = 0;
+ data.childrenTime = 0;
+ data.childrenGPUTime = 0;
+ data.childrenGPUSamplesCount = 0;
+ data.allocatedGCMemory = 0;
+ data.gpuTime = 0;
+ data.gpuSamplesCount = 0;
+ data.startTime = 0;
+ data.numberOfCalls = 1 + hierarchy.sampleIdx.size();
+ data.warningCount = 0;
+
+ ProcessProfilerOneSample (tdata, hierarchy.oneSample, data, additionalData);
+ for (int i=0;i<hierarchy.sampleIdx.size();i++)
+ ProcessProfilerOneSample (tdata, hierarchy.sampleIdx[i], data, additionalData);
+
+ UInt32 firstIndex = hierarchy.oneSample;
+ data.rootTime = tdata.GetRoot()->timeUS*1000;
+ if(additionalData[0])
+ data.rootGPUTime = additionalData[0]->gpuTime;
+ data.information = tdata.GetSample(firstIndex)->information;
+ data.startTime = tdata.GetSample(firstIndex)->startTimeUS;
+}
+
+std::string ProfilerProperty::GetProfilerColumn (ProfilerColumn column) const
+{
+ return ::GetProfilerColumn(m_ActiveHierarchy->data, m_ActiveHierarchy->oneSample, m_ActiveHierarchy->sampleIdx, m_AdditionalData, SupportsGPUProfiler(), column);
+}
+
+
+static void BuildHierarchyLevel (const ProfilerFrameData& frameData, int threadIdx, dynamic_array<UInt32>& allSamplesThisLevel, ProfilerHierarchy* parent, ProfilerColumn sortColumn, ProfilerViewType viewType, const dynamic_array<AdditionalProfilerSampleData*>& additionData)
+{
+ /////@TODO: Dont do this.
+ if (!parent->children.empty())
+ return;
+
+ Assert(parent->children.empty()); // should only be called once to build child list
+
+ const ProfilerFrameData::ThreadData& tdata = frameData.m_ThreadData[threadIdx];
+
+ // hierarchy merging
+ if (viewType == kViewHierarchy)
+ {
+ std::sort (allSamplesThisLevel.begin(), allSamplesThisLevel.end(), SortByFunctionName(frameData,threadIdx));
+
+ const char * lastUniqueName = NULL;
+ int count = 0;
+ for (int i=0;i<allSamplesThisLevel.size();i++)
+ {
+ const char* name = tdata.GetSample(allSamplesThisLevel[i])->information->name;
+ if (lastUniqueName == NULL || strcmp(name, lastUniqueName) != 0)
+ count++;
+ }
+ parent->children.reserve(count);
+
+ lastUniqueName = NULL;
+ for (int i=0;i<allSamplesThisLevel.size();i++)
+ {
+ const char* name = tdata.GetSample(allSamplesThisLevel[i])->information->name;
+ if (lastUniqueName == NULL || strcmp(name, lastUniqueName) != 0)
+ {
+ lastUniqueName = name;
+ parent->children.push_back(ProfilerHierarchy());
+ parent->children.back().oneSample = allSamplesThisLevel[i];
+ continue;
+ }
+
+ ProfilerHierarchy& element = parent->children.back();
+ element.sampleIdx.push_back(allSamplesThisLevel[i]);
+ }
+
+ }
+ // raw data. no merging of data is necessary
+ else
+ {
+ const size_t size = allSamplesThisLevel.size();
+ parent->children.resize(size, ProfilerHierarchy());
+ for (size_t i = 0; i < size; ++i)
+ {
+ ProfilerHierarchy& element = parent->children[i];
+ element.oneSample = allSamplesThisLevel[i];
+ }
+ }
+
+ // Calculate profiler display data for all generated children
+ for (int i=0;i<parent->children.size();i++)
+ {
+ ProfilerHierarchy& element = parent->children[i];
+ element.parent = parent;
+ ProcessProfilerSampleData (frameData, threadIdx, element, additionData);
+ }
+
+ // Sort by selected column
+ // IMPORTANT: stable_sort is necessary here. std::sort crashes since it doesn't take advantage of the std::swap of ProfilerHierarchy.
+ if (sortColumn != kDontSortProfilerColumn)
+ std::stable_sort (parent->children.begin(), parent->children.end(), SortByColumn(sortColumn));
+}
+
+static void BuildHierarchyLevel (ProfilerFrameData& frameData, int threadIdx, ProfilerHierarchy* parent, ProfilerColumn sortColumn, ProfilerViewType viewType, const dynamic_array<AdditionalProfilerSampleData*>& additionData)
+{
+ const ProfilerFrameData::ThreadData& tdata = frameData.m_ThreadData[threadIdx];
+
+ int childCount = tdata.GetSample(parent->oneSample)->nbChildren;
+ for(int i = 0; i < parent->sampleIdx.size(); i++)
+ childCount += tdata.GetSample(parent->sampleIdx[i])->nbChildren;
+
+ dynamic_array<UInt32> allSamplesThisLevel(kMemTempAlloc);
+ allSamplesThisLevel.reserve(childCount);
+ tdata.ExtractAllChildSamples(parent->oneSample, allSamplesThisLevel);
+ for(int i = 0; i < parent->sampleIdx.size(); i++)
+ tdata.ExtractAllChildSamples(parent->sampleIdx[i], allSamplesThisLevel);
+
+ BuildHierarchyLevel(frameData, threadIdx, allSamplesThisLevel, parent, sortColumn, viewType, additionData);
+}
+
+
+void ProfilerProperty::GetInstanceIDs(dynamic_array<SInt32>& instanceIDs)
+{
+ GetSortedInstanceIDs (m_AdditionalData, m_ActiveHierarchy->oneSample, m_ActiveHierarchy->sampleIdx, instanceIDs);
+}
+
+std::string ProfilerProperty::GetTooltip (ProfilerColumn column)
+{
+ if (column == kTotalGPUTimeColumn)
+ {
+ if (!SupportsGPUProfiler ())
+ return "The platform you are profiling does not support GPU profiling\nMac OS X 10.7 and higher supports profiling, many mobile platforms have no builtin GPU profiling capabilities.";
+ }
+
+ return "";
+}
+
+
+
+void ProfilerProperty::SetRoot(int frame, ProfilerColumn sortColumn, ProfilerViewType viewType)
+{
+ m_ProfilerViewType = viewType;
+ m_ProfilerSortColumn = sortColumn;
+
+ // @TODO: Dont cleanup here, but call it when done with the data
+ if(m_Root)
+ CleanupProperty();
+ Assert(m_Root == NULL);
+ Assert(m_FrameData == NULL);
+
+ ProfilerFrameData* frameData = ProfilerHistory::Get().GetFrameData(frame);
+ if (frameData == NULL)
+ return;
+
+ m_FrameData = frameData;
+ m_ThreadIdx = 0;
+
+ InitializeAdditionalProfilerData(*m_FrameData, m_ThreadIdx, m_AdditionalData);
+ FillAdditionalProfilerData(*m_FrameData, m_ThreadIdx, m_AdditionalData);
+
+ m_Root = new ProfilerHierarchy();
+ m_Root->oneSample = 0;
+ m_Root->parent = NULL;
+
+ BuildHierarchyLevel(*m_FrameData, m_ThreadIdx, m_Root, m_ProfilerSortColumn, m_ProfilerViewType, m_AdditionalData);
+ ProcessProfilerSampleData(*m_FrameData, m_ThreadIdx, *m_Root, m_AdditionalData);
+
+ m_ActiveHierarchy = m_Root;
+}
+
+void ProfilerProperty::InitializeDetailProperty (const ProfilerProperty& sourceProperty)
+{
+ m_FrameData = sourceProperty.m_FrameData;
+ m_ThreadIdx = sourceProperty.m_ThreadIdx;
+ m_ProfilerViewType = kViewDetailFlat;
+ m_ProfilerSortColumn = sourceProperty.m_ProfilerSortColumn;
+
+ InitializeAdditionalProfilerData(*m_FrameData, m_ThreadIdx, m_AdditionalData);
+ FillAdditionalProfilerData(*m_FrameData, m_ThreadIdx, m_AdditionalData);
+
+ m_Root = new ProfilerHierarchy();
+ m_Root->oneSample = sourceProperty.m_ActiveHierarchy->parent->oneSample;
+ m_Root->sampleIdx = sourceProperty.m_ActiveHierarchy->parent->sampleIdx;
+ m_Root->parent = NULL;
+
+ dynamic_array<UInt32> allSamples = sourceProperty.m_ActiveHierarchy->sampleIdx;
+ allSamples.push_back (sourceProperty.m_ActiveHierarchy->oneSample);
+
+ BuildHierarchyLevel(*m_FrameData, m_ThreadIdx, allSamples, m_Root, m_ProfilerSortColumn, m_ProfilerViewType, m_AdditionalData);
+ ProcessProfilerSampleData(*m_FrameData, m_ThreadIdx, *m_Root, m_AdditionalData);
+
+ m_ActiveHierarchy = m_Root;
+}
+
+std::string ProfilerProperty::GetFrameTime() const
+{
+ if (!m_FrameData)
+ return "--";
+
+ return GetFormattedTime(m_FrameData->m_ThreadData[m_ThreadIdx].GetRoot()->timeUS*1000);
+}
+
+std::string ProfilerProperty::GetFrameGpuTime() const
+{
+ if (m_FrameData){
+ return GetFormattedTime(m_FrameData->m_TotalGPUTimeInMicroSec*1000);
+ }
+ return "--";
+}
+
+std::string ProfilerProperty::GetFrameFPS() const
+{
+ if (m_FrameData)
+ {
+ double frame = 1000000.0 / (double)m_FrameData->m_ThreadData[m_ThreadIdx].GetRoot()->timeUS;
+ return Format("%.1f", (float)frame);
+ }
+ else
+ return "--";
+}
+
+bool ProfilerProperty::HasChildren() const
+{
+ // Detail view does not haven any children.
+ if (m_ProfilerViewType == kViewDetailFlat && m_Root != m_ActiveHierarchy)
+ return false;
+
+ if (m_OnlyShowGPUSamples)
+ return m_ActiveHierarchy->data.childrenGPUSamplesCount != 0;
+
+ if (m_FrameData->m_ThreadData[m_ThreadIdx].GetSample(m_ActiveHierarchy->oneSample)->nbChildren != 0)
+ return true;
+
+ dynamic_array<UInt32>& samples = m_ActiveHierarchy->sampleIdx;
+ for (int i=0;i<samples.size();i++)
+ {
+ if (m_FrameData->m_ThreadData[m_ThreadIdx].GetSample(samples[i])->nbChildren != 0)
+ return true;
+ }
+ return false;
+}
+
+bool ProfilerProperty::GetNext(bool expanded)
+{
+ if (!GetNextInternal (expanded))
+ return false;
+
+ while (m_OnlyShowGPUSamples && m_ActiveHierarchy->data.gpuSamplesCount == 0)
+ {
+ if (!GetNextInternal (expanded))
+ return false;
+ }
+
+ return true;
+}
+
+bool ProfilerProperty::GetNextInternal(bool expanded)
+{
+ if (m_ActiveHierarchy == NULL)
+ return false;
+
+ if (HasChildren() && expanded)
+ {
+ BuildHierarchyLevel(*m_FrameData, m_ThreadIdx, m_ActiveHierarchy, m_ProfilerSortColumn, m_ProfilerViewType, m_AdditionalData);
+
+ // Step into children list
+ m_ActiveHierarchy = &m_ActiveHierarchy->children[0];
+
+ // Increase depth
+ ++m_Depth;
+ }
+ else
+ {
+ if (m_ActiveHierarchy->parent == 0)
+ return false;
+
+ int nextIndex = (m_ActiveHierarchy - &m_ActiveHierarchy->parent->children[0]) + 1;
+
+ // Locating next profile sample object until one is found or end of samples is reached
+ // step out of children list to its parent if end of list is reached
+ while(nextIndex == m_ActiveHierarchy->parent->children.size())
+ {
+ // Go to parent
+ m_ActiveHierarchy = m_ActiveHierarchy->parent;
+
+ if (!m_ActiveHierarchy->parent)
+ {
+ // End of samples
+ return false;
+ }
+ nextIndex = (m_ActiveHierarchy - &m_ActiveHierarchy->parent->children[0]) + 1;
+
+ // Decrease depth
+ --m_Depth;
+ }
+
+ m_ActiveHierarchy = &m_ActiveHierarchy->parent->children[nextIndex];
+ }
+
+ // Read profile sample name
+ m_FunctionPath = m_FunctionName = m_ActiveHierarchy->data.information->name;
+
+ // Assemble path name (used by profile property to handle fold in / fold out)
+ ProfilerHierarchy* sample = m_ActiveHierarchy;
+ sample = sample ? sample->parent : NULL;
+ int depth = m_Depth - 1;
+
+ // Construct path string of all parent names and name of this sample
+ while (sample != NULL && sample->data.information)
+ {
+ // Using Format("%s/%s") takes 80ms to display profiler timeone on Windows, Core i7 2600K.
+ // Replacing that with manual string operations gets time down to 20ms.
+
+ //m_FunctionPath = Format("%s/%s", sample->data.information->name, m_FunctionPath.c_str());
+ const size_t nameLen = strlen(sample->data.information->name);
+ m_FunctionPath.reserve (m_FunctionPath.size() + nameLen + 1); // name length plus '/' char
+ m_FunctionPath.insert (0, sample->data.information->name, nameLen+1); // insert whole name and trailing zero
+ m_FunctionPath[nameLen] = '/'; // replace just inserted trailing zero with '/'
+
+ sample = sample->parent;
+ depth--;
+ }
+
+ // Found profile sample
+ return true;
+}
+
+void ProfilerProperty::CleanupProperty()
+{
+ m_Depth = 0;
+ delete m_Root; m_Root = NULL;
+ m_FrameData = NULL;
+ m_ActiveHierarchy = NULL;
+ for(int i = 0; i < m_AdditionalData.size(); i++)
+ ProfilerProperty::s_AdditionalDataPool.Deallocate(m_AdditionalData[i]);
+ m_AdditionalData.clear();
+}
+
+
+#endif // #if ENABLE_PROFILER && UNITY_EDITOR
diff --git a/Runtime/Profiler/ProfilerProperty.h b/Runtime/Profiler/ProfilerProperty.h
new file mode 100644
index 0000000..2b706fe
--- /dev/null
+++ b/Runtime/Profiler/ProfilerProperty.h
@@ -0,0 +1,162 @@
+#ifndef _PROFILERPROPERTY_H_
+#define _PROFILERPROPERTY_H_
+
+#include "Configuration/UnityConfigure.h"
+
+enum ProfilerViewType
+{
+ kViewHierarchy = 0, // Functions merged and sorted
+ kViewTimeline, // same as kViewRawHierarchy, only different in the UI
+ kViewRawHierarchy, // Unmerged and unsorted
+ kViewDetailFlat, // Unmerged and unsorted and no children
+};
+
+enum ProfilerColumn
+{
+ kDontSortProfilerColumn = -1,
+ kFunctionNameColumn = 0,
+
+ kTotalPercentColumn,
+ kSelfPercentColumn,
+ kCallsColumn,
+ kGCMemory,
+ kTotalTimeColumn,
+ kSelfTimeColumn,
+
+ kDrawCallsColumn,
+ kTotalGPUTimeColumn,
+ kSelfGPUTimeColumn,
+ kTotalGPUPercentColumn,
+ kSelfGPUPercentColumn,
+
+ kWarningColumn,
+
+ kObjectNameColumn,
+
+ kProfilerColumnCount
+};
+
+#if ENABLE_PROFILER && UNITY_EDITOR
+
+#include "Profiler.h"
+#include "ProfilerHistory.h"
+#include "Runtime/Utilities/MemoryPool.h"
+#include <iosfwd>
+
+class Object;
+struct ProfilerHierarchy;
+
+struct AdditionalProfilerSampleData
+{
+ ProfileTimeFormat gpuTime;
+ UInt32 drawCalls;
+ GpuSection gpuSection;
+ SInt32 instanceID;
+ UInt32 allocatedGCMemory;
+ UInt32 warningCount;
+};
+
+// ProfilerProperty class for navigation through profiling information map in Profiler
+class ProfilerProperty
+{
+public:
+ ProfilerProperty();
+ ~ProfilerProperty();
+
+ std::string GetProfilerColumn (ProfilerColumn column) const;
+
+ std::string GetFrameTime() const;
+ std::string GetFrameGpuTime() const;
+ std::string GetFrameFPS() const;
+
+ void InitializeDetailProperty (const ProfilerProperty& sourceProperty);
+
+ const std::string& GetFunctionName() const { return m_FunctionName; }
+ const std::string& GetFunctionPath() const { return m_FunctionPath; }
+
+ // Returns call stack depth (profiled function nested level)
+ int GetDepth() const { return m_Depth; }
+
+ // Checks if the property has children (other profiled functions called within)
+ bool HasChildren() const;
+
+ // Sets profile sample list root
+ void SetRoot(int frame, ProfilerColumn sortColumn, ProfilerViewType viewType);
+
+ // Retrieves next profile sample
+ bool GetNext(bool expanded);
+
+ void GetInstanceIDs(dynamic_array<SInt32>& instanceIDs);
+
+ // Returns current frame data
+ ProfilerFrameData* GetFrameData() { return m_FrameData; }
+
+ // Clean up property parameters (such as depth, ect)
+ void CleanupProperty();
+
+ std::string GetTooltip (ProfilerColumn column);
+
+ bool GetOnlyShowGPUSamples () { return m_OnlyShowGPUSamples; }
+ void SetOnlyShowGPUSamples (bool value) { m_OnlyShowGPUSamples = value; }
+
+ static MemoryPool s_AdditionalDataPool;
+
+private:
+
+ bool SupportsGPUProfiler () const { return !m_FrameData->m_ThreadData[m_ThreadIdx].m_GPUTimeSamples.empty(); }
+ bool GetNextInternal(bool expanded);
+
+ std::string m_FunctionName; // name (e.g. Camera.Render)
+ std::string m_FunctionPath; // hierarchical name, e.g. PlayerLoop/RenderCameras/Camera.Render
+
+ // Call stack depth (profiled function nested level)
+ int m_Depth;
+
+ bool m_OnlyShowGPUSamples;
+
+ // Requested profile data view type
+ ProfilerViewType m_ProfilerViewType;
+ ProfilerColumn m_ProfilerSortColumn;
+
+ ProfilerHierarchy* m_Root;
+ ProfilerFrameData* m_FrameData;
+ ProfilerHierarchy* m_ActiveHierarchy;
+ dynamic_array<AdditionalProfilerSampleData*>m_AdditionalData;
+ int m_ThreadIdx;
+};
+
+struct ProfilerSampleData
+{
+ // constant across all samples
+ ProfileTimeFormat rootTime;
+ ProfileTimeFormat rootGPUTime;
+
+ ProfileTimeFormat time;
+
+ ProfileTimeFormat gpuTime;
+
+ ProfileTimeFormat startTime;
+
+ UInt32 gpuSamplesCount;
+ // Stores execution time per frame of all function called within this one
+ ProfileTimeFormat childrenTime;
+ ProfileTimeFormat childrenGPUTime;
+
+ UInt32 childrenGPUSamplesCount;
+
+ // Stores amount of allocated GC memory
+ UInt32 allocatedGCMemory;
+
+ // Stores number of calls made to the function in one frame
+ UInt32 numberOfCalls;
+
+ UInt32 warningCount;
+
+ // Stores profile information
+ ProfilerInformation* information;
+};
+
+
+#endif // #if ENABLE_PROFILER && UNITY_EDITOR
+
+#endif /*_PROFILERPROPERTY_H_*/
diff --git a/Runtime/Profiler/ProfilerStats.cpp b/Runtime/Profiler/ProfilerStats.cpp
new file mode 100644
index 0000000..c4ddb6c
--- /dev/null
+++ b/Runtime/Profiler/ProfilerStats.cpp
@@ -0,0 +1,432 @@
+#include "UnityPrefix.h"
+#include "ProfilerStats.h"
+#include "Runtime/BaseClasses/BaseObject.h"
+#include "MemoryProfiler.h"
+#include "SerializationUtility.h"
+
+#if ENABLE_PROFILER
+
+using namespace std;
+struct ProfilerSample;
+
+
+void InitializeStatisticsProperties (dynamic_array<StatisticsProperty>& statisticProperties)
+{
+ AllProfilerStats* proxy = NULL;
+
+
+ #define ADD_STAT(_area,_graph,_name,_val,_format) { \
+ StatisticsProperty& prop = statisticProperties.push_back(); \
+ new (&prop) StatisticsProperty(); \
+ prop.name = _name; \
+ prop.offset = reinterpret_cast<UInt8*>(&proxy->_val) - reinterpret_cast<UInt8*>(proxy); \
+ prop.format = _format; \
+ prop.showGraph = _graph; \
+ prop.area = _area; \
+ }
+
+ // Any int stats value can be added by specifying the name, variable and display format into the macro
+#define ADD_STAT_CHART(_area,_name,_val) \
+ ADD_STAT(_area, true, _name, chartSample._val, kFormatTime); \
+ ADD_STAT(_area, false, "Selected" _name, chartSampleSelected._val, kFormatTime)
+
+ // CPU overview
+ ADD_STAT_CHART(kProfilerAreaCPU, "Rendering", rendering);
+ ADD_STAT_CHART(kProfilerAreaCPU, "Scripts", scripts);
+ ADD_STAT_CHART(kProfilerAreaCPU, "Physics", physics);
+ ADD_STAT_CHART(kProfilerAreaCPU, "GarbageCollector", gc);
+ ADD_STAT_CHART(kProfilerAreaCPU, "VSync", vsync);
+ ADD_STAT_CHART(kProfilerAreaCPU, "Others", others);
+
+ // GPU overview
+
+ ADD_STAT_CHART(kProfilerAreaGPU, "Opaque", gpuOpaque);
+ ADD_STAT_CHART(kProfilerAreaGPU, "Transparent", gpuTransparent);
+ ADD_STAT_CHART(kProfilerAreaGPU, "Shadows/Depth", gpuShadows);
+ ADD_STAT_CHART(kProfilerAreaGPU, "Deferred PrePass", gpuDeferredPrePass);
+ ADD_STAT_CHART(kProfilerAreaGPU, "Deferred Lighting", gpuDeferredLighting);
+ ADD_STAT_CHART(kProfilerAreaGPU, "PostProcess", gpuPostProcess);
+ ADD_STAT_CHART(kProfilerAreaGPU, "Other", gpuOther);
+
+
+ // Graphics
+ ADD_STAT(kProfilerAreaRendering, true, "Draw Calls", drawStats.drawCalls, kFormatCount);
+ ADD_STAT(kProfilerAreaRendering, true, "Triangles", drawStats.triangles, kFormatCount);
+ ADD_STAT(kProfilerAreaRendering, true, "Vertices", drawStats.vertices, kFormatCount);
+
+ // Memory
+ ADD_STAT(kProfilerAreaMemory, true, "Total Allocated", memoryStats.bytesUsedTotal, kFormatBytes);
+ ADD_STAT(kProfilerAreaMemory, true, "Texture Memory", drawStats.usedTextureBytes, kFormatBytes);
+ ADD_STAT(kProfilerAreaMemory, false, "Texture Count", memoryStats.textureCount, kFormatCount);
+ ADD_STAT(kProfilerAreaMemory, true, "Mesh Count", memoryStats.meshCount, kFormatCount);
+ ADD_STAT(kProfilerAreaMemory, true, "Material Count", memoryStats.materialCount, kFormatCount);
+ ADD_STAT(kProfilerAreaMemory, true, "Object Count", memoryStats.totalObjectsCount, kFormatCount);
+
+ // Audio
+ ADD_STAT(kProfilerAreaAudio, true, "Playing Sources", audioStats.playingSources, kFormatCount);
+ ADD_STAT(kProfilerAreaAudio, true, "Paused Sources", audioStats.pausedSources, kFormatCount);
+ ADD_STAT(kProfilerAreaAudio, true, "Audio Voices", audioStats.audioVoices, kFormatCount);
+ ADD_STAT(kProfilerAreaAudio, false, "Audio CPU Usage", audioStats.audioCPUusage, kFormatPercentage);
+ ADD_STAT(kProfilerAreaAudio, true, "Audio Memory", audioStats.audioMemUsage, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Max Audio Memory Usage", audioStats.audioMaxMemUsage, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, true, "Audio Clip Count", audioStats.audioClipCount, kFormatCount);
+ ADD_STAT(kProfilerAreaAudio, true, "Audio Source Count", audioStats.audioSourceCount, kFormatCount);
+ ADD_STAT(kProfilerAreaAudio, false, "Detailed Audio Memory Usage", audioStats.audioMemDetailsUsage, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Memory not accounted for by other types", audioStats.audioMemDetails.other, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "String data", audioStats.audioMemDetails.string, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "System object and various internals", audioStats.audioMemDetails.system, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Plugin objects and internals", audioStats.audioMemDetails.plugins, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Output module object and internals", audioStats.audioMemDetails.output, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Channel related memory", audioStats.audioMemDetails.channel, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "ChannelGroup objects and internals", audioStats.audioMemDetails.channelgroup, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Codecs allocated for streaming", audioStats.audioMemDetails.codec, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "File buffers and structures", audioStats.audioMemDetails.file, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Sound objects and internals", audioStats.audioMemDetails.sound, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Sound data stored in secondary RAM", audioStats.audioMemDetails.secondaryram, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "SoundGroup objects and internals", audioStats.audioMemDetails.soundgroup, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Stream buffer memory", audioStats.audioMemDetails.streambuffer, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "DSPConnection objects and internals", audioStats.audioMemDetails.dspconnection, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "DSP implementation objects", audioStats.audioMemDetails.dsp, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Realtime file format decoding DSP objects", audioStats.audioMemDetails.dspcodec, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Profiler memory footprint", audioStats.audioMemDetails.profile, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Buffer used to store recorded data from microphone", audioStats.audioMemDetails.recordbuffer, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Reverb implementation objects", audioStats.audioMemDetails.reverb, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Reverb channel properties structs", audioStats.audioMemDetails.reverbchannelprops, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Geometry objects and internals", audioStats.audioMemDetails.geometry, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Sync point memory", audioStats.audioMemDetails.syncpoint, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "EventSystem and various internals", audioStats.audioMemDetails.eventsystem, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "MusicSystem and various internals", audioStats.audioMemDetails.musicsystem, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Definition of objects contained in all loaded projects e.g. events, groups, categories", audioStats.audioMemDetails.fev, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Data loaded with preloadFSB", audioStats.audioMemDetails.memoryfsb, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "EventProject objects and internals", audioStats.audioMemDetails.eventproject, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "EventGroup objects and internals", audioStats.audioMemDetails.eventgroupi, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Objects used to manage wave banks", audioStats.audioMemDetails.soundbankclass, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Data used to manage lists of wave bank usage", audioStats.audioMemDetails.soundbanklist, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Stream objects and internals", audioStats.audioMemDetails.streaminstance, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Sound definition objects", audioStats.audioMemDetails.sounddefclass, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Sound definition static data objects", audioStats.audioMemDetails.sounddefdefclass, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Sound definition pool data", audioStats.audioMemDetails.sounddefpool, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Reverb definition objects", audioStats.audioMemDetails.reverbdef, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Reverb objects", audioStats.audioMemDetails.eventreverb, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "User property objects", audioStats.audioMemDetails.userproperty, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Event instance base objects", audioStats.audioMemDetails.eventinstance, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Complex event instance objects", audioStats.audioMemDetails.eventinstance_complex, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Simple event instance objects", audioStats.audioMemDetails.eventinstance_simple, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Event layer instance objects", audioStats.audioMemDetails.eventinstance_layer, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Event sound instance objects", audioStats.audioMemDetails.eventinstance_sound, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Event envelope objects", audioStats.audioMemDetails.eventenvelope, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Event envelope definition objects", audioStats.audioMemDetails.eventenvelopedef, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Event parameter objects", audioStats.audioMemDetails.eventparameter, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Event category objects", audioStats.audioMemDetails.eventcategory, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Event envelope point objects", audioStats.audioMemDetails.eventenvelopepoint, kFormatBytes);
+ ADD_STAT(kProfilerAreaAudio, false, "Event instance pool memory", audioStats.audioMemDetails.eventinstancepool, kFormatBytes);
+
+
+ // Physics
+ ////@TODO: Add some kind of warning when moving static colliders because that has a very high performance impact!
+ ADD_STAT(kProfilerAreaPhysics, true, "Active Rigidbodies", physicsStats.activeRigidbodies, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics, false, "Sleeping Rigidbodies", physicsStats.sleepingRigidbodies, kFormatCount);
+
+ ADD_STAT(kProfilerAreaPhysics, true, "Number of Contacts", physicsStats.numberOfShapePairs, kFormatCount);
+
+ ADD_STAT(kProfilerAreaPhysics, false, "Static Colliders", physicsStats.numberOfStaticColliders, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics, false, "Dynamic Colliders", physicsStats.numberOfDynamicColliders, kFormatCount);
+
+ // Physics (2D).
+ ADD_STAT(kProfilerAreaPhysics2D, false, "Total Bodies", physics2DStats.m_TotalBodyCount, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics2D, true, "Active Bodies", physics2DStats.m_ActiveBodyCount, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics2D, true, "Sleeping Bodies", physics2DStats.m_SleepingBodyCount, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics2D, true, "Dynamic Bodies", physics2DStats.m_DynamicBodyCount, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics2D, true, "Kinematic Bodies", physics2DStats.m_KinematicBodyCount, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics2D, true, "Discrete Bodies", physics2DStats.m_DiscreteBodyCount, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics2D, true, "Continuous Bodies", physics2DStats.m_ContinuousBodyCount, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics2D, true, "Joints", physics2DStats.m_JointCount, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics2D, true, "Contacts", physics2DStats.m_ContactCount, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics2D, false, "Active Collider Shapes", physics2DStats.m_ActiveColliderShapesCount, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics2D, false, "Sleeping Collider Shapes", physics2DStats.m_SleepingColliderShapesCount, kFormatCount);
+ ADD_STAT(kProfilerAreaPhysics2D, false, "Step Time", physics2DStats.m_StepTime, kFormatTime);
+ ADD_STAT(kProfilerAreaPhysics2D, false, "Contact Time", physics2DStats.m_CollideTime, kFormatTime);
+ ADD_STAT(kProfilerAreaPhysics2D, false, "Solve Time", physics2DStats.m_SolveTime, kFormatTime);
+ ADD_STAT(kProfilerAreaPhysics2D, false, "Solve Initialization Time", physics2DStats.m_SolveInitialization, kFormatTime);
+ ADD_STAT(kProfilerAreaPhysics2D, false, "Solve Velocity Time", physics2DStats.m_SolveVelocity, kFormatTime);
+ ADD_STAT(kProfilerAreaPhysics2D, false, "Solve Position Time", physics2DStats.m_SolvePosition, kFormatTime);
+ ADD_STAT(kProfilerAreaPhysics2D, false, "Solve Broadphase Time", physics2DStats.m_SolveBroadphase, kFormatTime);
+ ADD_STAT(kProfilerAreaPhysics2D, false, "Solve TOI Time", physics2DStats.m_SolveTimeOfImpact, kFormatTime);
+}
+
+static inline ProfilerString FormatNumber (int num)
+{
+ if (num < 1000)
+ return FormatString<ProfilerString>("%d", num);
+ else if (num < 1000000)
+ return FormatString<ProfilerString>("%1.1fk", (num*0.001F));
+ else
+ return FormatString<ProfilerString>("%1.1fM", num*0.000001F);
+}
+
+ProfilerString DrawStats::ToString () const
+{
+ int vramUsageMin = screenBytes + renderTextureBytes;
+ int vramUsageMax = screenBytes + usedTextureBytes + renderTextureBytes + vboTotalBytes;
+
+ return
+ FormatString<ProfilerString>("Draw Calls: %d \tTris: %s \t Verts: %s", drawCalls, FormatNumber(triangles).c_str(), FormatNumber(vertices).c_str()) +
+ FormatString<ProfilerString>("\nBatched Draw Calls: %d \tBatched Tris: %s \t Batched Verts: %s", batchedDrawCalls, FormatNumber(batchedTriangles).c_str(), FormatNumber(batchedVertices).c_str()) +
+ FormatString<ProfilerString>("\nUsed Textures: %d / %s", usedTextureCount, FormatBytes(usedTextureBytes).c_str()) +
+ FormatString<ProfilerString>("\nRenderTextures: %d / %s", renderTextureCount, FormatBytes(renderTextureBytes).c_str()) +
+ FormatString<ProfilerString>("\nRenderTexture Switches: %d", renderTextureStateChanges) +
+ FormatString<ProfilerString>("\nScreen: %s / %s", GetScreenResString().c_str(), FormatBytes(screenBytes).c_str()) +
+ FormatString<ProfilerString>("\nVRAM usage: %s to %s (of %s)", FormatBytes(vramUsageMin).c_str(), FormatBytes(vramUsageMax).c_str(), FormatBytes(totalAvailableVRamMBytes * 1024 * 1024).c_str()) +
+ FormatString<ProfilerString>("\nVBO Total: %d - %s", vboTotal, FormatBytes(vboTotalBytes).c_str()) +
+ FormatString<ProfilerString>("\nVB Uploads: %d - %s", vboUploads, FormatBytes(vboUploadBytes).c_str()) +
+ FormatString<ProfilerString>("\nIB Uploads: %d - %s", ibUploads, FormatBytes(ibUploadBytes).c_str()) +
+ FormatString<ProfilerString>("\nShadow Casters: %d\t ", shadowCasters);
+}
+
+
+ProfilerString GetFormattedSmallTime(ProfileTimeFormat time)
+{
+ if (time < 100000)
+ return FormatString<ProfilerString>("%d nano", (int)time);
+
+ time /= 100;
+
+ ProfilerString value = FormatString<ProfilerString>("%u", (unsigned)time);
+
+ int length = value.length();
+
+ if (length > 4)
+ {
+ value.insert(length - 4, 1, '.');
+ }
+ else
+ {
+ ProfilerString tmp;
+ tmp.assign(4, '0');
+
+ value.copy((char*) tmp.c_str() + (4 - length), length);
+
+ return "0." + tmp;
+ }
+
+ return value;
+}
+
+ProfilerString DebugStats::ToString () const
+{
+ return
+ FormatString<ProfilerString>("Profiler Memory: %s\n", FormatBytes(m_ProfilerMemoryUsage).c_str()) +
+ FormatString<ProfilerString>("Profiler Memory Misc: %s\n", FormatBytes(m_ProfilerMemoryUsageOthers).c_str())
+#if DEBUGMODE
+ + FormatString<ProfilerString>("Profiler Sample Count: %d\n", m_AllocatedProfileSamples)
+#endif
+ ;
+}
+
+ProfilerString MemoryStats::ToString () const
+{
+ ProfilerString str =
+ FormatString<ProfilerString>("Used Total: %s ", FormatBytes(bytesUsedTotal).c_str()) +
+ FormatString<ProfilerString>("Unity: %s ", FormatBytes(bytesUsedUnity).c_str()) +
+ FormatString<ProfilerString>("Mono: %s ", FormatBytes(bytesUsedMono).c_str()) +
+ FormatString<ProfilerString>("GfxDriver: %s ", FormatBytes(bytesUsedGFX).c_str()) +
+ FormatString<ProfilerString>("FMOD: %s ", FormatBytes(bytesUsedFMOD).c_str()) +
+ FormatString<ProfilerString>("Profiler: %s ", FormatBytes(bytesUsedProfiler).c_str()) +
+ FormatString<ProfilerString>("\nReserved Total: %s ", FormatBytes(bytesReservedTotal).c_str()) +
+ FormatString<ProfilerString>("Unity: %s ", FormatBytes(bytesReservedUnity).c_str()) +
+ FormatString<ProfilerString>("Mono: %s ", FormatBytes(bytesReservedMono).c_str()) +
+ FormatString<ProfilerString>("GfxDriver: %s ", FormatBytes(bytesReservedGFX).c_str()) +
+ FormatString<ProfilerString>("FMOD: %s ", FormatBytes(bytesReservedFMOD).c_str()) +
+ FormatString<ProfilerString>("Profiler: %s ", FormatBytes(bytesReservedProfiler).c_str()) +
+
+ FormatString<ProfilerString>("\nTotal System Memory Usage: %s ", FormatBytes(bytesVirtual).c_str()) +
+ FormatString<ProfilerString>("\n(WP8) Commited Limit: %s ", FormatBytes(bytesCommitedLimit).c_str()) +
+ FormatString<ProfilerString>("Commited Total: %s ", FormatBytes(bytesCommitedTotal).c_str()) +
+
+ FormatString<ProfilerString>("\n\nTextures: %d / %s", textureCount, FormatBytes(textureBytes).c_str()) +
+ FormatString<ProfilerString>("\nMeshes: %d / %s", meshCount, FormatBytes(meshBytes).c_str()) +
+ FormatString<ProfilerString>("\nMaterials: %d / %s", materialCount, FormatBytes(materialBytes).c_str()) +
+ FormatString<ProfilerString>("\nAnimationClips: %d / %s", animationClipCount, FormatBytes(animationClipBytes).c_str()) +
+ FormatString<ProfilerString>("\nAudioClips: %d / %s", audioCount, FormatBytes(audioBytes).c_str()) +
+ FormatString<ProfilerString>("\nAssets: %d ", assetCount) +
+ FormatString<ProfilerString>("\nGameObjects in Scene: %d ", gameObjectCount) +
+ FormatString<ProfilerString>("\nTotal Objects in Scene: %d ", sceneObjectCount) +
+ FormatString<ProfilerString>("\nTotal Object Count: %d", totalObjectsCount);// +
+/* Format("\nMost Occurring Objects:");
+ std::vector<int> sorted;
+ sorted.resize(classCount.size());
+ for(int i = 0; i < classCount.size(); i++)
+ sorted[i] = (classCount[i]<<12)+i;
+ std::sort(sorted.begin(), sorted.end());
+
+ for(int i = sorted.size()-1; i >= 0 ; i--)
+ {
+ int count = sorted[i] >> 12;
+ int index = sorted[i] & 0xFFF;
+ if(count != 0)
+ str += Format("\n\t%s: %d",Object::ClassIDToString(index).c_str(), count);
+ }*/
+ /*str += "\n\n";
+ for(int i = 0; i < memoryAllocatorInformation.size(); i++)
+ {
+ str += FormatString<ProfilerString>("\n%s: used %s (reserved %s)",memoryAllocatorInformation[i].name.c_str(), FormatBytes(memoryAllocatorInformation[i].used).c_str(), FormatBytes(memoryAllocatorInformation[i].reserved).c_str());
+ }
+ str += "\n\n";
+ str += memoryOverview;
+ */
+ return str;
+}
+
+ProfilerString DrawStats::GetScreenResString () const
+{
+ if (screenFSAA > 1)
+ return FormatString<ProfilerString>("%ix%i %ixAA", screenWidth, screenHeight, screenFSAA);
+ else
+ return FormatString<ProfilerString>("%ix%i", screenWidth, screenHeight);
+}
+
+#if ENABLE_PLAYERCONNECTION
+
+void AllProfilerStats::Serialize( dynamic_array<int>& bitstream )
+{
+ memoryStats.Serialize(bitstream);
+ WriteIntArray (bitstream, drawStats);
+ WriteIntArray (bitstream, physicsStats);
+ WriteIntArray (bitstream, physics2DStats);
+ debugStats.Serialize(bitstream); // not all ints
+ WriteIntArray (bitstream, audioStats);
+ WriteIntArray (bitstream, chartSample);
+ WriteIntArray (bitstream, chartSampleSelected);
+}
+
+void DebugStats::Serialize( dynamic_array<int>& bitstream )
+{
+ bitstream.push_back (m_ProfilerMemoryUsage);
+ bitstream.push_back (m_ProfilerMemoryUsageOthers);
+ bitstream.push_back (m_AllocatedProfileSamples);
+}
+
+void MemoryStats::Serialize( dynamic_array<int>& bitstream )
+{
+ bitstream.push_back (bytesUsedTotal/1024);
+ bitstream.push_back (bytesUsedUnity/1024);
+ bitstream.push_back (bytesUsedMono/1024);
+ bitstream.push_back (bytesUsedGFX/1024);
+ bitstream.push_back (bytesUsedFMOD/1024);
+ bitstream.push_back (bytesUsedProfiler/1024);
+
+ bitstream.push_back (bytesReservedTotal/1024);
+ bitstream.push_back (bytesReservedUnity/1024);
+ bitstream.push_back (bytesReservedMono/1024);
+ bitstream.push_back (bytesReservedGFX/1024);
+ bitstream.push_back (bytesReservedFMOD/1024);
+ bitstream.push_back (bytesReservedProfiler/1024);
+
+ bitstream.push_back (bytesVirtual/1024);
+ bitstream.push_back (bytesCommitedLimit/1024);
+ bitstream.push_back (bytesCommitedTotal/1024);
+
+ bitstream.push_back (textureCount);
+ bitstream.push_back (textureBytes);
+ bitstream.push_back (meshCount);
+ bitstream.push_back (meshBytes);
+ bitstream.push_back (materialCount);
+ bitstream.push_back (materialBytes);
+ bitstream.push_back (animationClipCount);
+ bitstream.push_back (animationClipBytes);
+ bitstream.push_back (audioCount);
+ bitstream.push_back (audioBytes);
+ bitstream.push_back (assetCount);
+ bitstream.push_back (sceneObjectCount);
+ bitstream.push_back (gameObjectCount);
+ bitstream.push_back (totalObjectsCount);
+
+ // write size, and index,entry for all non 0 entries
+ bitstream.push_back (classCount.size());
+ for(int i = 0; i < classCount.size(); i++)
+ {
+ if(classCount[i] != 0)
+ {
+ bitstream.push_back (i);
+ bitstream.push_back (classCount[i]);
+ }
+ }
+ bitstream.push_back (-1);
+
+ /// TODO
+// WriteString(bitstream, memoryOverview.c_str());
+
+}
+
+
+void AllProfilerStats::Deserialize( int** bitstream, bool swapdata )
+{
+ memoryStats.Deserialize(bitstream, swapdata);
+ ReadIntArray (bitstream, drawStats);
+ ReadIntArray (bitstream, physicsStats);
+ ReadIntArray (bitstream, physics2DStats);
+ debugStats.Deserialize(bitstream); // not all ints
+ ReadIntArray (bitstream, audioStats);
+ ReadIntArray (bitstream, chartSample);
+ ReadIntArray (bitstream, chartSampleSelected);
+}
+
+void DebugStats::Deserialize( int** bitstream )
+{
+ m_ProfilerMemoryUsage = *((*bitstream)++);
+ m_ProfilerMemoryUsageOthers = *((*bitstream)++);
+ m_AllocatedProfileSamples = *((*bitstream)++);
+}
+
+void MemoryStats::Deserialize( int** bitstream, bool swapdata )
+{
+ bytesUsedTotal = *((*bitstream)++)*1024;
+ bytesUsedUnity = *((*bitstream)++)*1024;
+ bytesUsedMono = *((*bitstream)++)*1024;
+ bytesUsedGFX = *((*bitstream)++)*1024;
+ bytesUsedFMOD = *((*bitstream)++)*1024;
+ bytesUsedProfiler = *((*bitstream)++)*1024;
+
+ bytesReservedTotal = *((*bitstream)++)*1024;
+ bytesReservedUnity = *((*bitstream)++)*1024;
+ bytesReservedMono = *((*bitstream)++)*1024;
+ bytesReservedGFX = *((*bitstream)++)*1024;
+ bytesReservedFMOD = *((*bitstream)++)*1024;
+ bytesReservedProfiler = *((*bitstream)++)*1024;
+
+ bytesVirtual = *((*bitstream)++)*1024;
+ bytesCommitedLimit = *((*bitstream)++)*1024;
+ bytesCommitedTotal = *((*bitstream)++)*1024;
+
+ textureCount = *((*bitstream)++);
+ textureBytes = *((*bitstream)++);
+ meshCount = *((*bitstream)++);
+ meshBytes = *((*bitstream)++);
+ materialCount = *((*bitstream)++);
+ materialBytes = *((*bitstream)++);
+ animationClipCount = *((*bitstream)++);
+ animationClipBytes = *((*bitstream)++);
+ audioCount = *((*bitstream)++);
+ audioBytes = *((*bitstream)++);
+ assetCount = *((*bitstream)++);
+ sceneObjectCount = *((*bitstream)++);
+ gameObjectCount = *((*bitstream)++);
+ totalObjectsCount = *((*bitstream)++);
+ int count;
+ count = *((*bitstream)++);
+ classCount.resize_initialized(count,0);
+ int index = *((*bitstream)++);
+ while(index != -1)
+ {
+ classCount[index] = *((*bitstream)++);
+ index = *((*bitstream)++);
+ }
+
+// ReadString(bitstream, memoryOverview, swapdata);
+
+}
+#endif
+
+#endif
+
+
diff --git a/Runtime/Profiler/ProfilerStats.h b/Runtime/Profiler/ProfilerStats.h
new file mode 100644
index 0000000..f49d7d6
--- /dev/null
+++ b/Runtime/Profiler/ProfilerStats.h
@@ -0,0 +1,323 @@
+#ifndef _PROFILERSTATS_H_
+#define _PROFILERSTATS_H_
+
+#include "Configuration/UnityConfigure.h"
+
+enum ValueFormat
+{
+ kFormatTime, // milliseconds
+ kFormatCount, // number (optionally with k/M)
+ kFormatBytes, // number b/kB/m
+ kFormatPercentage // Percentage in % * 10 as an int, so that we can represent 10.1%
+};
+
+enum ProfilerArea
+{
+ kProfilerAreaCPU,
+ kProfilerAreaGPU,
+ kProfilerAreaRendering,
+ kProfilerAreaMemory,
+ kProfilerAreaAudio,
+ kProfilerAreaPhysics,
+ kProfilerAreaPhysics2D,
+ kProfilerAreaDebug,
+ kProfilerAreaCount = kProfilerAreaDebug
+};
+
+#if ENABLE_PROFILER
+
+#include "TimeHelper.h"
+#include "Runtime/Utilities/dynamic_array.h"
+
+struct MemoryStats
+{
+ // used bytes: Total, unity(-profiler), mono, DX/OGL, Profiler, FMOD??, Executable??
+ // reserved bytes: Total, unity, mono, DX/OGL, Profiler, FMOD??, Executable??
+ size_t bytesUsedTotal;
+ size_t bytesUsedUnity;
+ size_t bytesUsedMono;
+ size_t bytesUsedGFX;
+ size_t bytesUsedFMOD;
+ size_t bytesUsedProfiler;
+
+ size_t bytesReservedTotal;
+ size_t bytesReservedUnity;
+ size_t bytesReservedMono;
+ size_t bytesReservedGFX;
+ size_t bytesReservedFMOD;
+ size_t bytesReservedProfiler;
+
+ size_t bytesVirtual;
+ size_t bytesCommitedLimit;
+ size_t bytesCommitedTotal;
+
+ int bytesUsedDelta;
+
+ int textureCount;
+ int textureBytes;
+
+ int meshCount;
+ int meshBytes;
+
+ int materialCount;
+ int materialBytes;
+
+ int animationClipCount;
+ int animationClipBytes;
+
+ int audioCount;
+ int audioBytes;
+
+ int assetCount;
+ int sceneObjectCount;
+ int gameObjectCount;
+
+ int totalObjectsCount;
+ int profilerMemUsed;
+ int profilerNumAllocations;
+ // NB! Everything above here will be cleared with a memset() in the constructor! ^^^^^^^^^^^^
+ dynamic_array<int> classCount;
+ ProfilerString memoryOverview;
+
+ MemoryStats () : classCount(kMemProfiler) { memset(this, 0, ptrdiff_t(&classCount) - ptrdiff_t(this)); classCount.clear(); memoryOverview.clear(); }
+#if ENABLE_PLAYERCONNECTION
+ void Serialize( dynamic_array<int>& bitstream );
+ void Deserialize( int** bitstream, bool swapdata );
+#endif
+
+ ProfilerString ToString () const;
+};
+
+struct DrawStats
+{
+ int drawCalls;
+ int triangles;
+ int vertices;
+
+ int batchedDrawCalls;
+ int batchedTriangles;
+ int batchedVertices;
+
+ int shadowCasters;
+
+ int usedTextureCount;
+ int usedTextureBytes;
+
+ int renderTextureCount;
+ int renderTextureBytes;
+ int renderTextureStateChanges;
+
+ int screenWidth;
+ int screenHeight;
+ int screenFSAA;
+ int screenBytes;
+
+ int vboTotal;
+ int vboTotalBytes;
+ int vboUploads;
+ int vboUploadBytes;
+ int ibUploads;
+ int ibUploadBytes;
+
+ int visibleSkinnedMeshes;
+
+ int totalAvailableVRamMBytes;
+
+ // int textureStateChanges;
+ // int lightStateChanges;
+ // int pixelShaderStateChanges;
+ // int vertexShaderStateChanges;
+
+ DrawStats () { memset(this, 0, sizeof(*this)); }
+ ProfilerString GetScreenResString () const;
+
+
+ ProfilerString ToString () const;
+};
+
+struct PhysicsStats
+{
+ int activeRigidbodies;
+ int sleepingRigidbodies;
+
+ int numberOfShapePairs;
+
+ int numberOfStaticColliders;
+ int numberOfDynamicColliders;
+
+ PhysicsStats () { memset(this, 0, sizeof(*this)); }
+};
+
+struct Physics2DStats
+{
+ int m_TotalBodyCount;
+ int m_ActiveBodyCount;
+ int m_SleepingBodyCount;
+ int m_DynamicBodyCount;
+ int m_KinematicBodyCount;
+ int m_DiscreteBodyCount;
+ int m_ContinuousBodyCount;
+ int m_JointCount;
+ int m_ContactCount;
+ int m_ActiveColliderShapesCount;
+ int m_SleepingColliderShapesCount;
+
+ int m_StepTime;
+ int m_CollideTime;
+ int m_SolveTime;
+ int m_SolveInitialization;
+ int m_SolveVelocity;
+ int m_SolvePosition;
+ int m_SolveBroadphase;
+ int m_SolveTimeOfImpact;
+
+ Physics2DStats () { memset(this, 0, sizeof(*this)); }
+};
+
+struct DebugStats
+{
+ DebugStats () { memset(this, 0, sizeof(*this)); }
+
+ int m_ProfilerMemoryUsage;
+ int m_ProfilerMemoryUsageOthers;
+ int m_AllocatedProfileSamples;
+
+ ProfilerString ToString () const;
+#if ENABLE_PLAYERCONNECTION
+ void Serialize( dynamic_array<int>& bitstream );
+ void Deserialize( int** bitstream );
+#endif
+};
+
+struct AudioStats
+{
+ AudioStats () { memset(this, 0, sizeof(*this)); }
+
+ int playingSources;
+ int pausedSources;
+
+ int audioCPUusage;
+ int audioMemUsage;
+ int audioMaxMemUsage;
+ int audioVoices;
+
+ int audioClipCount;
+ int audioSourceCount;
+
+ unsigned int audioMemDetailsUsage;
+
+ struct Details
+ {
+ unsigned int other; /* [out] Memory not accounted for by other types */
+ unsigned int string; /* [out] String data */
+ unsigned int system; /* [out] System object and various internals */
+ unsigned int plugins; /* [out] Plugin objects and internals */
+ unsigned int output; /* [out] Output module object and internals */
+ unsigned int channel; /* [out] Channel related memory */
+ unsigned int channelgroup; /* [out] ChannelGroup objects and internals */
+ unsigned int codec; /* [out] Codecs allocated for streaming */
+ unsigned int file; /* [out] File buffers and structures */
+ unsigned int sound; /* [out] Sound objects and internals */
+ unsigned int secondaryram; /* [out] Sound data stored in secondary RAM */
+ unsigned int soundgroup; /* [out] SoundGroup objects and internals */
+ unsigned int streambuffer; /* [out] Stream buffer memory */
+ unsigned int dspconnection; /* [out] DSPConnection objects and internals */
+ unsigned int dsp; /* [out] DSP implementation objects */
+ unsigned int dspcodec; /* [out] Realtime file format decoding DSP objects */
+ unsigned int profile; /* [out] Profiler memory footprint. */
+ unsigned int recordbuffer; /* [out] Buffer used to store recorded data from microphone */
+ unsigned int reverb; /* [out] Reverb implementation objects */
+ unsigned int reverbchannelprops; /* [out] Reverb channel properties structs */
+ unsigned int geometry; /* [out] Geometry objects and internals */
+ unsigned int syncpoint; /* [out] Sync point memory. */
+ unsigned int eventsystem; /* [out] EventSystem and various internals */
+ unsigned int musicsystem; /* [out] MusicSystem and various internals */
+ unsigned int fev; /* [out] Definition of objects contained in all loaded projects e.g. events, groups, categories */
+ unsigned int memoryfsb; /* [out] Data loaded with preloadFSB */
+ unsigned int eventproject; /* [out] EventProject objects and internals */
+ unsigned int eventgroupi; /* [out] EventGroup objects and internals */
+ unsigned int soundbankclass; /* [out] Objects used to manage wave banks */
+ unsigned int soundbanklist; /* [out] Data used to manage lists of wave bank usage */
+ unsigned int streaminstance; /* [out] Stream objects and internals */
+ unsigned int sounddefclass; /* [out] Sound definition objects */
+ unsigned int sounddefdefclass; /* [out] Sound definition static data objects */
+ unsigned int sounddefpool; /* [out] Sound definition pool data */
+ unsigned int reverbdef; /* [out] Reverb definition objects */
+ unsigned int eventreverb; /* [out] Reverb objects */
+ unsigned int userproperty; /* [out] User property objects */
+ unsigned int eventinstance; /* [out] Event instance base objects */
+ unsigned int eventinstance_complex; /* [out] Complex event instance objects */
+ unsigned int eventinstance_simple; /* [out] Simple event instance objects */
+ unsigned int eventinstance_layer; /* [out] Event layer instance objects */
+ unsigned int eventinstance_sound; /* [out] Event sound instance objects */
+ unsigned int eventenvelope; /* [out] Event envelope objects */
+ unsigned int eventenvelopedef; /* [out] Event envelope definition objects */
+ unsigned int eventparameter; /* [out] Event parameter objects */
+ unsigned int eventcategory; /* [out] Event category objects */
+ unsigned int eventenvelopepoint; /* [out] Event envelope point objects */
+ unsigned int eventinstancepool; /* [out] Event instance pool memory */
+ };
+
+ Details audioMemDetails;
+};
+
+// Stores samples for profiler charts
+struct ChartSample
+{
+ ChartSample () { memset(this, 0, sizeof(*this)); }
+
+ int rendering;
+ int scripts;
+ int physics;
+ int gc;
+ int vsync;
+ int others;
+
+ int gpuOpaque;
+ int gpuTransparent;
+ int gpuShadows;
+ int gpuPostProcess;
+ int gpuDeferredPrePass;
+ int gpuDeferredLighting;
+ int gpuOther;
+
+ int hasGPUProfiler;
+};
+
+struct AllProfilerStats
+{
+ MemoryStats memoryStats;
+ DrawStats drawStats;
+ PhysicsStats physicsStats;
+ Physics2DStats physics2DStats;
+ DebugStats debugStats;
+ AudioStats audioStats;
+ ChartSample chartSample;
+ ChartSample chartSampleSelected;
+#if ENABLE_PLAYERCONNECTION
+ void Serialize( dynamic_array<int>& bitstream );
+ void Deserialize( int** bitstream, bool swapdata );
+#endif
+};
+
+struct StatisticsProperty
+{
+ std::string name;
+ int offset;
+ ValueFormat format;
+ ProfilerArea area;
+ bool showGraph;
+};
+
+
+void InitializeStatisticsProperties (dynamic_array<StatisticsProperty>& statisticProperties);
+
+inline int GetStatisticsValue (int offset, AllProfilerStats& stats)
+{
+ AssertIf( offset < 0 || offset >= sizeof(AllProfilerStats) );
+ UInt8* dataPtr = reinterpret_cast<UInt8*>(&stats) + offset;
+ return *reinterpret_cast<int*> (dataPtr);
+}
+
+#endif
+#endif
diff --git a/Runtime/Profiler/SerializationUtility.cpp b/Runtime/Profiler/SerializationUtility.cpp
new file mode 100644
index 0000000..d9214ba
--- /dev/null
+++ b/Runtime/Profiler/SerializationUtility.cpp
@@ -0,0 +1,24 @@
+#include "UnityPrefix.h"
+#include "SerializationUtility.h"
+
+void WriteString(dynamic_array<int>& bitstream, const char* str)
+{
+ int len = strlen(str); // length including null terminator
+ int startindex = bitstream.size();
+ bitstream.resize_initialized( startindex + len/4 + 1);
+ memcpy((char*)&bitstream[startindex], str, len+1);
+}
+
+void WriteIntArray(dynamic_array<int>& bitstream, int* data, int count)
+{
+ for(int i = 0; i < count; i++)
+ bitstream.push_back (data[i]);
+}
+
+void ReadIntArray(int** bitstream, int* data, int count)
+{
+ for(int i = 0; i < count; i++)
+ data[i] = *((*bitstream)++);
+}
+
+
diff --git a/Runtime/Profiler/SerializationUtility.h b/Runtime/Profiler/SerializationUtility.h
new file mode 100644
index 0000000..4de6339
--- /dev/null
+++ b/Runtime/Profiler/SerializationUtility.h
@@ -0,0 +1,41 @@
+#ifndef _SERIALIZATIONUTILITY_H_
+#define _SERIALIZATIONUTILITY_H_
+
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/Serialize/SwapEndianBytes.h"
+
+void WriteString(dynamic_array<int>& bitstream, const char* str);
+
+template <typename stringtype>
+void ReadString(int** bitstream, stringtype& str, bool swapdata)
+{
+ char* chars = (char*)*bitstream;
+ if(swapdata)
+ {
+ int wordcount = strlen(chars)/4 + 1;
+ for(int i = 0; i < wordcount; i++)
+ SwapEndianBytes((*bitstream)[i]);
+ }
+ str = stringtype((char*)*bitstream);
+ (*bitstream) += str.length()/4 + 1;
+}
+
+void WriteIntArray(dynamic_array<int>& bitstream, int* data, int count);
+
+template <typename writestruct>
+void WriteIntArray(dynamic_array<int>& bitstream, writestruct& data)
+{
+ int count = sizeof(data)/4;
+ WriteIntArray(bitstream, (int*)&data, count);
+}
+
+void ReadIntArray(int** bitstream, int* data, int count);
+
+template <typename writestruct>
+void ReadIntArray(int** bitstream, writestruct& data)
+{
+ int count = sizeof(data)/4;
+ ReadIntArray(bitstream, (int*)&data, count);
+}
+
+#endif
diff --git a/Runtime/Profiler/SharkProfiler.cpp b/Runtime/Profiler/SharkProfiler.cpp
new file mode 100644
index 0000000..1b3d592
--- /dev/null
+++ b/Runtime/Profiler/SharkProfiler.cpp
@@ -0,0 +1,93 @@
+#include "UnityPrefix.h"
+#include "SharkProfiler.h"
+#include "Configuration/UnityConfigure.h"
+
+#define ENABLE_SHARK_PROFILER ENABLE_PROFILER && UNITY_OSX
+
+#if ENABLE_SHARK_PROFILE
+#include <mach-o/dyld.h>
+
+typedef int (*fpChudAcquireRemoteAccess) ( void );
+typedef int (*fpChudReleaseRemoteAccess) ( void );
+typedef int (*fpChudStartRemotePerfMonitor) ( const char *label );
+typedef int (*fpChudStopRemotePerfMonitor) ( void );
+typedef int (*fpChudInitialize) ( void );
+typedef int (*fpChudCleanup) ( void );
+typedef int (*fpChudIsInitialized) ( void );
+
+// declare storage for each API's function pointers
+static int gChudLoaded = -1;
+static fpChudAcquireRemoteAccess gChudAcquireRemoteAccess = NULL;
+static fpChudReleaseRemoteAccess gChudReleaseRemoteAccess = NULL;
+static fpChudStartRemotePerfMonitor gChudStartRemotePerfMonitor = NULL;
+static fpChudStopRemotePerfMonitor gChudStopRemotePerfMonitor = NULL;
+static fpChudInitialize gChudInitialize = NULL;
+static fpChudCleanup gChudCleanup = NULL;
+static fpChudIsInitialized gChudIsInitialized = NULL;
+
+static bool LoadChudFunctionPointers ()
+{
+ if (gChudLoaded == 1)
+ return true;
+ if (gChudLoaded == 0)
+ return false;
+
+ CFStringRef bundlePath = CFSTR("/System/Library/PrivateFrameworks/CHUD.framework/Versions/A/Frameworks/CHUDCore.framework");
+ CFURLRef bundleURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, bundlePath, kCFURLPOSIXPathStyle, true);
+ CFBundleRef bundle = CFBundleCreate (kCFAllocatorDefault, bundleURL);
+
+ if (bundle == NULL)
+ {
+ ErrorString("Failed loading Shark system library");
+ gChudLoaded = 0;
+ return false;
+ }
+
+ gChudAcquireRemoteAccess=(fpChudAcquireRemoteAccess)CFBundleGetFunctionPointerForName (bundle, CFSTR("chudAcquireRemoteAccess"));
+ gChudReleaseRemoteAccess=(fpChudReleaseRemoteAccess)CFBundleGetFunctionPointerForName (bundle, CFSTR("chudReleaseRemoteAccess"));
+ gChudStartRemotePerfMonitor=(fpChudStartRemotePerfMonitor)CFBundleGetFunctionPointerForName (bundle, CFSTR("chudStartRemotePerfMonitor"));
+ gChudStopRemotePerfMonitor=(fpChudStopRemotePerfMonitor)CFBundleGetFunctionPointerForName (bundle, CFSTR("chudStopRemotePerfMonitor"));
+ gChudInitialize=(fpChudInitialize)CFBundleGetFunctionPointerForName (bundle, CFSTR("chudInitialize"));
+ gChudCleanup=(fpChudCleanup)CFBundleGetFunctionPointerForName (bundle, CFSTR("chudCleanup"));
+ gChudIsInitialized=(fpChudIsInitialized)CFBundleGetFunctionPointerForName (bundle, CFSTR("chudIsInitialized"));
+
+ if (gChudAcquireRemoteAccess == NULL || gChudReleaseRemoteAccess == NULL || gChudStartRemotePerfMonitor == NULL
+ || gChudStartRemotePerfMonitor == NULL || gChudStopRemotePerfMonitor == NULL || gChudInitialize == NULL || gChudCleanup == NULL || gChudIsInitialized == NULL)
+ {
+ ErrorString("Failed loading Shark system library function");
+ gChudLoaded = 0;
+ return false;
+ }
+ else
+ {
+ gChudLoaded = 1;
+ return true;
+ }
+}
+#endif
+
+void SharkBeginRemoteProfiling ()
+{
+#if ENABLE_SHARK_PROFILER
+ if (!LoadChudFunctionPointers ())
+ return;
+
+ if (gChudInitialize() != 0)
+ ErrorString("Launching Shark in SharkBeginProfile failed");
+ if (gChudAcquireRemoteAccess() != 0)
+ ErrorString("Launching Shark in SharkBeginProfile failed");
+ if (gChudStartRemotePerfMonitor("Unity") != 0)
+ ErrorString("Launching Shark in SharkBeginProfile failed");
+#endif
+}
+
+void SharkEndRemoteProfiling ()
+{
+#if ENABLE_SHARK_PROFILER
+ if (!LoadChudFunctionPointers ())
+ return;
+
+ gChudStopRemotePerfMonitor();
+ gChudReleaseRemoteAccess();
+#endif
+} \ No newline at end of file
diff --git a/Runtime/Profiler/SharkProfiler.h b/Runtime/Profiler/SharkProfiler.h
new file mode 100644
index 0000000..60a352c
--- /dev/null
+++ b/Runtime/Profiler/SharkProfiler.h
@@ -0,0 +1,7 @@
+#ifndef _SHARK_PROFILER_H_
+#define _SHARK_PROFILER_H_
+
+void SharkBeginRemoteProfiling ();
+void SharkEndRemoteProfiling ();
+
+#endif //_SHARK_PROFILER_H_
diff --git a/Runtime/Profiler/TimeHelper.cpp b/Runtime/Profiler/TimeHelper.cpp
new file mode 100644
index 0000000..1f78f4a
--- /dev/null
+++ b/Runtime/Profiler/TimeHelper.cpp
@@ -0,0 +1,190 @@
+#include "UnityPrefix.h"
+#include "TimeHelper.h"
+
+#if UNITY_IPHONE || UNITY_OSX
+
+#include <mach/mach_time.h>
+
+static bool g_ProfileTimeInited = false;
+static UInt64 g_Numer;
+static UInt64 g_Denom;
+
+static void InitTime()
+{
+ if( !g_ProfileTimeInited )
+ {
+ mach_timebase_info_data_t timeInfo;
+ mach_timebase_info(&timeInfo);
+
+ g_Numer = timeInfo.numer;
+ g_Denom = timeInfo.denom;
+
+ g_ProfileTimeInited = true;
+ }
+}
+
+ProfileTimeFormat GetProfileTime(UInt64 elapsedTime)
+{
+ InitTime();
+ return ( (elapsedTime*g_Numer) / g_Denom );
+}
+
+ABSOLUTE_TIME ProfileTimeToAbsoluteTime(ProfileTimeFormat elapsedTime)
+{
+ InitTime();
+ return ( (elapsedTime*g_Denom) / g_Numer );
+}
+
+ABSOLUTE_TIME DivideAbsoluteTime(ABSOLUTE_TIME elapsedTime, int divisor)
+{
+ return ProfileTimeToAbsoluteTime(GetProfileTime(elapsedTime) / (UInt64)divisor);
+}
+
+
+ABSOLUTE_TIME SubtractAbsoluteTimeClamp (ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs)
+{
+ return (lhs < rhs) ? (ABSOLUTE_TIME)0 : lhs-rhs;
+}
+
+#elif UNITY_WIN | UNITY_XENON
+
+// Timer granularity of QuertPerformanceCounters is that of frequency.
+// Usually that is around 2M - resulting in a granularity of 500ns.
+// This is not optimal for performance profiling
+
+ABSOLUTE_TIME GetStartTime()
+{
+ LARGE_INTEGER start;
+
+ QueryPerformanceCounter(&start);
+
+ return (UInt64) start.QuadPart;
+}
+
+ABSOLUTE_TIME GetElapsedTime(ABSOLUTE_TIME startTime)
+{
+ LARGE_INTEGER end;
+
+ QueryPerformanceCounter(&end);
+
+ return (UInt64) end.QuadPart - startTime;
+}
+
+static bool s_HaveFrequency = false;
+static LARGE_INTEGER s_Frequency;
+
+ProfileTimeFormat GetProfileTime(ABSOLUTE_TIME elapsedTime)
+{
+ if (!s_HaveFrequency)
+ {
+ QueryPerformanceFrequency (&s_Frequency);
+ s_HaveFrequency = true;
+ }
+
+ return (elapsedTime * 1000000000LL) / s_Frequency.QuadPart;
+}
+
+ABSOLUTE_TIME ProfileTimeToAbsoluteTime(ProfileTimeFormat elapsedTime)
+{
+ if (!s_HaveFrequency)
+ {
+ QueryPerformanceFrequency (&s_Frequency);
+ s_HaveFrequency = true;
+ }
+
+ return (elapsedTime * s_Frequency.QuadPart) / 1000000000LL;
+}
+
+ABSOLUTE_TIME DivideAbsoluteTime(ABSOLUTE_TIME elapsedTime, int divisor)
+{
+ return elapsedTime / (UInt64)divisor;
+}
+
+#elif UNITY_PS3
+
+#include <sys/sys_time.h>
+
+inline ProfileTimeFormat timebase_frequency()
+{
+ static ProfileTimeFormat tf = sys_time_get_timebase_frequency();
+ return tf;
+}
+
+ProfileTimeFormat GetProfileTime(ABSOLUTE_TIME elapsedTime)
+{
+ return (elapsedTime * 1000000000LL) / timebase_frequency() ;
+}
+
+ABSOLUTE_TIME ProfileTimeToAbsoluteTime(ProfileTimeFormat elapsedTime)
+{
+ return (elapsedTime * timebase_frequency()) / 1000000000LL;
+}
+
+ABSOLUTE_TIME DivideAbsoluteTime(ABSOLUTE_TIME elapsedTime, int divisor)
+{
+ return elapsedTime / (UInt64)divisor;
+}
+
+#elif UNITY_ANDROID || UNITY_PEPPER || UNITY_LINUX || UNITY_FLASH || UNITY_WEBGL || UNITY_BB10 || UNITY_TIZEN
+
+ProfileTimeFormat GetProfileTime(ABSOLUTE_TIME elapsedTime)
+{
+ return elapsedTime;
+}
+
+ABSOLUTE_TIME ProfileTimeToAbsoluteTime(ProfileTimeFormat elapsedTime)
+{
+ return elapsedTime;
+}
+
+ABSOLUTE_TIME DivideAbsoluteTime(ABSOLUTE_TIME elapsedTime, int divisor)
+{
+ return elapsedTime / (UInt64)divisor;
+}
+
+ABSOLUTE_TIME SubtractAbsoluteTimeClamp(ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs)
+{
+ if (IsSmallerAbsoluteTime(lhs, rhs))
+ {
+ ABSOLUTE_TIME zero;
+ ABSOLUTE_TIME_INIT(zero);
+ return zero;
+ }
+ else
+ {
+ return lhs - rhs;
+ }
+}
+
+#elif UNITY_WII
+
+ABSOLUTE_TIME GetStartTime()
+{
+ // ToDo
+ return 0;
+}
+ABSOLUTE_TIME GetElapsedTime(ABSOLUTE_TIME startTime)
+{
+ // ToDo:
+ return 0;
+}
+ProfileTimeFormat GetProfileTime(ABSOLUTE_TIME elapsedTime)
+{
+ return OSTicksToNanoseconds (elapsedTime);
+}
+
+ABSOLUTE_TIME ProfileTimeToAbsoluteTime(ProfileTimeFormat elapsedTime)
+{
+ return OSNanosecondsToTicks (elapsedTime);
+}
+
+ABSOLUTE_TIME DivideAbsoluteTime(ABSOLUTE_TIME elapsedTime, int divisor)
+{
+ return elapsedTime / (UInt64)divisor;
+}
+
+#else
+
+#error IMPLEMENT ME
+
+#endif \ No newline at end of file
diff --git a/Runtime/Profiler/TimeHelper.h b/Runtime/Profiler/TimeHelper.h
new file mode 100644
index 0000000..d3e9350
--- /dev/null
+++ b/Runtime/Profiler/TimeHelper.h
@@ -0,0 +1,121 @@
+#ifndef _TIMEHELPER_H_
+#define _TIMEHELPER_H_
+
+// The profiler interprets this value as nanoseconds
+typedef UInt64 ProfileTimeFormat;
+#define kInvalidProfileTime (~ProfileTimeFormat(0))
+
+#if UNITY_IPHONE || UNITY_OSX
+
+ extern "C" { uint64_t mach_absolute_time(void); }
+
+ #define ABSOLUTE_TIME UInt64
+ #define ABSOLUTE_TIME_INIT(VAR) VAR = 0u;
+
+ #define START_TIME mach_absolute_time()
+ #define ELAPSED_TIME(VAR) (START_TIME - VAR)
+ #define COMBINED_TIME(VAR1, VAR2) (VAR1 + VAR2)
+ #define SUBTRACTED_TIME(VAR1, VAR2) (VAR1 - VAR2)
+
+
+ ABSOLUTE_TIME SubtractAbsoluteTimeClamp(ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs);
+ ABSOLUTE_TIME DivideAbsoluteTime(ABSOLUTE_TIME elapsedTime, int divisor);
+ inline bool IsEqualAbsoluteTime (ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { return rhs == lhs; }
+ inline bool IsSmallerAbsoluteTime (ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { return rhs > lhs; }
+
+ ProfileTimeFormat GetProfileTime(ABSOLUTE_TIME elapsedTime);
+ ABSOLUTE_TIME ProfileTimeToAbsoluteTime(ProfileTimeFormat elapsedTime);
+
+#elif UNITY_ANDROID || UNITY_PEPPER || UNITY_LINUX || UNITY_FLASH || UNITY_WEBGL || UNITY_BB10 || UNITY_TIZEN
+
+ #include <sys/time.h>
+
+ #define ABSOLUTE_TIME UInt64
+ #define ABSOLUTE_TIME_INIT(VAR) VAR = 0ull;
+
+ inline const ABSOLUTE_TIME _StartTime() { timeval time; gettimeofday(&time, 0); return time.tv_usec * 1000ULL + time.tv_sec * 1000000000ULL; }
+ #define START_TIME _StartTime()
+ #define ELAPSED_TIME(VAR) (START_TIME - VAR)
+ #define COMBINED_TIME(VAR1, VAR2) (VAR1 + VAR2)
+ #define SUBTRACTED_TIME(VAR1, VAR2) (VAR1 - VAR2)
+
+ ABSOLUTE_TIME SubtractAbsoluteTimeClamp(ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs);
+ ABSOLUTE_TIME DivideAbsoluteTime(ABSOLUTE_TIME elapsedTime, int divisor);
+ inline bool IsEqualAbsoluteTime (ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { return rhs == lhs; }
+ inline bool IsSmallerAbsoluteTime (ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { return rhs > lhs; }
+
+ ProfileTimeFormat GetProfileTime(ABSOLUTE_TIME elapsedTime);
+ ABSOLUTE_TIME ProfileTimeToAbsoluteTime(ProfileTimeFormat elapsedTime);
+
+#elif UNITY_WIN || UNITY_XENON
+ #define ABSOLUTE_TIME UInt64
+ #define ABSOLUTE_TIME_INIT(VAR) VAR = 0u;
+
+ #define START_TIME GetStartTime()
+ #define ELAPSED_TIME(VAR) GetElapsedTime(VAR)
+ #define COMBINED_TIME(VAR1, VAR2) VAR1 + VAR2
+ #define SUBTRACTED_TIME(VAR1, VAR2) VAR1 - VAR2
+
+ inline ABSOLUTE_TIME SubtractAbsoluteTimeClamp(ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { if (lhs > rhs) return lhs - rhs; else return 0; }
+ ABSOLUTE_TIME DivideAbsoluteTime(ABSOLUTE_TIME elapsedTime, int divisor);
+ inline bool IsEqualAbsoluteTime (ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { return lhs == rhs; }
+ inline bool IsSmallerAbsoluteTime (ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { return lhs < rhs; }
+
+ ABSOLUTE_TIME GetStartTime();
+ ABSOLUTE_TIME GetElapsedTime(ABSOLUTE_TIME startTime);
+ ProfileTimeFormat GetProfileTime(ABSOLUTE_TIME elapsedTime);
+ ABSOLUTE_TIME ProfileTimeToAbsoluteTime(ProfileTimeFormat elapsedTime);
+
+
+#elif UNITY_PS3
+
+ #include <sys/time_util.h>
+
+ #define ABSOLUTE_TIME UInt64
+ #define ABSOLUTE_TIME_INIT(VAR) VAR = 0u;
+
+ inline const ABSOLUTE_TIME _StartTime() { ABSOLUTE_TIME tb; SYS_TIMEBASE_GET(tb); return tb; }
+ #define START_TIME _StartTime()
+ #define ELAPSED_TIME(VAR) START_TIME - VAR
+ #define COMBINED_TIME(VAR1, VAR2) VAR1 + VAR2
+ #define SUBTRACTED_TIME(VAR1, VAR2) VAR1 - VAR2
+ ProfileTimeFormat GetProfileTime(ABSOLUTE_TIME elapsedTime);
+
+ inline ABSOLUTE_TIME SubtractAbsoluteTimeClamp(ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { if (lhs > rhs) return lhs - rhs; else return 0; }
+ ABSOLUTE_TIME DivideAbsoluteTime(ABSOLUTE_TIME elapsedTime, int divisor);
+ inline bool IsEqualAbsoluteTime (ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { return lhs == rhs; }
+ inline bool IsSmallerAbsoluteTime (ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { return lhs < rhs; }
+#elif UNITY_WII
+ #include <revolution/os.h>
+ #define ABSOLUTE_TIME OSTime
+ #define ABSOLUTE_TIME_INIT(VAR) VAR = 0u;
+
+ #define START_TIME OSGetTime()
+ #define ELAPSED_TIME(VAR) GetElapsedTime(VAR)
+ #define COMBINED_TIME(VAR1, VAR2) VAR1 + VAR2
+ #define SUBTRACTED_TIME(VAR1, VAR2) VAR1 - VAR2
+
+ inline ABSOLUTE_TIME SubtractAbsoluteTimeClamp(ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { if (lhs > rhs) return lhs - rhs; else return 0; }
+ ABSOLUTE_TIME DivideAbsoluteTime(ABSOLUTE_TIME elapsedTime, int divisor);
+ inline bool IsEqualAbsoluteTime (ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { return lhs == rhs; }
+ inline bool IsSmallerAbsoluteTime (ABSOLUTE_TIME lhs, ABSOLUTE_TIME rhs) { return lhs < rhs; }
+
+ ABSOLUTE_TIME GetStartTime();
+ ABSOLUTE_TIME GetElapsedTime(ABSOLUTE_TIME startTime);
+ ProfileTimeFormat GetProfileTime(ABSOLUTE_TIME elapsedTime);
+ ABSOLUTE_TIME ProfileTimeToAbsoluteTime(ProfileTimeFormat elapsedTime);
+
+#else
+ #error IMPLEMENT ME
+#endif
+
+inline float ProfileTimeToSeconds (ProfileTimeFormat elapsedTime) { return elapsedTime * 0.000000001; }
+inline float AbsoluteTimeToSeconds (ABSOLUTE_TIME absoluteTime) { return ProfileTimeToSeconds(GetProfileTime(absoluteTime)); }
+inline float GetElapsedTimeInSeconds (ABSOLUTE_TIME elapsedTime) { return ProfileTimeToSeconds(GetProfileTime(ELAPSED_TIME(elapsedTime))); }
+
+inline float AbsoluteTimeToMilliseconds (ABSOLUTE_TIME time)
+{
+ return AbsoluteTimeToSeconds(time) * 1000.0F;
+}
+
+#endif /*_TIMEHELPER_H_*/