diff --git a/NEW_RELEASE_NOTES.md b/NEW_RELEASE_NOTES.md index 808651d165..c084533f66 100644 --- a/NEW_RELEASE_NOTES.md +++ b/NEW_RELEASE_NOTES.md @@ -9,3 +9,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md). ## Release notes for next branch cut - Rename `sampler` parameter `unfilterable` to `filterable` [⚠️ **New Material Version**] +- Added `Renderer::shouldRenderFrame()` diff --git a/android/filament-android/src/main/cpp/Renderer.cpp b/android/filament-android/src/main/cpp/Renderer.cpp index 0020ea21d6..93b685427d 100644 --- a/android/filament-android/src/main/cpp/Renderer.cpp +++ b/android/filament-android/src/main/cpp/Renderer.cpp @@ -36,6 +36,12 @@ Java_com_google_android_filament_Renderer_nSkipFrame(JNIEnv *, jclass, jlong nat renderer->skipFrame(uint64_t(vsyncSteadyClockTimeNano)); } +extern "C" JNIEXPORT jboolean JNICALL +Java_com_google_android_filament_Renderer_nShouldRenderFrame(JNIEnv *, jclass, jlong nativeRenderer) { + Renderer *renderer = (Renderer *) nativeRenderer; + return (jboolean) renderer->shouldRenderFrame(); +} + extern "C" JNIEXPORT jboolean JNICALL Java_com_google_android_filament_Renderer_nBeginFrame(JNIEnv *, jclass, jlong nativeRenderer, jlong nativeSwapChain, jlong frameTimeNanos) { 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 509a3c080c..3f2821794e 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 @@ -311,6 +311,21 @@ public class Renderer { nSkipFrame(getNativeObject(), vsyncSteadyClockTimeNano); } + /** + * Returns true if the current frame should be rendered. + * + * This is a convenience method that returns the same value as {@link #beginFrame}. + * + * @return + * false the current frame should be skipped
+ * true the current frame can be rendered + * + * @see #beginFrame + */ + public boolean shouldRenderFrame() { + return nShouldRenderFrame(getNativeObject()); + } + /** * Sets up a frame for this Renderer. *

