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:
committed by
Mathias Agopian
parent
a389de3deb
commit
e8a7d9f0c2
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -141,6 +141,9 @@ struct HwStream : public HwBase {
|
||||
uint32_t height = 0;
|
||||
};
|
||||
|
||||
struct HwTimerQuery : public HwBase {
|
||||
};
|
||||
|
||||
/*
|
||||
* Base class of all Driver implementations
|
||||
*/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
164
filament/backend/src/opengl/TimerQuery.cpp
Normal file
164
filament/backend/src/opengl/TimerQuery.cpp
Normal 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
|
||||
100
filament/backend/src/opengl/TimerQuery.h
Normal file
100
filament/backend/src/opengl/TimerQuery.h
Normal 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
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user