diff options
Diffstat (limited to 'Runtime/Profiler')
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(¤t_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(¤t_offset, name, swapdata); + ReadString(¤t_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_*/ |