diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp index 0cb39b8a5ec..cae7f3436b4 100644 --- a/gfx/layers/opengl/CompositorOGL.cpp +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -173,6 +173,9 @@ CompositorOGL::CleanupResources() mQuadVBO = 0; } + mGLContext->MakeCurrent(); + mContextStateTracker.DestroyOGL(mGLContext); + // On the main thread the Widget will be destroyed soon and calling MakeCurrent // after that could cause a crash (at least with GLX, see bug 1059793), unless // context is marked as destroyed. @@ -664,6 +667,8 @@ CompositorOGL::SetRenderTarget(CompositingRenderTarget *aSurface) = static_cast(aSurface); if (mCurrentRenderTarget != surface) { mCurrentRenderTarget = surface; + mContextStateTracker.PopOGLSection(gl(), "Frame"); + mContextStateTracker.PushOGLSection(gl(), "Frame"); surface->BindRenderTarget(); } } @@ -768,6 +773,8 @@ CompositorOGL::BeginFrame(const nsIntRegion& aInvalidRegion, CompositingRenderTargetOGL::RenderTargetForWindow(this, IntSize(width, height)); mCurrentRenderTarget->BindRenderTarget(); + + mContextStateTracker.PushOGLSection(gl(), "Frame"); #ifdef DEBUG mWindowRenderTarget = mCurrentRenderTarget; #endif @@ -1345,6 +1352,8 @@ CompositorOGL::EndFrame() } #endif + mContextStateTracker.PopOGLSection(gl(), "Frame"); + mFrameInProgress = false; if (mTarget) { diff --git a/gfx/layers/opengl/CompositorOGL.h b/gfx/layers/opengl/CompositorOGL.h index 96975e0b255..db4c213f8a9 100644 --- a/gfx/layers/opengl/CompositorOGL.h +++ b/gfx/layers/opengl/CompositorOGL.h @@ -6,6 +6,7 @@ #ifndef MOZILLA_GFX_COMPOSITOROGL_H #define MOZILLA_GFX_COMPOSITOROGL_H +#include "ContextStateTracker.h" #include "gfx2DGlue.h" #include "GLContextTypes.h" // for GLContext, etc #include "GLDefs.h" // for GLuint, LOCAL_GL_TEXTURE_2D, etc @@ -387,6 +388,8 @@ private: RefPtr mTexturePool; + ContextStateTrackerOGL mContextStateTracker; + bool mDestroyed; /** diff --git a/gfx/thebes/ContextStateTracker.cpp b/gfx/thebes/ContextStateTracker.cpp new file mode 100644 index 00000000000..5fa4ca6ffbb --- /dev/null +++ b/gfx/thebes/ContextStateTracker.cpp @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ContextStateTracker.h" +#include "GLContext.h" +#include "ProfilerMarkers.h" + +namespace mozilla { + +void +ContextStateTrackerOGL::PushOGLSection(GLContext* aGL, const char* aSectionName) +{ + if (!profiler_feature_active("gpu")) { + return; + } + + if (!aGL->IsSupported(gl::GLFeature::query_objects)) { + return; + } + + if (mSectionStack.Length() > 0) { + // We need to end the query since we're starting a new section and restore it + // when this section is finished. + aGL->fEndQuery(LOCAL_GL_TIME_ELAPSED); + Top().mCpuTimeEnd = TimeStamp::Now(); + } + + ContextState newSection(aSectionName); + + GLuint queryObject; + aGL->fGenQueries(1, &queryObject); + newSection.mStartQueryHandle = queryObject; + newSection.mCpuTimeStart = TimeStamp::Now(); + + aGL->fBeginQuery(LOCAL_GL_TIME_ELAPSED_EXT, queryObject); + + mSectionStack.AppendElement(newSection); +} + +void +ContextStateTrackerOGL::PopOGLSection(GLContext* aGL, const char* aSectionName) +{ + // We might have ignored a section start if we started profiling + // in the middle section. If so we will ignore this unmatched end. + if (mSectionStack.Length() == 0) { + return; + } + + int i = mSectionStack.Length() - 1; + MOZ_ASSERT(strcmp(mSectionStack[i].mSectionName, aSectionName) == 0); + aGL->fEndQuery(LOCAL_GL_TIME_ELAPSED); + mSectionStack[i].mCpuTimeEnd = TimeStamp::Now(); + mCompletedSections.AppendElement(mSectionStack[i]); + mSectionStack.RemoveElementAt(i); + + if (i - 1 >= 0) { + const char* sectionToRestore = Top().mSectionName; + + // We need to restore the outer section + // Well do this by completing this section and adding a new + // one with the same name + mCompletedSections.AppendElement(Top()); + mSectionStack.RemoveElementAt(i - 1); + + ContextState newSection(sectionToRestore); + + GLuint queryObject; + aGL->fGenQueries(1, &queryObject); + newSection.mStartQueryHandle = queryObject; + newSection.mCpuTimeStart = TimeStamp::Now(); + + aGL->fBeginQuery(LOCAL_GL_TIME_ELAPSED_EXT, queryObject); + + mSectionStack.AppendElement(newSection); + } + + Flush(aGL); +} + +void +ContextStateTrackerOGL::Flush(GLContext* aGL) +{ + TimeStamp now = TimeStamp::Now(); + + while (mCompletedSections.Length() != 0) { + // On mac we see QUERY_RESULT_AVAILABLE cause a GL flush if we query it + // too early. For profiling we rather have the last 200ms of data missing + // while causing let's measurement distortions. + if (mCompletedSections[0].mCpuTimeEnd + TimeDuration::FromMilliseconds(200) > now) { + break; + } + + GLuint handle = mCompletedSections[0].mStartQueryHandle; + + // We've waiting 200ms, content rendering at > 20 FPS will be ready. We + // shouldn't see any flushes now. + GLuint returned = 0; + aGL->fGetQueryObjectuiv(handle, LOCAL_GL_QUERY_RESULT_AVAILABLE, &returned); + + if (!returned) { + break; + } + + GLuint gpuTime = 0; + aGL->fGetQueryObjectuiv(handle, LOCAL_GL_QUERY_RESULT, &gpuTime); + + aGL->fDeleteQueries(1, &handle); + + PROFILER_MARKER_PAYLOAD("gpu_timer_query", new GPUMarkerPayload( + mCompletedSections[0].mCpuTimeStart, + mCompletedSections[0].mCpuTimeEnd, + 0, + gpuTime + )); + + mCompletedSections.RemoveElementAt(0); + } +} + +void +ContextStateTrackerOGL::DestroyOGL(GLContext* aGL) +{ + while (mCompletedSections.Length() != 0) { + GLuint handle = (GLuint)mCompletedSections[0].mStartQueryHandle; + aGL->fDeleteQueries(1, &handle); + mCompletedSections.RemoveElementAt(0); + } +} + +} + diff --git a/gfx/thebes/ContextStateTracker.h b/gfx/thebes/ContextStateTracker.h new file mode 100644 index 00000000000..dbb8ba6f94c --- /dev/null +++ b/gfx/thebes/ContextStateTracker.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CONTEXTSTATETRACKER_H +#define GFX_CONTEXTSTATETRACKER_H + +#include "GLTypes.h" +#include "mozilla/TimeStamp.h" +#include "nsTArray.h" +#include + +namespace mozilla { +namespace gl { +class GLContext; +} + +/** + * This class tracks the state of the context for debugging and profiling. + * Each section pushes a new stack entry and must be matched by an end section. + * All nested section must be ended before ending a parent section. + */ +class ContextStateTracker { +public: + ContextStateTracker() {} + +private: + + bool IsProfiling() { return true; } + +protected: + typedef GLuint TimerQueryHandle; + + class ContextState { + public: + ContextState(const char* aSectionName) + : mSectionName(aSectionName) + {} + + const char* mSectionName; + mozilla::TimeStamp mCpuTimeStart; + mozilla::TimeStamp mCpuTimeEnd; + TimerQueryHandle mStartQueryHandle; + }; + + ContextState& Top() { + MOZ_ASSERT(mSectionStack.Length()); + return mSectionStack[mSectionStack.Length() - 1]; + } + + nsTArray mCompletedSections; + nsTArray mSectionStack; +}; + +class ID3D11DeviceContext; + +class ContextStateTrackerD3D11 MOZ_FINAL : public ContextStateTracker { +public: + // TODO Implement me + void PushD3D11Section(ID3D11DeviceContext* aCtxt, const char* aSectionName) {} + void PopD3D11Section(ID3D11DeviceContext* aCtxt, const char* aSectionName) {} + void DestroyD3D11(ID3D11DeviceContext* aCtxt) {} + +private: + void Flush(); +}; + +class ContextStateTrackerOGL MOZ_FINAL : public ContextStateTracker { + typedef mozilla::gl::GLContext GLContext; +public: + void PushOGLSection(GLContext* aGL, const char* aSectionName); + void PopOGLSection(GLContext* aGL, const char* aSectionName); + void DestroyOGL(GLContext* aGL); +private: + void Flush(GLContext* aGL); +}; + +} +#endif + diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build index 9ab8142cd7a..bb6f4cf3239 100644 --- a/gfx/thebes/moz.build +++ b/gfx/thebes/moz.build @@ -5,6 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. EXPORTS += [ + 'ContextStateTracker.h', 'DrawMode.h', 'gfx2DGlue.h', 'gfx3DMatrix.h', @@ -196,6 +197,7 @@ if CONFIG['INTEL_ARCHITECTURE']: SOURCES['gfxAlphaRecoverySSE2.cpp'].flags += CONFIG['SSE2_FLAGS'] SOURCES += [ + 'ContextStateTracker.cpp', # Includes mac system header conflicting with point/size, # and includes glxXlibSurface.h which drags in Xrender.h 'gfxASurface.cpp', diff --git a/tools/profiler/GeckoProfiler.h b/tools/profiler/GeckoProfiler.h index 12a7eefec89..64767f11373 100644 --- a/tools/profiler/GeckoProfiler.h +++ b/tools/profiler/GeckoProfiler.h @@ -146,6 +146,11 @@ static inline void profiler_free_backtrace(ProfilerBacktrace* aBacktrace) {} static inline bool profiler_is_active() { return false; } +// Check if an external profiler feature is active. +// Supported: +// * gpu +static inline bool profiler_feature_active(const char*) { return false; } + // Internal-only. Used by the event tracer. static inline void profiler_responsiveness(const mozilla::TimeStamp& aTime) {} diff --git a/tools/profiler/GeckoProfilerFunc.h b/tools/profiler/GeckoProfilerFunc.h index 62f2efbc38c..9fb7619def4 100644 --- a/tools/profiler/GeckoProfilerFunc.h +++ b/tools/profiler/GeckoProfilerFunc.h @@ -44,6 +44,8 @@ void mozilla_sampler_free_backtrace(ProfilerBacktrace* aBacktrace); bool mozilla_sampler_is_active(); +bool mozilla_sampler_feature_active(const char* aName); + void mozilla_sampler_responsiveness(const mozilla::TimeStamp& time); void mozilla_sampler_frame_number(int frameNumber); diff --git a/tools/profiler/GeckoProfilerImpl.h b/tools/profiler/GeckoProfilerImpl.h index 6b08a5b90a1..833d0a88348 100644 --- a/tools/profiler/GeckoProfilerImpl.h +++ b/tools/profiler/GeckoProfilerImpl.h @@ -123,6 +123,12 @@ bool profiler_is_active() return mozilla_sampler_is_active(); } +static inline +bool profiler_feature_active(const char* aName) +{ + return mozilla_sampler_feature_active(aName); +} + static inline void profiler_responsiveness(const mozilla::TimeStamp& aTime) { diff --git a/tools/profiler/ProfilerMarkers.cpp b/tools/profiler/ProfilerMarkers.cpp index fbcdc13dfaa..21d9093200b 100644 --- a/tools/profiler/ProfilerMarkers.cpp +++ b/tools/profiler/ProfilerMarkers.cpp @@ -84,6 +84,34 @@ ProfilerMarkerTracing::streamPayloadImp(JSStreamWriter& b) b.EndObject(); } +GPUMarkerPayload::GPUMarkerPayload( + const mozilla::TimeStamp& aCpuTimeStart, + const mozilla::TimeStamp& aCpuTimeEnd, + uint64_t aGpuTimeStart, + uint64_t aGpuTimeEnd) + + : ProfilerMarkerPayload(aCpuTimeStart, aCpuTimeEnd) + , mCpuTimeStart(aCpuTimeStart) + , mCpuTimeEnd(aCpuTimeEnd) + , mGpuTimeStart(aGpuTimeStart) + , mGpuTimeEnd(aGpuTimeEnd) +{ + +} + +void +GPUMarkerPayload::streamPayloadImp(JSStreamWriter& b) +{ + b.BeginObject(); + streamCommonProps("gpu_timer_query", b); + + b.NameValue("cpustart", profiler_time(mCpuTimeStart)); + b.NameValue("cpuend", profiler_time(mCpuTimeEnd)); + b.NameValue("gpustart", (int)mGpuTimeStart); + b.NameValue("gpuend", (int)mGpuTimeEnd); + b.EndObject(); +} + ProfilerMarkerImagePayload::ProfilerMarkerImagePayload(gfxASurface *aImg) : mImg(aImg) {} diff --git a/tools/profiler/ProfilerMarkers.h b/tools/profiler/ProfilerMarkers.h index 28f1b63259b..4a4b860df53 100644 --- a/tools/profiler/ProfilerMarkers.h +++ b/tools/profiler/ProfilerMarkers.h @@ -8,6 +8,7 @@ #include "JSStreamWriter.h" #include "mozilla/TimeStamp.h" +#include "mozilla/Attributes.h" #include "nsAutoPtr.h" #include "Units.h" // For ScreenIntPoint @@ -52,6 +53,8 @@ public: return streamPayload(b); } + mozilla::TimeStamp GetStartTime() const { return mStartTime; } + protected: /** * Called from the main thread @@ -186,4 +189,26 @@ private: mozilla::TimeStamp mVsyncTimestamp; }; +class GPUMarkerPayload : public ProfilerMarkerPayload +{ +public: + GPUMarkerPayload(const mozilla::TimeStamp& aCpuTimeStart, + const mozilla::TimeStamp& aCpuTimeEnd, + uint64_t aGpuTimeStart, + uint64_t aGpuTimeEnd); + ~GPUMarkerPayload() {} + +protected: + virtual void + streamPayload(JSStreamWriter& b) MOZ_OVERRIDE { return streamPayloadImp(b); } + +private: + void streamPayloadImp(JSStreamWriter& b); + + mozilla::TimeStamp mCpuTimeStart; + mozilla::TimeStamp mCpuTimeEnd; + uint64_t mGpuTimeStart; + uint64_t mGpuTimeEnd; +}; + #endif // PROFILER_MARKERS_H diff --git a/tools/profiler/TableTicker.h b/tools/profiler/TableTicker.h index 04ce07e9776..b164e797376 100644 --- a/tools/profiler/TableTicker.h +++ b/tools/profiler/TableTicker.h @@ -67,6 +67,7 @@ class TableTicker: public Sampler { mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank"); mProfileJS = hasFeature(aFeatures, aFeatureCount, "js"); mProfileJava = hasFeature(aFeatures, aFeatureCount, "java"); + mProfileGPU = hasFeature(aFeatures, aFeatureCount, "gpu"); mProfilePower = hasFeature(aFeatures, aFeatureCount, "power"); // Users sometimes ask to filter by a list of threads but forget to request // profiling non main threads. Let's make it implificit if we have a filter @@ -200,6 +201,7 @@ class TableTicker: public Sampler { bool HasUnwinderThread() const { return mUnwinderThread; } bool ProfileJS() const { return mProfileJS; } bool ProfileJava() const { return mProfileJava; } + bool ProfileGPU() const { return mProfileGPU; } bool ProfilePower() const { return mProfilePower; } bool ProfileThreads() const { return mProfileThreads; } bool InPrivacyMode() const { return mPrivacyMode; } @@ -226,6 +228,7 @@ protected: bool mUseStackWalk; bool mJankOnly; bool mProfileJS; + bool mProfileGPU; bool mProfileThreads; bool mUnwinderThread; bool mProfileJava; diff --git a/tools/profiler/platform.cpp b/tools/profiler/platform.cpp index fe59dba7335..34754483f00 100644 --- a/tools/profiler/platform.cpp +++ b/tools/profiler/platform.cpp @@ -44,6 +44,7 @@ int sFrameNumber = 0; int sLastFrameNumber = 0; int sInitCount = 0; // Each init must have a matched shutdown. static bool sIsProfiling = false; // is raced on +static bool sIsGPUProfiling = false; // is raced on // env variables to control the profiler const char* PROFILER_MODE = "MOZ_PROFILER_MODE"; @@ -681,6 +682,8 @@ const char** mozilla_sampler_get_features() // Tell the JS engine to emmit pseudostack entries in the // pro/epilogue. "js", + // GPU Profiling (may not be supported by the GL) + "gpu", // Profile the registered secondary threads. "threads", // Do not include user-identifiable information @@ -782,6 +785,7 @@ void mozilla_sampler_start(int aProfileEntries, double aInterval, } sIsProfiling = true; + sIsGPUProfiling = t->ProfileGPU(); if (Sampler::CanNotifyObservers()) { nsCOMPtr os = mozilla::services::GetObserverService(); @@ -865,6 +869,19 @@ void mozilla_sampler_resume() { } } +bool mozilla_sampler_feature_active(const char* aName) +{ + if (!profiler_is_active()) { + return false; + } + + if (strcmp(aName, "gpu") == 0) { + return sIsGPUProfiling; + } + + return false; +} + bool mozilla_sampler_is_active() { return sIsProfiling; @@ -1041,7 +1058,10 @@ void mozilla_sampler_add_marker(const char *aMarker, ProfilerMarkerPayload *aPay if (!stack) { return; } - mozilla::TimeDuration delta = mozilla::TimeStamp::Now() - sStartTime; + + TimeStamp origin = (aPayload && !aPayload->GetStartTime().IsNull()) ? + aPayload->GetStartTime() : mozilla::TimeStamp::Now(); + mozilla::TimeDuration delta = origin - sStartTime; stack->addMarker(aMarker, payload.forget(), static_cast(delta.ToMilliseconds())); }