diff --git a/android/filament-android/src/main/cpp/Renderer.cpp b/android/filament-android/src/main/cpp/Renderer.cpp index dee60182f7..cf39d8cc70 100644 --- a/android/filament-android/src/main/cpp/Renderer.cpp +++ b/android/filament-android/src/main/cpp/Renderer.cpp @@ -96,3 +96,15 @@ Java_com_google_android_filament_Renderer_nReadPixels(JNIEnv *env, jclass, return 0; } + +extern "C" JNIEXPORT jdouble JNICALL +Java_com_google_android_filament_Renderer_nGetUserTime(JNIEnv*, jclass, jlong nativeRenderer) { + Renderer *renderer = (Renderer *) nativeRenderer; + return renderer->getUserTime(); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_Renderer_nResetUserTime(JNIEnv*, jclass, jlong nativeRenderer) { + Renderer *renderer = (Renderer *) nativeRenderer; + renderer->resetUserTime(); +} diff --git a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java index 682650ca34..0031590c36 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java @@ -89,6 +89,14 @@ public class Renderer { } } + double getUserTime() { + return nGetUserTime(getNativeObject()); + } + + void resetUserTime() { + nResetUserTime(getNativeObject()); + } + long getNativeObject() { if (mNativeObject == 0) { throw new IllegalStateException("Calling method on destroyed Renderer"); @@ -112,4 +120,6 @@ public class Renderer { Buffer storage, int remaining, int left, int top, int type, int alignment, int stride, int format, Object handler, Runnable callback); + private static native double nGetUserTime(long nativeRenderer); + private static native void nResetUserTime(long nativeRenderer); } diff --git a/docs/Materials.md.html b/docs/Materials.md.html index 9ee59bae51..3a7cf52f72 100644 --- a/docs/Materials.md.html +++ b/docs/Materials.md.html @@ -1330,6 +1330,7 @@ type aliases: **getResolution()** | float4 | Resolution of the view in pixels: `width`, `height`, `1 / width`, `1 / height` **getWorldCameraPosition()** | float3 | Position of the camera/eye in world space **getTime()** | float | Current time in seconds, may be reset regularly to avoid precision loss +**getUserTime()** | float4 | Current time in seconds: `time`, `(double)time - time`, `0`, `0` **getExposure()** | float | Photometric exposure of the camera **getEV100()** | float | [Exposure value at ISO 100](https://en.wikipedia.org/wiki/Exposure_value) of the camera diff --git a/filament/include/filament/Renderer.h b/filament/include/filament/Renderer.h index ab7846507d..180de93f9c 100644 --- a/filament/include/filament/Renderer.h +++ b/filament/include/filament/Renderer.h @@ -288,6 +288,63 @@ public: * beginFrame() */ void endFrame(); + + /** + * Returns the time in second of the last call to beginFrame(). This value is constant for all + * views rendered during a frame. The epoch is set with resetUserTime(). + * + * In materials, this value can be queried using `vec4 getUserTime()`. The value returned + * is a highp vec4 encoded as follows: + * + * time.x = (float)Renderer.getUserTime(); + * time.y = Renderer.getUserTime() - time.x; + * + * It follows that the following invariants are true: + * + * (double)time.x + (double)time.y == Renderer.getUserTime() + * time.x == (float)Renderer.getUserTime() + * + * This encoding allows the shader code to perform high precision (i.e. double) time + * calculations when needed despite the lack of double precision in the shader, for e.g.: + * + * To compute (double)time * vertex in the material, use the following construct: + * + * vec3 result = time.x * vertex + time.y * vertex; + * + * + * Most of the time, high precision computations are not required, but be aware that the + * precision of time.x rapidly diminishes as time passes: + * + * time | precision + * --------+---------- + * 16.7s | us + * 4h39 | ms + * 77h | 1/60s + * + * + * In other words, it only possible to get microsecond accuracy for about 16s or millisecond + * accuracy for just under 5h. + * + * This problem can be mitigated by calling resetUserTime(), or using high precision time as + * described above. + * + * @return The time is seconds since resetUserTime() was last called. + * + * @see + * resetUserTime() + */ + double getUserTime() const; + + /** + * Sets the user time epoch to now, i.e. resets the user time to zero. + * + * Use this method used to keep the precision of time high in materials, in practice it should + * be called at least when the application is paused, e.g. Activity.onPause() in Android. + * + * @see + * getUserTime() + */ + void resetUserTime(); }; } // namespace filament diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 96fc60da23..40a9ff42f0 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -137,7 +137,7 @@ FEngine::FEngine(Backend backend, Platform* platform, void* sharedGLContext) : mPostProcessSib(PostProcessSib::getSib()), mCommandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE, CONFIG_COMMAND_BUFFERS_SIZE), mPerRenderPassAllocator("per-renderpass allocator", CONFIG_PER_RENDER_PASS_ARENA_SIZE), - mEpoch(std::chrono::steady_clock::now()), + mEngineEpoch(std::chrono::steady_clock::now()), mDriverBarrier(1) { SYSTRACE_ENABLE(); diff --git a/filament/src/PostProcessManager.cpp b/filament/src/PostProcessManager.cpp index 54cbd043a5..e47044199a 100644 --- a/filament/src/PostProcessManager.cpp +++ b/filament/src/PostProcessManager.cpp @@ -62,7 +62,7 @@ void PostProcessManager::setSource(uint32_t viewportWidth, uint32_t viewportHeig SamplerBuffer sb(engine.getPostProcessSib()); sb.setSampler(PostProcessSib::COLOR_BUFFER, pos->texture, params); - auto duration = engine.getTime(); + auto duration = engine.getEngineTime(); float fraction = (duration.count() % 1000000000) / 1000000000.0f; UniformBuffer& ub = mPostProcessUb; diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index eb6189d60b..53b4eaf935 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -33,6 +33,8 @@ #include #include +#include + using namespace math; using namespace utils; @@ -55,6 +57,7 @@ FRenderer::FRenderer(FEngine& engine) : void FRenderer::init() noexcept { DriverApi& driver = mEngine.getDriverApi(); + mUserEpoch = mEngine.getEngineEpoch(); mRenderTarget = driver.createDefaultRenderTarget(); mIsRGB16FSupported = driver.isRenderTargetFormatSupported(driver::TextureFormat::RGB16F); mIsRGB8Supported = driver.isRenderTargetFormatSupported(driver::TextureFormat::RGB8); @@ -95,6 +98,10 @@ void FRenderer::terminate(FEngine& engine) { } } +void FRenderer::resetUserTime() { + mUserEpoch = std::chrono::steady_clock::now(); +} + driver::TextureFormat FRenderer::getHdrFormat(const View& view) const noexcept { const bool translucent = mSwapChain->isTransparent(); if (translucent) return driver::TextureFormat::RGBA16F; @@ -170,7 +177,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) { return; } - view.prepare(engine, driver, arena, svp); + view.prepare(engine, driver, arena, svp, getShaderUserTime()); // TODO: froxelization could actually start now, instead of in ColorPass::renderColorPass() /* @@ -345,6 +352,12 @@ bool FRenderer::beginFrame(FSwapChain* swapChain) { return false; } + // latch the frame time + std::chrono::duration time{ getUserTime() }; + float h = (float)time.count(); + float l = float(time.count() - h); + mShaderUserTime = { h, l, 0, 0 }; + // ask the engine to do what it needs to (e.g. updates light buffer, materials...) engine.prepare(); @@ -475,4 +488,12 @@ void Renderer::endFrame() { upcast(this)->endFrame(); } +double Renderer::getUserTime() const { + return upcast(this)->getUserTime().count(); +} + +void Renderer::resetUserTime() { + upcast(this)->resetUserTime(); +} + } // namespace filament diff --git a/filament/src/View.cpp b/filament/src/View.cpp index 6c2992ac2e..d5d54fbdb1 100644 --- a/filament/src/View.cpp +++ b/filament/src/View.cpp @@ -397,7 +397,7 @@ void FView::prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaSc } void FView::prepare(FEngine& engine, driver::DriverApi& driver, ArenaScope& arena, - Viewport const& viewport) noexcept { + Viewport const& viewport, math::float4 const& userTime) noexcept { JobSystem& js = engine.getJobSystem(); /* @@ -537,8 +537,9 @@ void FView::prepare(FEngine& engine, driver::DriverApi& driver, ArenaScope& aren * Update driver state */ - float fraction = (engine.getTime().count() % 1000000000) / 1000000000.0f; + float fraction = (engine.getEngineTime().count() % 1000000000) / 1000000000.0f; getUb().setUniform(offsetof(PerViewUib, time), fraction); + getUb().setUniform(offsetof(PerViewUib, userTime), userTime); // upload the renderables's dirty UBOs engine.getRenderableManager().prepare(driver, diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index df162e75af..90bec5d43f 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -203,15 +203,15 @@ public: return mBackend; } - duration getTime() const noexcept { - return clock::now() - getEpoch(); - } - void* streamAlloc(size_t size, size_t alignment) noexcept; utils::JobSystem& getJobSystem() noexcept { return mJobSystem; } - Epoch getEpoch() const { return mEpoch; } + + Epoch getEngineEpoch() const { return mEngineEpoch; } + duration getEngineTime() const noexcept { + return clock::now() - getEngineEpoch(); + } void shutdown(); @@ -347,7 +347,7 @@ private: utils::JobSystem mJobSystem; - Epoch mEpoch; + Epoch mEngineEpoch; mutable FMaterial const* mDefaultMaterial = nullptr; mutable FMaterial const* mSkyboxMaterials[2] = { nullptr, nullptr }; diff --git a/filament/src/details/Renderer.h b/filament/src/details/Renderer.h index 49d1c0394c..3abd994a22 100644 --- a/filament/src/details/Renderer.h +++ b/filament/src/details/Renderer.h @@ -59,6 +59,8 @@ public: FEngine& getEngine() const noexcept { return mEngine; } + math::float4 getShaderUserTime() const { return mShaderUserTime; } + // do all the work here! void render(FView const* view); void renderJob(ArenaScope& arena, FView& view); @@ -69,6 +71,8 @@ public: bool beginFrame(FSwapChain* swapChain); void endFrame(); + void resetUserTime(); + void readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, driver::PixelBufferDescriptor&& buffer); @@ -76,6 +80,7 @@ public: void terminate(FEngine& engine); private: + friend class Renderer; using Command = RenderPass::Command; // this class is defined in RenderPass.cpp @@ -123,6 +128,15 @@ private: driver::TextureFormat getHdrFormat(const View& view) const noexcept; driver::TextureFormat getLdrFormat() const noexcept; + using clock = std::chrono::steady_clock; + using Epoch = clock::time_point; + using duration = clock::duration; + + Epoch getUserEpoch() const { return mUserEpoch; } + duration getUserTime() const noexcept { + return clock::now() - getUserEpoch(); + } + // keep a reference to our engine FEngine& mEngine; FrameSkipper mFrameSkipper; @@ -133,6 +147,8 @@ private: FrameInfoManager mFrameInfoManager; bool mIsRGB16FSupported : 1; bool mIsRGB8Supported : 1; + Epoch mUserEpoch; + math::float4 mShaderUserTime; // per-frame arena for this Renderer LinearAllocatorArena& mPerRenderPassArena; diff --git a/filament/src/details/View.h b/filament/src/details/View.h index ece01403f4..4f80444235 100644 --- a/filament/src/details/View.h +++ b/filament/src/details/View.h @@ -62,7 +62,7 @@ public: void terminate(FEngine& engine); void prepare(FEngine& engine, driver::DriverApi& driver, ArenaScope& arena, - Viewport const& viewport) noexcept; + Viewport const& viewport, math::float4 const& userTime) noexcept; void setScene(FScene* scene) { mScene = scene; } FScene const* getScene() const noexcept { return mScene; } diff --git a/libs/filabridge/include/private/filament/UibGenerator.h b/libs/filabridge/include/private/filament/UibGenerator.h index 7492b0c643..ed829f6869 100644 --- a/libs/filabridge/include/private/filament/UibGenerator.h +++ b/libs/filabridge/include/private/filament/UibGenerator.h @@ -77,6 +77,8 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init) float ev100; alignas(16) math::float4 iblSH[9]; // actually float3 entries (std140 requires float4 alignment) + + math::float4 userTime; // time(s), (double)time - (float)time, 0, 0 }; diff --git a/libs/filabridge/src/UibGenerator.cpp b/libs/filabridge/src/UibGenerator.cpp index d8275e7c86..0df5069894 100644 --- a/libs/filabridge/src/UibGenerator.cpp +++ b/libs/filabridge/src/UibGenerator.cpp @@ -70,6 +70,8 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept { .add("ev100", 1, UniformInterfaceBlock::Type::FLOAT) // ibl .add("iblSH", 9, UniformInterfaceBlock::Type::FLOAT3) + // user time + .add("userTime", 1, UniformInterfaceBlock::Type::FLOAT4) .build(); return uib; } diff --git a/shaders/src/common_getters.fs b/shaders/src/common_getters.fs index f115df4204..428a36544e 100644 --- a/shaders/src/common_getters.fs +++ b/shaders/src/common_getters.fs @@ -42,6 +42,11 @@ float getTime() { return frameUniforms.time; } +/** @public-api */ +HIGHP vec4 getUserTime() { + return frameUniforms.userTime; +} + /** @public-api */ float getExposure() { return frameUniforms.exposure;