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