add support for timer queries in the backends

This adds a simple query API to the backend.
There are 2 methods to create/destroy queries, which are
essentially futures.  And 3 methods to mesure elapsed time:
beginTimerQuery/endTimerQuery and getTimerQueryValue.
The begin/end pair is not nestable.

On the GL backend side, there are 2 implementations of this, one uses
arb_timer_query or disjoint_timer_query, the other uses fences.
We need both implementations because on some GPUs, including
qualcomm's elapsed-time timer query is useless, as it measures
cpu time. 

Metal/Vulkan implementations will be part of subsequent PRs. Vulkan
will be able to implement this with vkWriteTimestamp which is
reported to be accurate.

An immediate benefit is that we can now get frame times on MacOS, which
should allow it to use dynamic-resolution.
This commit is contained in:
Pixelflinger
2020-03-27 18:16:15 -07:00
committed by Mathias Agopian
parent a389de3deb
commit e8a7d9f0c2
19 changed files with 570 additions and 338 deletions

View File

@@ -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)

View File

@@ -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<HwSwapChain>;
using TextureHandle = Handle<HwTexture>;
using UniformBufferHandle = Handle<HwUniformBuffer>;
using VertexBufferHandle = Handle<HwVertexBuffer>;
using TimerQueryHandle = Handle<HwTimerQuery>;
} // namespace backend
} // namespace filament

View File

@@ -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
*/

View File

@@ -141,6 +141,9 @@ struct HwStream : public HwBase {
uint32_t height = 0;
};
struct HwTimerQuery : public HwBase {
};
/*
* Base class of all Driver implementations
*/

View File

@@ -245,9 +245,12 @@ void MetalDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch,
void MetalDriver::createStreamFromTextureIdR(Handle<HwStream>, intptr_t externalTextureId,
uint32_t width, uint32_t height) {
}
void MetalDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
}
Handle<HwVertexBuffer> MetalDriver::createVertexBufferS() noexcept {
return alloc_handle<MetalVertexBuffer, HwVertexBuffer>();
}
@@ -304,6 +307,10 @@ Handle<HwStream> MetalDriver::createStreamFromTextureIdS() noexcept {
return {};
}
Handle<HwTimerQuery> MetalDriver::createTimerQueryS() noexcept {
return {};
}
void MetalDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
if (vbh) {
destruct_handle<MetalVertexBuffer>(mHandleMap, vbh);
@@ -390,6 +397,9 @@ void MetalDriver::destroyStream(Handle<HwStream> sh) {
// no-op
}
void MetalDriver::destroyTimerQuery(Handle<HwTimerQuery> 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<HwTexture> th, void* image, size_
}
void MetalDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
}
bool MetalDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
return false;
}
void MetalDriver::generateMipmaps(Handle<HwTexture> th) {
@@ -1041,6 +1054,12 @@ void MetalDriver::draw(backend::PipelineState ps, Handle<HwRenderPrimitive> rph)
indexBufferOffset:primitive->offset];
}
void MetalDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
}
void MetalDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
}
void MetalDriver::enumerateSamplerGroups(
const MetalProgram* program,
const std::function<void(const SamplerGroup::Sampler*, size_t)>& f) {

View File

@@ -91,6 +91,9 @@ void NoopDriver::destroySwapChain(Handle<HwSwapChain> sch) {
void NoopDriver::destroyStream(Handle<HwStream> sh) {
}
void NoopDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
}
Handle<HwStream> NoopDriver::createStreamNative(void* nativeStream) {
return {};
}
@@ -165,6 +168,10 @@ void NoopDriver::setupExternalImage(void* image) {
void NoopDriver::cancelExternalImage(void* image) {
}
bool NoopDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
return false;
}
void NoopDriver::setExternalImage(Handle<HwTexture> th, void* image) {
}
@@ -255,4 +262,10 @@ void NoopDriver::blit(TargetBufferFlags buffers,
void NoopDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph) {
}
void NoopDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
}
void NoopDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
}
} // namespace filament

View File

@@ -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

View File

@@ -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

View File

