diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 3ede8ac097..d48a646df4 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -70,6 +70,8 @@ if (NOT FILAMENT_USE_EXTERNAL_GLES3) src/opengl/OpenGLProgram.cpp src/opengl/OpenGLProgram.h src/opengl/OpenGLPlatform.cpp + src/opengl/TimerQuery.cpp + src/opengl/TimerQuery.h include/private/backend/OpenGLPlatform.h ) if (EGL) diff --git a/filament/backend/include/backend/Handle.h b/filament/backend/include/backend/Handle.h index 279dc4340e..9616fdd382 100644 --- a/filament/backend/include/backend/Handle.h +++ b/filament/backend/include/backend/Handle.h @@ -36,6 +36,7 @@ struct HwTexture; struct HwUniformBuffer; struct HwSwapChain; struct HwStream; +struct HwTimerQuery; /* * A type handle to a h/w resource @@ -110,6 +111,7 @@ using SwapChainHandle = Handle; using TextureHandle = Handle; using UniformBufferHandle = Handle; using VertexBufferHandle = Handle; +using TimerQueryHandle = Handle; } // namespace backend } // namespace filament diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index d33f501a39..637929ff4d 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -219,6 +219,9 @@ DECL_DRIVER_API_R_N(backend::StreamHandle, createStreamFromTextureId, uint32_t, width, uint32_t, height) +DECL_DRIVER_API_R_0(backend::TimerQueryHandle, createTimerQuery) + + /* * Destroying driver objects * ------------------------- @@ -234,6 +237,7 @@ DECL_DRIVER_API_N(destroyTexture, backend::TextureHandle, th) DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth) DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch) DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh) +DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh) /* * Synchronous APIs @@ -256,6 +260,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported) DECL_DRIVER_API_SYNCHRONOUS_0(bool, canGenerateMipmaps) DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage, void*, image) DECL_DRIVER_API_SYNCHRONOUS_N(void, cancelExternalImage, void*, image) +DECL_DRIVER_API_SYNCHRONOUS_N(bool, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime) /* * Updating driver objects @@ -332,6 +337,13 @@ DECL_DRIVER_API_N(setRenderPrimitiveRange, uint32_t, maxIndex, uint32_t, count) +DECL_DRIVER_API_N(beginTimerQuery, + backend::TimerQueryHandle, query) + +DECL_DRIVER_API_N(endTimerQuery, + backend::TimerQueryHandle, query) + + /* * Swap chain */ diff --git a/filament/backend/src/DriverBase.h b/filament/backend/src/DriverBase.h index 2728e6b2e9..3594b71acc 100644 --- a/filament/backend/src/DriverBase.h +++ b/filament/backend/src/DriverBase.h @@ -141,6 +141,9 @@ struct HwStream : public HwBase { uint32_t height = 0; }; +struct HwTimerQuery : public HwBase { +}; + /* * Base class of all Driver implementations */ diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 40b8418983..8b94753d8e 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -245,9 +245,12 @@ void MetalDriver::createSwapChainHeadlessR(Handle sch, void MetalDriver::createStreamFromTextureIdR(Handle, intptr_t externalTextureId, uint32_t width, uint32_t height) { - } +void MetalDriver::createTimerQueryR(Handle tqh, int) { +} + + Handle MetalDriver::createVertexBufferS() noexcept { return alloc_handle(); } @@ -304,6 +307,10 @@ Handle MetalDriver::createStreamFromTextureIdS() noexcept { return {}; } +Handle MetalDriver::createTimerQueryS() noexcept { + return {}; +} + void MetalDriver::destroyVertexBuffer(Handle vbh) { if (vbh) { destruct_handle(mHandleMap, vbh); @@ -390,6 +397,9 @@ void MetalDriver::destroyStream(Handle sh) { // no-op } +void MetalDriver::destroyTimerQuery(Handle tqh) { +} + void MetalDriver::terminate() { // Wait for all frames to finish by submitting and waiting on a dummy command buffer. // This must be done before calling bufferPool->reset() to ensure no buffers are in flight. @@ -568,7 +578,10 @@ void MetalDriver::setExternalImagePlane(Handle th, void* image, size_ } void MetalDriver::setExternalStream(Handle th, Handle sh) { +} +bool MetalDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { + return false; } void MetalDriver::generateMipmaps(Handle th) { @@ -1041,6 +1054,12 @@ void MetalDriver::draw(backend::PipelineState ps, Handle rph) indexBufferOffset:primitive->offset]; } +void MetalDriver::beginTimerQuery(Handle tqh) { +} + +void MetalDriver::endTimerQuery(Handle tqh) { +} + void MetalDriver::enumerateSamplerGroups( const MetalProgram* program, const std::function& f) { diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index 20ef782c46..531b9447fd 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -91,6 +91,9 @@ void NoopDriver::destroySwapChain(Handle sch) { void NoopDriver::destroyStream(Handle sh) { } +void NoopDriver::destroyTimerQuery(Handle tqh) { +} + Handle NoopDriver::createStreamNative(void* nativeStream) { return {}; } @@ -165,6 +168,10 @@ void NoopDriver::setupExternalImage(void* image) { void NoopDriver::cancelExternalImage(void* image) { } +bool NoopDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { + return false; +} + void NoopDriver::setExternalImage(Handle th, void* image) { } @@ -255,4 +262,10 @@ void NoopDriver::blit(TargetBufferFlags buffers, void NoopDriver::draw(PipelineState pipelineState, Handle rph) { } +void NoopDriver::beginTimerQuery(Handle tqh) { +} + +void NoopDriver::endTimerQuery(Handle tqh) { +} + } // namespace filament diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index 4e3b35db06..4c8c2f2d62 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -56,7 +56,9 @@ OpenGLContext::OpenGLContext() noexcept { #endif if (strstr(renderer, "Adreno")) { + bugs.dont_use_timer_query = true; // verified } else if (strstr(renderer, "Mali")) { + bugs.dont_use_timer_query = true; // not verified bugs.vao_doesnt_store_element_array_buffer_binding = true; if (strstr(renderer, "Mali-T")) { bugs.disable_glFlush = true; @@ -213,6 +215,7 @@ void OpenGLContext::initExtensionsGLES(GLint major, GLint minor, ExtentionSet co ext.APPLE_color_buffer_packed_float = hasExtension(exts, "GL_APPLE_color_buffer_packed_float"); ext.texture_compression_s3tc = hasExtension(exts, "WEBGL_compressed_texture_s3tc"); ext.EXT_multisampled_render_to_texture = hasExtension(exts, "GL_EXT_multisampled_render_to_texture"); + ext.EXT_disjoint_timer_query = hasExtension(exts, "GL_EXT_disjoint_timer_query"); ext.KHR_debug = hasExtension(exts, "GL_KHR_debug"); ext.EXT_texture_compression_s3tc_srgb = hasExtension(exts, "GL_EXT_texture_compression_s3tc_srgb"); // ES 3.2 implies EXT_color_buffer_float diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index 6820520843..0e403a1b46 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -85,6 +85,9 @@ public: inline void depthMask(GLboolean flag) noexcept; inline void depthFunc(GLenum func) noexcept; inline void polygonOffset(GLfloat factor, GLfloat units) noexcept; + inline void beginQuery(GLenum target, GLuint query) noexcept; + inline void endQuery(GLenum target) noexcept; + inline GLuint getQuery(GLenum target) noexcept; inline void setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept; inline void viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept; @@ -120,6 +123,7 @@ public: bool KHR_debug = false; bool EXT_texture_sRGB = false; bool EXT_texture_compression_s3tc_srgb = false; + bool EXT_disjoint_timer_query = false; } ext; struct { @@ -145,6 +149,9 @@ public: // Some drivers declare GL_EXT_texture_filter_anisotropic but don't support // calling glSamplerParameter() with GL_TEXTURE_MAX_ANISOTROPY_EXT bool disable_texture_filter_anisotropic = false; + + // Some drivers don't implement timer queries correctly + bool dont_use_timer_query = false; } bugs; // state getters -- as needed. @@ -239,6 +246,10 @@ private: vec4gli scissor { 0 }; vec4gli viewport { 0 }; } window; + + struct { + GLuint timer = -1u; + } queries; } state; RenderPrimitive mDefaultVAO; @@ -524,6 +535,40 @@ void OpenGLContext::polygonOffset(GLfloat factor, GLfloat units) noexcept { }); } +void OpenGLContext::beginQuery(GLenum target, GLuint query) noexcept { + switch (target) { + case GL_TIME_ELAPSED: + if (state.queries.timer != -1u) { + // this is an error + break; + } + state.queries.timer = query; + break; + default: + return; + } + glBeginQuery(target, query); +} + +void OpenGLContext::endQuery(GLenum target) noexcept { + switch (target) { + case GL_TIME_ELAPSED: + state.queries.timer = -1u; + break; + default: + return; + } + glEndQuery(target); +} + +GLuint OpenGLContext::getQuery(GLenum target) noexcept { + switch (target) { + case GL_TIME_ELAPSED: + return state.queries.timer; + default: + return 0; + } +} } // namesapce filament #endif //TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index 09bf7a62c3..e2c4eb68f9 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -23,6 +23,8 @@ #include "OpenGLBlitter.h" #include "OpenGLDriverFactory.h" #include "OpenGLProgram.h" +#include "TimerQuery.h" +#include "OpenGLContext.h" #include #include @@ -74,6 +76,8 @@ Driver* OpenGLDriverFactory::create( using namespace backend; using namespace GLUtils; +// ------------------------------------------------------------------------------------------------ + UTILS_NOINLINE Driver* OpenGLDriver::create( OpenGLPlatform* const platform, void* const sharedGLContext) noexcept { @@ -148,6 +152,25 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform) noexcept mOpenGLBlitter->init(); mContext.resetProgram(); } + + if (mContext.ext.EXT_disjoint_timer_query || GL41_HEADERS) { + // timer queries are available + if (mContext.bugs.dont_use_timer_query && mPlatform.canCreateFence()) { + // however, they don't work well, revert to using fences if we can. + mTimerQueryImpl = new TimerQueryFence(mPlatform); + } else { + mTimerQueryImpl = new TimerQueryNative(mContext); + } + mFrameTimeSupported = true; + } else if (mPlatform.canCreateFence()) { + // no timer queries, but we can use fences + mTimerQueryImpl = new TimerQueryFence(mPlatform); + mFrameTimeSupported = true; + } else { + // no queries, no fences -- that's a problem + mTimerQueryImpl = new TimerQueryFallback(); + mFrameTimeSupported = false; + } } OpenGLDriver::~OpenGLDriver() noexcept { @@ -176,6 +199,9 @@ void OpenGLDriver::terminate() { if (mOpenGLBlitter) { mOpenGLBlitter->terminate(); } + + delete mTimerQueryImpl; + mPlatform.terminate(); } @@ -418,6 +444,10 @@ Handle OpenGLDriver::createStreamFromTextureIdS() noexcept { return Handle( allocateHandle(sizeof(GLStream)) ); } +Handle OpenGLDriver::createTimerQueryS() noexcept { + return Handle( allocateHandle(sizeof(GLTimerQuery)) ); +} + void OpenGLDriver::createVertexBufferR( Handle vbh, uint8_t bufferCount, @@ -1002,6 +1032,14 @@ void OpenGLDriver::createStreamFromTextureIdR(Handle sh, } } +void OpenGLDriver::createTimerQueryR(Handle tqh, int) { + DEBUG_MARKER() + + GLTimerQuery* tq = construct(tqh); + glGenQueries(1u, &tq->gl.query); + CHECK_GL_ERROR(utils::slog.e) +} + // ------------------------------------------------------------------------------------------------ // Destroying driver objects // ------------------------------------------------------------------------------------------------ @@ -1159,6 +1197,16 @@ void OpenGLDriver::destroyStream(Handle sh) { } } +void OpenGLDriver::destroyTimerQuery(Handle tqh) { + DEBUG_MARKER() + + if (tqh) { + GLTimerQuery* tq = handle_cast(tqh); + glDeleteQueries(1u, &tq->gl.query); + destruct(tqh, tq); + } +} + // ------------------------------------------------------------------------------------------------ // Synchronous APIs // These are called on the application's thread @@ -1350,8 +1398,7 @@ bool OpenGLDriver::isRenderTargetFormatSupported(TextureFormat format) { } bool OpenGLDriver::isFrameTimeSupported() { - // TODO: Measuring the frame time is currently only done using fences - return mPlatform.canCreateFence(); + return mFrameTimeSupported; } // ------------------------------------------------------------------------------------------------ @@ -1857,6 +1904,39 @@ void OpenGLDriver::replaceStream(GLTexture* texture, GLStream* newStream) noexce texture->hwStream = newStream; } +void OpenGLDriver::beginTimerQuery(Handle tqh) { + GLTimerQuery* tq = handle_cast(tqh); + // reset the state of the result availability + tq->elapsed.store(0, std::memory_order_relaxed); + mTimerQueryImpl->beginTimeElapsedQuery(tq); +} + +void OpenGLDriver::endTimerQuery(Handle tqh) { + GLTimerQuery* tq = handle_cast(tqh); + mTimerQueryImpl->endTimeElapsedQuery(tq); + + whenFrameBegins([this, tq]() -> bool { + if (!mTimerQueryImpl->queryResultAvailable(tq)) { + // we need to try this one again later + return false; + } + tq->elapsed.store(mTimerQueryImpl->queryResult(tq), std::memory_order_relaxed); + return true; + }); +} + +bool OpenGLDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { + GLTimerQuery* tq = handle_cast(tqh); + uint64_t d = tq->elapsed.load(std::memory_order_relaxed); + if (!d) { + return false; + } + if (elapsedTime) { + *elapsedTime = d; + } + return true; +} + void OpenGLDriver::beginRenderPass(Handle rth, const RenderPassParams& params) { DEBUG_MARKER() @@ -2504,12 +2584,16 @@ void OpenGLDriver::readPixels(Handle src, CHECK_GL_ERROR(utils::slog.e) } -void OpenGLDriver::whenGpuCommandsComplete(std::function fn) noexcept { +void OpenGLDriver::whenGpuCommandsComplete(std::function fn) noexcept { GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); mGpuCommandCompleteOps.emplace_back(sync, std::move(fn)); CHECK_GL_ERROR(utils::slog.e) } +void OpenGLDriver::whenFrameBegins(std::function fn) noexcept { + mFrameBeginsOps.push_back(std::move(fn)); +} + void OpenGLDriver::executeGpuCommandsCompleteOps() noexcept { auto& v = mGpuCommandCompleteOps; auto it = v.begin(); @@ -2530,6 +2614,18 @@ void OpenGLDriver::executeGpuCommandsCompleteOps() noexcept { } } +void OpenGLDriver::executeFrameBeginsOps() noexcept { + auto& v = mFrameBeginsOps; + auto it = v.begin(); + while (it != v.end()) { + if ((*it)()) { + it = v.erase(it); + } else { + ++it; + } + } +} + // ------------------------------------------------------------------------------------------------ // Rendering ops // ------------------------------------------------------------------------------------------------ @@ -2539,6 +2635,7 @@ void OpenGLDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId, auto& gl = mContext; insertEventMarker("beginFrame"); executeGpuCommandsCompleteOps(); + executeFrameBeginsOps(); if (UTILS_UNLIKELY(!mExternalStreams.empty())) { OpenGLPlatform& platform = mPlatform; for (GLTexture const* t : mExternalStreams) { @@ -2577,6 +2674,10 @@ void OpenGLDriver::finish(int) { DEBUG_MARKER() glFinish(); executeGpuCommandsCompleteOps(); + executeFrameBeginsOps(); + // since we executed a glFinish(), all pending tasks should be done + assert(mGpuCommandCompleteOps.empty()); + assert(mFrameBeginsOps.empty()); } UTILS_NOINLINE diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index d6410b1d78..9659321f2c 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -43,6 +43,7 @@ class TargetBufferInfo; class OpenGLProgram; class OpenGLBlitter; +class TimerQueryInterface; class OpenGLDriver final : public backend::DriverBase { inline explicit OpenGLDriver(backend::OpenGLPlatform* platform) noexcept; @@ -122,6 +123,18 @@ public: ~DebugMarker() noexcept; }; + struct GLTimerQuery : public backend::HwTimerQuery { + struct { + GLuint query = 0; + struct { + uint64_t elapsed = 0; + std::atomic_bool available; + } emulation; + } gl; + // 0 means not available, otherwise query result in ns. + std::atomic elapsed{}; + }; + struct GLStream : public backend::HwStream { static constexpr size_t ROUND_ROBIN_TEXTURE_COUNT = 3; // 3 maximum using HwStream::HwStream; @@ -372,9 +385,19 @@ private: void setExternalTexture(GLTexture* t, void* image); + // tasks executed on the main thread after the fence signaled void whenGpuCommandsComplete(std::function fn) noexcept; void executeGpuCommandsCompleteOps() noexcept; - std::vector>> mGpuCommandCompleteOps; + std::vector>> mGpuCommandCompleteOps; + + // tasks regularly executed on the main thread at frame begin time until they return true + void whenFrameBegins(std::function fn) noexcept; + void executeFrameBeginsOps() noexcept; + std::vector> mFrameBeginsOps; + + // timer query implementation + TimerQueryInterface* mTimerQueryImpl = nullptr; + bool mFrameTimeSupported = false; }; // ------------------------------------------------------------------------------------------------ diff --git a/filament/backend/src/opengl/TimerQuery.cpp b/filament/backend/src/opengl/TimerQuery.cpp new file mode 100644 index 0000000000..a511066e5d --- /dev/null +++ b/filament/backend/src/opengl/TimerQuery.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TimerQuery.h" + +#include "private/backend/OpenGLPlatform.h" + +#include +#include +#include + +namespace filament { + +using namespace backend; +using namespace GLUtils; + +// ------------------------------------------------------------------------------------------------ + +TimerQueryInterface::~TimerQueryInterface() = default; + +// ------------------------------------------------------------------------------------------------ + +TimerQueryNative::TimerQueryNative(OpenGLContext& context) + : gl(context) { +} + +TimerQueryNative::~TimerQueryNative() = default; + +void TimerQueryNative::beginTimeElapsedQuery(GLTimerQuery* query) { + gl.beginQuery(GL_TIME_ELAPSED, query->gl.query); + CHECK_GL_ERROR(utils::slog.e) +} + +void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* query) { + gl.endQuery(GL_TIME_ELAPSED); + CHECK_GL_ERROR(utils::slog.e) +} + +bool TimerQueryNative::queryResultAvailable(GLTimerQuery* query) { + GLuint available = 0; + glGetQueryObjectuiv(query->gl.query, GL_QUERY_RESULT_AVAILABLE, &available); + CHECK_GL_ERROR(utils::slog.e) + return available != 0; +} + +uint64_t TimerQueryNative::queryResult(GLTimerQuery* query) { + GLuint64 elapsedTime = 0; + // IOS doesn't have glGetQueryObjectui64v, we'll never end-up here on ios anyways +#ifndef IOS + glGetQueryObjectui64v(query->gl.query, GL_QUERY_RESULT, &elapsedTime); +#endif + CHECK_GL_ERROR(utils::slog.e) + return elapsedTime; +} + +// ------------------------------------------------------------------------------------------------ + +TimerQueryFence::TimerQueryFence(backend::OpenGLPlatform& platform) + : mPlatform(platform) { + mQueue.reserve(2); + mThread = std::thread([this]() { + auto& queue = mQueue; + bool exitRequested; + do { + std::unique_lock lock(mLock); + mCondition.wait(lock, [this, &queue]() -> bool { + return mExitRequested || !queue.empty(); + }); + exitRequested = mExitRequested; + if (!queue.empty()) { + Job job(queue.front()); + queue.erase(queue.begin()); + lock.unlock(); + job(); + } + } while (!exitRequested); + }); +} + +TimerQueryFence::~TimerQueryFence() { + if (mThread.joinable()) { + std::unique_lock lock(mLock); + mExitRequested = true; + lock.unlock(); + mCondition.notify_one(); + mThread.join(); + } +} + +void TimerQueryFence::enqueue(TimerQueryFence::Job&& job) { + std::unique_lock lock(mLock); + mQueue.push_back(std::forward(job)); + lock.unlock(); + mCondition.notify_one(); +} + +void TimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* query) { + Platform::Fence* fence = mPlatform.createFence(); + query->gl.emulation.available.store(false); + push([this, fence, query]() { + mPlatform.waitFence(fence, FENCE_WAIT_FOR_EVER); + query->gl.emulation.elapsed = clock::now().time_since_epoch().count(); + mPlatform.destroyFence(fence); + }); +} + +void TimerQueryFence::endTimeElapsedQuery(GLTimerQuery* query) { + Platform::Fence* fence = mPlatform.createFence(); + push([this, fence, query]() { + mPlatform.waitFence(fence, FENCE_WAIT_FOR_EVER); + query->gl.emulation.elapsed = clock::now().time_since_epoch().count() - query->gl.emulation.elapsed; + query->gl.emulation.available.store(true); + mPlatform.destroyFence(fence); + }); +} + +bool TimerQueryFence::queryResultAvailable(GLTimerQuery* query) { + return query->gl.emulation.available.load(); +} + +uint64_t TimerQueryFence::queryResult(GLTimerQuery* query) { + return query->gl.emulation.elapsed; +} + +// ------------------------------------------------------------------------------------------------ + +TimerQueryFallback::TimerQueryFallback() = default; + +TimerQueryFallback::~TimerQueryFallback() = default; + +void TimerQueryFallback::beginTimeElapsedQuery(TimerQueryInterface::GLTimerQuery* query) { + // this implementation clearly doesn't work at all, but we have no h/w support + query->gl.emulation.available.store(false, std::memory_order_relaxed); + query->gl.emulation.elapsed = clock::now().time_since_epoch().count(); +} + +void TimerQueryFallback::endTimeElapsedQuery(TimerQueryInterface::GLTimerQuery* query) { + // this implementation clearly doesn't work at all, but we have no h/w support + query->gl.emulation.elapsed = clock::now().time_since_epoch().count() - query->gl.emulation.elapsed; + query->gl.emulation.available.store(true, std::memory_order_relaxed); +} + +bool TimerQueryFallback::queryResultAvailable(TimerQueryInterface::GLTimerQuery* query) { + return query->gl.emulation.available.load(std::memory_order_relaxed); +} + +uint64_t TimerQueryFallback::queryResult(TimerQueryInterface::GLTimerQuery* query) { + return query->gl.emulation.elapsed; +} + +} // namespace filament diff --git a/filament/backend/src/opengl/TimerQuery.h b/filament/backend/src/opengl/TimerQuery.h new file mode 100644 index 0000000000..45a9a87d43 --- /dev/null +++ b/filament/backend/src/opengl/TimerQuery.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_DRIVER_TIMERQUERY_H +#define TNT_FILAMENT_DRIVER_TIMERQUERY_H + +#include "OpenGLDriver.h" + +#include + +#include +#include + +namespace filament { + +/* + * we need two implementation of timer queries (only elapsed time), because + * on some gpu disjoint_timer_queyr/arb_timer_query is much less accurate than + * using fences. + * + * These classes implement the various strategies... + */ + +class TimerQueryInterface { +protected: + using GLTimerQuery = OpenGLDriver::GLTimerQuery; + using clock = std::chrono::steady_clock; + +public: + virtual ~TimerQueryInterface(); + virtual void beginTimeElapsedQuery(GLTimerQuery* query) = 0; + virtual void endTimeElapsedQuery(GLTimerQuery* query) = 0; + virtual bool queryResultAvailable(GLTimerQuery* query) = 0; + virtual uint64_t queryResult(GLTimerQuery* query) = 0; +}; + +class TimerQueryNative : public TimerQueryInterface { +public: + explicit TimerQueryNative(OpenGLContext& context); + ~TimerQueryNative() override; +private: + void beginTimeElapsedQuery(GLTimerQuery* query) override; + void endTimeElapsedQuery(GLTimerQuery* query) override; + bool queryResultAvailable(GLTimerQuery* query) override; + uint64_t queryResult(GLTimerQuery* query) override; + OpenGLContext& gl; +}; + +class TimerQueryFence : public TimerQueryInterface { +public: + explicit TimerQueryFence(backend::OpenGLPlatform& platform); + ~TimerQueryFence() override; +private: + using Job = std::function; + void beginTimeElapsedQuery(GLTimerQuery* query) override; + void endTimeElapsedQuery(GLTimerQuery* query) override; + bool queryResultAvailable(GLTimerQuery* query) override; + uint64_t queryResult(GLTimerQuery* query) override; + void enqueue(Job&& job); + + template + void push(CALLABLE&& func, ARGS&& ... args) { + enqueue(Job(std::bind(std::forward(func), std::forward(args)...))); + } + + backend::OpenGLPlatform& mPlatform; + std::thread mThread; + mutable utils::Mutex mLock; + mutable utils::Condition mCondition; + std::vector mQueue; + bool mExitRequested = false; +}; + +class TimerQueryFallback : public TimerQueryInterface { +public: + explicit TimerQueryFallback(); + ~TimerQueryFallback() override; +private: + void beginTimeElapsedQuery(GLTimerQuery* query) override; + void endTimeElapsedQuery(GLTimerQuery* query) override; + bool queryResultAvailable(GLTimerQuery* query) override; + uint64_t queryResult(GLTimerQuery* query) override; +}; + +} // namespace filament + +#endif //TNT_FILAMENT_DRIVER_TIMERQUERY_H diff --git a/filament/backend/src/opengl/gl_headers.cpp b/filament/backend/src/opengl/gl_headers.cpp index 8c6148afd5..0258f556a4 100644 --- a/filament/backend/src/opengl/gl_headers.cpp +++ b/filament/backend/src/opengl/gl_headers.cpp @@ -42,6 +42,9 @@ PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glFramebufferTexture2DMultisampleEXT PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR; PFNGLGETDEBUGMESSAGELOGKHRPROC glGetDebugMessageLogKHR; #endif +#ifdef GL_EXT_disjoint_timer_query +PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v; +#endif static std::once_flag sGlExtInitialized; @@ -91,6 +94,11 @@ void importGLESExtensionsEntryPoints() { glGetDebugMessageLogKHR = (PFNGLGETDEBUGMESSAGELOGKHRPROC)eglGetProcAddress( "glGetDebugMessageLogKHR"); +#endif +#ifdef GL_EXT_disjoint_timer_query + glGetQueryObjectui64v = + (PFNGLGETQUERYOBJECTUI64VEXTPROC)eglGetProcAddress( + "glGetQueryObjectui64vEXT"); #endif }); } diff --git a/filament/backend/src/opengl/gl_headers.h b/filament/backend/src/opengl/gl_headers.h index 06e2a500b9..e5a0229539 100644 --- a/filament/backend/src/opengl/gl_headers.h +++ b/filament/backend/src/opengl/gl_headers.h @@ -47,6 +47,10 @@ #ifdef GL_KHR_debug extern PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR; extern PFNGLGETDEBUGMESSAGELOGKHRPROC glGetDebugMessageLogKHR; +#endif +#ifdef GL_EXT_disjoint_timer_query + extern PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v; + #define GL_TIME_ELAPSED 0x88BF #endif } @@ -82,6 +86,7 @@ * requires the following 3.1 define in order to compile. */ #define GL_TEXTURE_2D_MULTISAMPLE 0x9100 + #define GL_TIME_ELAPSED 0x88BF #else #include diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index ded60c5d1f..8862a5683b 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -457,6 +457,9 @@ void VulkanDriver::createStreamFromTextureIdR(Handle sh, intptr_t exte uint32_t width, uint32_t height) { } +void VulkanDriver::createTimerQueryR(Handle tqh, int) { +} + Handle VulkanDriver::createVertexBufferS() noexcept { return alloc_handle(); } @@ -513,6 +516,10 @@ Handle VulkanDriver::createStreamFromTextureIdS() noexcept { return {}; } +Handle VulkanDriver::createTimerQueryS() noexcept { + return {}; +} + void VulkanDriver::destroySamplerGroup(Handle sbh) { if (sbh) { // Unlike most of the other "Hw" handles, the sampler buffer is an abstract concept and does @@ -559,6 +566,9 @@ void VulkanDriver::destroySwapChain(Handle sch) { void VulkanDriver::destroyStream(Handle sh) { } +void VulkanDriver::destroyTimerQuery(Handle tqh) { +} + Handle VulkanDriver::createStreamNative(void* nativeStream) { return {}; } @@ -684,6 +694,10 @@ void VulkanDriver::setupExternalImage(void* image) { void VulkanDriver::cancelExternalImage(void* image) { } +bool VulkanDriver::getTimerQueryValue(Handle tqh, uint64_t* elapsedTime) { + return false; +} + void VulkanDriver::setExternalImage(Handle th, void* image) { } @@ -1182,6 +1196,13 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle r vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId); } + +void VulkanDriver::beginTimerQuery(Handle tqh) { +} + +void VulkanDriver::endTimerQuery(Handle tqh) { +} + #ifndef NDEBUG void VulkanDriver::debugCommand(const char* methodName) { static const std::set OUTSIDE_COMMANDS = { diff --git a/filament/src/FrameInfo.cpp b/filament/src/FrameInfo.cpp index d7cc600fbf..89084725b2 100644 --- a/filament/src/FrameInfo.cpp +++ b/filament/src/FrameInfo.cpp @@ -16,11 +16,7 @@ #include "FrameInfo.h" -#include "details/Fence.h" - -#include #include -#include #include @@ -28,145 +24,39 @@ namespace filament { using namespace utils; +using namespace details; -// ------------------------------------------------------------------------------------------------ - -FrameInfoManager::FrameInfoManager(FEngine& engine) - : mEngine(engine), - mPoolArena("FrameInfo", sizeof(FrameInfo) * POOL_COUNT) { - mFrameInfoHistory.resize(HISTORY_COUNT); +FrameInfoManager::FrameInfoManager(FEngine& engine) : mEngine(engine) { + backend::DriverApi& driver = mEngine.getDriverApi(); + for (auto& query : mQueries) { + query = driver.createTimerQuery(); + } } FrameInfoManager::~FrameInfoManager() noexcept = default; -void FrameInfo::beginFrame(FrameInfoManager* mgr) { - Fence* fence = mgr->getEngine().createFence(FFence::Type::HARD); - mgr->push([this, fence]() { - Fence::waitAndDestroy(fence, Fence::Mode::DONT_FLUSH); - laps[START] = clock::now(); - }); +void FrameInfoManager::terminate() { + backend::DriverApi& driver = mEngine.getDriverApi(); + for (auto& query : mQueries) { + driver.destroyTimerQuery(query); + } } -void FrameInfo::lap(FrameInfoManager* mgr, lap_id id) { - Fence* fence = mgr->getEngine().createFence(FFence::Type::HARD); - mgr->push([this, fence, id]() { - Fence::waitAndDestroy(fence, Fence::Mode::DONT_FLUSH); - laps[id] = clock::now(); - }); -} - -void FrameInfo::endFrame(FrameInfoManager* mgr) { - Fence* fence = mgr->getEngine().createFence(FFence::Type::HARD); - mgr->push([this, mgr, fence]() { - char buf[256]; - snprintf(buf, 256, "GPU time [id=%u]", frame); - SYSTRACE_NAME(buf); - - Fence::waitAndDestroy(fence, Fence::Mode::DONT_FLUSH); - laps[FINISH] = clock::now(); - mgr->finish(this); - }); -} - -// ------------------------------------------------------------------------------------------------ - void FrameInfoManager::beginFrame(uint32_t frameId) { - SYSTRACE_CONTEXT(); - SYSTRACE_ASYNC_BEGIN("frame latency", frameId); - - FrameInfo* info = obtain(); - mCurrentFrameInfo = info; - if (info) { - info->frame = frameId; - info->beginFrame(this); + backend::DriverApi& driver = mEngine.getDriverApi(); + driver.beginTimerQuery(mQueries[mIndex]); + uint64_t elapsed = 0; + if (driver.getTimerQueryValue(mQueries[mLast], &elapsed)) { + mLast = (mLast + 1) % POOL_COUNT; + mFrameTime = std::chrono::duration(elapsed); } } void FrameInfoManager::endFrame() { - FrameInfo* const info = mCurrentFrameInfo; - if (info) { - mCurrentFrameInfo = nullptr; - info->endFrame(this); - } + backend::DriverApi& driver = mEngine.getDriverApi(); + driver.endTimerQuery(mQueries[mIndex]); + mIndex = (mIndex + 1) % POOL_COUNT; } -void FrameInfoManager::cancelFrame() { - FrameInfo* info = mCurrentFrameInfo; - if (info) { - mCurrentFrameInfo = nullptr; - push([this, info]() { - mPoolArena.free(info); - }); - } -} - -UTILS_ALWAYS_INLINE -inline FrameInfo* FrameInfoManager::obtain() noexcept { - return mPoolArena.alloc(1); -} - -void FrameInfoManager::finish(FrameInfo* info) noexcept { - SYSTRACE_CONTEXT(); - SYSTRACE_ASYNC_END("frame latency", info->frame); - - // store the new frame info into the history array - std::unique_lock lock(mLock); - auto& history = mFrameInfoHistory; - if (history.size() >= HISTORY_COUNT) { - // if the history has grown enough, remove the oldest element - history.erase(history.begin()); - } - // add a copy of the new element to the history - history.push_back(*info); - lock.unlock(); - - // return the item to the pool without the lock held - mPoolArena.free(info); -} - -// ------------------------------------------------------------------------------------------------ - -FrameInfoManager::SyncThread::~SyncThread() { - if (mThread.joinable()) { - requestExitAndWait(); - } -} - -void FrameInfoManager::SyncThread::run() { - mThread = std::thread(&SyncThread::loop, this); -} - -void FrameInfoManager::SyncThread::requestExitAndWait() { - std::unique_lock lock(mLock); - mExitRequested = true; - lock.unlock(); - mCondition.notify_one(); - mThread.join(); -} - -void FrameInfoManager::SyncThread::enqueue(SyncThread::Job&& job) { - std::unique_lock lock(mLock); - mQueue.push_back(std::forward(job)); - lock.unlock(); - mCondition.notify_one(); -} - -void FrameInfoManager::SyncThread::loop() { - JobSystem::setThreadPriority(JobSystem::Priority::URGENT_DISPLAY); - JobSystem::setThreadName("SyncThread"); - auto& queue = mQueue; - bool exitRequested; - do { - std::unique_lock lock(mLock); - mCondition.wait(lock, [this, &queue]() -> bool { return mExitRequested || !queue.empty(); }); - exitRequested = mExitRequested; - if (!queue.empty()) { - Job job(queue.front()); - queue.pop_front(); - lock.unlock(); - job(); - } - } while (!exitRequested); -} } // namespace filament diff --git a/filament/src/FrameInfo.h b/filament/src/FrameInfo.h index f002a9ce70..d8b3b2f5dd 100644 --- a/filament/src/FrameInfo.h +++ b/filament/src/FrameInfo.h @@ -19,195 +19,40 @@ #include "details/Engine.h" -#include +#include "backend/Handle.h" -#include - -#include #include -#include -#include -#include -#include #include -#include - -// set EXTRA_TIMING_INFO to enable and print extra timing info about the render loop -#define EXTRA_TIMING_INFO false +#include namespace filament { - -using namespace details; - -class FrameInfoManager; - -class FrameInfo { -public: - friend class FrameInfoManager; - using clock = std::chrono::steady_clock; - using time_point = clock::time_point; - using duration = std::chrono::duration; - - enum lap_id { - START = 0, // don't use for FrameInfo::lap() - FINISH = 1, // don't use for FrameInfo::lap() - LAP_0, - LAP_1, - LAP_2, - LAP_3, - LAP_4, - LAP_5, - }; - - void beginFrame(FrameInfoManager* mgr); - void lap(FrameInfoManager* mgr, lap_id id); - void endFrame(FrameInfoManager* mgr); - - static constexpr size_t MAX_LAPS_IDS = 8; - - uint32_t frame = 0; - time_point laps[MAX_LAPS_IDS] = { time_point::max() }; -}; +namespace details { +class FEngine; +} // namespace details class FrameInfoManager { - friend class FrameInfo; - static constexpr size_t HISTORY_COUNT = 5; static constexpr size_t POOL_COUNT = 8; - // set this to true to enable extra timing info - static constexpr bool mLapRecordsEnabled = EXTRA_TIMING_INFO; - public: - using clock = FrameInfo::clock; - using time_point = FrameInfo::time_point; - using duration = FrameInfo::duration; + using duration = std::chrono::duration; - explicit FrameInfoManager(FEngine& engine); + explicit FrameInfoManager(details::FEngine& engine); ~FrameInfoManager() noexcept; - - FEngine& getEngine() { return mEngine; } - - void run() { - mSyncThread.run(); - } - - void terminate() { - mSyncThread.requestExitAndWait(); - } - - // call this immediately after "make current" - void beginFrame(uint32_t frameId); - - // call this between beginFrame and endFrame to record a time - void lap(FrameInfo::lap_id id) { - if (mLapRecordsEnabled) { - FrameInfo* const info = mCurrentFrameInfo; - assert(info); - info->lap(this, id); - } - } - - // call this immediately before "swap buffers" - void endFrame(); - - void cancelFrame(); - - constexpr bool isLapRecordsEnabled() const noexcept { - return mLapRecordsEnabled; - } + void terminate(); + void beginFrame(uint32_t frameId); // call this immediately after "make current" + void endFrame(); // call this immediately before "swap buffers" duration getLastFrameTime() const noexcept { - std::unique_lock lock(mLock); - FrameInfo const& info = mFrameInfoHistory.front(); - return info.laps[FrameInfo::FINISH] - info.laps[FrameInfo::START]; - } - - std::vector getHistory() const noexcept { - std::unique_lock lock(mLock); - return mFrameInfoHistory; - } - - // no user serviceable part below... - - template - void push(CALLABLE&& func, ARGS&&... args) { - mSyncThread.push(std::forward(func), std::forward(args)...); - } - - static constexpr size_t getHistorySize() noexcept { - return HISTORY_COUNT; + return mFrameTime; } private: - - class SyncThread { - public: - using Job = std::function; - - SyncThread() = default; - ~SyncThread(); - - void run(); - void requestExitAndWait(); - - template - void push(CALLABLE&& func, ARGS&&... args) { - enqueue(Job(std::bind(std::forward(func), std::forward(args)...))); - } - - private: - void enqueue(SyncThread::Job&& job); - void loop(); - std::thread mThread; - mutable std::mutex mLock; - mutable std::condition_variable mCondition; - std::deque mQueue; - bool mExitRequested = false; - }; - - FrameInfo* obtain() noexcept; - void finish(FrameInfo* info) noexcept; - - using PoolArena = utils::Arena, utils::LockingPolicy::SpinLock>; - FEngine& mEngine; - PoolArena mPoolArena; - SyncThread mSyncThread; - FrameInfo* mCurrentFrameInfo = nullptr; - - mutable std::mutex mLock; - std::vector mFrameInfoHistory; -}; - - -template -class Series { -public: - Series() { - mIn.resize(MEDIAN); - mOut.resize(HISTORY); - } - - void push(T value, float b = 1.0f - math::fast::exp(-0.125f)) noexcept { - mIn.push_back(value); - mIn.pop_front(); - std::array median; - std::copy_n(mIn.begin(), median.size(), median.begin()); - std::sort(median.begin(), median.end()); - mLowPass += b * (median[median.size() / 2] - mLowPass); - mOut.push_back(mLowPass); - mOut.pop_front(); - } - - T operator[](size_t i) const noexcept { return mOut[i]; } - typename std::deque::iterator begin() const { return mOut.begin(); } - typename std::deque::iterator end() const { return mOut.end(); } - T const& oldest() const noexcept { return mOut.front(); } - T const& latest() const noexcept { return mOut.back(); } - - std::deque mIn; - std::deque mOut; - T mLowPass = {}; + details::FEngine& mEngine; + backend::Handle mQueries[POOL_COUNT]; + duration mFrameTime{}; + uint32_t mIndex = 0; + uint32_t mLast = 0; }; diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index b9ff91a779..af0fd97929 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -97,10 +97,6 @@ void FRenderer::init() noexcept { // this will clip all HDR data, but we don't have a choice mHdrQualityHigh = TextureFormat::RGB8; } - - if (UTILS_HAS_THREADING) { - mFrameInfoManager.run(); - } } FRenderer::~FRenderer() noexcept { @@ -716,19 +712,18 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, backend::FrameFinishedCallback int64_t monotonic_clock_ns (std::chrono::steady_clock::now().time_since_epoch().count()); driver.beginFrame(monotonic_clock_ns, mFrameId, callback, user); + if (!mFrameSkipper.beginFrame()) { + driver.endFrame(mFrameId); + engine.flush(); + return false; + } + // This need to occur after the backend beginFrame() because some backends need to start // a command buffer before creating a fence. if (UTILS_HAS_THREADING) { mFrameInfoManager.beginFrame(mFrameId); } - if (!mFrameSkipper.beginFrame()) { - mFrameInfoManager.cancelFrame(); - driver.endFrame(mFrameId); - engine.flush(); - return false; - } - // latch the frame time std::chrono::duration time{ getUserTime() }; float h = float(time.count()); @@ -747,16 +742,15 @@ void FRenderer::endFrame() { FEngine& engine = getEngine(); FEngine::DriverApi& driver = engine.getDriverApi(); - FrameInfoManager& frameInfoManager = mFrameInfoManager; - if (UTILS_HAS_THREADING) { // on debug builds this helps catching cases where we're writing to // the buffer form another thread, which is currently not allowed. driver.debugThreading(); - frameInfoManager.endFrame(); + mFrameInfoManager.endFrame(); } + mFrameSkipper.endFrame(); if (mSwapChain) { @@ -779,19 +773,6 @@ void FRenderer::endFrame() { // make sure we're done with the gcs js.waitAndRelease(job); - -#if EXTRA_TIMING_INFO - if (UTILS_UNLIKELY(frameInfoManager.isLapRecordsEnabled())) { - auto history = frameInfoManager.getHistory(); - FrameInfo const& info = history.back(); - FrameInfo::duration rendering = info.laps[FrameInfo::LAP_0] - info.laps[FrameInfo::START]; - FrameInfo::duration postprocess = info.laps[FrameInfo::FINISH] - info.laps[FrameInfo::LAP_0]; - mRendering.push(rendering.count()); - mPostProcess.push(postprocess.count()); - slog.d << mRendering.latest() << ", " - << mPostProcess.latest() << io::endl; - } -#endif } void FRenderer::readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, diff --git a/filament/src/details/Renderer.h b/filament/src/details/Renderer.h index 5454b3cbc0..5ddc2215f0 100644 --- a/filament/src/details/Renderer.h +++ b/filament/src/details/Renderer.h @@ -156,11 +156,6 @@ private: // per-frame arena for this Renderer LinearAllocatorArena& mPerRenderPassArena; - -#if EXTRA_TIMING_INFO - Series mRendering; - Series mPostProcess; -#endif }; FILAMENT_UPCAST(Renderer)