beginFrame manages frame pacing, and returns whether or not a frame should be @@ -731,6 +746,7 @@ public class Renderer { private static native void nSetPresentationTime(long nativeObject, long monotonicClockNanos); private static native void nSetVsyncTime(long nativeObject, long steadyClockTimeNano); private static native void nSkipFrame(long nativeObject, long vsyncSteadyClockTimeNano); + private static native boolean nShouldRenderFrame(long nativeObject); private static native boolean nBeginFrame(long nativeRenderer, long nativeSwapChain, long frameTimeNanos); private static native void nEndFrame(long nativeRenderer); private static native void nRender(long nativeRenderer, long nativeView); diff --git a/filament/include/filament/Renderer.h b/filament/include/filament/Renderer.h index c6033a3db8..99c4a8b3d9 100644 --- a/filament/include/filament/Renderer.h +++ b/filament/include/filament/Renderer.h @@ -280,9 +280,23 @@ public: void skipFrame(uint64_t vsyncSteadyClockTimeNano = 0u); /** - * Set-up a frame for this Renderer. + * Returns true if the current frame should be rendered. * - * beginFrame() manages frame pacing, and returns whether or not a frame should be drawn. The + * This is a convenience method that returns the same value as beginFrame(). + * + * @return + * *false* the current frame should be skipped, + * *true* the current frame can be rendered + * + * @see + * beginFrame() + */ + bool shouldRenderFrame() const noexcept; + + /** + * Set up a frame for this Renderer. + * + * beginFrame() manages frame-pacing, and returns whether a frame should be drawn. The * goal of this is to skip frames when the GPU falls behind in order to keep the frame * latency low. * diff --git a/filament/src/FrameSkipper.cpp b/filament/src/FrameSkipper.cpp index c58f986953..4ff05c6522 100644 --- a/filament/src/FrameSkipper.cpp +++ b/filament/src/FrameSkipper.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2025 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. @@ -22,6 +22,7 @@ #include #include +#include #include @@ -31,20 +32,20 @@ using namespace utils; using namespace backend; FrameSkipper::FrameSkipper(size_t const latency) noexcept - : mLast(std::clamp(latency, size_t(1), MAX_FRAME_LATENCY) - 1) { + : mLatency(std::clamp(latency, size_t(1), MAX_FRAME_LATENCY) - 1) { } FrameSkipper::~FrameSkipper() noexcept = default; void FrameSkipper::terminate(DriverApi& driver) noexcept { - for (auto fence : mDelayedFences) { + for (auto& fence : mDelayedFences) { if (fence) { - driver.destroyFence(fence); + driver.destroyFence(std::move(fence)); } } } -bool FrameSkipper::beginFrame(DriverApi& driver) noexcept { +bool FrameSkipper::shouldRenderFrame(DriverApi& driver) const noexcept { auto& fences = mDelayedFences; if (fences.front()) { // Do we have a latency old fence? @@ -58,9 +59,9 @@ bool FrameSkipper::beginFrame(DriverApi& driver) noexcept { return true; } -void FrameSkipper::endFrame(DriverApi& driver) noexcept { +void FrameSkipper::submitFrame(DriverApi& driver) noexcept { auto& fences = mDelayedFences; - size_t const last = mLast; + size_t const last = mLatency; // pop the oldest fence and advance the other ones if (fences.front()) { diff --git a/filament/src/FrameSkipper.h b/filament/src/FrameSkipper.h index 814299341a..3e6bd4fc4e 100644 --- a/filament/src/FrameSkipper.h +++ b/filament/src/FrameSkipper.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2025 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. @@ -64,17 +64,17 @@ public: void terminate(backend::DriverApi& driver) noexcept; - // Returns false if we need to skip this frame, because the GPU is running behind the CPU; - // In that case, don't call render endFrame() - // Returns true if rendering can proceed. Always call endFrame() when done. - bool beginFrame(backend::DriverApi& driver) noexcept; + // Returns false if we should skip this frame because the GPU is running behind the CPU. + // In that case, don't call submitFrame(). + // Returns true if rendering can proceed. Always call submitFrame() when done. + bool shouldRenderFrame(backend::DriverApi& driver) const noexcept; - void endFrame(backend::DriverApi& driver) noexcept; + void submitFrame(backend::DriverApi& driver) noexcept; private: using Container = std::array, MAX_FRAME_LATENCY>; - mutable Container mDelayedFences{}; - uint8_t const mLast; + Container mDelayedFences{}; + uint8_t const mLatency; }; } // namespace filament diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp index 836e4255be..444e6cb26d 100644 --- a/filament/src/Renderer.cpp +++ b/filament/src/Renderer.cpp @@ -50,6 +50,10 @@ void Renderer::skipFrame(uint64_t const vsyncSteadyClockTimeNano) { downcast(this)->skipFrame(vsyncSteadyClockTimeNano); } +bool Renderer::shouldRenderFrame() const noexcept { + return downcast(this)->shouldRenderFrame(); +} + bool Renderer::beginFrame(SwapChain* swapChain, uint64_t const vsyncSteadyClockTimeNano) { return downcast(this)->beginFrame(downcast(swapChain), vsyncSteadyClockTimeNano); } diff --git a/filament/src/details/Renderer.cpp b/filament/src/details/Renderer.cpp index 4e0ecdae75..c51291deec 100644 --- a/filament/src/details/Renderer.cpp +++ b/filament/src/details/Renderer.cpp @@ -296,6 +296,12 @@ void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) { js.waitAndRelease(job); } +bool FRenderer::shouldRenderFrame() const noexcept { + FEngine& engine = mEngine; + FEngine::DriverApi& driver = engine.getDriverApi(); + return mFrameSkipper.shouldRenderFrame(driver); +} + bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano) { assert_invariant(swapChain); @@ -389,7 +395,7 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN engine.prepare(); }; - if (mFrameSkipper.beginFrame(driver)) { + if (mFrameSkipper.shouldRenderFrame(driver)) { // if beginFrame() returns true, we are expecting a call to endFrame(), // so do the beginFrame work right now, instead of requiring a call to render() beginFrameInternal(); @@ -436,7 +442,7 @@ void FRenderer::endFrame() { } mFrameInfoManager.endFrame(driver); - mFrameSkipper.endFrame(driver); + mFrameSkipper.submitFrame(driver); driver.endFrame(mFrameId); diff --git a/filament/src/details/Renderer.h b/filament/src/details/Renderer.h index e957158a95..0b747e2eab 100644 --- a/filament/src/details/Renderer.h +++ b/filament/src/details/Renderer.h @@ -94,6 +94,9 @@ public: // skip a frame void skipFrame(uint64_t vsyncSteadyClockTimeNano); + // Whether a frame should be rendered or not. + bool shouldRenderFrame() const noexcept; + // start a frame bool beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano); diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index aa6e6d890a..fe9f292458 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -628,6 +628,7 @@ class_("Renderer") .function("setPresentationTime", &Renderer::setPresentationTime) .function("setVsyncTime", &Renderer::setVsyncTime) .function("skipFrame", &Renderer::skipFrame) + .function("shouldRenderFrame", &Renderer::shouldRenderFrame) .function("beginFrame", EMBIND_LAMBDA(bool, (Renderer* self, SwapChain* swapChain), { return self->beginFrame(swapChain); }), allow_raw_pointers())