@@ -23,6 +23,8 @@
#include "OpenGLBlitter.h"
#include "OpenGLDriverFactory.h"
#include "OpenGLProgram.h"
#include "TimerQuery.h"
#include "OpenGLContext.h"
#include <utils/compiler.h>
#include <utils/Log.h>
@@ -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<HwStream> OpenGLDriver::createStreamFromTextureIdS() noexcept {
return Handle<HwStream>( allocateHandle(sizeof(GLStream)) );
}
Handle<HwTimerQuery> OpenGLDriver::createTimerQueryS() noexcept {
return Handle<HwTimerQuery>( allocateHandle(sizeof(GLTimerQuery)) );
}
void OpenGLDriver::createVertexBufferR(
Handle<HwVertexBuffer> vbh,
uint8_t bufferCount,
@@ -1002,6 +1032,14 @@ void OpenGLDriver::createStreamFromTextureIdR(Handle<HwStream> sh,
}
}
void OpenGLDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
DEBUG_MARKER()
GLTimerQuery* tq = construct<GLTimerQuery>(tqh);
glGenQueries(1u, &tq->gl.query);
CHECK_GL_ERROR(utils::slog.e)
}
// ------------------------------------------------------------------------------------------------
// Destroying driver objects
// ------------------------------------------------------------------------------------------------
@@ -1159,6 +1197,16 @@ void OpenGLDriver::destroyStream(Handle<HwStream> sh) {
}
}
void OpenGLDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
DEBUG_MARKER()
if (tqh) {
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(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<HwTimerQuery> tqh) {
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
// reset the state of the result availability
tq->elapsed.store(0, std::memory_order_relaxed);
mTimerQueryImpl->beginTimeElapsedQuery(tq);
}
void OpenGLDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(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<HwTimerQuery> tqh, uint64_t* elapsedTime) {
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(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<HwRenderTarget> rth,
const RenderPassParams& params) {
DEBUG_MARKER()
@@ -2504,12 +2584,16 @@ void OpenGLDriver::readPixels(Handle<HwRenderTarget> src,
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLDriver::whenGpuCommandsComplete(std::function<void(void)> fn) noexcept {
void OpenGLDriver::whenGpuCommandsComplete(std::function<void()> 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<bool()> 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

View File

@@ -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<uint64_t> 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<void()> fn) noexcept;
void executeGpuCommandsCompleteOps() noexcept;
std::vector<std::pair<GLsync, std::function<void(void)>>> mGpuCommandCompleteOps;
std::vector<std::pair<GLsync, std::function<void()>>> mGpuCommandCompleteOps;
// tasks regularly executed on the main thread at frame begin time until they return true
void whenFrameBegins(std::function<bool()> fn) noexcept;
void executeFrameBeginsOps() noexcept;
std::vector<std::function<bool()>> mFrameBeginsOps;
// timer query implementation
TimerQueryInterface* mTimerQueryImpl = nullptr;
bool mFrameTimeSupported = false;
};
// ------------------------------------------------------------------------------------------------

View File

@@ -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 <utils/compiler.h>
#include <utils/Log.h>
#include <utils/Systrace.h>
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<utils::Mutex> 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<utils::Mutex> lock(mLock);
mExitRequested = true;
lock.unlock();
mCondition.notify_one();
mThread.join();
}
}
void TimerQueryFence::enqueue(TimerQueryFence::Job&& job) {
std::unique_lock<utils::Mutex> lock(mLock);
mQueue.push_back(std::forward<Job>(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

View File

@@ -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 <utils/Condition.h>
#include <thread>
#include <vector>
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()>;
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<typename CALLABLE, typename ... ARGS>
void push(CALLABLE&& func, ARGS&& ... args) {
enqueue(Job(std::bind(std::forward<CALLABLE>(func), std::forward<ARGS>(args)...)));
}
backend::OpenGLPlatform& mPlatform;
std::thread mThread;
mutable utils::Mutex mLock;
mutable utils::Condition mCondition;
std::vector<Job> 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

View File

@@ -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
});
}

View File

@@ -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 <bluegl/BlueGL.h>

View File

@@ -457,6 +457,9 @@ void VulkanDriver::createStreamFromTextureIdR(Handle<HwStream> sh, intptr_t exte
uint32_t width, uint32_t height) {
}
void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
}
Handle<HwVertexBuffer> VulkanDriver::createVertexBufferS() noexcept {
return alloc_handle<VulkanVertexBuffer, HwVertexBuffer>();
}
@@ -513,6 +516,10 @@ Handle<HwStream> VulkanDriver::createStreamFromTextureIdS() noexcept {
return {};
}
Handle<HwTimerQuery> VulkanDriver::createTimerQueryS() noexcept {
return {};
}
void VulkanDriver::destroySamplerGroup(Handle<HwSamplerGroup> 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<HwSwapChain> sch) {
void VulkanDriver::destroyStream(Handle<HwStream> sh) {
}
void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
}
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
return {};
}
@@ -684,6 +694,10 @@ void VulkanDriver::setupExternalImage(void* image) {
void VulkanDriver::cancelExternalImage(void* image) {
}
bool VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
return false;
}
void VulkanDriver::setExternalImage(Handle<HwTexture> th, void* image) {
}
@@ -1182,6 +1196,13 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
}
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
}
void VulkanDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
}
#ifndef NDEBUG
void VulkanDriver::debugCommand(const char* methodName) {
static const std::set<utils::StaticString> OUTSIDE_COMMANDS = {

View File

@@ -16,11 +16,7 @@
#include "FrameInfo.h"
#include "details/Fence.h"
#include <utils/JobSystem.h>
#include <utils/Log.h>
#include <utils/Systrace.h>
#include <math/scalar.h>
@@ -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<uint64_t, std::nano>(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<FrameInfo>(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<std::mutex> 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<std::mutex> lock(mLock);
mExitRequested = true;
lock.unlock();
mCondition.notify_one();
mThread.join();
}
void FrameInfoManager::SyncThread::enqueue(SyncThread::Job&& job) {
std::unique_lock<std::mutex> lock(mLock);
mQueue.push_back(std::forward<SyncThread::Job>(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<std::mutex> 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

View File

@@ -19,195 +19,40 @@
#include "details/Engine.h"
#include <filament/Fence.h>
#include "backend/Handle.h"
#include <utils/Allocator.h>
#include <deque>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
#include <assert.h>
#include <math/fast.h>
// set EXTRA_TIMING_INFO to enable and print extra timing info about the render loop
#define EXTRA_TIMING_INFO false
#include <stdint.h>
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<float, std::milli>;
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<float, std::milli>;
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<std::mutex> lock(mLock);
FrameInfo const& info = mFrameInfoHistory.front();
return info.laps[FrameInfo::FINISH] - info.laps[FrameInfo::START];
}
std::vector<FrameInfo> getHistory() const noexcept {
std::unique_lock<std::mutex> lock(mLock);
return mFrameInfoHistory;
}
// no user serviceable part below...
template<typename CALLABLE, typename ... ARGS>
void push(CALLABLE&& func, ARGS&&... args) {
mSyncThread.push(std::forward<CALLABLE>(func), std::forward<ARGS>(args)...);
}
static constexpr size_t getHistorySize() noexcept {
return HISTORY_COUNT;
return mFrameTime;
}
private:
class SyncThread {
public:
using Job = std::function<void()>;
SyncThread() = default;
~SyncThread();
void run();
void requestExitAndWait();
template<typename CALLABLE, typename ... ARGS>
void push(CALLABLE&& func, ARGS&&... args) {
enqueue(Job(std::bind(std::forward<CALLABLE>(func), std::forward<ARGS>(args)...)));
}
private:
void enqueue(SyncThread::Job&& job);
void loop();
std::thread mThread;
mutable std::mutex mLock;
mutable std::condition_variable mCondition;
std::deque<Job> mQueue;
bool mExitRequested = false;
};
FrameInfo* obtain() noexcept;
void finish(FrameInfo* info) noexcept;
using PoolArena = utils::Arena<utils::ObjectPoolAllocator<FrameInfo>, utils::LockingPolicy::SpinLock>;
FEngine& mEngine;
PoolArena mPoolArena;
SyncThread mSyncThread;
FrameInfo* mCurrentFrameInfo = nullptr;
mutable std::mutex mLock;
std::vector<FrameInfo> mFrameInfoHistory;
};
template<typename T, size_t MEDIAN = 5, size_t HISTORY = 16>
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<T, MEDIAN> 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<T>::iterator begin() const { return mOut.begin(); }
typename std::deque<T>::iterator end() const { return mOut.end(); }
T const& oldest() const noexcept { return mOut.front(); }
T const& latest() const noexcept { return mOut.back(); }
std::deque<T> mIn;
std::deque<T> mOut;
T mLowPass = {};
details::FEngine& mEngine;
backend::Handle<backend::HwTimerQuery> mQueries[POOL_COUNT];
duration mFrameTime{};
uint32_t mIndex = 0;
uint32_t mLast = 0;
};

View File

@@ -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<double> 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,

View File

@@ -156,11 +156,6 @@ private:
// per-frame arena for this Renderer
LinearAllocatorArena& mPerRenderPassArena;
#if EXTRA_TIMING_INFO
Series<float> mRendering;
Series<float> mPostProcess;
#endif
};
FILAMENT_UPCAST(Renderer)