Compare commits
9 Commits
ImmediateG
...
rc/1.67.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e4029532f | ||
|
|
be7149918d | ||
|
|
f95a58ad72 | ||
|
|
5a9b56a1b7 | ||
|
|
8310896232 | ||
|
|
cc66f7c230 | ||
|
|
5695a5b43d | ||
|
|
3cd2f0c386 | ||
|
|
93437c33f8 |
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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, ...);
|
||||
|
||||
|
||||
60
filament/backend/src/AndroidNdk.cpp
Normal file
60
filament/backend/src/AndroidNdk.cpp
Normal 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
|
||||
79
filament/backend/src/AndroidNdk.h
Normal file
79
filament/backend/src/AndroidNdk.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#define LIBRARY_GLX "libGL.so.1"
|
||||
#define LIBRARY_X11 "libX11.so.6"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user