Compare commits

...

9 Commits

Author SHA1 Message Date
Powei Feng
1e4029532f vk: fix leaking swapchains again (#9410)
- Adjust order of destroy call in both headless and platform
   swapchains.  We need to be careful of the order due to
   assumptions made by the base class's destroy().
 - remove `=0` since VulkanPlatformSwapChainBase::destroy() has
   an implementation.

Fixes #9403
2025-11-17 17:04:10 -08:00
Mathias Agopian
be7149918d AndroidNativeWindow must also work on 32-bits architectures (#9424) 2025-11-10 14:54:09 -08:00
Mathias Agopian
f95a58ad72 Fix a race condition when tearing down FrameInfo (#9413)
* HandleAllocator::deallocate() was unsafe

It needs to know the concrete type to call the proper destructor, so
if it was given a base type handle (e.g. Handle<HwFoo>) it would not
destroy it properly.

* add AsyncJobQueue::cancelAll()

* Fix a race condition when tearing down FrameInfo

It is actually invalid to destroy a Handle<HwFence> while inside
fenceWait().

Updated the HwFence implementations so that they don't pretend they can
handle being destroyed during fenceWait(), they can't.

FrameInfo now cancels all the pending callbacks and waits for the 
currently executing one to terminate, *before* destroying the
handles.

Reenable the gpuFrameComplete metric, as it should be working now.
2025-11-10 14:52:32 -08:00
Mathias Agopian
5a9b56a1b7 Properly use SDK 26+ APIs (#9417)
With minSDK less than 26, it's unsafe to use APIs in nativewindow.h 
that appeared at API 26 or more, even when using the weak symbol
technique. This is because pre 26, libnativewindow.h didn't exist and
therefore the build system cannot link against it.

Currently Filament's minSdk is 21, which means we should never use
nativewindow.h API after 26.

In this PR we chose the strategy at compile time; either the more
modern weak symbol or the old way with dlsym based on __ANDROID_API__.
2025-11-07 10:28:16 -08:00
Powei Feng
8310896232 vk: fix thread-safety wrt queryFrameTimestamps() (#9415)
queryFrameTimestamps() and queryCompositorTiming() are both
synchronous APIs, but VuklanSwapchain is not a thread-safe
ref-counted handle (i.e. fvkmemory::ThreadSafeResource). We move
the Platform::SwapChain* pointers to a thread-safe map that is
mapped by the handle id.
2025-11-07 10:05:11 -08:00
Benjamin Doherty
cc66f7c230 Add missing mutex header 2025-11-05 15:35:16 -08:00
Mathias Agopian
5695a5b43d add a feature flag for FrameInfo::gpuFrameComplete (#9409)
There are several race conditions caused by this feature. So we add
this flag and disable it by default, for now.
2025-11-05 15:33:30 -08:00
Benjamin Doherty
3cd2f0c386 Bump MATERIAL_VERSION to 67 2025-11-05 10:35:03 -08:00
Benjamin Doherty
93437c33f8 Bump version to 1.67.1 2025-11-05 10:33:25 -08:00
34 changed files with 456 additions and 177 deletions

View File

@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.67.0'
implementation 'com.google.android.filament:filament-android:1.67.1'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.67.0'
pod 'Filament', '~> 1.67.1'
```
## Documentation

View File

@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.67.0
VERSION_NAME=1.67.1
POM_DESCRIPTION=Real-time physically based rendering engine for Android.

View File

@@ -104,6 +104,7 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
list(APPEND SRCS src/VirtualMachineEnv.cpp)
endif ()
if (ANDROID)
list(APPEND SRCS src/AndroidNdk.cpp)
list(APPEND SRCS src/AndroidNativeWindow.cpp)
list(APPEND SRCS src/AndroidSwapChainHelper.cpp)
list(APPEND SRCS src/AndroidFrameCallback.cpp)

View File

@@ -19,6 +19,7 @@
#include "AndroidSwapChainHelper.h"
#include "AndroidFrameCallback.h"
#include "AndroidNdk.h"
#include <backend/AcquiredImage.h>
#include <backend/DriverEnums.h>
@@ -46,7 +47,7 @@ class ExternalStreamManagerAndroid;
* A concrete implementation of OpenGLPlatform and subclass of PlatformEGL that supports
* EGL on Android. It adds Android streaming functionality to PlatformEGL.
*/
class PlatformEGLAndroid : public PlatformEGL {
class PlatformEGLAndroid : public PlatformEGL, public AndroidNdk {
public:
PlatformEGLAndroid() noexcept;

View File

@@ -18,6 +18,7 @@
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
#include "AndroidFrameCallback.h"
#include "AndroidNdk.h"
#include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h>
@@ -26,7 +27,7 @@
namespace filament::backend {
class VulkanPlatformAndroid : public VulkanPlatform {
class VulkanPlatformAndroid : public VulkanPlatform, public AndroidNdk {
public:
ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer,
bool sRGB) noexcept;

View File

@@ -337,6 +337,7 @@ DECL_DRIVER_API_SYNCHRONOUS_N(int64_t, getStreamTimestamp, backend::StreamHandle
DECL_DRIVER_API_SYNCHRONOUS_N(void, updateStreams, backend::DriverApi*, driver)
DECL_DRIVER_API_SYNCHRONOUS_N(backend::FenceStatus, getFenceStatus, backend::FenceHandle, fh)
DECL_DRIVER_API_SYNCHRONOUS_N(backend::FenceStatus, fenceWait, backend::FenceHandle, fh, uint64_t, timeout)
DECL_DRIVER_API_SYNCHRONOUS_N(void, fenceCancel, backend::FenceHandle, fh)
DECL_DRIVER_API_SYNCHRONOUS_N(void, getPlatformSync, backend::SyncHandle, sh,
backend::CallbackHandler*, handler, backend::Platform::SyncCallback, cb, void*, userData)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatSupported, backend::TextureFormat, format)

View File

@@ -169,10 +169,11 @@ public:
* Destroy the object D at Handle<B> and frees Handle<B>
* e.g.:
* Handle<HwTexture> h = ...;
* deallocate(h);
* deallocate<GLTexture>(h);
*/
template<typename D>
void deallocate(Handle<D>& handle) noexcept {
template<typename D, typename B,
typename = std::enable_if_t<std::is_base_of_v<B, D>, D>>
void deallocate(Handle<B>& handle) noexcept {
D const* d = handle_cast<const D*>(handle);
deallocate(handle, d);
}

View File

@@ -16,6 +16,7 @@
#include "AndroidNativeWindow.h"
#include <android/api-level.h>
#include <android/native_window.h>
#include <utils/compiler.h>
@@ -30,11 +31,16 @@
namespace filament::backend {
std::pair<int, bool> NativeWindow::isValid(ANativeWindow* const anw) noexcept {
#if __ANDROID_API__ >= 26
// libnativewindow.so is not available before API level 26, this means we can't call
// any method above 25 (even protected by __builtin_available()).
if (__builtin_available(android 28, *)) {
// this a proxy for is_valid()
auto const result = ANativeWindow_getBuffersDataSpace(anw);
return { result, result >= 0 };
}
#endif
// fallback on using private APIs
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
if (UTILS_LIKELY(pWindow->query)) {
@@ -82,6 +88,10 @@ AndroidProducerThrottling::AndroidProducerThrottling() {
// note: we don't need to dlclose() mNativeWindowLib here, because the library will be cleaned
// when the process ends and dlopen() are ref-counted. dlclose() NDK documentation documents
// not to call dlclose().
// libnativewindow.so is not available before API level 26, this means we can't call
// any method above 25 (even protected by __builtin_available()).
void* nativeWindowLibHandle = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
if (nativeWindowLibHandle) {
mSetProducerThrottlingEnabled =

View File

@@ -37,7 +37,12 @@ struct NativeWindow {
GET_COMPOSITOR_TIMING = 26,
GET_FRAME_TIMESTAMPS = 27,
};
#if defined(__LP64__)
uint64_t pad[18];
#else
uint32_t pad[21];
#endif
int (*query)(ANativeWindow const*, int, int*);
int (*perform)(ANativeWindow*, int, ...);

View File

@@ -0,0 +1,60 @@
/*
* 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.
* 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 "AndroidNdk.h"
#include <android/hardware_buffer.h>
#include <utils/compiler.h>
#include <mutex>
#include <dlfcn.h>
namespace filament::backend {
UTILS_UNUSED
static std::once_flag sInitOnce{};
template <typename T>
UTILS_UNUSED
static void loadSymbol(void* handle, const char* symbol, T& pfn) {
pfn = T(dlsym(handle, symbol));
}
#if FILAMENT_USE_DLSYM(26)
AndroidNdk::Ndk AndroidNdk::ndk{};
#endif
AndroidNdk::AndroidNdk() {
#if FILAMENT_USE_DLSYM(26)
std::call_once(sInitOnce, [] {
// note: we don't need to dlclose() mNativeWindowLib here, because the library will be cleaned
// when the process ends and dlopen() are ref-counted. dlclose() NDK documentation documents
// not to call dlclose().
if (__builtin_available(android 26, *)) {
void* h = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
if (h) {
loadSymbol(h, "AHardwareBuffer_acquire", ndk.AHardwareBuffer_acquire);
loadSymbol(h, "AHardwareBuffer_release", ndk.AHardwareBuffer_release);
loadSymbol(h, "AHardwareBuffer_describe", ndk.AHardwareBuffer_describe);
}
}
});
#endif
}
} // filament::backend

View File

@@ -0,0 +1,79 @@
/*
* 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.
* 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 FILAMENT_BACKEND_ANDROIDNDK_H
#define FILAMENT_BACKEND_ANDROIDNDK_H
#include <android/native_window.h>
#include <android/hardware_buffer.h>
#define FILAMENT_REQUIRES_API(x) __attribute__((__availability__(android,introduced=x)))
#define FILAMENT_USE_DLSYM(api) (__ANDROID_API__ < (api))
namespace filament::backend {
class AndroidNdk {
public:
AndroidNdk();
#if FILAMENT_USE_DLSYM(26)
static void AHardwareBuffer_acquire(
AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) {
ndk.AHardwareBuffer_acquire(buffer);
}
static void AHardwareBuffer_release(
AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) {
ndk.AHardwareBuffer_release(buffer);
}
static void AHardwareBuffer_describe(
AHardwareBuffer const* buffer, AHardwareBuffer_Desc* desc) FILAMENT_REQUIRES_API(26) {
ndk.AHardwareBuffer_describe(buffer, desc);
}
#else
static void AHardwareBuffer_acquire(
AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) {
::AHardwareBuffer_acquire(buffer);
}
static void AHardwareBuffer_release(
AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) {
::AHardwareBuffer_release(buffer);
}
static void AHardwareBuffer_describe(
AHardwareBuffer const* buffer, AHardwareBuffer_Desc* desc) FILAMENT_REQUIRES_API(26) {
::AHardwareBuffer_describe(buffer, desc);
}
#endif
private:
#if FILAMENT_USE_DLSYM(26)
static struct Ndk {
void (*AHardwareBuffer_acquire)(AHardwareBuffer*);
void (*AHardwareBuffer_release)(AHardwareBuffer*);
void (*AHardwareBuffer_describe)(AHardwareBuffer const*, AHardwareBuffer_Desc*);
} ndk;
#endif
};
} // filament::backend
#endif //FILAMENT_BACKEND_ANDROIDNDK_H

View File

@@ -1039,21 +1039,27 @@ void MetalDriver::updateStreams(DriverApi* driver) {
void MetalDriver::destroyFence(Handle<HwFence> fh) {
if (fh) {
auto* fence = handle_cast<MetalFence>(fh);
fence->cancel();
// note: it's invalid to call this during a fenceWait(fh) on another thread. For this
// reason there is no point signaling the waiters. There should be no waiters.
destruct_handle<MetalFence>(fh);
}
}
void MetalDriver::fenceCancel(FenceHandle const fh) {
// Even though this is a synchronous call, the fence handle must be (and stay) valid
assert_invariant(fh);
auto* fence = handle_cast<MetalFence>(fh);
fence->cancel();
}
FenceStatus MetalDriver::getFenceStatus(Handle<HwFence> fh) {
return fenceWait(fh, 0);
}
FenceStatus MetalDriver::fenceWait(FenceHandle fh, uint64_t const timeout) {
// Even though this is a synchronous call, the fence handle must be (and stay) valid
assert_invariant(fh);
auto* fence = handle_cast<MetalFence>(fh);
if (!fence) {
return FenceStatus::ERROR;
}
return fence->wait(timeout);
}

View File

@@ -1395,10 +1395,6 @@ void MetalFence::onSignal(MetalFenceSignalBlock block) {
FenceStatus MetalFence::wait(uint64_t timeoutNs) {
if (@available(iOS 12, *)) {
using ns = std::chrono::nanoseconds;
// keep a reference on the stack so our state cannot be destroyed while we wait
auto state = this->state;
std::unique_lock<std::mutex> guard(state->mutex);
while (state->status == FenceStatus::TIMEOUT_EXPIRED) {
if (timeoutNs == FENCE_WAIT_FOR_EVER) {

View File

@@ -166,6 +166,9 @@ void NoopDriver::getPlatformSync(Handle<HwSync> sh, CallbackHandler* handler,
void NoopDriver::destroyFence(Handle<HwFence> fh) {
}
void NoopDriver::fenceCancel(FenceHandle fh) {
}
FenceStatus NoopDriver::getFenceStatus(Handle<HwFence> fh) {
return FenceStatus::CONDITION_SATISFIED;
}

View File

@@ -1788,7 +1788,7 @@ void OpenGLDriver::createFenceR(Handle<HwFence> fh, ImmutableCString&& tag) {
mHandleAllocator.associateTagToHandle(fh.getId(), std::move(tag));
GLFence* f = handle_cast<GLFence*>(fh);
GLFence* const f = handle_cast<GLFence*>(fh);
assert_invariant(f->state);
bool const platformCanCreateFence = mPlatform.canCreateFence();
@@ -1804,8 +1804,9 @@ void OpenGLDriver::createFenceR(Handle<HwFence> fh, ImmutableCString&& tag) {
return;
}
// This is the case where we need to use OpenGL fences
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
// This is the case where we need to use OpenGL fences, as soon as we return, the user
// is allowed to destroy the fence, so we need to keep a reference to the internal state.
std::weak_ptr<GLFence::State> const weak = f->state;
whenGpuCommandsComplete([weak] {
if (auto const state = weak.lock()) {
@@ -2291,76 +2292,81 @@ void OpenGLDriver::destroyFence(Handle<HwFence> fh) {
GLFence const* const f = handle_cast<GLFence*>(fh);
if (mPlatform.canCreateFence() || mContext.isES2()) {
mPlatform.destroyFence(f->fence);
} else {
// signal waiters it's time to give-up
std::unique_lock const lock(f->state->lock);
f->state->status = FenceStatus::ERROR;
f->state->cond.notify_all();
}
// note: it's invalid to call this during a fenceWait(fh) on another thread. For this
// reason there is no point signaling the waiters. There should be no waiters.
destruct(fh, f);
}
}
void OpenGLDriver::fenceCancel(FenceHandle fh) {
// Even though this is a synchronous call, the fence handle must be (and stay) valid
assert_invariant(fh);
GLFence const* const f = handle_cast<GLFence*>(fh);
assert_invariant(f->state);
std::lock_guard const lock(f->state->lock);
f->state->status = FenceStatus::ERROR;
f->state->cond.notify_all();
}
FenceStatus OpenGLDriver::getFenceStatus(Handle<HwFence> fh) {
return fenceWait(fh, 0);
}
FenceStatus OpenGLDriver::fenceWait(FenceHandle fh, uint64_t const timeout) {
if (fh) {
// we have to take into account that the STL's wait_for() actually works with
// time_points relative to steady_clock::now() internally.
using namespace std::chrono;
auto const now = steady_clock::now();
steady_clock::time_point until = steady_clock::time_point::max();
if (now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
until = now + nanoseconds(timeout);
}
// Even though this is a synchronous call, the fence handle must be (and stay) valid
assert_invariant(fh);
GLFence const* const f = handle_cast<GLFence*>(fh);
assert_invariant(f->state);
GLFence const* f = handle_cast<GLFence*>(fh);
assert_invariant(f->state);
bool const platformCanCreateFence = mPlatform.canCreateFence();
// immediately acquire a reference on our state, so that things don't go south if
// the HwFence gets destroyed (on the main thread) while we wait.
std::shared_ptr const state{ f->state };
if (mContext.isES2() || platformCanCreateFence) {
if (platformCanCreateFence) {
std::unique_lock lock(state->lock);
if (f->fence == nullptr) {
// we've been called before the fence was created asynchronously,
// so we need to wait for that, before using the real fence.
// By construction, "f" can't be destroyed while we wait, because its
// construction call is in the queue and a destroy call will have to come later.
state->cond.wait_until(lock, until, [f] {
return f->fence != nullptr;
});
if (f->fence == nullptr) {
// the only possible choice here is that we timed out
assert_invariant(state->status == FenceStatus::TIMEOUT_EXPIRED);
return FenceStatus::TIMEOUT_EXPIRED;
}
}
lock.unlock();
// here we know that we have the platform fence
assert_invariant(f->fence);
return mPlatform.waitFence(f->fence, timeout);
}
// platform doesn't support fences -- nothing we can do.
return FenceStatus::ERROR;
}
// This is the case where we need to use OpenGL fences
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
std::unique_lock lock(state->lock);
state->cond.wait_until(lock, until, [&state] {
return state->status != FenceStatus::TIMEOUT_EXPIRED;
});
return state->status;
#endif
// we have to take into account that the STL's wait_for() actually works with
// time_points relative to steady_clock::now() internally.
using namespace std::chrono;
auto const now = steady_clock::now();
steady_clock::time_point until = steady_clock::time_point::max();
if (now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
until = now + nanoseconds(timeout);
}
return FenceStatus::ERROR;
// we don't need to acquire a reference to f->state here because `f` already has one, and
// `f` is not supposed to become invalid while we wait.
bool const platformCanCreateFence = mPlatform.canCreateFence();
if (mContext.isES2() || platformCanCreateFence) {
if (platformCanCreateFence) {
std::unique_lock lock(f->state->lock);
if (f->fence == nullptr) {
// we've been called before the fence was created asynchronously,
// so we need to wait for that, before using the real fence.
// By construction, "f" can't be destroyed while we wait, because its
// construction call is in the queue and a destroy call will have to come later.
f->state->cond.wait_until(lock, until, [f] {
return f->fence != nullptr;
});
if (f->fence == nullptr) {
// the only possible choice here is that we timed out
assert_invariant(f->state->status == FenceStatus::TIMEOUT_EXPIRED);
return FenceStatus::TIMEOUT_EXPIRED;
}
}
lock.unlock();
// here we know that we have the platform fence
assert_invariant(f->fence);
return mPlatform.waitFence(f->fence, timeout);
}
// platform doesn't support fences -- nothing we can do.
return FenceStatus::ERROR;
}
// This is the case where we need to use OpenGL fences
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
std::unique_lock lock(f->state->lock);
f->state->cond.wait_until(lock, until, [f] {
return f->state->status != FenceStatus::TIMEOUT_EXPIRED;
});
return f->state->status;
#endif
}
void OpenGLDriver::getPlatformSync(Handle<HwSync> sh, CallbackHandler* handler,

View File

@@ -719,6 +719,10 @@ void PlatformEGLAndroid::SwapChainEGLAndroid::terminate(PlatformEGLAndroid& plat
}
bool PlatformEGLAndroid::SwapChainEGLAndroid::setPresentFrameId(uint64_t frameId) const noexcept {
if (!nativeWindow) {
// nativeWindow is null in the headless case
return false;
}
return mImpl.setPresentFrameId(nativeWindow, frameId);
}

View File

@@ -26,6 +26,8 @@
#include <dlfcn.h>
#include <mutex>
#define LIBRARY_GLX "libGL.so.1"
#define LIBRARY_X11 "libX11.so.6"

View File

@@ -77,46 +77,41 @@ struct VulkanFence : public HwFence, fvkmemory::ThreadSafeResource {
VulkanFence() {}
void setFence(std::shared_ptr<VulkanCmdFence> fence) {
std::lock_guard const lock(mState->lock);
mState->sharedFence = std::move(fence);
mState->cond.notify_all();
std::lock_guard const l(lock);
sharedFence = std::move(fence);
cond.notify_all();
}
std::shared_ptr<VulkanCmdFence>& getSharedFence() {
std::lock_guard const lock(mState->lock);
return mState->sharedFence;
std::lock_guard const l(lock);
return sharedFence;
}
std::pair<std::shared_ptr<VulkanCmdFence>, bool>
wait(std::chrono::steady_clock::time_point const until) {
// hold a reference so that our state doesn't disappear while we wait
std::shared_ptr state{ mState };
std::unique_lock lock(state->lock);
state->cond.wait_until(lock, until, [&state] {
return bool(state->sharedFence) || state->canceled;
std::unique_lock l(lock);
cond.wait_until(l, until, [this] {
return bool(sharedFence) || canceled;
});
// here mSharedFence will be null if we timed out
return { state->sharedFence, state->canceled };
return { sharedFence, canceled };
}
void cancel() const {
std::shared_ptr const state{ mState };
std::unique_lock const lock(state->lock);
if (state->sharedFence) {
state->sharedFence->cancel();
std::lock_guard const l(lock);
if (sharedFence) {
sharedFence->cancel();
}
state->canceled = true;
state->cond.notify_all();
canceled = true;
cond.notify_all();
}
private:
struct State {
std::mutex lock;
std::condition_variable cond;
bool canceled = false;
std::shared_ptr<VulkanCmdFence> sharedFence;
};
std::shared_ptr<State> mState{ std::make_shared<State>() };
mutable std::mutex lock;
mutable std::condition_variable cond;
mutable bool canceled = false;
std::shared_ptr<VulkanCmdFence> sharedFence;
};
struct VulkanSync : fvkmemory::ThreadSafeResource, public HwSync {
@@ -138,12 +133,12 @@ struct VulkanTimerQuery : public HwTimerQuery, fvkmemory::ThreadSafeResource {
mStoppingQueryIndex(stoppingIndex) {}
void setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept {
std::unique_lock const lock(mFenceMutex);
std::lock_guard const lock(mFenceMutex);
mFence = std::move(fence);
}
bool isCompleted() noexcept {
std::unique_lock const lock(mFenceMutex);
std::lock_guard const lock(mFenceMutex);
// QueryValue is a synchronous call and might occur before beginTimerQuery has written
// anything into the command buffer, which is an error according to the validation layer
// that ships in the Android NDK. Even when AVAILABILITY_BIT is set, validation seems to

View File

@@ -45,6 +45,7 @@
#endif
#include <chrono>
#include <mutex>
using namespace bluevk;
@@ -932,6 +933,9 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags);
swapChain.inc();
mResourceManager.associateHandle(sch.getId(), std::move(tag));
std::unique_lock<std::mutex> lock(mTiming.lock);
mTiming.nativeSwapchains.emplace(sch.getId(), swapChain->swapChain);
}
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
@@ -1106,6 +1110,9 @@ void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
mCurrentSwapChain = {};
}
swapChain.dec();
std::unique_lock<std::mutex> lock(mTiming.lock);
mTiming.nativeSwapchains.erase(sch.getId());
}
void VulkanDriver::destroyStream(Handle<HwStream> sh) {
@@ -1168,9 +1175,19 @@ void VulkanDriver::updateStreams(CommandStream* driver) {
}
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
if (fh) {
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
// note: it's invalid to call this during a fenceWait(fh) on another thread. For this
// reason there is no point signaling the waiters. There should be no waiters.
fence.dec();
}
}
void VulkanDriver::fenceCancel(FenceHandle const fh) {
// Even though this is a synchronous call, the fence handle must be (and stay) valid
assert_invariant(fh);
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
fence->cancel();
fence.dec();
}
FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> const fh) {
@@ -1178,6 +1195,9 @@ FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> const fh) {
}
FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout) {
// Even though this is a synchronous call, the fence handle must be (and stay) valid
assert_invariant(fh);
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
// we have to take into account that the STL's wait_for() actually works with
@@ -1783,12 +1803,15 @@ bool VulkanDriver::queryCompositorTiming(Handle<HwSwapChain> const swapChain,
if (!swapChain) {
return false;
}
auto sc = resource_ptr<VulkanSwapChain>::cast(&mResourceManager, swapChain);
if (!sc) {
// can happen if the SwapChainHandle is not initialized yet (still in CommandStream)
return false;
HandleId const id = swapChain.getId();
std::unique_lock<std::mutex> lock(mTiming.lock);
auto& swapchains = mTiming.nativeSwapchains;
if (auto itr = swapchains.find(id); itr != swapchains.end()) {
lock.unlock();
return mPlatform->queryCompositorTiming(itr->second, outCompositorTiming);
}
return sc->queryCompositorTiming(outCompositorTiming);
return false;
}
bool VulkanDriver::queryFrameTimestamps(Handle<HwSwapChain> const swapChain, uint64_t const frameId,
@@ -1797,12 +1820,14 @@ bool VulkanDriver::queryFrameTimestamps(Handle<HwSwapChain> const swapChain, uin
if (!swapChain) {
return false;
}
auto sc = resource_ptr<VulkanSwapChain>::cast(&mResourceManager, swapChain);
if (!sc) {
// can happen if the SwapChainHandle is not initialized yet (still in CommandStream)
return false;
HandleId const id = swapChain.getId();
std::unique_lock<std::mutex> lock(mTiming.lock);
auto& swapchains = mTiming.nativeSwapchains;
if (auto itr = swapchains.find(id); itr != swapchains.end()) {
lock.unlock();
return mPlatform->queryFrameTimestamps(itr->second, frameId, outFrameTimestamps);
}
return sc->queryFrameTimestamps(frameId, outFrameTimestamps);
return false;
}
void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {

View File

@@ -162,6 +162,15 @@ private:
VulkanQueryManager mQueryManager;
VulkanExternalImageManager mExternalImageManager;
// This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the
// Platform::Swapchain pointer, but queryFrameTimestamps() and queryCompositorTiming() are
// synchronous calls, making access to VulkanSwapchain unsafe (this difference vs other backends
// is due to the ref-counting of vulkan resources).
struct {
std::mutex lock;
std::unordered_map<HandleId, Platform::SwapChain*> nativeSwapchains;
} mTiming;
// This is necessary for us to write to push constants after binding a pipeline.
using DescriptorSetLayoutHandleList = std::array<resource_ptr<VulkanDescriptorSetLayout>,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;

View File

@@ -90,14 +90,6 @@ struct VulkanSwapChain : public HwSwapChain, fvkmemory::Resource {
mFrameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
}
bool queryCompositorTiming(CompositorTiming* outCompositorTiming) const {
return mPlatform->queryCompositorTiming(swapChain, outCompositorTiming);
}
bool queryFrameTimestamps(uint64_t frameId, FrameTimestamps* outFrameTimestamps) const {
return mPlatform->queryFrameTimestamps(swapChain, frameId, outFrameTimestamps);
}
private:
static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS;

View File

@@ -408,7 +408,6 @@ VkResult VulkanPlatformSurfaceSwapChain::recreate() {
}
void VulkanPlatformSurfaceSwapChain::destroy() {
VulkanPlatformSwapChainBase::destroy();
// The next part is not ideal. We don't have a good signal on when it's ok to destroy
// a swapchain. This is a spec oversight and mentioned as much:
// https://github.com/KhronosGroup/Vulkan-Docs/issues/1678
@@ -425,6 +424,8 @@ void VulkanPlatformSurfaceSwapChain::destroy() {
// phone). If necessary, we can revisit and implement the workaround [1].
vkQueueWaitIdle(mQueue);
VulkanPlatformSwapChainBase::destroy();
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
if (mImageReady[i] != VK_NULL_HANDLE) {
vkDestroySemaphore(mDevice, mImageReady[i], VKALLOC);
@@ -475,7 +476,6 @@ VkResult VulkanPlatformHeadlessSwapChain::acquire(VulkanPlatform::ImageSyncData*
}
void VulkanPlatformHeadlessSwapChain::destroy() {
VulkanPlatformSwapChainBase::destroy();
// This is only ever called from the destructor since headless does not recreate.
for (auto image: mSwapChainBundle.colors) {
vkDestroyImage(mDevice, image, VKALLOC);
@@ -485,7 +485,10 @@ void VulkanPlatformHeadlessSwapChain::destroy() {
}
}
mSwapChainBundle.colors.clear();
// No need to manually call through to the super because the super's destructor will be called
// Still need to call through to free the depth image. But must do it after releasing the color
// images.
VulkanPlatformSwapChainBase::destroy();
}
}// namespace filament::backend

View File

@@ -62,7 +62,7 @@ struct VulkanPlatformSwapChainBase : public Platform::SwapChain {
virtual bool queryFrameTimestamps(uint64_t frameId, FrameTimestamps* outFrameTimestamps) const;
protected:
virtual void destroy() = 0;
virtual void destroy();
VkImage createImage(VkExtent2D extent, VkFormat format, bool isProtected);

View File

@@ -675,6 +675,10 @@ void WebGPUDriver::destroyFence(Handle<HwFence> fenceHandle) {
}
}
void WebGPUDriver::fenceCancel(FenceHandle fh) {
// it's okay to implement cancel as a no-op, because not all API support truly canceling.
}
FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fenceHandle) {
const auto fence = handleCast<WebGPUFence>(fenceHandle);
if (!fence) {

View File

@@ -49,7 +49,7 @@ TEST(HandlesTest, useAfterFreePool) {
HandleAllocatorTest allocator("Test Handles", POOL_SIZE_BYTES);
Handle<MyHandle> handleA = allocator.allocate<Concrete>();
allocator.deallocate(handleA);
allocator.deallocate<Concrete>(handleA);
Handle<MyHandle> handleB = allocator.allocate<Concrete>();
@@ -69,7 +69,7 @@ TEST(HandlesTest, useAfterFreeHeap) {
// This one is guaranteed to be a heap handle.
Handle<MyHandle> handleA = allocator.allocate<Concrete>();
EXPECT_TRUE(handleA.getId() & HANDLE_HEAP_FLAG);
allocator.deallocate(handleA);
allocator.deallocate<Concrete>(handleA);
Handle<MyHandle> handleB = allocator.allocate<Concrete>();

View File

@@ -47,9 +47,10 @@ namespace filament {
using namespace utils;
using namespace backend;
FrameInfoManager::FrameInfoManager(DriverApi& driver) noexcept
FrameInfoManager::FrameInfoManager(FEngine& engine, DriverApi& driver) noexcept
: mJobQueue("FrameInfoGpuComplete", JobSystem::Priority::URGENT_DISPLAY),
mHasTimerQueries(driver.isFrameTimeSupported()) {
mHasTimerQueries(driver.isFrameTimeSupported()),
mDisableGpuFrameComplete(engine.features.engine.frame_info.disable_gpu_frame_complete_metric) {
if (mHasTimerQueries) {
for (auto& query : mQueries) {
query.handle = driver.createTimerQuery();
@@ -68,24 +69,29 @@ void FrameInfoManager::terminate(FEngine& engine) noexcept {
}
}
// Destroy the fences that are still alive, they will error out.
for (size_t i = 0, c = mFrameTimeHistory.size(); i < c; i++) {
auto& info = mFrameTimeHistory[i];
if (info.fence) {
driver.destroyFence(std::move(info.fence));
if (!mDisableGpuFrameComplete) {
// remove all pending callbacks. This is okay to do because they have no
// side effect.
mJobQueue.cancelAll();
// request cancel for all the fences, which may speed up drainAndExit() below
for (auto& info : mFrameTimeHistory) {
if (info.fence) {
driver.fenceCancel(info.fence);
}
}
// wait for all pending callbacks to be called & terminate the thread
mJobQueue.drainAndExit();
// Destroy the fences that are still alive, they will error out.
for (size_t i = 0, c = mFrameTimeHistory.size(); i < c; i++) {
auto& info = mFrameTimeHistory[i];
if (info.fence) {
driver.destroyFence(std::move(info.fence));
}
}
}
// for extra safety submit the current command buffer (because nothing else will while we
// wait in drainAndExit()), this is in case the backend is already waiting on a h/w fence
// e.g. vkWaitForFences().
driver.flush();
// make sure the driver commands above will be processed
engine.flush();
// wait for all pending callbacks to be called & terminate the thread
mJobQueue.drainAndExit();
}
void FrameInfoManager::beginFrame(FSwapChain* swapChain, DriverApi& driver,
@@ -95,8 +101,10 @@ void FrameInfoManager::beginFrame(FSwapChain* swapChain, DriverApi& driver,
auto& history = mFrameTimeHistory;
// don't exceed the capacity, drop the oldest entry
if (UTILS_LIKELY(history.size() == history.capacity())) {
assert_invariant(history.back().fence);
driver.destroyFence(std::move(history.back().fence));
if (!mDisableGpuFrameComplete) {
assert_invariant(history.back().fence);
driver.destroyFence(std::move(history.back().fence));
}
history.pop_back();
}
@@ -185,46 +193,51 @@ void FrameInfoManager::beginFrame(FSwapChain* swapChain, DriverApi& driver,
}
void FrameInfoManager::endFrame(DriverApi& driver) noexcept {
// create a Fence to capture the GPU complete time
FenceHandle const fence = driver.createFence();
auto& front = mFrameTimeHistory.front();
front.fence = fence;
front.endFrame = std::chrono::steady_clock::now();
if (!mDisableGpuFrameComplete) {
// create a Fence to capture the GPU complete time
FenceHandle const fence = driver.createFence();
front.fence = fence;
}
if (mHasTimerQueries) {
// close the timer query
driver.endTimerQuery(mQueries[mIndex].handle);
}
// queue custom backend command to query the current time
driver.queueCommand([&jobQueue = mJobQueue, &driver, &front] {
driver.queueCommand([&jobQueue = mJobQueue, &driver, &front,
disableGpuFrameComplete = mDisableGpuFrameComplete] {
// backend frame end-time
front.backendEndFrame = std::chrono::steady_clock::now();
if (UTILS_UNLIKELY(!jobQueue.isValid())) {
if (UTILS_UNLIKELY(disableGpuFrameComplete || !jobQueue.isValid())) {
front.gpuFrameComplete = {};
front.ready.store(true, std::memory_order_release);
return;
}
// now launch a job that'll wait for the gpu to complete
jobQueue.push([&driver, &front] {
FenceStatus const status = driver.fenceWait(front.fence, FENCE_WAIT_FOR_EVER);
if (status == FenceStatus::CONDITION_SATISFIED) {
front.gpuFrameComplete = std::chrono::steady_clock::now();
} else if (status == FenceStatus::TIMEOUT_EXPIRED) {
// that should never happen because:
// - we wait forever
// - made sure that the createFence() command was processed on the backed
// (because we're inside a custom command)
} else {
// We got an error, fenceWait might not be supported
front.gpuFrameComplete = {};
}
// finally, signal that the data is available
front.ready.store(true, std::memory_order_release);
});
if (!disableGpuFrameComplete) {
// now launch a job that'll wait for the gpu to complete
jobQueue.push([&driver, &front] {
FenceStatus const status = driver.fenceWait(front.fence, FENCE_WAIT_FOR_EVER);
if (status == FenceStatus::CONDITION_SATISFIED) {
front.gpuFrameComplete = std::chrono::steady_clock::now();
} else if (status == FenceStatus::TIMEOUT_EXPIRED) {
// that should never happen because:
// - we wait forever
// - made sure that the createFence() command was processed on the backed
// (because we're inside a custom command)
} else {
// We got an error, fenceWait might not be supported
front.gpuFrameComplete = {};
}
// finally, signal that the data is available
front.ready.store(true, std::memory_order_release);
});
}
});
mIndex = (mIndex + 1) % POOL_COUNT;

View File

@@ -35,6 +35,7 @@
#include <array>
#include <atomic>
#include <chrono>
#include <iterator>
#include <ratio>
#include <type_traits>
@@ -130,6 +131,41 @@ public:
using reference = value_type&;
using const_reference = value_type const&;
private:
template<typename U>
class Iterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = typename std::remove_const<T>::type;
using difference_type = std::ptrdiff_t;
using pointer = U*;
using reference = U&;
using QueuePtr = typename std::conditional<std::is_const<U>::value,
const CircularQueue*, CircularQueue*>::type;
Iterator() = default;
Iterator(QueuePtr queue, size_t pos) noexcept : mQueue(queue), mPos(pos) {}
// allow conversion from iterator to const_iterator
operator Iterator<const T>() const { return { mQueue, mPos }; }
reference operator*() const { return (*mQueue)[mPos]; }
pointer operator->() const { return &(*mQueue)[mPos]; }
Iterator& operator++() { ++mPos; return *this; }
Iterator operator++(int) { Iterator temp = *this; ++(*this); return temp; }
friend bool operator==(const Iterator& a, const Iterator& b) { return a.mQueue == b.mQueue && a.mPos == b.mPos; }
friend bool operator!=(const Iterator& a, const Iterator& b) { return !(a == b); }
private:
QueuePtr mQueue = nullptr;
size_t mPos = 0;
};
public:
using iterator = Iterator<T>;
using const_iterator = Iterator<const T>;
CircularQueue() = default;
~CircularQueue() {
@@ -162,6 +198,13 @@ public:
return *this;
}
iterator begin() noexcept { return iterator(this, 0); }
iterator end() noexcept { return iterator(this, size()); }
const_iterator begin() const noexcept { return const_iterator(this, 0); }
const_iterator end() const noexcept { return const_iterator(this, size()); }
const_iterator cbegin() const noexcept { return const_iterator(this, 0); }
const_iterator cend() const noexcept { return const_iterator(this, size()); }
size_t capacity() const noexcept {
return CAPACITY;
}
@@ -211,7 +254,9 @@ public:
}
T const& operator[](size_t pos) const noexcept {
return const_cast<CircularQueue&>(*this)[pos];
assert_invariant(pos < size());
size_t const index = (mFront + CAPACITY - pos) % CAPACITY;
return *std::launder(reinterpret_cast<T const*>(&mStorage[index]));
}
T const& front() const noexcept {
@@ -256,7 +301,7 @@ public:
uint32_t historySize;
};
explicit FrameInfoManager(backend::DriverApi& driver) noexcept;
explicit FrameInfoManager(FEngine& engine, backend::DriverApi& driver) noexcept;
~FrameInfoManager() noexcept;
@@ -296,6 +341,7 @@ private:
utils::AsyncJobQueue mJobQueue;
FSwapChain* mLastSeenSwapChain = nullptr;
bool const mHasTimerQueries = false;
bool const mDisableGpuFrameComplete = false;
};

View File

@@ -758,6 +758,9 @@ public:
CORRECTNESS_ASSERTION_DEFAULT;
bool assert_texture_can_generate_mipmap = CORRECTNESS_ASSERTION_DEFAULT;
} debug;
struct {
bool disable_gpu_frame_complete_metric = false;
} frame_info;
} engine;
struct {
struct {
@@ -829,6 +832,9 @@ public:
{ "material.enable_material_instance_uniform_batching",
"Make all MaterialInstances share a common large uniform buffer and use sub-allocations within it.",
&features.material.enable_material_instance_uniform_batching, false },
{ "engine.frame_info.disable_gpu_complete_metric",
"Disable Renderer::FrameInfo::gpuFrameComplete reporting",
&features.engine.frame_info.disable_gpu_frame_complete_metric, false },
}};
utils::Slice<const FeatureFlag> getFeatureFlags() const noexcept {

View File

@@ -93,7 +93,7 @@ FRenderer::FRenderer(FEngine& engine) :
mEngine(engine),
mFrameSkipper(),
mRenderTargetHandle(engine.getDefaultRenderTarget()),
mFrameInfoManager(engine.getDriverApi()),
mFrameInfoManager(engine, engine.getDriverApi()),
mHdrTranslucent(TextureFormat::RGBA16F),
mHdrQualityMedium(TextureFormat::R11F_G11F_B10F),
mHdrQualityHigh(TextureFormat::RGB16F),

View File

@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.67.0"
spec.version = "1.67.1"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.67.0/filament-v1.67.0-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.67.1/filament-v1.67.1-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {

View File

@@ -28,7 +28,7 @@
namespace filament {
// update this when a new version of filament wouldn't work with older materials
static constexpr size_t MATERIAL_VERSION = 66;
static constexpr size_t MATERIAL_VERSION = 67;
/**
* Supported shading models

View File

@@ -42,6 +42,9 @@ public:
// drainAndExit() must be called first
~AsyncJobQueue() noexcept;
// cancel all pending jobs, but doesn't exist the thread
void cancelAll() noexcept;
// blocks until all jobs are executed and quits the thread
void drainAndExit();

View File

@@ -60,6 +60,13 @@ AsyncJobQueue::~AsyncJobQueue() noexcept {
#endif
}
void AsyncJobQueue::cancelAll() noexcept {
#if !defined(__EMSCRIPTEN__)
std::unique_lock const lock(mLock);
mQueue.clear();
#endif
}
void AsyncJobQueue::push(Job&& job) {
#if !defined(__EMSCRIPTEN__)
std::unique_lock lock(mLock);

View File

@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.67.0",
"version": "1.67.1",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",