Compare commits
67
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
531bfd6f6e | ||
|
|
db272484f9 | ||
|
|
9dadf400d2 | ||
|
|
16c7a3c968 | ||
|
|
626f338cba | ||
|
|
ad27d48fd3 | ||
|
|
3a503976c8 | ||
|
|
ce6fa82026 | ||
|
|
e8349ab5cc | ||
|
|
4773fc4647 | ||
|
|
d47a69a529 | ||
|
|
d3b74e96b4 | ||
|
|
209d3f7550 | ||
|
|
92e65cb3fd | ||
|
|
a4945939de | ||
|
|
086760b307 | ||
|
|
ce1b63ce38 | ||
|
|
b7b8983653 | ||
|
|
d52fb1f4fd | ||
|
|
901c87761b | ||
|
|
081fe6a434 | ||
|
|
4e05cd73c6 | ||
|
|
49f9708312 | ||
|
|
2791195a92 | ||
|
|
65f78c06bb | ||
|
|
71175ee410 | ||
|
|
c9b310450c | ||
|
|
54c636d54c | ||
|
|
f503205be2 | ||
|
|
02106110fe | ||
|
|
5a0f78d915 | ||
|
|
f548c588d9 | ||
|
|
487fb273f1 | ||
|
|
111ac62d2f | ||
|
|
2fac13e154 | ||
|
|
bcae04c9ef | ||
|
|
55fc91b35f | ||
|
|
ff2827b226 | ||
|
|
13e1d2a042 | ||
|
|
b09cbaf0db | ||
|
|
b570d489e3 | ||
|
|
7f7daddbfa | ||
|
|
cfe6a423d3 | ||
|
|
ba99844f22 | ||
|
|
ea4c20ec2c | ||
|
|
0a661c1f3c | ||
|
|
021c4f6691 | ||
|
|
be6b6ae7be | ||
|
|
ad3a5b70b6 | ||
|
|
80803f9693 | ||
|
|
3b0e44c9a3 | ||
|
|
c5c2d42467 | ||
|
|
ff145f6ccf | ||
|
|
48bb10a4b5 | ||
|
|
01a2480175 | ||
|
|
9e921936c6 | ||
|
|
8bc4d3a9df | ||
|
|
0fdf1cac1d | ||
|
|
387942cc39 | ||
|
|
c81cedd887 | ||
|
|
abe8a49d17 | ||
|
|
96cba96058 | ||
|
|
4fccd957db | ||
|
|
d49554d5ce | ||
|
|
867161773e | ||
|
|
e2658506f6 | ||
|
|
6218164f79 |
@@ -179,7 +179,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Run build script
|
||||
run: ./build.sh -W debug test_filamat filament
|
||||
run: ./build.sh -W debug test_filamat filament gltf_viewer
|
||||
- name: Run test
|
||||
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
|
||||
|
||||
@@ -6,3 +6,5 @@
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
|
||||
|
||||
@@ -147,6 +147,15 @@ Java_com_google_android_filament_View_nSetDynamicResolutionOptions(JNIEnv*, jcla
|
||||
view->setDynamicResolutionOptions(options);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nGetLastDynamicResolutionScale(JNIEnv *env, jclass, jlong nativeView, jfloatArray out_) {
|
||||
jfloat* out = env->GetFloatArrayElements(out_, nullptr);
|
||||
View *view = (View *) nativeView;
|
||||
math::float2 result = view->getLastDynamicResolutionScale();
|
||||
std::copy_n(result.v, 2, out);
|
||||
env->ReleaseFloatArrayElements(out_, out, 0);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetShadowType(JNIEnv*, jclass, jlong nativeView, jint type) {
|
||||
View* view = (View*) nativeView;
|
||||
|
||||
@@ -99,6 +99,15 @@ final class Asserts {
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull @Size(min = 2)
|
||||
static float[] assertFloat2(@Nullable float[] out) {
|
||||
if (out == null) out = new float[2];
|
||||
else if (out.length < 2) {
|
||||
throw new ArrayIndexOutOfBoundsException("Array length must be at least 2");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@NonNull @Size(min = 4)
|
||||
static float[] assertFloat4(@Nullable float[] out) {
|
||||
if (out == null) out = new float[4];
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.EnumSet;
|
||||
|
||||
import static com.google.android.filament.Asserts.assertFloat3In;
|
||||
import static com.google.android.filament.Asserts.assertFloat4In;
|
||||
import static com.google.android.filament.Colors.LinearColor;
|
||||
|
||||
import com.google.android.filament.proguard.UsedByNative;
|
||||
|
||||
@@ -674,6 +673,21 @@ public class View {
|
||||
return mDynamicResolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last dynamic resolution scale factor used by this view. This value is updated
|
||||
* when Renderer::render(View*) is called
|
||||
* @param out A 2-float array where the value will be stored, or null in which case the array is
|
||||
* allocated.
|
||||
* @return A 2-float array containing the horizontal and the vertical scale factors
|
||||
* @see Renderer#render(View)
|
||||
*/
|
||||
@NonNull @Size(min = 2)
|
||||
public float[] getLastDynamicResolutionScale(@Nullable @Size(min = 2) float[] out) {
|
||||
out = Asserts.assertFloat2(out);
|
||||
nGetLastDynamicResolutionScale(getNativeObject(), out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rendering quality for this view (e.g. color precision).
|
||||
*
|
||||
@@ -1317,6 +1331,7 @@ public class View {
|
||||
private static native void nSetDithering(long nativeView, int dithering);
|
||||
private static native int nGetDithering(long nativeView);
|
||||
private static native void nSetDynamicResolutionOptions(long nativeView, boolean enabled, boolean homogeneousScaling, float minScale, float maxScale, float sharpness, int quality);
|
||||
private static native void nGetLastDynamicResolutionScale(long nativeView, float[] out);
|
||||
private static native void nSetRenderQuality(long nativeView, int hdrColorBufferQuality);
|
||||
private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar);
|
||||
private static native void nSetShadowType(long nativeView, int type);
|
||||
|
||||
+14
@@ -40,7 +40,21 @@ fun loadTexture(engine: Engine, resources: Resources, resourceId: Int, type: Tex
|
||||
options.inPremultiplied = type == TextureType.COLOR
|
||||
|
||||
val bitmap = BitmapFactory.decodeResource(resources, resourceId, options)
|
||||
return buildTexture(engine, bitmap, type)
|
||||
}
|
||||
|
||||
fun loadTexture(engine: Engine, bytes: ByteArray, type: TextureType, offset: Int = 0, length: Int = bytes.size): Texture {
|
||||
val options = BitmapFactory.Options()
|
||||
// Color is the only type of texture we want to pre-multiply with the alpha channel
|
||||
// Pre-multiplication is the default behavior, so we need to turn it off here
|
||||
options.inPremultiplied = type == TextureType.COLOR
|
||||
|
||||
val bitmap = BitmapFactory.decodeByteArray(bytes, offset, length, options)
|
||||
return buildTexture(engine, bitmap, type)
|
||||
}
|
||||
|
||||
|
||||
private fun buildTexture(engine: Engine, bitmap: Bitmap, type: TextureType): Texture {
|
||||
val texture = Texture.Builder()
|
||||
.width(bitmap.width)
|
||||
.height(bitmap.height)
|
||||
|
||||
@@ -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)
|
||||
@@ -213,6 +214,14 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/VulkanMemory.h
|
||||
src/vulkan/VulkanPipelineCache.cpp
|
||||
src/vulkan/VulkanPipelineCache.h
|
||||
src/vulkan/VulkanPipelineStateSerializer.cpp
|
||||
src/vulkan/VulkanPipelineStateSerializer.h
|
||||
src/vulkan/VulkanSamplerStateSerializer.h
|
||||
src/vulkan/VulkanSamplerStateSerializer.cpp
|
||||
src/vulkan/VulkanRenderPassStateSerializer.h
|
||||
src/vulkan/VulkanRenderPassStateSerializer.cpp
|
||||
src/vulkan/VulkanDescriptorSetLayoutSerializer.h
|
||||
src/vulkan/VulkanDescriptorSetLayoutSerializer.cpp
|
||||
src/vulkan/VulkanPipelineLayoutCache.cpp
|
||||
src/vulkan/VulkanPipelineLayoutCache.h
|
||||
src/vulkan/VulkanQueryManager.cpp
|
||||
@@ -251,10 +260,21 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/utils/Spirv.h
|
||||
src/vulkan/utils/StaticVector.h
|
||||
)
|
||||
if (LINUX OR WIN32)
|
||||
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp)
|
||||
if (LINUX)
|
||||
list(APPEND SRCS
|
||||
src/vulkan/platform/VulkanPlatformLinux.cpp
|
||||
include/backend/platforms/VulkanPlatformLinux.h
|
||||
)
|
||||
elseif (WIN32)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatformWindows.h
|
||||
src/vulkan/platform/VulkanPlatformWindows.cpp
|
||||
)
|
||||
elseif (APPLE OR IOS)
|
||||
list(APPEND SRCS src/vulkan/platform/VulkanPlatformApple.mm)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatformApple.h
|
||||
src/vulkan/platform/VulkanPlatformApple.mm
|
||||
)
|
||||
elseif (ANDROID)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatformAndroid.h
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -437,22 +437,14 @@ protected:
|
||||
std::shared_ptr<VulkanCmdFence> fenceStatus;
|
||||
};
|
||||
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
|
||||
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const = 0;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept;
|
||||
uint64_t flags) const noexcept = 0;
|
||||
|
||||
virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept;
|
||||
|
||||
private:
|
||||
// Platform dependent helper methods
|
||||
static ExtensionSet getSwapchainInstanceExtensionsImpl();
|
||||
|
||||
// Platform dependent helper methods
|
||||
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) noexcept;
|
||||
|
||||
friend struct VulkanPlatformPrivate;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanPlatformApple : public VulkanPlatform {
|
||||
protected:
|
||||
ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanPlatformLinux : public VulkanPlatform {
|
||||
protected:
|
||||
ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanPlatformWindows : public VulkanPlatform {
|
||||
protected:
|
||||
ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H
|
||||
@@ -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, ...);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -67,8 +67,12 @@
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
#if defined(__ANDROID__)
|
||||
#include "backend/platforms/VulkanPlatformAndroid.h"
|
||||
#else
|
||||
#include "backend/platforms/VulkanPlatform.h"
|
||||
#elif defined(__APPLE__)
|
||||
#include "backend/platforms/VulkanPlatformApple.h"
|
||||
#elif defined(__linux__)
|
||||
#include "backend/platforms/VulkanPlatformLinux.h"
|
||||
#elif defined(WIN32)
|
||||
#include "backend/platforms/VulkanPlatformWindows.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -117,8 +121,14 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
#if defined(__ANDROID__)
|
||||
return new VulkanPlatformAndroid();
|
||||
#elif defined(__APPLE__)
|
||||
return new VulkanPlatformApple();
|
||||
#elif defined(__linux__)
|
||||
return new VulkanPlatformLinux();
|
||||
#elif defined(WIN32)
|
||||
return new VulkanPlatformWindows();
|
||||
#else
|
||||
return new VulkanPlatform();
|
||||
return nullptr;
|
||||
#endif
|
||||
#else
|
||||
return nullptr;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "VulkanDescriptorSetLayoutCache.h"
|
||||
#include "VulkanDescriptorSetLayoutSerializer.h"
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
@@ -100,22 +101,32 @@ uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
|
||||
return count;
|
||||
}
|
||||
|
||||
uint64_t computeImmutableSamplerHash(utils::FixedCapacityVector<VkSampler> const& samplers) {
|
||||
uint64_t computeImmutableSamplerHash(utils::FixedCapacityVector<VkSampler> const& samplers, VulkanSamplerCache* cache) {
|
||||
size_t const size = samplers.size();
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
} else if (size == 1) {
|
||||
return (uint64_t) samplers[0];
|
||||
}
|
||||
return utils::hash::murmur3((uint32_t*) samplers.data(), samplers.size() * 2, 0);
|
||||
|
||||
utils::FixedCapacityVector<uint32_t> samplerHash;
|
||||
if (size > 0) {
|
||||
samplerHash.reserve(MAX_SAMPLER_COUNT);
|
||||
for (auto sampler : samplers) {
|
||||
samplerHash.push_back(cache->getKey(sampler));
|
||||
}
|
||||
}
|
||||
|
||||
if (size == 1) {
|
||||
return (uint64_t) samplerHash[0];
|
||||
}
|
||||
return utils::hash::murmur3((uint32_t*) samplerHash.data(), samplerHash.size() * 2, 0);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanDescriptorSetLayoutCache::VulkanDescriptorSetLayoutCache(VkDevice device,
|
||||
fvkmemory::ResourceManager* resourceManager)
|
||||
fvkmemory::ResourceManager* resourceManager, VulkanSamplerCache* cache)
|
||||
: mDevice(device),
|
||||
mResourceManager(resourceManager) {}
|
||||
mResourceManager(resourceManager), mSamplerCache(cache) {}
|
||||
|
||||
VulkanDescriptorSetLayoutCache::~VulkanDescriptorSetLayoutCache() = default;
|
||||
|
||||
@@ -131,7 +142,7 @@ VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
|
||||
utils::FixedCapacityVector<VkSampler> immutableSamplers) {
|
||||
LayoutKey key = {
|
||||
.bitmask = bitmasks,
|
||||
.immutableSamplerHash = computeImmutableSamplerHash(immutableSamplers),
|
||||
.immutableSamplerHash = computeImmutableSamplerHash(immutableSamplers, mSamplerCache),
|
||||
};
|
||||
if (auto itr = mVkLayouts.find(key); itr != mVkLayouts.end()) {
|
||||
return itr->second;
|
||||
@@ -153,9 +164,20 @@ VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
|
||||
.bindingCount = count,
|
||||
.pBindings = toBind,
|
||||
};
|
||||
LayoutKeyHashFn hashFunc;
|
||||
uint32_t hashedKey = hashFunc(key);
|
||||
utils::FixedCapacityVector<uint32_t> immutableSamplersHashes;
|
||||
immutableSamplersHashes.reserve(MAX_SAMPLER_COUNT);
|
||||
for (auto sampler: immutableSamplers) {
|
||||
immutableSamplersHashes.push_back(mSamplerCache->getKey(sampler));
|
||||
}
|
||||
VulkanDescriptorSetLayoutSerializer setSer(dlinfo, immutableSamplersHashes, hashedKey);
|
||||
|
||||
VkDescriptorSetLayout vklayout;
|
||||
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
|
||||
mVkLayouts[key] = vklayout;
|
||||
|
||||
mLayoutToKey[vklayout] = hashedKey;
|
||||
return vklayout;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include "VulkanSamplerCache.h"
|
||||
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
@@ -35,7 +37,8 @@ namespace filament::backend {
|
||||
|
||||
class VulkanDescriptorSetLayoutCache {
|
||||
public:
|
||||
VulkanDescriptorSetLayoutCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
||||
VulkanDescriptorSetLayoutCache(VkDevice device, fvkmemory::ResourceManager* resourceManager,
|
||||
VulkanSamplerCache* cache);
|
||||
~VulkanDescriptorSetLayoutCache();
|
||||
|
||||
void terminate() noexcept;
|
||||
@@ -48,10 +51,19 @@ public:
|
||||
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
||||
fvkutils::SamplerBitmask externalSamplers,
|
||||
utils::FixedCapacityVector<VkSampler> immutableSamplers = {});
|
||||
uint32_t getKey(VkDescriptorSetLayout layout) {
|
||||
uint32_t key = 0;
|
||||
auto iter = mLayoutToKey.find(layout);
|
||||
if (iter != mLayoutToKey.end()) {
|
||||
key = iter->second;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
fvkmemory::ResourceManager* mResourceManager;
|
||||
VulkanSamplerCache* mSamplerCache;
|
||||
|
||||
struct LayoutKey {
|
||||
// this describes the layout using bitset.
|
||||
@@ -69,6 +81,7 @@ private:
|
||||
};
|
||||
|
||||
tsl::robin_map<LayoutKey, VkDescriptorSetLayout, LayoutKeyHashFn, LayoutKeyEqual> mVkLayouts;
|
||||
std::map<VkDescriptorSetLayout, uint32_t> mLayoutToKey;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "VulkanDescriptorSetLayoutSerializer.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
namespace filament::backend {
|
||||
VulkanDescriptorSetLayoutSerializer::VulkanDescriptorSetLayoutSerializer(
|
||||
const VkDescriptorSetLayoutCreateInfo& info,
|
||||
utils::FixedCapacityVector<uint32_t> immutableSamplers, uint32_t hash) {
|
||||
std::stringstream filename;
|
||||
filename << "descriptor_set_layout_" << hash << ".json";
|
||||
|
||||
std::ofstream file(filename.str());
|
||||
if (file.is_open()) {
|
||||
std::stringstream buffer;
|
||||
buffer << "[" << std::endl;
|
||||
for (uint32_t i = 0; i < info.bindingCount; ++i) {
|
||||
buffer << "{" << std::endl;
|
||||
buffer << "\"binding\":" << info.pBindings[i].binding << "," << std::endl;
|
||||
buffer << "\"descriptor_type\":" << info.pBindings[i].descriptorType << "," << std::endl;
|
||||
buffer << "\"stage_flags\":" << info.pBindings[i].stageFlags;
|
||||
if (immutableSamplers.size()) {
|
||||
buffer << "," << std::endl;
|
||||
buffer << "\"immutable_samplers\":[" << std::endl;
|
||||
for (uint32_t i = 0; i < immutableSamplers.size(); ++i) {
|
||||
buffer << immutableSamplers[i];
|
||||
if (i < immutableSamplers.size() - 1) {
|
||||
buffer << ",";
|
||||
}
|
||||
}
|
||||
buffer << "]";
|
||||
}
|
||||
buffer << std::endl;
|
||||
buffer << "}";
|
||||
if (i < info.bindingCount - 1) {
|
||||
buffer << ",";
|
||||
}
|
||||
buffer << std::endl;
|
||||
}
|
||||
buffer << "]" << std::endl;
|
||||
|
||||
file << buffer.str();
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
VulkanPipelineLayoutSerializer::VulkanPipelineLayoutSerializer(
|
||||
const VkPipelineLayoutCreateInfo& info, VulkanDescriptorSetLayoutCache* cache,
|
||||
uint32_t hash) {
|
||||
std::stringstream filename;
|
||||
filename << "pipeline_layout_" << hash << ".json";
|
||||
std::ofstream file(filename.str());
|
||||
if (file.is_open()) {
|
||||
std::stringstream buffer;
|
||||
buffer << "{" << std::endl;
|
||||
buffer << "\"flags\":" << info.flags << "," << std::endl;
|
||||
|
||||
buffer << "\"layouts\":[" << std::endl;
|
||||
for (uint32_t i = 0; i < info.setLayoutCount; ++i) {
|
||||
buffer << cache->getKey(info.pSetLayouts[i]);
|
||||
if (i < info.setLayoutCount - 1) {
|
||||
buffer << ",";
|
||||
}
|
||||
}
|
||||
buffer << "]," << std::endl;
|
||||
|
||||
buffer << "\"push_constants\":[" << std::endl;
|
||||
for (uint32_t i = 0; i < info.pushConstantRangeCount; ++i) {
|
||||
buffer << "{" << std::endl;
|
||||
buffer << "\"stage_flags\":" << info.pPushConstantRanges[i].stageFlags << "," << std::endl;
|
||||
buffer << "\"offset\":" << info.pPushConstantRanges[i].offset << ","
|
||||
<< std::endl;
|
||||
buffer << "\"size\":" << info.pPushConstantRanges[i].size
|
||||
<< std::endl;
|
||||
buffer << "}" << std::endl;
|
||||
if (i < info.pushConstantRangeCount - 1) {
|
||||
buffer << ",";
|
||||
}
|
||||
}
|
||||
buffer << "]" << std::endl;
|
||||
|
||||
buffer << "}" << std::endl;
|
||||
file << buffer.str();
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 TNT_FILAMENT_BACKEND_VULKANDESCRIPTORSETLAYOUTSERIALIZER_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANDESCRIPTORSETLAYOUTSERIALIZER_H
|
||||
|
||||
#include "VulkanPipelineLayoutCache.h"
|
||||
#include "VulkanYcbcrConversionCache.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanDescriptorSetLayoutSerializer {
|
||||
public:
|
||||
VulkanDescriptorSetLayoutSerializer(VulkanDescriptorSetLayoutSerializer const&) = delete;
|
||||
VulkanDescriptorSetLayoutSerializer& operator=(
|
||||
VulkanDescriptorSetLayoutSerializer const&) = delete;
|
||||
|
||||
VulkanDescriptorSetLayoutSerializer(const VkDescriptorSetLayoutCreateInfo& info,
|
||||
utils::FixedCapacityVector<uint32_t> immutableSamplers,
|
||||
uint32_t hash);
|
||||
};
|
||||
|
||||
class VulkanPipelineLayoutSerializer {
|
||||
public:
|
||||
VulkanPipelineLayoutSerializer(VulkanPipelineLayoutSerializer const&) = delete;
|
||||
VulkanPipelineLayoutSerializer& operator=(VulkanPipelineLayoutSerializer const&) = delete;
|
||||
|
||||
VulkanPipelineLayoutSerializer(const VkPipelineLayoutCreateInfo& layout,
|
||||
VulkanDescriptorSetLayoutCache* cache, uint32_t hash);
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANDESCRIPTORSETLAYOUTSERIALIZER_H
|
||||
@@ -45,6 +45,7 @@
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -229,16 +230,16 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
|
||||
mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(),
|
||||
mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(),
|
||||
mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext, &mSemaphoreManager),
|
||||
mPipelineLayoutCache(mPlatform->getDevice()),
|
||||
mPipelineCache(mPlatform->getDevice()),
|
||||
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
|
||||
mBufferCache(mContext, mResourceManager, mAllocator),
|
||||
mFramebufferCache(mPlatform->getDevice()),
|
||||
mYcbcrConversionCache(mPlatform->getDevice()),
|
||||
mSamplerCache(mPlatform->getDevice()),
|
||||
mSamplerCache(mPlatform->getDevice(), &mYcbcrConversionCache),
|
||||
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
|
||||
mReadPixels(mPlatform->getDevice()),
|
||||
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager, &mSamplerCache),
|
||||
mPipelineLayoutCache(mPlatform->getDevice(), &mDescriptorSetLayoutCache),
|
||||
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mQueryManager(mPlatform->getDevice()),
|
||||
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
|
||||
@@ -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
|
||||
@@ -1646,7 +1666,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
rpkey.subpassMask = uint8_t(params.subpassMask);
|
||||
|
||||
VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey);
|
||||
mPipelineCache.bindRenderPass(renderPass, 0);
|
||||
mPipelineCache.bindRenderPass(renderPass, mFramebufferCache.getRenderPassKey(renderPass), 0);
|
||||
|
||||
// Create the VkFramebuffer or fetch it from cache.
|
||||
VulkanFboCache::FboKey fbkey = rt->getFboKey();
|
||||
@@ -1765,6 +1785,7 @@ void VulkanDriver::nextSubpass(int) {
|
||||
VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass,
|
||||
mFramebufferCache.getRenderPassKey(mCurrentRenderPass.renderPass),
|
||||
++mCurrentRenderPass.currentSubpass);
|
||||
|
||||
if (mCurrentRenderPass.params.subpassMask & 0x1) {
|
||||
@@ -1783,12 +1804,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 +1821,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) {
|
||||
@@ -2135,7 +2161,8 @@ void VulkanDriver::bindPipelineImpl(PipelineState const& pipelineState,
|
||||
mPipelineState.pipelineLayout = pipelineLayout;
|
||||
mPipelineState.descriptorSetMask = descriptorSetMask;
|
||||
|
||||
mPipelineCache.bindLayout(pipelineLayout);
|
||||
auto key = mPipelineLayoutCache.getKey(pipelineLayout);
|
||||
mPipelineCache.bindLayout(pipelineLayout, key);
|
||||
mPipelineCache.bindPipeline(mCurrentRenderPass.commandBuffer);
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,6 @@ private:
|
||||
|
||||
VulkanSemaphoreManager mSemaphoreManager;
|
||||
VulkanCommands mCommands;
|
||||
VulkanPipelineLayoutCache mPipelineLayoutCache;
|
||||
VulkanPipelineCache mPipelineCache;
|
||||
VulkanStagePool mStagePool;
|
||||
VulkanBufferCache mBufferCache;
|
||||
@@ -158,10 +157,20 @@ private:
|
||||
VulkanBlitter mBlitter;
|
||||
VulkanReadPixels mReadPixels;
|
||||
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
|
||||
VulkanPipelineLayoutCache mPipelineLayoutCache;
|
||||
VulkanDescriptorSetCache mDescriptorSetCache;
|
||||
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>;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "VulkanFboCache.h"
|
||||
#include "VulkanRenderPassStateSerializer.h"
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "vulkan/utils/Image.h"
|
||||
@@ -69,6 +70,15 @@ VulkanFboCache::~VulkanFboCache() {
|
||||
<< "Please explicitly call terminate() while the VkDevice is still alive.";
|
||||
}
|
||||
|
||||
uint32_t VulkanFboCache::getRenderPassKey(VkRenderPass pass) noexcept {
|
||||
uint32_t key = 0;
|
||||
auto iter = mRenderPassToKey.find(pass);
|
||||
if (iter != mRenderPassToKey.end()) {
|
||||
key = iter->second;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
|
||||
FboMap::iterator iter = mFramebufferCache.find(config);
|
||||
if (UTILS_LIKELY(iter != mFramebufferCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
|
||||
@@ -117,6 +127,7 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
|
||||
VkResult error = vkCreateFramebuffer(mDevice, &info, VKALLOC, &framebuffer);
|
||||
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create framebuffer."
|
||||
<< " error=" << static_cast<int32_t>(error);
|
||||
|
||||
mFramebufferCache[config] = {framebuffer, mCurrentTime};
|
||||
return framebuffer;
|
||||
}
|
||||
@@ -326,6 +337,12 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
|
||||
VkResult error = vkCreateRenderPass(mDevice, &renderPassInfo, VKALLOC, &renderPass);
|
||||
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create render pass."
|
||||
<< " error=" << error;
|
||||
|
||||
RenderPassHash hashFunc;
|
||||
uint32_t key = hashFunc(config);
|
||||
VulkanRenderPassStateSerializer passSer(renderPassInfo, key);
|
||||
mRenderPassToKey[renderPass] = key;
|
||||
|
||||
mRenderPassCache[config] = {renderPass, mCurrentTime};
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
#include <map>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -97,6 +98,8 @@ public:
|
||||
explicit VulkanFboCache(VkDevice device);
|
||||
~VulkanFboCache();
|
||||
|
||||
uint32_t getRenderPassKey(VkRenderPass pass) noexcept;
|
||||
|
||||
// Retrieves or creates a VkFramebuffer handle.
|
||||
VkFramebuffer getFramebuffer(FboKey const& config) noexcept;
|
||||
|
||||
@@ -118,6 +121,7 @@ private:
|
||||
FboMap mFramebufferCache;
|
||||
|
||||
tsl::robin_map<RenderPassKey, RenderPassVal, RenderPassHash, RenderPassEq> mRenderPassCache;
|
||||
std::map<VkRenderPass, uint32_t> mRenderPassToKey;
|
||||
tsl::robin_map<VkRenderPass, uint32_t> mRenderPassRefCount;
|
||||
uint32_t mCurrentTime = 0;
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <utils/compiler.h> // UTILS_FALLTHROUGH
|
||||
#include <utils/Panic.h> // ASSERT_POSTCONDITION
|
||||
#include <utils/CString.h>
|
||||
#include <fstream>
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -179,6 +180,23 @@ VulkanAttachment createSwapchainAttachment(const fvkmemory::resource_ptr<VulkanT
|
||||
};
|
||||
}
|
||||
|
||||
static inline uint64_t serializeShaderModule(utils::CString progName, VkShaderModuleCreateInfo info,
|
||||
VkShaderStageFlagBits stage) {
|
||||
std::stringstream filename;
|
||||
filename << progName.c_str() << "_" << stage << "_";
|
||||
constexpr uint32_t wordReadCount = 8;
|
||||
uint64_t key = utils::hash::murmur3(info.pCode, wordReadCount, 0);
|
||||
key |= uint64_t(utils::hash::murmur3(info.pCode + (wordReadCount / 2), wordReadCount, 0)) << 32;
|
||||
filename << key << ".bin";
|
||||
|
||||
std::ofstream file(filename.str());
|
||||
if (file.is_open()) {
|
||||
file.write(reinterpret_cast<const char*>(info.pCode), info.codeSize);
|
||||
}
|
||||
file.close();
|
||||
return key;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout&& layout,
|
||||
@@ -280,6 +298,12 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
.codeSize = dataSize,
|
||||
.pCode = data,
|
||||
};
|
||||
if (i == 0) {
|
||||
mVertexShaderHash = serializeShaderModule(name, moduleInfo, VK_SHADER_STAGE_VERTEX_BIT);
|
||||
} else if (i == 1) {
|
||||
mFragmentShaderHash =
|
||||
serializeShaderModule(name, moduleInfo, VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
}
|
||||
VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, &module);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create shader module."
|
||||
|
||||
@@ -65,6 +65,8 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
||||
|
||||
using DescriptorSetLayoutArray = std::array<VkDescriptorSetLayout,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
using DescriptorSetLayoutHashArray = std::array<uint64_t,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
// The bitmask representation of a set layout.
|
||||
struct Bitmask {
|
||||
@@ -280,6 +282,10 @@ struct VulkanProgram : public HwProgram, fvkmemory::Resource {
|
||||
mInfo->pushConstantDescription.write(cmdbuf, layout, stage, index, value);
|
||||
}
|
||||
|
||||
inline uint64_t getVertexShaderHash() { return mVertexShaderHash;
|
||||
}
|
||||
inline uint64_t getFragmentShaderHash() { return mFragmentShaderHash; }
|
||||
|
||||
// TODO: handle compute shaders.
|
||||
// The expected order of shaders - from frontend to backend - is vertex, fragment, compute.
|
||||
static constexpr uint8_t const MAX_SHADER_MODULES = 2;
|
||||
@@ -295,6 +301,8 @@ private:
|
||||
};
|
||||
|
||||
PipelineInfo* mInfo;
|
||||
uint64_t mVertexShaderHash;
|
||||
uint64_t mFragmentShaderHash;
|
||||
VkDevice mDevice = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanPipelineStateSerializer.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
@@ -42,8 +43,9 @@ VulkanPipelineCache::VulkanPipelineCache(VkDevice device)
|
||||
bluevk::vkCreatePipelineCache(mDevice, &createInfo, VKALLOC, &mPipelineCache);
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept {
|
||||
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout, uint32_t key) noexcept {
|
||||
mPipelineRequirements.layout = layout;
|
||||
mPipelineLayoutKey = key;
|
||||
}
|
||||
|
||||
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::getOrCreatePipeline() noexcept {
|
||||
@@ -77,6 +79,7 @@ void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
|
||||
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {
|
||||
assert_invariant(mPipelineRequirements.shaders[0] && "Vertex shader is not bound.");
|
||||
assert_invariant(mPipelineRequirements.layout && "No pipeline layout specified");
|
||||
VulkanPipelineStateSerializer serializer(mProgramName);
|
||||
|
||||
VkPipelineShaderStageCreateInfo shaderStages[SHADER_MODULE_COUNT];
|
||||
shaderStages[0] = {
|
||||
@@ -89,6 +92,12 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
shaderStages[1].module = mPipelineRequirements.shaders[1];
|
||||
|
||||
VulkanPipelineStateSerializer::ShaderStageInfo vtx = { shaderStages[0].stage, mVertexShaderHash,
|
||||
shaderStages[0].pName };
|
||||
VulkanPipelineStateSerializer::ShaderStageInfo frg = { shaderStages[1].stage, mFragmentShaderHash,
|
||||
shaderStages[1].pName };
|
||||
serializer << vtx << frg;
|
||||
|
||||
bool const hasFragmentShader = shaderStages[1].module != VK_NULL_HANDLE;
|
||||
|
||||
VkPipelineColorBlendAttachmentState colorBlendAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT];
|
||||
@@ -107,9 +116,11 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
for (uint32_t i = 0; i < VERTEX_ATTRIBUTE_COUNT; i++) {
|
||||
if (mPipelineRequirements.vertexAttributes[i].format > 0) {
|
||||
vertexAttributes[numVertexAttribs++] = mPipelineRequirements.vertexAttributes[i];
|
||||
serializer << mPipelineRequirements.vertexAttributes[i];
|
||||
}
|
||||
if (mPipelineRequirements.vertexBuffers[i].stride > 0) {
|
||||
vertexBuffers[numVertexBuffers++] = mPipelineRequirements.vertexBuffers[i];
|
||||
serializer << mPipelineRequirements.vertexBuffers[i];
|
||||
}
|
||||
}
|
||||
VkPipelineVertexInputStateCreateInfo vertexInputState = {
|
||||
@@ -123,11 +134,15 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||
.topology = (VkPrimitiveTopology) mPipelineRequirements.topology,
|
||||
};
|
||||
serializer << inputAssemblyState;
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewportState = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.viewportCount = 1,
|
||||
.scissorCount = 1,
|
||||
};
|
||||
serializer << viewportState;
|
||||
|
||||
VkDynamicState dynamicStateEnables[] = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
@@ -137,6 +152,8 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
.dynamicStateCount = 2,
|
||||
.pDynamicStates = dynamicStateEnables,
|
||||
};
|
||||
serializer << dynamicState;
|
||||
|
||||
auto const& raster = mPipelineRequirements.rasterState;
|
||||
VkPipelineRasterizationStateCreateInfo vkRaster = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||
@@ -150,6 +167,8 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
.depthBiasSlopeFactor = raster.depthBiasSlopeFactor,
|
||||
.lineWidth = 1.0f,
|
||||
};
|
||||
serializer << vkRaster;
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo vkMs = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.rasterizationSamples = (VkSampleCountFlagBits) raster.rasterizationSamples,
|
||||
@@ -158,6 +177,8 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
.alphaToCoverageEnable = raster.alphaToCoverageEnable,
|
||||
.alphaToOneEnable = VK_FALSE,
|
||||
};
|
||||
serializer << vkMs;
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo vkDs = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||
.depthTestEnable = VK_TRUE,
|
||||
@@ -177,6 +198,12 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
.writeMask = 0u,
|
||||
.reference = 0u,
|
||||
};
|
||||
serializer << vkDs;
|
||||
|
||||
//layout
|
||||
serializer.setPipelineLayoutKey(mPipelineLayoutKey);
|
||||
// renderPass
|
||||
serializer.setRenderPassKey(mRenderPassKey);
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
@@ -215,6 +242,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
colorBlendAttachments[i] = colorBlendAttachments[0];
|
||||
}
|
||||
}
|
||||
serializer << colorBlendState;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = ("
|
||||
@@ -230,12 +258,20 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
FVK_LOGE << "vkCreateGraphicsPipelines error " << error;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PipelineHashFn hashFunc;
|
||||
|
||||
serializer.setID(hashFunc(mPipelineRequirements));
|
||||
return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value();
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept {
|
||||
mPipelineRequirements.shaders[0] = program->getVertexShader();
|
||||
mPipelineRequirements.shaders[1] = program->getFragmentShader();
|
||||
mProgramName = program->name;
|
||||
mVertexShaderHash = program->getVertexShaderHash();
|
||||
mFragmentShaderHash= program->getFragmentShaderHash();
|
||||
|
||||
|
||||
// If this is a debug build, validate the current shader.
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
@@ -250,9 +286,10 @@ void VulkanPipelineCache::bindRasterState(RasterState const& rasterState) noexce
|
||||
mPipelineRequirements.rasterState = rasterState;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept {
|
||||
void VulkanPipelineCache::bindRenderPass(VkRenderPass renderPass, uint32_t passKey, int subpassIndex) noexcept {
|
||||
mPipelineRequirements.renderPass = renderPass;
|
||||
mPipelineRequirements.subpassIndex = subpassIndex;
|
||||
mRenderPassKey = passKey;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept {
|
||||
|
||||
@@ -88,7 +88,7 @@ public:
|
||||
|
||||
VulkanPipelineCache(VkDevice device);
|
||||
|
||||
void bindLayout(VkPipelineLayout layout) noexcept;
|
||||
void bindLayout(VkPipelineLayout layout, uint32_t key) noexcept;
|
||||
|
||||
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
|
||||
void bindPipeline(VulkanCommandBuffer* commands);
|
||||
@@ -96,7 +96,7 @@ public:
|
||||
// Each of the following methods are fast and do not make Vulkan calls.
|
||||
void bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept;
|
||||
void bindRasterState(RasterState const& rasterState) noexcept;
|
||||
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
|
||||
void bindRenderPass(VkRenderPass renderPass, uint32_t passKey, int subpassIndex) noexcept;
|
||||
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
|
||||
void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
|
||||
VkVertexInputBindingDescription const* bufferDesc, uint8_t count);
|
||||
@@ -207,6 +207,11 @@ private:
|
||||
|
||||
// Current requirements for the pipeline layout, pipeline, and descriptor sets.
|
||||
PipelineKey mPipelineRequirements = {};
|
||||
utils::CString mProgramName;
|
||||
uint64_t mVertexShaderHash;
|
||||
uint64_t mFragmentShaderHash;
|
||||
uint32_t mPipelineLayoutKey;
|
||||
uint32_t mRenderPassKey;
|
||||
|
||||
// Current bindings for the pipeline and descriptor sets.
|
||||
PipelineKey mBoundPipeline = {};
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "VulkanPipelineLayoutCache.h"
|
||||
#include "VulkanDescriptorSetLayoutSerializer.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -23,7 +24,11 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
fvkmemory::resource_ptr<VulkanProgram> program) {
|
||||
PipelineLayoutKey key = {};
|
||||
uint8_t descSetLayoutCount = 0;
|
||||
key.descSetLayouts = descriptorSetLayouts;
|
||||
DescriptorSetLayoutHashArray hashArray;
|
||||
for (uint32_t i = 0; i < descriptorSetLayouts.size(); ++i) {
|
||||
hashArray[i] = mDescriptorSetCache->getKey(descriptorSetLayouts[i]);
|
||||
}
|
||||
key.descSetLayouts = hashArray;
|
||||
for (auto layoutHandle: descriptorSetLayouts) {
|
||||
if (layoutHandle == VK_NULL_HANDLE) {
|
||||
break;
|
||||
@@ -62,11 +67,14 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.setLayoutCount = (uint32_t) descSetLayoutCount,
|
||||
.pSetLayouts = key.descSetLayouts.data(),
|
||||
.pSetLayouts = descriptorSetLayouts.data(),
|
||||
.pushConstantRangeCount = pushConstantRangeCount,
|
||||
.pPushConstantRanges = pushConstantRanges,
|
||||
};
|
||||
|
||||
PipelineLayoutKeyHashFn hashFunc;
|
||||
uint32_t hashedKey = hashFunc(key);
|
||||
VulkanPipelineLayoutSerializer pipelineSer(info, mDescriptorSetCache, hashedKey);
|
||||
VkPipelineLayout layout;
|
||||
vkCreatePipelineLayout(mDevice, &info, VKALLOC, &layout);
|
||||
|
||||
@@ -74,6 +82,8 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
.handle = layout,
|
||||
.lastUsed = mTimestamp++,
|
||||
};
|
||||
|
||||
mPipelineToKey[layout] = hashedKey;
|
||||
return layout;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define TNT_FILAMENT_BACKEND_VULKANPIPELINELAYOUTCACHE_H
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanDescriptorSetLayoutCache.h"
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
@@ -30,10 +31,11 @@ namespace filament::backend {
|
||||
class VulkanPipelineLayoutCache {
|
||||
public:
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||
using DescriptorSetLayoutHashArray = VulkanDescriptorSetLayout::DescriptorSetLayoutHashArray;
|
||||
|
||||
VulkanPipelineLayoutCache(VkDevice device)
|
||||
VulkanPipelineLayoutCache(VkDevice device, VulkanDescriptorSetLayoutCache* cache)
|
||||
: mDevice(device),
|
||||
mTimestamp(0) {}
|
||||
mTimestamp(0), mDescriptorSetCache(cache) {}
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
@@ -45,7 +47,7 @@ public:
|
||||
};
|
||||
|
||||
struct PipelineLayoutKey {
|
||||
DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 4
|
||||
DescriptorSetLayoutHashArray descSetLayouts = {}; // 8 * 4
|
||||
PushConstantKey pushConstant[Program::SHADER_TYPE_COUNT] = {}; // 2 * 3
|
||||
uint16_t padding = 0;
|
||||
};
|
||||
@@ -58,6 +60,14 @@ public:
|
||||
// are described in the program.
|
||||
VkPipelineLayout getLayout(DescriptorSetLayoutArray const& descriptorSetLayouts,
|
||||
fvkmemory::resource_ptr<VulkanProgram> program);
|
||||
uint32_t getKey(VkPipelineLayout layout) {
|
||||
uint32_t key = 0;
|
||||
auto iter = mPipelineToKey.find(layout);
|
||||
if (iter != mPipelineToKey.end()) {
|
||||
key = iter->second;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
private:
|
||||
using Timestamp = uint64_t;
|
||||
@@ -79,6 +89,8 @@ private:
|
||||
VkDevice mDevice;
|
||||
Timestamp mTimestamp;
|
||||
PipelineLayoutMap mPipelineLayouts;
|
||||
std::map<VkPipelineLayout, uint32_t> mPipelineToKey;
|
||||
VulkanDescriptorSetLayoutCache* mDescriptorSetCache;
|
||||
};
|
||||
|
||||
} // filament::backend
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <fstream>
|
||||
|
||||
#include "VulkanPipelineStateSerializer.h"
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#if defined(__clang__)
|
||||
// Vulkan functions often immediately dereference pointers, so it's fine to pass in a pointer
|
||||
// to a stack-allocated variable.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wreturn-stack-address"
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPipelineStateSerializer::VulkanPipelineStateSerializer(const utils::CString& name) {
|
||||
mBuffer << "{" << std::endl;
|
||||
mBuffer << "\"program_name\":" << "\"" << name.c_str() << "\"" << "," << std::endl;
|
||||
}
|
||||
VulkanPipelineStateSerializer::~VulkanPipelineStateSerializer() {
|
||||
//Shader Modules
|
||||
mBuffer << "\"shader_modules\":[" << std::endl;
|
||||
for (uint32_t i = 0; i < mShaderStages.size(); ++i) {
|
||||
mBuffer << "{" << std::endl;
|
||||
mBuffer << "\"stage\":" << mShaderStages[i].stage << "," << std::endl;
|
||||
mBuffer << "\"key\":" << mShaderStages[i].module << "," << std::endl;
|
||||
mBuffer << "\"name\":" << "\"" << mShaderStages[i].mName << "\"" << std::endl;
|
||||
mBuffer << "}";
|
||||
if (i < mShaderStages.size() - 1) {
|
||||
mBuffer << ",";
|
||||
}
|
||||
mBuffer << std::endl;
|
||||
}
|
||||
mBuffer << "]," << std::endl;
|
||||
|
||||
//Vertex Attribute
|
||||
mBuffer << "\"vertex_attributes\":[" << std::endl;
|
||||
for (uint32_t i = 0; i < mVtxAttribs.size(); ++i) {
|
||||
mBuffer << "{" << std::endl;
|
||||
mBuffer << "\"location\":" << mVtxAttribs[i].location << "," << std::endl;
|
||||
mBuffer << "\"binding\":" << mVtxAttribs[i].binding << "," << std::endl;
|
||||
mBuffer << "\"format\":" << mVtxAttribs[i].format << "," << std::endl;
|
||||
mBuffer << "\"offset\":" << mVtxAttribs[i].offset << std::endl;
|
||||
mBuffer << "}";
|
||||
if (i < mVtxAttribs.size() - 1) {
|
||||
mBuffer << ",";
|
||||
}
|
||||
mBuffer << std::endl;
|
||||
}
|
||||
mBuffer << "]," << std::endl;
|
||||
|
||||
// Vertex Bindings
|
||||
mBuffer << "\"vertex_bindings\":[" << std::endl;
|
||||
for (uint32_t i = 0; i < mVtxBindings.size(); ++i) {
|
||||
mBuffer << "{" << std::endl;
|
||||
mBuffer << "\"binding\":" << mVtxBindings[i].binding << "," << std::endl;
|
||||
mBuffer << "\"stride\":" << mVtxBindings[i].stride << "," << std::endl;
|
||||
mBuffer << "\"input_rate\":" << mVtxBindings[i].inputRate << std::endl;
|
||||
mBuffer << "}";
|
||||
if (i < mVtxBindings.size() - 1) {
|
||||
mBuffer << ",";
|
||||
}
|
||||
mBuffer << std::endl;
|
||||
}
|
||||
mBuffer << "]" << std::endl;
|
||||
|
||||
mBuffer << "}" << std::endl;
|
||||
|
||||
std::ofstream file(mFileName);
|
||||
if (file.is_open()) {
|
||||
file << mBuffer.str();
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
void VulkanPipelineStateSerializer::setPipelineLayoutKey(uint32_t pipelineLayoutKey) {
|
||||
mBuffer << "\"layout\":" << pipelineLayoutKey << "," << std::endl;
|
||||
}
|
||||
|
||||
void VulkanPipelineStateSerializer::setRenderPassKey(uint32_t renderPassKey) {
|
||||
mBuffer << "\"render_pass\":" << renderPassKey << "," << std::endl;
|
||||
}
|
||||
|
||||
void VulkanPipelineStateSerializer::setID(uint32_t key) {
|
||||
std::stringstream temp;
|
||||
temp << "pipeline_" << key << ".json";
|
||||
temp >> mFileName;
|
||||
}
|
||||
|
||||
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
|
||||
const VkPipelineInputAssemblyStateCreateInfo& inputAsm) {
|
||||
mBuffer << "\"topology\":" << inputAsm.topology << "," << std::endl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
|
||||
const VkPipelineViewportStateCreateInfo& viewport) {
|
||||
mBuffer << "\"viewport_count\":" << viewport.viewportCount << "," << std::endl;
|
||||
mBuffer << "\"scissor_count\":" << viewport.scissorCount << "," << std::endl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
|
||||
const VkPipelineRasterizationStateCreateInfo& rasterState) {
|
||||
mBuffer << "\"raster_states\":{" << std::endl;
|
||||
mBuffer << "\"depth_clamp_enable\":" << rasterState.depthClampEnable << "," << std::endl;
|
||||
mBuffer << "\"polygon_mode\":" << rasterState.polygonMode << "," << std::endl;
|
||||
mBuffer << "\"cull_mode\":" << rasterState.cullMode << "," << std::endl;
|
||||
mBuffer << "\"front_face\":" << rasterState.frontFace << "," << std::endl;
|
||||
mBuffer << "\"depth_bias_enable\":" << rasterState.depthBiasEnable << "," << std::endl;
|
||||
mBuffer << "\"depth_bias_constant_factor\":" << rasterState.depthBiasConstantFactor << ","
|
||||
<< std::endl;
|
||||
mBuffer << "\"depth_bias_clamp\":" << rasterState.depthBiasClamp << "," << std::endl;
|
||||
mBuffer << "\"depth_bias_slope_factor\":" << rasterState.depthBiasSlopeFactor << "," << std::endl;
|
||||
mBuffer << "\"line_width\":" << rasterState.lineWidth << std::endl;
|
||||
mBuffer << "}," << std::endl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
|
||||
const VkPipelineDepthStencilStateCreateInfo& depthStencil) {
|
||||
mBuffer << "\"depth_stencil_states\":{" << std::endl;
|
||||
mBuffer << "\"depth_test_enable\":" << depthStencil.depthTestEnable << "," << std::endl;
|
||||
mBuffer << "\"depth_write_enable\":" << depthStencil.depthWriteEnable << "," << std::endl;
|
||||
mBuffer << "\"depth_compare_op\":" << depthStencil.depthCompareOp << "," << std::endl;
|
||||
mBuffer << "\"depth_bounds_test_enable\":" << depthStencil.depthBoundsTestEnable << ","
|
||||
<< std::endl;
|
||||
mBuffer << "\"min_depth_bounds\":" << depthStencil.minDepthBounds << "," << std::endl;
|
||||
mBuffer << "\"max_depth_bounds\":" << depthStencil.maxDepthBounds << "," << std::endl;
|
||||
mBuffer << "\"front\":{" << std::endl;
|
||||
mBuffer << "\"fail_op\":" << depthStencil.front.failOp << "," << std::endl;
|
||||
mBuffer << "\"pass_op\":" << depthStencil.front.passOp << "," << std::endl;
|
||||
mBuffer << "\"depth_fail_op\":" << depthStencil.front.depthFailOp << "," << std::endl;
|
||||
mBuffer << "\"compare_op\":" << depthStencil.front.compareOp << "," << std::endl;
|
||||
mBuffer << "\"compare_mask\":" << depthStencil.front.compareMask << "," << std::endl;
|
||||
mBuffer << "\"write_mask\":" << depthStencil.front.writeMask << "," << std::endl;
|
||||
mBuffer << "\"reference\":" << depthStencil.front.reference << std::endl;
|
||||
mBuffer << "}," << std::endl;
|
||||
mBuffer << "\"back\":{" << std::endl;
|
||||
mBuffer << "\"fail_op\":" << depthStencil.back.failOp << "," << std::endl;
|
||||
mBuffer << "\"pass_op\":" << depthStencil.back.passOp << "," << std::endl;
|
||||
mBuffer << "\"depth_fail_op\":" << depthStencil.back.depthFailOp << "," << std::endl;
|
||||
mBuffer << "\"compare_op\":" << depthStencil.back.compareOp << "," << std::endl;
|
||||
mBuffer << "\"compare_mask\":" << depthStencil.back.compareMask << "," << std::endl;
|
||||
mBuffer << "\"write_mask\":" << depthStencil.back.writeMask << "," << std::endl;
|
||||
mBuffer << "\"reference\":" << depthStencil.back.reference << std::endl;
|
||||
mBuffer << "}" << std::endl;
|
||||
mBuffer << "}," << std::endl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
|
||||
const VkPipelineMultisampleStateCreateInfo& multiSample) {
|
||||
mBuffer << "\"multisample_states\":{" << std::endl;
|
||||
mBuffer << "\"rasterization_samples\":" << multiSample.rasterizationSamples << "," << std::endl;
|
||||
mBuffer << "\"sample_shading_enable\":" << multiSample.sampleShadingEnable << "," << std::endl;
|
||||
mBuffer << "\"min_sample_shading\":" << multiSample.minSampleShading << "," << std::endl;
|
||||
mBuffer << "\"alpha_to_coverage_enable\":" << multiSample.alphaToCoverageEnable << ","
|
||||
<< std::endl;
|
||||
mBuffer << "\"alpha_to_one_enable\":" << multiSample.alphaToOneEnable << std::endl;
|
||||
mBuffer << "}," << std::endl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
|
||||
const VkPipelineDynamicStateCreateInfo& dyStates) {
|
||||
mBuffer << "\"dynamic_states\":[" << std::endl;
|
||||
for (uint32_t i = 0; i < dyStates.dynamicStateCount; ++i) {
|
||||
mBuffer << dyStates.pDynamicStates[i];
|
||||
if (i < dyStates.dynamicStateCount - 1) {
|
||||
mBuffer << ",";
|
||||
}
|
||||
mBuffer << std::endl;
|
||||
}
|
||||
mBuffer << "]," << std::endl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
|
||||
const VkPipelineColorBlendStateCreateInfo& blendStateInfo) {
|
||||
mBuffer << "\"color_blend_state_attachments\":[" << std::endl;
|
||||
for (uint32_t i = 0; i < blendStateInfo.attachmentCount; ++i) {
|
||||
mBuffer << "{" << std::endl;
|
||||
mBuffer << "\"blend_enable\":" << blendStateInfo.pAttachments[i].blendEnable << ","
|
||||
<< std::endl;
|
||||
mBuffer << "\"src_color_blend_factor\":" << blendStateInfo.pAttachments[i].srcColorBlendFactor
|
||||
<< ","
|
||||
<< std::endl;
|
||||
mBuffer << "\"dst_color_blend_factor\":" << blendStateInfo.pAttachments[i].dstColorBlendFactor
|
||||
<< ","
|
||||
<< std::endl;
|
||||
mBuffer << "\"color_blend_op\":" << blendStateInfo.pAttachments[i].colorBlendOp << ","
|
||||
<< std::endl;
|
||||
mBuffer << "\"src_alpha_blend_factor\":" << blendStateInfo.pAttachments[i].srcAlphaBlendFactor
|
||||
<< ","
|
||||
<< std::endl;
|
||||
mBuffer << "\"dst_alpha_blend_factor\":" << blendStateInfo.pAttachments[i].dstAlphaBlendFactor
|
||||
<< ","
|
||||
<< std::endl;
|
||||
mBuffer << "\"alpha_blend_op\":" << blendStateInfo.pAttachments[i].alphaBlendOp << ","
|
||||
<< std::endl;
|
||||
mBuffer << "\"color_write_mask\":" << blendStateInfo.pAttachments[i].colorWriteMask
|
||||
<< std::endl;
|
||||
mBuffer << "}";
|
||||
if (i < blendStateInfo.attachmentCount - 1) {
|
||||
mBuffer << ",";
|
||||
}
|
||||
mBuffer << std::endl;
|
||||
}
|
||||
mBuffer << "]," << std::endl;
|
||||
return *this;
|
||||
}
|
||||
|
||||
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
|
||||
const ShaderStageInfo& shaderStages) {
|
||||
mShaderStages.push_back(shaderStages);
|
||||
return *this;
|
||||
}
|
||||
|
||||
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
|
||||
const VkVertexInputAttributeDescription& attrib) {
|
||||
mVtxAttribs.push_back(attrib);
|
||||
return *this;
|
||||
}
|
||||
VulkanPipelineStateSerializer& VulkanPipelineStateSerializer::operator<<(
|
||||
const VkVertexInputBindingDescription& binding) {
|
||||
mVtxBindings.push_back(binding);
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 TNT_FILAMENT_BACKEND_VULKANPIPELINESTATESERIALIZER_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANPIPELINESTATESERIALIZER_H
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <iostream>
|
||||
#include <type_traits>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
// VulkanPipelineStateSerializer stores to a file the pipeline states.
|
||||
class VulkanPipelineStateSerializer {
|
||||
public:
|
||||
VulkanPipelineStateSerializer(VulkanPipelineStateSerializer const&) = delete;
|
||||
VulkanPipelineStateSerializer& operator=(VulkanPipelineStateSerializer const&) = delete;
|
||||
|
||||
VulkanPipelineStateSerializer(const utils::CString& name);
|
||||
~VulkanPipelineStateSerializer();
|
||||
|
||||
void setID(uint32_t key);
|
||||
void setPipelineLayoutKey(uint32_t pipelineLayoutKey);
|
||||
void setRenderPassKey(uint32_t renderPassKey);
|
||||
VulkanPipelineStateSerializer& operator<<(
|
||||
const VkPipelineInputAssemblyStateCreateInfo& inputAsm);
|
||||
VulkanPipelineStateSerializer& operator<<(const VkPipelineViewportStateCreateInfo& viewport);
|
||||
VulkanPipelineStateSerializer& operator<<(const VkPipelineDynamicStateCreateInfo& dyStates);
|
||||
VulkanPipelineStateSerializer& operator<<(
|
||||
const VkPipelineRasterizationStateCreateInfo& rasterState);
|
||||
VulkanPipelineStateSerializer& operator<<(
|
||||
const VkPipelineDepthStencilStateCreateInfo& depthStencil);
|
||||
VulkanPipelineStateSerializer& operator<<(const VkPipelineMultisampleStateCreateInfo& multiSample);
|
||||
VulkanPipelineStateSerializer& operator<<(
|
||||
const VkPipelineColorBlendStateCreateInfo& blendStateInfo);
|
||||
|
||||
struct ShaderStageInfo {
|
||||
VkShaderStageFlagBits stage;
|
||||
uint64_t module;
|
||||
std::string mName;
|
||||
};
|
||||
VulkanPipelineStateSerializer& operator<<(const ShaderStageInfo& shaderStages);
|
||||
VulkanPipelineStateSerializer& operator<<(
|
||||
const VkVertexInputAttributeDescription& attrib);
|
||||
VulkanPipelineStateSerializer& operator<<(const VkVertexInputBindingDescription& binding);
|
||||
|
||||
private:
|
||||
std::string mFileName;
|
||||
std::stringstream mBuffer;
|
||||
|
||||
//temp data
|
||||
utils::CString mProgramName;
|
||||
std::vector<ShaderStageInfo> mShaderStages;
|
||||
std::vector<VkVertexInputAttributeDescription> mVtxAttribs;
|
||||
std::vector<VkVertexInputBindingDescription> mVtxBindings;
|
||||
};
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif //TNT_FILAMENT_BACKEND_VULKANPIPELINESTATESERIALIZER_H
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <fstream>
|
||||
|
||||
#include "VulkanRenderPassStateSerializer.h"
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#if defined(__clang__)
|
||||
// Vulkan functions often immediately dereference pointers, so it's fine to pass in a pointer
|
||||
// to a stack-allocated variable.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wreturn-stack-address"
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanRenderPassStateSerializer::VulkanRenderPassStateSerializer(
|
||||
const VkRenderPassCreateInfo& info, uint32_t key) {
|
||||
std::stringstream filename;
|
||||
filename << "render_pass_" << key << ".json";
|
||||
|
||||
std::ofstream file(filename.str());
|
||||
if (file.is_open()) {
|
||||
std::stringstream buffer;
|
||||
buffer << "{" << std::endl;
|
||||
buffer << "\"attachments\":[" << std::endl;
|
||||
for (uint32_t i = 0; i < info.attachmentCount; ++i) {
|
||||
buffer << "{" << std::endl;
|
||||
buffer << "\"flags\":" << info.pAttachments[i].flags << "," << std::endl;
|
||||
buffer << "\"format\":" << info.pAttachments[i].format << "," << std::endl;
|
||||
buffer << "\"samples\":" << info.pAttachments[i].samples << "," << std::endl;
|
||||
buffer << "\"load_op\":" << info.pAttachments[i].loadOp << "," << std::endl;
|
||||
buffer << "\"store_op\":" << info.pAttachments[i].storeOp << "," << std::endl;
|
||||
buffer << "\"stencil_load_op\":" << info.pAttachments[i].stencilLoadOp << "," << std::endl;
|
||||
buffer << "\"stencil_store_op\":" << info.pAttachments[i].stencilStoreOp << "," << std::endl;
|
||||
buffer << "\"initial_layout\":" << info.pAttachments[i].initialLayout << "," << std::endl;
|
||||
buffer << "\"final_layout\":" << info.pAttachments[i].finalLayout << std::endl;
|
||||
buffer << "}";
|
||||
if (i < info.attachmentCount - 1) {
|
||||
buffer << ",";
|
||||
}
|
||||
buffer << std::endl;
|
||||
}
|
||||
buffer << "]," << std::endl;
|
||||
|
||||
buffer << "\"subpasses\":[" << std::endl;
|
||||
for (uint32_t i = 0; i < info.subpassCount; ++i) {
|
||||
buffer << "{" << std::endl;
|
||||
buffer << "\"flags\":" << info.pSubpasses[i].flags << "," << std::endl;
|
||||
buffer << "\"pipeline_bind_point\":" << info.pSubpasses[i].pipelineBindPoint;
|
||||
|
||||
//------------depth stencil-------------------
|
||||
if (info.pSubpasses[i].pDepthStencilAttachment) {
|
||||
buffer << "," << std::endl;
|
||||
buffer << "\"depth_stencil_attachment\":{" << std::endl;
|
||||
buffer << "\"attachement\":"
|
||||
<< info.pSubpasses[i].pDepthStencilAttachment->attachment << ","
|
||||
<< std::endl;
|
||||
buffer << "\"layout\":" << info.pSubpasses[i].pDepthStencilAttachment->layout
|
||||
<< std::endl;
|
||||
buffer << "}";
|
||||
}
|
||||
|
||||
//------------Resolve-------------------
|
||||
if (info.pSubpasses[i].pResolveAttachments) {
|
||||
buffer << "," << std::endl;
|
||||
buffer << "\"resolve_attachments\":[" << std::endl;
|
||||
for (uint32_t j = 0; j < info.pSubpasses[i].colorAttachmentCount; ++j) {
|
||||
buffer << "{" << std::endl;
|
||||
buffer << "\"attachement\":"
|
||||
<< info.pSubpasses[i].pResolveAttachments[j].attachment << ","
|
||||
<< std::endl;
|
||||
buffer << "\"layout\":" << info.pSubpasses[i].pResolveAttachments[j].layout
|
||||
<< std::endl;
|
||||
buffer << "}" << std::endl;
|
||||
if (j < info.pSubpasses[i].colorAttachmentCount - 1) {
|
||||
buffer << ",";
|
||||
}
|
||||
buffer << std::endl;
|
||||
}
|
||||
buffer << "]";
|
||||
}
|
||||
|
||||
//------------Color-------------------
|
||||
if (info.pSubpasses[i].pColorAttachments) {
|
||||
buffer << "," << std::endl;
|
||||
buffer << "\"color_attachments\":[" << std::endl;
|
||||
for (uint32_t j = 0; j < info.pSubpasses[i].colorAttachmentCount; ++j) {
|
||||
buffer << "{" << std::endl;
|
||||
buffer << "\"attachement\":"
|
||||
<< info.pSubpasses[i].pColorAttachments[j].attachment << ","
|
||||
<< std::endl;
|
||||
buffer << "\"layout\":" << info.pSubpasses[i].pColorAttachments[j].layout
|
||||
<< std::endl;
|
||||
buffer << "}" << std::endl;
|
||||
if (j < info.pSubpasses[i].colorAttachmentCount - 1) {
|
||||
buffer << ",";
|
||||
}
|
||||
buffer << std::endl;
|
||||
}
|
||||
buffer << "]";
|
||||
}
|
||||
|
||||
//------------Input-------------------
|
||||
if (info.pSubpasses[i].pInputAttachments) {
|
||||
buffer << "," << std::endl;
|
||||
buffer << "\"input_attachments\":[" << std::endl;
|
||||
for (uint32_t j = 0; j < info.pSubpasses[i].inputAttachmentCount; ++j) {
|
||||
buffer << "{" << std::endl;
|
||||
buffer << "\"attachement\":"
|
||||
<< info.pSubpasses[i].pInputAttachments[j].attachment << ","
|
||||
<< std::endl;
|
||||
buffer << "\"layout\":" << info.pSubpasses[i].pInputAttachments[j].layout
|
||||
<< std::endl;
|
||||
buffer << "}" << std::endl;
|
||||
if (j < info.pSubpasses[i].inputAttachmentCount - 1) {
|
||||
buffer << ",";
|
||||
}
|
||||
buffer << std::endl;
|
||||
}
|
||||
buffer << "]" << std::endl;
|
||||
}
|
||||
|
||||
buffer << "}";
|
||||
if (i < info.subpassCount - 1) {
|
||||
buffer << ",";
|
||||
}
|
||||
buffer << std::endl;
|
||||
}
|
||||
buffer << "]" << std::endl;
|
||||
|
||||
buffer << "}";
|
||||
file << buffer.str();
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 TNT_FILAMENT_BACKEND_VULKANRENDERPASSSTATESERIALIZER_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANRENDERPASSSTATESERIALIZER_H
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <iostream>
|
||||
#include <type_traits>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanRenderPassStateSerializer {
|
||||
public:
|
||||
VulkanRenderPassStateSerializer(VulkanRenderPassStateSerializer const&) = delete;
|
||||
VulkanRenderPassStateSerializer& operator=(VulkanRenderPassStateSerializer const&) = delete;
|
||||
|
||||
VulkanRenderPassStateSerializer(const VkRenderPassCreateInfo& info, uint32_t key);
|
||||
};
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif //TNT_FILAMENT_BACKEND_VULKANRENDERPASSSTATESERIALIZER_H
|
||||
@@ -21,12 +21,14 @@
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include "VulkanSamplerStateSerializer.h"
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanSamplerCache::VulkanSamplerCache(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
VulkanSamplerCache::VulkanSamplerCache(VkDevice device, VulkanYcbcrConversionCache* conversionCache)
|
||||
: mDevice(device), mConversionCache(conversionCache) {}
|
||||
|
||||
VkSampler VulkanSamplerCache::getSampler(Params params) {
|
||||
auto iter = mCache.find(params);
|
||||
@@ -57,11 +59,16 @@ VkSampler VulkanSamplerCache::getSampler(Params params) {
|
||||
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
|
||||
.unnormalizedCoordinates = VK_FALSE,
|
||||
};
|
||||
VulkanSamplerStateSerializer samplerSer(params,
|
||||
params.conversion != VK_NULL_HANDLE ? mConversionCache->getKey(params.conversion) : 0);
|
||||
|
||||
VkSampler sampler;
|
||||
VkResult result = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create sampler."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
mCache.insert({ params, sampler });
|
||||
SamplerHashFn hashFn;
|
||||
mSamplerToKey[sampler] = hashFn(params);
|
||||
return sampler;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
|
||||
|
||||
#include "VulkanYcbcrConversionCache.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/Hash.h>
|
||||
#include <map>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <tsl/robin_map.h>
|
||||
@@ -37,11 +39,20 @@ public:
|
||||
|
||||
static_assert(sizeof(Params) == 16);
|
||||
|
||||
explicit VulkanSamplerCache(VkDevice device);
|
||||
explicit VulkanSamplerCache(VkDevice device, VulkanYcbcrConversionCache* conversionCache);
|
||||
VkSampler getSampler(Params params);
|
||||
uint32_t getKey(VkSampler sampler) {
|
||||
uint32_t key = 0;
|
||||
auto iter = mSamplerToKey.find(sampler);
|
||||
if (iter != mSamplerToKey.end()) {
|
||||
key = iter->second;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
void terminate() noexcept;
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
VulkanYcbcrConversionCache* mConversionCache;
|
||||
|
||||
struct SamplerEqualTo {
|
||||
bool operator()(Params lhs, Params rhs) const noexcept {
|
||||
@@ -51,6 +62,7 @@ private:
|
||||
};
|
||||
using SamplerHashFn = utils::hash::MurmurHashFn<Params>;
|
||||
tsl::robin_map<Params, VkSampler, SamplerHashFn, SamplerEqualTo> mCache;
|
||||
std::map<VkSampler, uint32_t> mSamplerToKey;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "VulkanSamplerStateSerializer.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
namespace filament::backend {
|
||||
VulkanYcbcrConversionSerializer::VulkanYcbcrConversionSerializer(
|
||||
const VulkanYcbcrConversionCache::Params& params) {
|
||||
using ConversionHashFn = utils::hash::MurmurHashFn<VulkanYcbcrConversionCache::Params>;
|
||||
ConversionHashFn hashFn;
|
||||
uint32_t key = hashFn(params);
|
||||
std::stringstream filename;
|
||||
filename << "ycbcr_conv_" << key << ".json";
|
||||
|
||||
std::ofstream file(filename.str());
|
||||
if (file.is_open()) {
|
||||
std::stringstream buffer;
|
||||
auto const& chroma = params.conversion;
|
||||
buffer << "{" << std::endl;
|
||||
buffer << "\"format\":" << params.format << "," << std::endl;
|
||||
buffer << "\"external_format\":" << params.externalFormat << "," << std::endl;
|
||||
buffer << "\"ycbcr_model\":" << uint8_t(chroma.ycbcrModel) << "," << std::endl;
|
||||
buffer << "\"ycbcr_range\":" << uint8_t(chroma.ycbcrRange) << "," << std::endl;
|
||||
buffer << "\"swizzle_array\":[" << uint8_t(chroma.r) << "," << uint8_t(chroma.g) << ","
|
||||
<< uint8_t(chroma.b) << "," << uint8_t(chroma.a) << "]," << std::endl;
|
||||
buffer << "\"x_chroma_offset\":" << uint8_t(chroma.xChromaOffset) << "," << std::endl;
|
||||
buffer << "\"y_chroma_offset\":" << uint8_t(chroma.yChromaOffset) << "," << std::endl;
|
||||
buffer << "\"chroma_filter\":" << uint8_t(chroma.chromaFilter) << std::endl;
|
||||
buffer << "}" << std::endl;
|
||||
|
||||
file << buffer.str();
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
VulkanSamplerStateSerializer::VulkanSamplerStateSerializer(const VulkanSamplerCache::Params& params, uint32_t ycbcr_conv_key) {
|
||||
using ConversionHashFn = utils::hash::MurmurHashFn<VulkanSamplerCache::Params>;
|
||||
ConversionHashFn hashFn;
|
||||
uint32_t key = hashFn(params);
|
||||
std::stringstream filename;
|
||||
filename << "sampler_" << key << ".json";
|
||||
|
||||
std::ofstream file(filename.str());
|
||||
if (file.is_open()) {
|
||||
std::stringstream buffer;
|
||||
auto const& samplerParams = params.sampler;
|
||||
buffer << "{" << std::endl;
|
||||
buffer << "\"filter_mag\":" << uint32_t(samplerParams.filterMag) << "," << std::endl;
|
||||
buffer << "\"filter_min\":" << uint32_t(samplerParams.filterMin) << "," << std::endl;
|
||||
buffer << "\"wrap_s\":" << uint32_t(samplerParams.wrapS) << "," << std::endl;
|
||||
buffer << "\"wrap_t\":" << uint32_t(samplerParams.wrapT) << "," << std::endl;
|
||||
buffer << "\"wrap_r\":" << uint32_t(samplerParams.wrapR) << "," << std::endl;
|
||||
buffer << "\"anisotropy_log2\":" << uint32_t(samplerParams.anisotropyLog2) << "," << std::endl;
|
||||
buffer << "\"compare_mode\":" << uint32_t(samplerParams.compareMode) << "," << std::endl;
|
||||
buffer << "\"compare_func\":" << uint32_t(samplerParams.compareFunc) << "," << std::endl;
|
||||
buffer << "\"ycbcr_conv_key\":" << ycbcr_conv_key << std::endl;
|
||||
buffer << "}" << std::endl;
|
||||
|
||||
file << buffer.str();
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 TNT_FILAMENT_BACKEND_VULKANSAMPLERSTATESERIALIZER_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANSAMPLERSTATESERIALIZER_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include "VulkanYcbcrConversionCache.h"
|
||||
#include "VulkanSamplerCache.h"
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanYcbcrConversionSerializer {
|
||||
public:
|
||||
VulkanYcbcrConversionSerializer(VulkanYcbcrConversionSerializer const&) = delete;
|
||||
VulkanYcbcrConversionSerializer& operator=(VulkanYcbcrConversionSerializer const&) = delete;
|
||||
|
||||
VulkanYcbcrConversionSerializer(const VulkanYcbcrConversionCache::Params& params);
|
||||
};
|
||||
// VulkanPipelineStateSerializer stores to a file the pipeline states.
|
||||
class VulkanSamplerStateSerializer {
|
||||
public:
|
||||
VulkanSamplerStateSerializer(VulkanSamplerStateSerializer const&) = delete;
|
||||
VulkanSamplerStateSerializer& operator=(VulkanSamplerStateSerializer const&) = delete;
|
||||
|
||||
VulkanSamplerStateSerializer(const VulkanSamplerCache::Params& params, uint32_t ycbcr_conv_key);
|
||||
};
|
||||
}
|
||||
|
||||
#endif //TNT_FILAMENT_BACKEND_VULKANSAMPLERSTATESERIALIZER_H
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "VulkanYcbcrConversionCache.h"
|
||||
#include "VulkanSamplerStateSerializer.h"
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
@@ -53,6 +54,7 @@ VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
|
||||
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
|
||||
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
|
||||
};
|
||||
VulkanYcbcrConversionSerializer ycbcr_ser(params);
|
||||
|
||||
// We could put this in the platform class, but that seems like a bit of an overkill
|
||||
#if defined(__ANDROID__)
|
||||
@@ -73,6 +75,8 @@ VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
|
||||
mCache.insert({ params, conversion });
|
||||
ConversionHashFn hashFn;
|
||||
mConversionToKey[conversion] = hashFn(params);
|
||||
return conversion;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <tsl/robin_map.h>
|
||||
#include <map>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -40,6 +41,14 @@ public:
|
||||
|
||||
explicit VulkanYcbcrConversionCache(VkDevice device);
|
||||
VkSamplerYcbcrConversion getConversion(Params params);
|
||||
uint32_t getKey(VkSamplerYcbcrConversion conv) {
|
||||
uint32_t key = 0;
|
||||
auto iter = mConversionToKey.find(conv);
|
||||
if (iter != mConversionToKey.end()) {
|
||||
key = iter->second;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
void terminate() noexcept;
|
||||
|
||||
private:
|
||||
@@ -54,6 +63,7 @@ private:
|
||||
};
|
||||
using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
|
||||
tsl::robin_map<Params, VkSamplerYcbcrConversion, ConversionHashFn, ConversionEqualTo> mCache;
|
||||
std::map<VkSamplerYcbcrConversion, uint32_t> mConversionToKey;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -1022,13 +1022,4 @@ VkExternalFenceHandleTypeFlagBits VulkanPlatform::getFenceExportFlags() const no
|
||||
return static_cast<VkExternalFenceHandleTypeFlagBits>(0);
|
||||
}
|
||||
|
||||
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
|
||||
return getSwapchainInstanceExtensionsImpl();
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
return createVkSurfaceKHRImpl(nativeWindow, instance, flags);
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -502,13 +502,4 @@ bool VulkanPlatformAndroid::queryFrameTimestamps(SwapChain const* swapchain, uin
|
||||
return vulkanSwapchain->queryFrameTimestamps(frameId, outFrameTimestamps);
|
||||
}
|
||||
|
||||
|
||||
// Deprecated platform dependent helper methods
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { return {}; }
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
return SurfaceBundle{};
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
#include <backend/platforms/VulkanPlatformApple.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "vulkan/VulkanDriverFactory.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
@@ -36,15 +35,15 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||
VulkanPlatform::ExtensionSet VulkanPlatformApple::getSwapchainInstanceExtensions() const {
|
||||
ExtensionSet const ret = {
|
||||
VK_EXT_METAL_SURFACE_EXTENSION_NAME,
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatformApple::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
CAMetalLayer* mlayer = (__bridge CAMetalLayer*) nativeWindow;
|
||||
FILAMENT_CHECK_POSTCONDITION(mlayer) << "Unable to obtain Metal-backed layer.";
|
||||
|
||||
+5
-32
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.
|
||||
@@ -14,11 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
#include <backend/platforms/VulkanPlatformLinux.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "vulkan/VulkanDriverFactory.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
@@ -74,17 +73,13 @@
|
||||
void* library = nullptr;
|
||||
} g_x11_vk;
|
||||
}// anonymous namespace
|
||||
#elif defined(WIN32)
|
||||
// No platform specific includes
|
||||
#else
|
||||
// Not a supported Vulkan platform
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||
VulkanPlatform::ExtensionSet VulkanPlatformLinux::getSwapchainInstanceExtensions() const {
|
||||
VulkanPlatform::ExtensionSet const ret = {
|
||||
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
|
||||
@@ -95,15 +90,13 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
|
||||
#endif
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatformLinux::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
|
||||
// On certain platforms, the extent of the surface cannot be queried from Vulkan. In those
|
||||
@@ -182,26 +175,6 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativ
|
||||
<< "vkCreateXlibSurfaceKHR error=" << static_cast<int32_t>(result);
|
||||
}
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
// On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to
|
||||
// vkGetPhysicalDeviceSurfaceCapabilitiesKHR()) does not correctly handle the fact that
|
||||
// each native window has its own DPI_AWARENESS_CONTEXT, and erroneously uses the context
|
||||
// of the calling thread. As a workaround, we set the current thread's DPI_AWARENESS_CONTEXT
|
||||
// to that of the native window we've been given. This isn't a perfect solution, because an
|
||||
// application could create swap chains on multiple native windows with varying DPI-awareness,
|
||||
// but even then, at least one of the windows would be guaranteed to work correctly.
|
||||
SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext((HWND) nativeWindow));
|
||||
|
||||
VkWin32SurfaceCreateInfoKHR const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
|
||||
.hinstance = GetModuleHandle(nullptr),
|
||||
.hwnd = (HWND) nativeWindow,
|
||||
};
|
||||
VkResult const result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateWin32SurfaceKHR failed."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
#endif
|
||||
return std::make_tuple(surface, extent);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 <backend/platforms/VulkanPlatformWindows.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(WIN32)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatformWindows::getSwapchainInstanceExtensions() const {
|
||||
VulkanPlatform::ExtensionSet const ret = {
|
||||
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatformWindows::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
VkExtent2D extent;
|
||||
|
||||
// On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to
|
||||
// vkGetPhysicalDeviceSurfaceCapabilitiesKHR()) does not correctly handle the fact that
|
||||
// each native window has its own DPI_AWARENESS_CONTEXT, and erroneously uses the context
|
||||
// of the calling thread. As a workaround, we set the current thread's DPI_AWARENESS_CONTEXT
|
||||
// to that of the native window we've been given. This isn't a perfect solution, because an
|
||||
// application could create swap chains on multiple native windows with varying DPI-awareness,
|
||||
// but even then, at least one of the windows would be guaranteed to work correctly.
|
||||
SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext((HWND) nativeWindow));
|
||||
|
||||
VkWin32SurfaceCreateInfoKHR const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
|
||||
.hinstance = GetModuleHandle(nullptr),
|
||||
.hwnd = (HWND) nativeWindow,
|
||||
};
|
||||
VkResult const result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateWin32SurfaceKHR failed."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
return std::make_tuple(surface, VkExtent2D{});
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -243,10 +243,10 @@ public:
|
||||
/** Utility to set the projection matrix from the field-of-view.
|
||||
*
|
||||
* @param fovInDegrees full field-of-view in degrees. 0 < \p fov < 180.
|
||||
* @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
|
||||
* @param near distance in world units from the camera to the near plane. \p near > 0.
|
||||
* @param far distance in world units from the camera to the far plane. \p far > \p near.
|
||||
* @param direction direction of the \p fovInDegrees parameter.
|
||||
* @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
|
||||
* @param near distance in world units from the camera to the near plane. \p near > 0.
|
||||
* @param far distance in world units from the camera to the far plane. \p far > \p near.
|
||||
* @param direction direction of the \p fovInDegrees parameter.
|
||||
*
|
||||
* @see Fov.
|
||||
*/
|
||||
@@ -256,9 +256,9 @@ public:
|
||||
/** Utility to set the projection matrix from the focal length.
|
||||
*
|
||||
* @param focalLengthInMillimeters lens's focal length in millimeters. \p focalLength > 0.
|
||||
* @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
|
||||
* @param near distance in world units from the camera to the near plane. \p near > 0.
|
||||
* @param far distance in world units from the camera to the far plane. \p far > \p near.
|
||||
* @param aspect aspect ratio \f$ \frac{width}{height} \f$. \p aspect > 0.
|
||||
* @param near distance in world units from the camera to the near plane. \p near > 0.
|
||||
* @param far distance in world units from the camera to the far plane. \p far > \p near.
|
||||
*/
|
||||
void setLensProjection(double focalLengthInMillimeters,
|
||||
double aspect, double near, double far);
|
||||
@@ -270,8 +270,8 @@ public:
|
||||
* that is all 3 axis are mapped to [-1, 1].
|
||||
*
|
||||
* @param projection custom projection matrix used for rendering and culling
|
||||
* @param near distance in world units from the camera to the near plane. \p near > 0.
|
||||
* @param far distance in world units from the camera to the far plane. \p far > \p near.
|
||||
* @param near distance in world units from the camera to the near plane.
|
||||
* @param far distance in world units from the camera to the far plane. \p far != \p near.
|
||||
*/
|
||||
void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept;
|
||||
|
||||
@@ -282,8 +282,8 @@ public:
|
||||
*
|
||||
* @param projection custom projection matrix used for rendering
|
||||
* @param projectionForCulling custom projection matrix used for culling
|
||||
* @param near distance in world units from the camera to the near plane. \p near > 0.
|
||||
* @param far distance in world units from the camera to the far plane. \p far > \p near.
|
||||
* @param near distance in world units from the camera to the near plane.
|
||||
* @param far distance in world units from the camera to the far plane. \p far != \p near.
|
||||
*/
|
||||
void setCustomProjection(math::mat4 const& projection, math::mat4 const& projectionForCulling,
|
||||
double near, double far) noexcept;
|
||||
|
||||
@@ -541,6 +541,14 @@ public:
|
||||
*/
|
||||
DynamicResolutionOptions getDynamicResolutionOptions() const noexcept;
|
||||
|
||||
/**
|
||||
* Returns the last dynamic resolution scale factor used by this view. This value is updated
|
||||
* when Renderer::render(View*) is called
|
||||
* @return a float2 where x is the horizontal and y the vertical scale factor.
|
||||
* @see Renderer::render
|
||||
*/
|
||||
math::float2 getLastDynamicResolutionScale() const noexcept;
|
||||
|
||||
/**
|
||||
* Sets the rendering quality for this view. Refer to RenderQuality for more
|
||||
* information about the different settings available.
|
||||
|
||||
+28
-5
@@ -16,14 +16,30 @@
|
||||
|
||||
#include "details/Camera.h"
|
||||
|
||||
#include <filament/Camera.h>
|
||||
|
||||
#include <math/mat4.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace math;
|
||||
|
||||
void Camera::setProjection(double const fovInDegrees, double const aspect, double const near, double const far,
|
||||
Fov const direction) {
|
||||
void Camera::setProjection(double const fovInDegrees, double const aspect,
|
||||
double const near, double const far, Fov const direction) {
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(near > 0 && far > near)
|
||||
<< "Camera preconditions not met in setProjection(): near <= 0 or far <= near, near="
|
||||
<< near << ", far=" << far;
|
||||
|
||||
setCustomProjection(
|
||||
projection(direction, fovInDegrees, aspect, near),
|
||||
projection(direction, fovInDegrees, aspect, near, far),
|
||||
@@ -32,6 +48,11 @@ void Camera::setProjection(double const fovInDegrees, double const aspect, doubl
|
||||
|
||||
void Camera::setLensProjection(double const focalLengthInMillimeters,
|
||||
double const aspect, double const near, double const far) {
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(near > 0 && far > near)
|
||||
<< "Camera preconditions not met in setLensProjection(): near <= 0 or far <= near, near="
|
||||
<< near << ", far=" << far;
|
||||
|
||||
setCustomProjection(
|
||||
projection(focalLengthInMillimeters, aspect, near),
|
||||
projection(focalLengthInMillimeters, aspect, near, far),
|
||||
@@ -149,8 +170,9 @@ utils::Entity Camera::getEntity() const noexcept {
|
||||
return downcast(this)->getEntity();
|
||||
}
|
||||
|
||||
void Camera::setExposure(float const aperture, float const shutterSpeed, float const ISO) noexcept {
|
||||
downcast(this)->setExposure(aperture, shutterSpeed, ISO);
|
||||
void Camera::setExposure(
|
||||
float const aperture, float const shutterSpeed, float const sensitivity) noexcept {
|
||||
downcast(this)->setExposure(aperture, shutterSpeed, sensitivity);
|
||||
}
|
||||
|
||||
float Camera::getAperture() const noexcept {
|
||||
@@ -177,7 +199,8 @@ double Camera::getFocalLength() const noexcept {
|
||||
return downcast(this)->getFocalLength();
|
||||
}
|
||||
|
||||
double Camera::computeEffectiveFocalLength(double const focalLength, double const focusDistance) noexcept {
|
||||
double Camera::computeEffectiveFocalLength(
|
||||
double const focalLength, double const focusDistance) noexcept {
|
||||
return FCamera::computeEffectiveFocalLength(focalLength, focusDistance);
|
||||
}
|
||||
|
||||
|
||||
+56
-43
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -226,6 +226,8 @@ bool Froxelizer::prepare(
|
||||
filament::Viewport const& viewport,
|
||||
const mat4f& projection, float const projectionNear, float const projectionFar,
|
||||
float4 const& clipTransform) noexcept {
|
||||
assert_invariant(projectionFar > projectionNear);
|
||||
assert_invariant(projectionNear > 0);
|
||||
setViewport(viewport);
|
||||
setProjection(projection, projectionNear, projectionFar);
|
||||
|
||||
@@ -383,8 +385,28 @@ bool Froxelizer::update() noexcept {
|
||||
bool uniformsNeedUpdating = false;
|
||||
|
||||
if (UTILS_UNLIKELY(mDirtyFlags & (OPTIONS_CHANGED|PROJECTION_CHANGED))) {
|
||||
float const zLightFar = clamp(mUserZLightFar, mNear, mFar);
|
||||
float zLightNear = clamp(mUserZLightNear, mNear, mFar);
|
||||
|
||||
// sanitize the user's near/far
|
||||
float zLightNear = mUserZLightNear;
|
||||
float zLightFar = mUserZLightFar;
|
||||
if (zLightFar == zLightNear) {
|
||||
zLightNear = mNear;
|
||||
zLightFar = mFar;
|
||||
}
|
||||
if (zLightFar < zLightNear) {
|
||||
std::swap(zLightFar, zLightNear);
|
||||
}
|
||||
if (zLightNear < mNear || zLightNear >= mFar) {
|
||||
zLightNear = mNear;
|
||||
}
|
||||
if (zLightFar > mFar || zLightFar <= mNear) {
|
||||
zLightFar = mFar;
|
||||
}
|
||||
|
||||
assert_invariant(zLightNear < zLightFar);
|
||||
assert_invariant(zLightNear >= mNear && zLightNear <= mFar);
|
||||
assert_invariant(zLightFar <= mFar && zLightNear >= mNear);
|
||||
|
||||
zLightNear = std::min(zLightNear, zLightFar);
|
||||
if (zLightFar != mZLightFar || zLightNear != mZLightNear) {
|
||||
mDirtyFlags |= VIEWPORT_CHANGED;
|
||||
|
||||
@@ -178,6 +178,10 @@ View::DynamicResolutionOptions View::getDynamicResolutionOptions() const noexcep
|
||||
return downcast(this)->getDynamicResolutionOptions();
|
||||
}
|
||||
|
||||
math::float2 View::getLastDynamicResolutionScale() const noexcept {
|
||||
return downcast(this)->getLastDynamicResolutionScale();
|
||||
}
|
||||
|
||||
void View::setRenderQuality(const RenderQuality& renderQuality) noexcept {
|
||||
downcast(this)->setRenderQuality(renderQuality);
|
||||
}
|
||||
|
||||
@@ -24,27 +24,35 @@
|
||||
#include <filament/Camera.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <math/scalar.h>
|
||||
|
||||
#include <math/mat4.h>
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec3.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
using namespace filament::math;
|
||||
using namespace utils;
|
||||
|
||||
namespace filament {
|
||||
|
||||
static constexpr const float MIN_APERTURE = 0.5f;
|
||||
static constexpr const float MAX_APERTURE = 64.0f;
|
||||
static constexpr const float MIN_SHUTTER_SPEED = 1.0f / 25000.0f;
|
||||
static constexpr const float MAX_SHUTTER_SPEED = 60.0f;
|
||||
static constexpr const float MIN_SENSITIVITY = 10.0f;
|
||||
static constexpr const float MAX_SENSITIVITY = 204800.0f;
|
||||
static constexpr float MIN_APERTURE = 0.5f;
|
||||
static constexpr float MAX_APERTURE = 64.0f;
|
||||
static constexpr float MIN_SHUTTER_SPEED = 1.0f / 25000.0f;
|
||||
static constexpr float MAX_SHUTTER_SPEED = 60.0f;
|
||||
static constexpr float MIN_SENSITIVITY = 10.0f;
|
||||
static constexpr float MAX_SENSITIVITY = 204800.0f;
|
||||
|
||||
FCamera::FCamera(FEngine& engine, Entity const e)
|
||||
: mEngine(engine),
|
||||
mEntity(e) {
|
||||
setProjection(Projection::PERSPECTIVE, -1.0, 1.0, -1.0, 1.0, 0.1, 1.0);
|
||||
}
|
||||
|
||||
mat4 FCamera::projection(Fov const direction, double const fovInDegrees,
|
||||
@@ -84,12 +92,16 @@ mat4 FCamera::projection(double const focalLengthInMillimeters,
|
||||
* All methods for setting the projection funnel through here
|
||||
*/
|
||||
|
||||
void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p,
|
||||
mat4 const& c, double const near, double const far) noexcept {
|
||||
void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& projection,
|
||||
mat4 const& projectionForCulling, double const near, double const far) noexcept {
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(near != far)
|
||||
<< "Camera preconditions not met in setCustomProjection(): near = far = " << near;
|
||||
|
||||
for (auto& eyeProjection: mEyeProjection) {
|
||||
eyeProjection = p;
|
||||
eyeProjection = projection;
|
||||
}
|
||||
mProjectionForCulling = c;
|
||||
mProjectionForCulling = projectionForCulling;
|
||||
mNear = near;
|
||||
mFar = far;
|
||||
}
|
||||
@@ -97,10 +109,15 @@ void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p,
|
||||
void UTILS_NOINLINE FCamera::setCustomEyeProjection(mat4 const* projection, size_t const count,
|
||||
mat4 const& projectionForCulling, double const near, double const far) {
|
||||
const Engine::Config& config = mEngine.getConfig();
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(near != far)
|
||||
<< "Camera preconditions not met in setCustomEyeProjection(): near = far = " << near;
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(count >= config.stereoscopicEyeCount)
|
||||
<< "All eye projections must be supplied together, count must be >= "
|
||||
"config.stereoscopicEyeCount ("
|
||||
<< config.stereoscopicEyeCount << ")";
|
||||
|
||||
for (int i = 0; i < config.stereoscopicEyeCount; i++) {
|
||||
mEyeProjection[i] = projection[i];
|
||||
}
|
||||
@@ -290,10 +307,10 @@ CameraInfo::CameraInfo(FCamera const& camera,
|
||||
model = mat4f{ modelMatrix };
|
||||
view = mat4f{ inverse(modelMatrix) };
|
||||
worldTransform = inWorldTransform;
|
||||
zn = (float)camera.getNear();
|
||||
zf = (float)camera.getCullingFar();
|
||||
zn = float(camera.getNear());
|
||||
zf = float(camera.getCullingFar());
|
||||
ev100 = Exposure::ev100(camera);
|
||||
f = (float)camera.getFocalLength();
|
||||
f = float(camera.getFocalLength());
|
||||
A = f / camera.getAperture();
|
||||
d = std::max(zn, camera.getFocusDistance());
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
+149
-139
@@ -20,9 +20,11 @@
|
||||
#include "details/BufferAllocator.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <private/utils/Tracing.h>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace filament {
|
||||
|
||||
namespace {
|
||||
@@ -33,6 +35,80 @@ using AllocationId = BufferAllocator::AllocationId;
|
||||
using allocation_size_t = BufferAllocator::allocation_size_t;
|
||||
} // anonymous namespace
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// FenceManager
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void UboManager::FenceManager::track(DriverApi& driver, std::unordered_set<AllocationId>&& allocationIds) {
|
||||
if (allocationIds.empty()) {
|
||||
return;
|
||||
}
|
||||
mFenceAllocationList.emplace_back(driver.createFence(), std::move(allocationIds));
|
||||
}
|
||||
|
||||
void UboManager::FenceManager::reclaimCompletedResources(DriverApi& driver,
|
||||
std::function<void(AllocationId)> const& onReclaimed) {
|
||||
uint32_t signaledCount = 0;
|
||||
bool seenSignaledFence = false;
|
||||
|
||||
// Iterate from the newest fence to the oldest.
|
||||
for (auto it = mFenceAllocationList.rbegin(); it != mFenceAllocationList.rend(); ++it) {
|
||||
const Handle<HwFence>& fence = it->first;
|
||||
const FenceStatus status = driver.getFenceStatus(fence);
|
||||
|
||||
// If we have already seen a signaled fence, we can assume all older fences
|
||||
// are also complete, regardless of their reported status (e.g., TIMEOUT_EXPIRED).
|
||||
// This is guaranteed by the in-order execution of GPU command queues.
|
||||
if (seenSignaledFence) {
|
||||
signaledCount++;
|
||||
#ifndef NDEBUG
|
||||
if (UTILS_UNLIKELY(status != FenceStatus::CONDITION_SATISFIED)) {
|
||||
LOG(WARNING) << "A fence is either in an error state or hasn't signaled, but a newer "
|
||||
"fence has. Will release the resource anyway.";
|
||||
}
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if (status == FenceStatus::CONDITION_SATISFIED) {
|
||||
seenSignaledFence = true;
|
||||
signaledCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (signaledCount == 0) {
|
||||
// No fences have completed, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
auto firstToKeep = mFenceAllocationList.begin() + signaledCount;
|
||||
|
||||
// Invoke the callback for all resources protected by completed fences.
|
||||
for (auto it = mFenceAllocationList.begin(); it != firstToKeep; ++it) {
|
||||
for (const AllocationId& id : it->second) {
|
||||
onReclaimed(id);
|
||||
}
|
||||
// Destroy the fence handle as it's no longer needed.
|
||||
driver.destroyFence(std::move(it->first));
|
||||
}
|
||||
|
||||
mFenceAllocationList.erase(mFenceAllocationList.begin(), firstToKeep);
|
||||
}
|
||||
|
||||
void UboManager::FenceManager::reset(DriverApi& driver) {
|
||||
for (auto& [fence, _] : mFenceAllocationList) {
|
||||
if (fence) {
|
||||
driver.destroyFence(std::move(fence));
|
||||
}
|
||||
}
|
||||
mFenceAllocationList.clear();
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// UboManager
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
UboManager::UboManager(DriverApi& driver, allocation_size_t defaultSlotSizeInBytes,
|
||||
allocation_size_t defaultTotalSizeInBytes)
|
||||
: mAllocator(defaultTotalSizeInBytes, defaultSlotSizeInBytes) {
|
||||
@@ -41,17 +117,16 @@ UboManager::UboManager(DriverApi& driver, allocation_size_t defaultSlotSizeInByt
|
||||
|
||||
void UboManager::beginFrame(DriverApi& driver,
|
||||
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>& materialInstances) {
|
||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
||||
// Check finished frames and decrement GPU count accordingly.
|
||||
checkFenceAndUnlockSlots(driver);
|
||||
mFenceManager.reclaimCompletedResources(driver,
|
||||
[this](AllocationId id) { mAllocator.releaseGpu(id); });
|
||||
|
||||
// Actually merge the slots.
|
||||
mAllocator.releaseFreeSlots();
|
||||
|
||||
// Traverse all MIs and see which of them need slot allocation.
|
||||
AllocationResult allocationResult =
|
||||
updateMaterialInstanceAllocations(materialInstances, ON_DEMAND);
|
||||
|
||||
if (allocationResult == SUCCESS) {
|
||||
if (allocateOnDemand(materialInstances) == SUCCESS) {
|
||||
// No need to grow the buffer, so we can just map the buffer for writing and return.
|
||||
mMemoryMappedBufferHandle = driver.mapBuffer(mUbHandle, 0, mUboSize, MapBufferAccessFlags::WRITE_BIT,
|
||||
"UboManager");
|
||||
@@ -64,24 +139,20 @@ void UboManager::beginFrame(DriverApi& driver,
|
||||
reallocate(driver, requiredSize);
|
||||
|
||||
// Allocate slots for each MI on the new Ubo.
|
||||
UTILS_UNUSED_IN_RELEASE const AllocationResult needReallocationAgain =
|
||||
updateMaterialInstanceAllocations(materialInstances, ALWAYS);
|
||||
assert_invariant(needReallocationAgain != REALLOCATION_REQUIRED);
|
||||
allocateAllInstances(materialInstances);
|
||||
|
||||
// Map the buffer so that we can write to it
|
||||
mMemoryMappedBufferHandle =
|
||||
driver.mapBuffer(mUbHandle, 0, mUboSize, MapBufferAccessFlags::WRITE_BIT, "UboManager");
|
||||
|
||||
// Migrate all MI data to the new allocated slots.
|
||||
// Invalidate the migrated MIs, so that next commit() call must be triggered.
|
||||
for (const auto& materialInstance : materialInstances) {
|
||||
materialInstance.second.forEach([this, &driver](const FMaterialInstance* mi) {
|
||||
materialInstance.second.forEach([](const FMaterialInstance* mi) {
|
||||
if (!mi->isUsingUboBatching()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const AllocationId allocationId = mi->getAllocationId();
|
||||
assert_invariant(BufferAllocator::isValid(allocationId));
|
||||
updateSlot(driver, allocationId, mi->getUniformBuffer().toBufferDescriptor(driver));
|
||||
mi->getUniformBuffer().invalidate();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -113,17 +184,11 @@ void UboManager::endFrame(DriverApi& driver,
|
||||
});
|
||||
}
|
||||
|
||||
mFenceAllocationList.emplace_back( driver.createFence(), std::move(allocationIds) );
|
||||
mFenceManager.track(driver, std::move(allocationIds));
|
||||
}
|
||||
|
||||
void UboManager::terminate(DriverApi& driver) {
|
||||
for (auto& [fence, _]: mFenceAllocationList) {
|
||||
if (fence) {
|
||||
driver.destroyFence(std::move(fence));
|
||||
}
|
||||
}
|
||||
mFenceAllocationList.clear();
|
||||
|
||||
mFenceManager.reset(driver);
|
||||
driver.destroyBufferObject(mUbHandle);
|
||||
}
|
||||
|
||||
@@ -142,6 +207,66 @@ void UboManager::retireSlot(BufferAllocator::AllocationId id) {
|
||||
mAllocator.retire(id);
|
||||
}
|
||||
|
||||
UboManager::AllocationResult UboManager::allocateOnDemand(
|
||||
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
|
||||
materialInstances) {
|
||||
// Collect all MIs that need allocation into two groups.
|
||||
std::vector<FMaterialInstance*> newInstances;
|
||||
std::vector<FMaterialInstance*> existingInstances;
|
||||
for (const auto& [_, miList] : materialInstances) {
|
||||
miList.forEach([&](FMaterialInstance* mi) {
|
||||
if (!mi->isUsingUboBatching()) {
|
||||
return;
|
||||
}
|
||||
if (BufferAllocator::isValid(mi->getAllocationId())) {
|
||||
existingInstances.push_back(mi);
|
||||
} else {
|
||||
newInstances.push_back(mi);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool reallocationNeeded = false;
|
||||
|
||||
// Pass 1: Allocate slots for new material instances (that don't have a slot yet).
|
||||
for (FMaterialInstance* mi : newInstances) {
|
||||
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
|
||||
mi->assignUboAllocation(mUbHandle, newId, newOffset);
|
||||
if (!BufferAllocator::isValid(newId)) {
|
||||
reallocationNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: Allocate slots for existing material instances that need to be orphaned.
|
||||
for (FMaterialInstance* mi : existingInstances) {
|
||||
if (mi->getUniformBuffer().isDirty() && mAllocator.isLockedByGpu(mi->getAllocationId())) {
|
||||
mAllocator.retire(mi->getAllocationId());
|
||||
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
|
||||
mi->assignUboAllocation(mUbHandle, newId, newOffset);
|
||||
if (!BufferAllocator::isValid(newId)) {
|
||||
reallocationNeeded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reallocationNeeded ? REALLOCATION_REQUIRED : SUCCESS;
|
||||
}
|
||||
|
||||
void UboManager::allocateAllInstances(
|
||||
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
|
||||
materialInstances) {
|
||||
for (const auto& [_, miList] : materialInstances) {
|
||||
miList.forEach([this](FMaterialInstance* mi) {
|
||||
if (!mi->isUsingUboBatching()) {
|
||||
return;
|
||||
}
|
||||
auto [newId, newOffset] = mAllocator.allocate(mi->getUniformBuffer().getSize());
|
||||
assert_invariant(BufferAllocator::isValid(newId));
|
||||
mi->assignUboAllocation(mUbHandle, newId, newOffset);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
allocation_size_t UboManager::getTotalSize() const noexcept {
|
||||
return mUboSize;
|
||||
}
|
||||
@@ -150,128 +275,13 @@ allocation_size_t UboManager::getAllocationOffset(AllocationId id) const {
|
||||
return mAllocator.getAllocationOffset(id);
|
||||
}
|
||||
|
||||
void UboManager::checkFenceAndUnlockSlots(DriverApi& driver) {
|
||||
uint32_t signaledCount = 0;
|
||||
bool seenSignaledFence = false;
|
||||
|
||||
// Iterate from the newest fence to the oldest.
|
||||
for (auto it = mFenceAllocationList.rbegin(); it != mFenceAllocationList.rend(); ++it) {
|
||||
const Handle<HwFence>& fence = it->first;
|
||||
const FenceStatus status = driver.getFenceStatus(fence);
|
||||
|
||||
// If we have already seen a signaled fence, we can assume all older fences
|
||||
// are also complete, regardless of their reported status (e.g., TIMEOUT_EXPIRED).
|
||||
// This is guaranteed by the in-order execution of GPU command queues.
|
||||
if (seenSignaledFence) {
|
||||
signaledCount++;
|
||||
#ifndef NDEBUG
|
||||
if (UTILS_UNLIKELY(status != FenceStatus::CONDITION_SATISFIED)) {
|
||||
LOG(WARNING) << "A fence is either an error or hasn't signaled, but the new fence has "
|
||||
"been "
|
||||
"signaled. Will release the resource anyways."
|
||||
<< io::endl;
|
||||
}
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
if (status == FenceStatus::CONDITION_SATISFIED) {
|
||||
seenSignaledFence = true;
|
||||
signaledCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (signaledCount == 0) {
|
||||
// No fences have completed, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
auto firstToKeep = mFenceAllocationList.begin() + signaledCount;
|
||||
|
||||
// Release GPU locks for all completed fences.
|
||||
for (auto it = mFenceAllocationList.begin(); it != firstToKeep; ++it) {
|
||||
for (const AllocationId& id: it->second) {
|
||||
mAllocator.releaseGpu(id);
|
||||
}
|
||||
// Destroy the fence handle as it's no longer needed.
|
||||
driver.destroyFence(std::move(it->first));
|
||||
}
|
||||
|
||||
mFenceAllocationList.erase(mFenceAllocationList.begin(), firstToKeep);
|
||||
}
|
||||
|
||||
UboManager::AllocationResult UboManager::updateMaterialInstanceAllocations(
|
||||
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
|
||||
materialInstances,
|
||||
AllocationMode allocationMode) {
|
||||
AllocationResult result = SUCCESS;
|
||||
for (const auto& materialInstance: materialInstances) {
|
||||
materialInstance.second.forEach([this, &result, allocationMode](FMaterialInstance* mi) {
|
||||
if (tryAllocateMaterialInstanceSlot(mi, mAllocator, mUbHandle, allocationMode) ==
|
||||
REALLOCATION_REQUIRED) {
|
||||
result = REALLOCATION_REQUIRED;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
UboManager::AllocationResult UboManager::tryAllocateMaterialInstanceSlot(FMaterialInstance* mi,
|
||||
BufferAllocator& allocator, const Handle<HwBufferObject>& ubHandle,
|
||||
AllocationMode allocationMode) {
|
||||
if (!mi->isUsingUboBatching()) {
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
const AllocationId id = mi->getAllocationId();
|
||||
auto allocateAndAssign = [&](AllocationId originalId) -> AllocationResult {
|
||||
auto [newId, newOffset] = allocator.allocate(mi->getUniformBuffer().getSize());
|
||||
// Special handling for instances that were previously UNALLOCATED:
|
||||
// If the new allocation also fails, keep it UNALLOCATED to signal initial failure.
|
||||
if (originalId == BufferAllocator::UNALLOCATED &&
|
||||
newId == BufferAllocator::REALLOCATION_REQUIRED) {
|
||||
newId = BufferAllocator::UNALLOCATED;
|
||||
}
|
||||
|
||||
// Even if the allocation is not valid, we need to set it to let the following
|
||||
// process knows.
|
||||
mi->assignUboAllocation(ubHandle, newId, newOffset);
|
||||
|
||||
if (!BufferAllocator::isValid(newId)) {
|
||||
return REALLOCATION_REQUIRED;
|
||||
}
|
||||
return SUCCESS;
|
||||
};
|
||||
|
||||
if (!BufferAllocator::isValid(id) || allocationMode == ALWAYS) {
|
||||
// The material instance is first time being allocated (if !isValid)
|
||||
// or is being forcibly re-allocated (if ALWAYS).
|
||||
return allocateAndAssign(id);
|
||||
}
|
||||
|
||||
if (mi->getUniformBuffer().isDirty() && allocator.isLockedByGpu(id)) {
|
||||
// If the uniform buffer is updated and the slot is still being locked by GPU,
|
||||
// we will need to allocate a new slot to write the updated content.
|
||||
// This is known as "orphaning".
|
||||
allocator.retire(id);
|
||||
return allocateAndAssign(id);
|
||||
}
|
||||
// Else, the slot is valid and no need to allocate a new slot.
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
void UboManager::reallocate(DriverApi& driver, allocation_size_t requiredSize) {
|
||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
||||
if (mUbHandle) {
|
||||
driver.destroyBufferObject(mUbHandle);
|
||||
}
|
||||
|
||||
for (auto& [fence, allocations]: mFenceAllocationList) {
|
||||
driver.destroyFence(std::move(fence));
|
||||
}
|
||||
mFenceAllocationList.clear();
|
||||
|
||||
mFenceManager.reset(driver);
|
||||
mAllocator.reset(requiredSize);
|
||||
mUboSize = requiredSize;
|
||||
mUbHandle = driver.createBufferObject(requiredSize, BufferObjectBinding::UNIFORM,
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
#include <backend/Handle.h>
|
||||
#include <private/backend/DriverApi.h>
|
||||
|
||||
#include <functional>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -40,6 +42,47 @@ class FMaterialInstance;
|
||||
// undefined behavior.
|
||||
class UboManager {
|
||||
public:
|
||||
// This utility tracks resources that are in-use by the GPU across multiple frames.
|
||||
// It uses backend fences to determine when the GPU has finished with a set of resources,
|
||||
// allowing them to be safely reclaimed or reused.
|
||||
//
|
||||
// The typical usage is to `track()` a set of resources at the end of a frame and
|
||||
// call `reclaimCompletedResources()` at the beginning of a future frame to free up
|
||||
// resources from completed GPU work.
|
||||
//
|
||||
// This class is designed for single-threaded access.
|
||||
class FenceManager {
|
||||
public:
|
||||
using AllocationId = BufferAllocator::AllocationId;
|
||||
|
||||
FenceManager() = default;
|
||||
~FenceManager() = default;
|
||||
|
||||
FenceManager(FenceManager const&) = delete;
|
||||
FenceManager(FenceManager&&) = delete;
|
||||
|
||||
|
||||
// Creates a new fence to track a set of allocation IDs for the current frame.
|
||||
// This marks the beginning of GPU's usage of these resources.
|
||||
void track(backend::DriverApi& driver, std::unordered_set<AllocationId>&& allocationIds);
|
||||
|
||||
|
||||
// Checks all tracked fences and invokes a callback for resources associated with
|
||||
// completed fences. This should be called once per frame.
|
||||
void reclaimCompletedResources(backend::DriverApi& driver,
|
||||
std::function<void(AllocationId)> const& onReclaimed);
|
||||
|
||||
// Destroys all tracked fences and clears the tracking list.
|
||||
// This is used for cleanup during termination or major reallocations.
|
||||
void reset(backend::DriverApi& driver);
|
||||
|
||||
private:
|
||||
// Not ideal, but we need to know which slots to decrement gpuUseCount for each frame.
|
||||
using FenceAndAllocations =
|
||||
std::pair<backend::Handle<backend::HwFence>, std::unordered_set<AllocationId>>;
|
||||
std::vector<FenceAndAllocations> mFenceAllocationList;
|
||||
};
|
||||
|
||||
explicit UboManager(backend::DriverApi& driver,
|
||||
BufferAllocator::allocation_size_t defaultSlotSizeInBytes,
|
||||
BufferAllocator::allocation_size_t defaultTotalSizeInBytes);
|
||||
@@ -82,30 +125,25 @@ public:
|
||||
private:
|
||||
constexpr static float BUFFER_SIZE_GROWTH_MULTIPLIER = 1.5f;
|
||||
|
||||
// Query the offset by the allocation id.
|
||||
[[nodiscard]] BufferAllocator::allocation_size_t getAllocationOffset(
|
||||
BufferAllocator::AllocationId id) const;
|
||||
|
||||
void checkFenceAndUnlockSlots(backend::DriverApi& driver);
|
||||
|
||||
enum AllocationResult {
|
||||
SUCCESS,
|
||||
REALLOCATION_REQUIRED
|
||||
};
|
||||
|
||||
enum AllocationMode {
|
||||
ON_DEMAND,
|
||||
ALWAYS
|
||||
};
|
||||
// Query the offset by the allocation id.
|
||||
[[nodiscard]] BufferAllocator::allocation_size_t getAllocationOffset(
|
||||
BufferAllocator::AllocationId id) const;
|
||||
|
||||
AllocationResult updateMaterialInstanceAllocations(
|
||||
AllocationResult allocateOnDemand(
|
||||
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
|
||||
materialInstances,
|
||||
AllocationMode allocationMode);
|
||||
static AllocationResult tryAllocateMaterialInstanceSlot(FMaterialInstance* mi,
|
||||
BufferAllocator& allocator, const backend::Handle<backend::HwBufferObject>& ubHandle,
|
||||
AllocationMode allocationMode);
|
||||
materialInstances);
|
||||
|
||||
void allocateAllInstances(
|
||||
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
|
||||
materialInstances);
|
||||
|
||||
void reallocate(backend::DriverApi& driver, BufferAllocator::allocation_size_t requiredSize);
|
||||
|
||||
BufferAllocator::allocation_size_t calculateRequiredSize(
|
||||
const std::unordered_map<const FMaterial*, ResourceList<FMaterialInstance>>&
|
||||
materialInstances);
|
||||
@@ -114,11 +152,7 @@ private:
|
||||
backend::MemoryMappedBufferHandle mMemoryMappedBufferHandle;
|
||||
BufferAllocator::allocation_size_t mUboSize{};
|
||||
|
||||
// Not ideal, but we need to know which slots to decrement gpuUseCount for each frame.
|
||||
using FenceAllocationList = std::vector<std::pair<backend::Handle<backend::HwFence>,
|
||||
std::unordered_set<BufferAllocator::AllocationId>>>;
|
||||
FenceAllocationList mFenceAllocationList;
|
||||
|
||||
FenceManager mFenceManager;
|
||||
BufferAllocator mAllocator;
|
||||
};
|
||||
|
||||
|
||||
@@ -338,6 +338,10 @@ public:
|
||||
return mDynamicResolution;
|
||||
}
|
||||
|
||||
math::float2 getLastDynamicResolutionScale() const noexcept {
|
||||
return mScale;
|
||||
}
|
||||
|
||||
void setRenderQuality(RenderQuality const& renderQuality) noexcept {
|
||||
mRenderQuality = renderQuality;
|
||||
}
|
||||
|
||||
@@ -44,10 +44,12 @@ list(APPEND RESGEN_SOURCE ${DUMMY_SRC})
|
||||
# away in Release builds
|
||||
if (TNT_DEV)
|
||||
add_executable(test_${TARGET}
|
||||
MockDriver.h
|
||||
filament_AtlasAllocator_test.cpp
|
||||
test_BufferAllocator.cpp
|
||||
test_BufferAllocatorStress.cpp
|
||||
test_CircularQueue.cpp
|
||||
test_FenceManager.cpp
|
||||
filament_test_exposure.cpp
|
||||
filament_rendering_test.cpp
|
||||
filament_bimap_test.cpp
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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 TNT_FILAMENT_TEST_MOCK_DRIVER_H
|
||||
#define TNT_FILAMENT_TEST_MOCK_DRIVER_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "private/backend/CommandStream.h"
|
||||
#include "private/backend/Driver.h"
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::backend;
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::InSequence;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::Return;
|
||||
|
||||
// A mock implementation of the backend::Driver for unit testing.
|
||||
//
|
||||
// This mock is primarily designed for testing synchronous APIs and functions that
|
||||
// return a value immediately (those with the "S" suffix, like `createBufferObjectS`).
|
||||
// It does not fully support asynchronous command stream behavior, so its use for
|
||||
// testing complex rendering logic is limited.
|
||||
class MockDriver : public Driver {
|
||||
public:
|
||||
MockDriver() = default;
|
||||
~MockDriver() override = default;
|
||||
|
||||
uint64_t nextFakeHandle = 1;
|
||||
|
||||
MOCK_METHOD(void, terminate, (), (override));
|
||||
MOCK_METHOD(backend::StreamHandle, createStreamNative,
|
||||
(void* stream, utils::ImmutableCString tag), (override));
|
||||
MOCK_METHOD(backend::StreamHandle, createStreamAcquired, (utils::ImmutableCString tag),
|
||||
(override));
|
||||
MOCK_METHOD(void, setAcquiredImage,
|
||||
(backend::StreamHandle stream, void* image, const math::mat3f& transform,
|
||||
backend::CallbackHandler* handler, backend::StreamCallback cb, void* userData),
|
||||
(override));
|
||||
MOCK_METHOD(void, setStreamDimensions,
|
||||
(backend::StreamHandle stream, uint32_t width, uint32_t height), (override));
|
||||
MOCK_METHOD(int64_t, getStreamTimestamp, (backend::StreamHandle stream), (override));
|
||||
MOCK_METHOD(void, updateStreams, (backend::DriverApi * driver), (override));
|
||||
MOCK_METHOD(backend::FenceStatus, getFenceStatus, (backend::FenceHandle fh), (override));
|
||||
MOCK_METHOD(backend::FenceStatus, fenceWait, (backend::FenceHandle fh, uint64_t timeout),
|
||||
(override));
|
||||
MOCK_METHOD(void, getPlatformSync,
|
||||
(backend::SyncHandle sh, backend::CallbackHandler* handler,
|
||||
backend::Platform::SyncCallback cb, void* userData),
|
||||
(override));
|
||||
MOCK_METHOD(bool, isTextureFormatSupported, (backend::TextureFormat format), (override));
|
||||
MOCK_METHOD(bool, isTextureSwizzleSupported, (), (override));
|
||||
MOCK_METHOD(bool, isTextureFormatMipmappable, (backend::TextureFormat format), (override));
|
||||
MOCK_METHOD(bool, isRenderTargetFormatSupported, (backend::TextureFormat format), (override));
|
||||
MOCK_METHOD(bool, isFrameBufferFetchSupported, (), (override));
|
||||
MOCK_METHOD(bool, isFrameBufferFetchMultiSampleSupported, (), (override));
|
||||
MOCK_METHOD(bool, isFrameTimeSupported, (), (override));
|
||||
MOCK_METHOD(bool, isAutoDepthResolveSupported, (), (override));
|
||||
MOCK_METHOD(bool, isSRGBSwapChainSupported, (), (override));
|
||||
MOCK_METHOD(bool, isMSAASwapChainSupported, (uint32_t samples), (override));
|
||||
MOCK_METHOD(bool, isProtectedContentSupported, (), (override));
|
||||
MOCK_METHOD(bool, isStereoSupported, (), (override));
|
||||
MOCK_METHOD(bool, isParallelShaderCompileSupported, (), (override));
|
||||
MOCK_METHOD(bool, isDepthStencilResolveSupported, (), (override));
|
||||
MOCK_METHOD(bool, isDepthStencilBlitSupported, (backend::TextureFormat format), (override));
|
||||
MOCK_METHOD(bool, isProtectedTexturesSupported, (), (override));
|
||||
MOCK_METHOD(bool, isDepthClampSupported, (), (override));
|
||||
MOCK_METHOD(uint8_t, getMaxDrawBuffers, (), (override));
|
||||
MOCK_METHOD(size_t, getMaxUniformBufferSize, (), (override));
|
||||
MOCK_METHOD(size_t, getMaxTextureSize, (backend::SamplerType target), (override));
|
||||
MOCK_METHOD(size_t, getMaxArrayTextureLayers, (), (override));
|
||||
MOCK_METHOD(math::float2, getClipSpaceParams, (), (override));
|
||||
MOCK_METHOD(void, setupExternalImage2, (backend::Platform::ExternalImageHandleRef image),
|
||||
(override));
|
||||
MOCK_METHOD(void, setupExternalImage, (void* image), (override));
|
||||
MOCK_METHOD(backend::TimerQueryResult, getTimerQueryValue,
|
||||
(backend::TimerQueryHandle tqh, uint64_t* elapsedTime), (override));
|
||||
MOCK_METHOD(bool, isWorkaroundNeeded, (backend::Workaround workaround), (override));
|
||||
MOCK_METHOD(backend::FeatureLevel, getFeatureLevel, (), (override));
|
||||
MOCK_METHOD(size_t, getUniformBufferOffsetAlignment, (), (override));
|
||||
MOCK_METHOD(bool, isCompositorTimingSupported, (), (override));
|
||||
MOCK_METHOD(bool, queryFrameTimestamps, (SwapChainHandle, uint64_t, FrameTimestamps*), (override));
|
||||
MOCK_METHOD(bool, queryCompositorTiming, (SwapChainHandle, CompositorTiming*), (override));
|
||||
MOCK_METHOD(void, fenceCancel, (backend::FenceHandle), (override));
|
||||
|
||||
|
||||
ShaderModel getShaderModel() const noexcept final { return ShaderModel::DESKTOP; }
|
||||
utils::FixedCapacityVector<ShaderLanguage> getShaderLanguages(
|
||||
ShaderLanguage preferredLanguage) const noexcept final {
|
||||
return {
|
||||
ShaderLanguage::ESSL3,
|
||||
ShaderLanguage::ESSL1,
|
||||
ShaderLanguage::SPIRV,
|
||||
ShaderLanguage::MSL,
|
||||
ShaderLanguage::METAL_LIBRARY,
|
||||
ShaderLanguage::WGSL,
|
||||
};
|
||||
}
|
||||
void debugCommandBegin(CommandStream* cmds, bool synchronous,
|
||||
const char* methodName) noexcept override {}
|
||||
void debugCommandEnd(CommandStream* cmds, bool synchronous,
|
||||
const char* methodName) noexcept override {}
|
||||
void purge() noexcept override {}
|
||||
|
||||
template<typename T>
|
||||
friend class ConcreteDispatcher;
|
||||
|
||||
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
|
||||
UTILS_ALWAYS_INLINE inline void methodName(paramsDecl) {}
|
||||
#define DECL_DRIVER_API_SYNCHRONOUS(RetType, methodName, paramsDecl, params)
|
||||
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
|
||||
RetType methodName##S() noexcept override { \
|
||||
return RetType((RetType::HandleId) nextFakeHandle++); \
|
||||
} \
|
||||
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl) {}
|
||||
|
||||
#include "private/backend/DriverAPI.inc"
|
||||
|
||||
Dispatcher getDispatcher() const noexcept override {
|
||||
Dispatcher dispatcher;
|
||||
// Currently we don't have a good way to mock the functions for asynchronous backend API.
|
||||
// We set all API to nullptr to let the users know it doesn't work.
|
||||
#define DECL_DRIVER_API(methodName, paramsDecl, params) dispatcher.methodName##_ = nullptr;
|
||||
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
|
||||
dispatcher.methodName##_ = nullptr;
|
||||
#define DECL_DRIVER_API_SYNCHRONOUS(RetType, methodName, paramsDecl, params)
|
||||
#include "private/backend/DriverAPI.inc"
|
||||
|
||||
return dispatcher;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // TNT_FILAMENT_TEST_MOCK_DRIVER_H
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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 "MockDriver.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "details/UboManager.h"
|
||||
#include "private/backend/CommandBufferQueue.h"
|
||||
|
||||
#include "private/backend/CommandStream.h"
|
||||
#include "private/backend/Driver.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace filament;
|
||||
using namespace backend;
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::Return;
|
||||
|
||||
static constexpr size_t CONFIG_MIN_COMMAND_BUFFERS_SIZE = 1 * 1024 * 1024;
|
||||
static constexpr size_t CONFIG_COMMAND_BUFFERS_SIZE = 3 * CONFIG_MIN_COMMAND_BUFFERS_SIZE;
|
||||
|
||||
class FenceManagerTest : public ::testing::Test {
|
||||
protected:
|
||||
FenceManagerTest()
|
||||
: mCommandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE, CONFIG_COMMAND_BUFFERS_SIZE,
|
||||
/*mPaused=*/false),
|
||||
mCommandStream(mMockDriver, mCommandBufferQueue.getCircularBuffer()),
|
||||
mDriverApi(mCommandStream) {}
|
||||
|
||||
MockDriver mMockDriver;
|
||||
CommandBufferQueue mCommandBufferQueue;
|
||||
CommandStream mCommandStream;
|
||||
DriverApi& mDriverApi;
|
||||
UboManager::FenceManager mFenceManager;
|
||||
};
|
||||
|
||||
TEST_F(FenceManagerTest, TrackEmptySet) {
|
||||
mFenceManager.track(mDriverApi, {});
|
||||
EXPECT_EQ(mMockDriver.nextFakeHandle, 1); // No fence allocation
|
||||
}
|
||||
|
||||
TEST_F(FenceManagerTest, TrackNonEmptySet) {
|
||||
mFenceManager.track(mDriverApi, { 1, 2, 3 });
|
||||
EXPECT_EQ(mMockDriver.nextFakeHandle, 2);
|
||||
}
|
||||
|
||||
TEST_F(FenceManagerTest, ReclaimWithNoFences) {
|
||||
std::vector<UboManager::FenceManager::AllocationId> reclaimedIds;
|
||||
mFenceManager.reclaimCompletedResources(mDriverApi,
|
||||
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.push_back(id); });
|
||||
|
||||
EXPECT_TRUE(reclaimedIds.empty());
|
||||
}
|
||||
|
||||
TEST_F(FenceManagerTest, ReclaimWhenFenceNotSignaled) {
|
||||
mFenceManager.track(mDriverApi, { 10 });
|
||||
|
||||
EXPECT_CALL(mMockDriver, getFenceStatus(Handle<HwFence>(1)))
|
||||
.WillOnce(Return(FenceStatus::TIMEOUT_EXPIRED));
|
||||
|
||||
std::unordered_set<UboManager::FenceManager::AllocationId> reclaimedIds;
|
||||
mFenceManager.reclaimCompletedResources(mDriverApi,
|
||||
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.insert(id); });
|
||||
|
||||
EXPECT_TRUE(reclaimedIds.empty());
|
||||
}
|
||||
|
||||
TEST_F(FenceManagerTest, ReclaimWhenFenceSignaled) {
|
||||
mFenceManager.track(mDriverApi, { 10, 20 });
|
||||
EXPECT_EQ(mMockDriver.nextFakeHandle, 2);
|
||||
|
||||
EXPECT_CALL(mMockDriver, getFenceStatus(Handle<HwFence>(1)))
|
||||
.WillOnce(Return(FenceStatus::CONDITION_SATISFIED));
|
||||
|
||||
std::unordered_set<UboManager::FenceManager::AllocationId> reclaimedIds;
|
||||
mFenceManager.reclaimCompletedResources(mDriverApi,
|
||||
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.insert(id); });
|
||||
|
||||
// Verify that the correct IDs were reclaimed.
|
||||
ASSERT_EQ(reclaimedIds.size(), 2);
|
||||
EXPECT_TRUE(reclaimedIds.contains(10));
|
||||
EXPECT_TRUE(reclaimedIds.contains(20));
|
||||
|
||||
// Verify that the fence is no longer tracked by calling reclaim again.
|
||||
reclaimedIds.clear();
|
||||
EXPECT_CALL(mMockDriver, getFenceStatus(_)).Times(0); // No fences left to check.
|
||||
mFenceManager.reclaimCompletedResources(mDriverApi,
|
||||
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.insert(id); });
|
||||
EXPECT_TRUE(reclaimedIds.empty());
|
||||
}
|
||||
|
||||
TEST_F(FenceManagerTest, ReclaimMultipleFencesPartial) {
|
||||
// Frame 1
|
||||
mFenceManager.track(mDriverApi, { 1 });
|
||||
EXPECT_EQ(mMockDriver.nextFakeHandle, 2);
|
||||
|
||||
// Frame 2
|
||||
mFenceManager.track(mDriverApi, { 2 });
|
||||
EXPECT_EQ(mMockDriver.nextFakeHandle, 3);
|
||||
|
||||
// Frame 3
|
||||
mFenceManager.track(mDriverApi, { 3 });
|
||||
EXPECT_EQ(mMockDriver.nextFakeHandle, 4);
|
||||
|
||||
// Now, reclaim. Assume fence 1 and 2 are done, but 3 is not.
|
||||
// The implementation iterates from newest to oldest.
|
||||
EXPECT_CALL(mMockDriver, getFenceStatus(Handle<HwFence>(3)))
|
||||
.WillOnce(Return(FenceStatus::TIMEOUT_EXPIRED));
|
||||
EXPECT_CALL(mMockDriver, getFenceStatus(Handle<HwFence>(2)))
|
||||
.WillOnce(Return(FenceStatus::CONDITION_SATISFIED));
|
||||
EXPECT_CALL(mMockDriver, getFenceStatus(Handle<HwFence>(1)))
|
||||
.WillOnce(Return(FenceStatus::CONDITION_SATISFIED));
|
||||
|
||||
std::vector<UboManager::FenceManager::AllocationId> reclaimedIds;
|
||||
mFenceManager.reclaimCompletedResources(mDriverApi,
|
||||
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.push_back(id); });
|
||||
|
||||
// Verify that resources from the first two frames were reclaimed.
|
||||
ASSERT_EQ(reclaimedIds.size(), 2);
|
||||
EXPECT_EQ(reclaimedIds[0], 1);
|
||||
EXPECT_EQ(reclaimedIds[1], 2);
|
||||
}
|
||||
|
||||
TEST_F(FenceManagerTest, Reset) {
|
||||
mFenceManager.track(mDriverApi, { 10 });
|
||||
EXPECT_EQ(mMockDriver.nextFakeHandle, 2);
|
||||
|
||||
mFenceManager.track(mDriverApi, { 20 });
|
||||
EXPECT_EQ(mMockDriver.nextFakeHandle, 3);
|
||||
|
||||
mFenceManager.reset(mDriverApi);
|
||||
|
||||
// After reset, reclaiming should do nothing.
|
||||
std::vector<UboManager::FenceManager::AllocationId> reclaimedIds;
|
||||
EXPECT_CALL(mMockDriver, getFenceStatus(_)).Times(0);
|
||||
mFenceManager.reclaimCompletedResources(mDriverApi,
|
||||
[&reclaimedIds](BufferAllocator::AllocationId id) { reclaimedIds.push_back(id); });
|
||||
EXPECT_TRUE(reclaimedIds.empty());
|
||||
}
|
||||
|
||||
} // namespace anonymous
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,11 @@ set(PUBLIC_HDRS
|
||||
)
|
||||
|
||||
set(SRCS
|
||||
src/DirIncluder.h
|
||||
src/DirIncluder.cpp
|
||||
src/IncludeCallback.h
|
||||
src/Includes.h
|
||||
src/Includes.cpp
|
||||
src/JsonishLexeme.h
|
||||
src/JsonishLexer.h
|
||||
src/JsonishParser.h
|
||||
@@ -53,6 +58,8 @@ install(DIRECTORY ${PUBLIC_HDR_DIR}/ DESTINATION include)
|
||||
project(test_matp)
|
||||
set(TARGET test_matp)
|
||||
set(SRCS
|
||||
tests/test_includes.cpp
|
||||
tests/test_includer.cpp
|
||||
tests/test_matp.cpp
|
||||
tests/TestMaterialParser.h
|
||||
tests/test_compute_material.cpp
|
||||
|
||||
@@ -155,6 +155,9 @@ public:
|
||||
return mWorkarounds;
|
||||
}
|
||||
|
||||
bool getInsertLineDirectives() const noexcept { return mInsertLineDirectives; }
|
||||
bool getInsertLineDirectiveChecks() const noexcept { return mInsertLineDirectiveChecks; }
|
||||
|
||||
protected:
|
||||
bool mDebug = false;
|
||||
bool mIsValid = true;
|
||||
@@ -174,6 +177,8 @@ protected:
|
||||
filament::UserVariantFilterMask mVariantFilter = 0;
|
||||
Workarounds mWorkarounds = Workarounds::ALL;
|
||||
bool mIncludeEssl1 = true;
|
||||
bool mInsertLineDirectives = true;
|
||||
bool mInsertLineDirectiveChecks = true;
|
||||
};
|
||||
|
||||
} // namespace matp
|
||||
|
||||
@@ -20,9 +20,11 @@
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <utils/Status.h>
|
||||
#include "Config.h"
|
||||
|
||||
#include <utils/Path.h>
|
||||
#include <utils/Status.h>
|
||||
|
||||
namespace filamat {
|
||||
class MaterialBuilder;
|
||||
}
|
||||
@@ -36,7 +38,13 @@ class MaterialLexeme;
|
||||
class MaterialParser {
|
||||
public:
|
||||
MaterialParser();
|
||||
|
||||
// Resolves #include directives in the material by looking at the given file structure.
|
||||
// Returns the string of resolved material with the state that indicates whether the include
|
||||
// resolution was successful.
|
||||
std::pair<utils::Status, utils::CString> resolveIncludes(
|
||||
const std::unique_ptr<const char[]>& buffer, ssize_t size,
|
||||
const utils::Path& materialFilePath,
|
||||
bool insertLineDirectives, bool insertLineDirectiveCheck);
|
||||
// Parses a string material so that it can be used in MaterialBuilder.
|
||||
// Call MaterialBuilder::init before passing in the builder; call MaterialBuilder::build to
|
||||
// create filamat::Package after.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace matc {
|
||||
namespace matp {
|
||||
|
||||
bool DirIncluder::operator()(const utils::CString& includedBy, IncludeResult& result) {
|
||||
auto getHeaderPath = [&result, &includedBy, this]() {
|
||||
@@ -66,5 +66,5 @@ bool DirIncluder::operator()(const utils::CString& includedBy, IncludeResult& re
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace matc
|
||||
} // namespace matp
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include <utils/Path.h>
|
||||
|
||||
namespace matc {
|
||||
namespace matp {
|
||||
|
||||
// Functor callback handler used to resolve includes relative to a root include directory.
|
||||
class DirIncluder {
|
||||
@@ -38,6 +38,6 @@ private:
|
||||
|
||||
};
|
||||
|
||||
} // namespace matc
|
||||
} // namespace matp
|
||||
|
||||
#endif
|
||||
@@ -18,10 +18,11 @@
|
||||
#define TNT_INCLUDER_H
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Status.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace matc {
|
||||
namespace matp {
|
||||
|
||||
struct IncludeResult {
|
||||
// The include name of the root file, as if it were being included.
|
||||
@@ -30,7 +31,7 @@ struct IncludeResult {
|
||||
const utils::CString includeName;
|
||||
|
||||
// The following fields should be filled out by the IncludeCallback when processing an include,
|
||||
// or when calling resolveIncludes for the root file.
|
||||
// or when calling resolveIncludesRecursively for the root file.
|
||||
|
||||
// The full contents of the include file. This may contain additional, recursive include
|
||||
// directives.
|
||||
@@ -58,12 +59,12 @@ struct IncludeResult {
|
||||
* @param result is the IncludeResult that the callback should fill out.
|
||||
* @return true, if the include was resolved successfully, false otherwise.
|
||||
*
|
||||
* For an example of implementing this callback, see tools/matc/src/matc/DirIncluder.h.
|
||||
* For an example of implementing this callback, see DirIncluder.h.
|
||||
*/
|
||||
using IncludeCallback = std::function<bool(
|
||||
const utils::CString& includedBy,
|
||||
IncludeResult& result)>;
|
||||
|
||||
} // namespace matc
|
||||
} // namespace matp
|
||||
|
||||
#endif
|
||||
@@ -19,10 +19,11 @@
|
||||
#include <utils/Log.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/sstream.h>
|
||||
#include <utils/Status.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace matc {
|
||||
namespace matp {
|
||||
|
||||
static bool isWhitespace(char c) {
|
||||
return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v');
|
||||
@@ -75,12 +76,11 @@ static void findCommentRanges(const char (&beginToken)[BTSize], const char (&end
|
||||
}
|
||||
|
||||
|
||||
bool resolveIncludes(IncludeResult& root, IncludeCallback callback,
|
||||
utils::Status resolveIncludesRecursively(IncludeResult& root, IncludeCallback callback,
|
||||
const ResolveOptions& options, size_t depth) {
|
||||
if (depth > 30) {
|
||||
// This is probably an include cycle. Stop here and report an error so we don't overflow.
|
||||
utils::slog.e << "Include depth > 30. Include cycle?" << utils::io::endl;
|
||||
return false;
|
||||
return utils::Status::invalidArgument("Include depth > 30. Include cycle?");
|
||||
}
|
||||
|
||||
utils::CString& text = root.text;
|
||||
@@ -88,7 +88,7 @@ bool resolveIncludes(IncludeResult& root, IncludeCallback callback,
|
||||
std::vector<FoundInclude> includes = parseForIncludes(text);
|
||||
if (includes.empty()) {
|
||||
// No more to resolve.
|
||||
return true;
|
||||
return utils::Status::ok();
|
||||
}
|
||||
|
||||
const size_t lineNumberOffset = includes[0].line;
|
||||
@@ -196,28 +196,30 @@ bool resolveIncludes(IncludeResult& root, IncludeCallback callback,
|
||||
const auto include = includes[0];
|
||||
// Ask the includer to resolve this include.
|
||||
if (!callback) {
|
||||
return false;
|
||||
return utils::Status::invalidArgument("No callback is provided");
|
||||
}
|
||||
IncludeResult resolved {
|
||||
IncludeResult includeResult{
|
||||
.includeName = include.name
|
||||
};
|
||||
if (!callback(root.name, resolved)) {
|
||||
utils::slog.e << "The included file \"" << include.name.c_str()
|
||||
if (!callback(root.name, includeResult)) {
|
||||
utils::io::sstream errorMessage;
|
||||
errorMessage << "The included file \"" << include.name.c_str()
|
||||
<< "\" could not be found." << utils::io::endl;
|
||||
return false;
|
||||
return utils::Status::invalidArgument(errorMessage.c_str());
|
||||
}
|
||||
|
||||
// Recursively resolve all of its includes.
|
||||
if (!resolveIncludes(resolved, callback, options, depth + 1)) {
|
||||
return false;
|
||||
if (utils::Status resolveStatus =
|
||||
resolveIncludesRecursively(includeResult, callback, options, depth + 1);
|
||||
!resolveStatus.isOk()) {
|
||||
return resolveStatus;
|
||||
}
|
||||
|
||||
text.replace(include.startPosition, include.length, resolved.text);
|
||||
text.replace(include.startPosition, include.length, includeResult.text);
|
||||
|
||||
includes = parseForIncludes(text);
|
||||
}
|
||||
|
||||
return true;
|
||||
return utils::Status::ok();
|
||||
}
|
||||
|
||||
std::vector<FoundInclude> parseForIncludes(const utils::CString& source) {
|
||||
@@ -308,4 +310,4 @@ std::vector<FoundInclude> parseForIncludes(const utils::CString& source) {
|
||||
return results;
|
||||
}
|
||||
|
||||
} // namespace matc
|
||||
} // namespace matp
|
||||
@@ -20,10 +20,11 @@
|
||||
#include "IncludeCallback.h"
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Status.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace matc {
|
||||
namespace matp {
|
||||
|
||||
struct ResolveOptions {
|
||||
// If true, insert #line directives before / after each include.
|
||||
@@ -39,9 +40,10 @@ struct ResolveOptions {
|
||||
};
|
||||
|
||||
// Recursively handle all the includes inside of root.
|
||||
// Returns true if all includes were handled successfully, false otherwise.
|
||||
// callback may be null, in which case any #include directives found will result in a failure.
|
||||
bool resolveIncludes(IncludeResult& root, IncludeCallback callback,
|
||||
// Returns utils::Status::StatusCode::OK if all includes were handled successfully, otherwise an
|
||||
// error status with a failure message.
|
||||
// Callback may be null, in which case any #include directives found will result in a failure.
|
||||
utils::Status resolveIncludesRecursively(IncludeResult& root, IncludeCallback callback,
|
||||
const ResolveOptions& options, size_t depth = 0);
|
||||
|
||||
struct FoundInclude {
|
||||
@@ -53,6 +55,6 @@ struct FoundInclude {
|
||||
|
||||
std::vector<FoundInclude> parseForIncludes(const utils::CString& source);
|
||||
|
||||
} // namespace matc
|
||||
} // namespace matp
|
||||
|
||||
#endif
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#include <filament-matp/MaterialParser.h>
|
||||
|
||||
#include "DirIncluder.h"
|
||||
#include "Includes.h"
|
||||
#include "JsonishLexer.h"
|
||||
#include "JsonishParser.h"
|
||||
#include "MaterialLexeme.h"
|
||||
@@ -393,6 +395,35 @@ utils::Status MaterialParser::processMaterialParameters(filamat::MaterialBuilder
|
||||
return status;
|
||||
}
|
||||
|
||||
std::pair<utils::Status, utils::CString> MaterialParser::resolveIncludes(
|
||||
const std::unique_ptr<const char[]>& buffer, ssize_t size,
|
||||
const utils::Path& materialFilePath,
|
||||
bool insertLineDirectives, bool insertLineDirectiveCheck) {
|
||||
|
||||
ResolveOptions const options {
|
||||
.insertLineDirectives = insertLineDirectives,
|
||||
.insertLineDirectiveCheck = insertLineDirectiveCheck
|
||||
};
|
||||
|
||||
// Inline material (i.e. resolve #include directives)
|
||||
DirIncluder includer;
|
||||
includer.setIncludeDirectory(materialFilePath.getParent());
|
||||
utils::CString fileName = utils::CString(materialFilePath.getName().c_str());
|
||||
|
||||
// This is both the source and the result.
|
||||
// This will be mutated by the below `resolveIncludesRecursively` call.
|
||||
IncludeResult result {
|
||||
.includeName = fileName,
|
||||
.text = utils::CString(buffer.get(), size),
|
||||
.name = CString("")
|
||||
};
|
||||
|
||||
utils::Status resolveStatus =
|
||||
matp::resolveIncludesRecursively(result, std::move(includer), options);
|
||||
|
||||
return { resolveStatus, result.text };
|
||||
}
|
||||
|
||||
utils::Status MaterialParser::processTemplateSubstitutions(
|
||||
const Config& config, ssize_t& size, std::unique_ptr<const char[]>& buffer) {
|
||||
const auto& templateMap = config.getTemplateMap();
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <matc/IncludeCallback.h>
|
||||
#include "IncludeCallback.h"
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator()(const utils::CString& includedBy, matc::IncludeResult& result) {
|
||||
bool operator()(const utils::CString& includedBy, matp::IncludeResult& result) {
|
||||
auto key = result.includeName.c_str();
|
||||
auto found = mIncludeMap.find(key);
|
||||
|
||||
@@ -16,17 +16,17 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <matc/DirIncluder.h>
|
||||
#include "DirIncluder.h"
|
||||
|
||||
using namespace utils;
|
||||
using namespace matc;
|
||||
using namespace matp;
|
||||
|
||||
const utils::Path root = utils::Path(__FILE__).getParent();
|
||||
|
||||
// TODO: these tests are disabled as they fail on CI, which needs investigation.
|
||||
|
||||
TEST(DirIncluder, DISABLED_IncludeNonexistent) {
|
||||
matc::DirIncluder includer;
|
||||
DirIncluder includer;
|
||||
{
|
||||
IncludeResult i {
|
||||
.includeName = CString("nonexistent.h")
|
||||
@@ -45,7 +45,7 @@ TEST(DirIncluder, DISABLED_IncludeNonexistent) {
|
||||
}
|
||||
|
||||
TEST(DirIncluder, DISABLED_IncludeFile) {
|
||||
matc::DirIncluder includer;
|
||||
DirIncluder includer;
|
||||
includer.setIncludeDirectory(root);
|
||||
|
||||
IncludeResult result {
|
||||
@@ -63,7 +63,7 @@ TEST(DirIncluder, DISABLED_IncludeFile) {
|
||||
}
|
||||
|
||||
TEST(DirIncluder, DISABLED_IncludeFileFromIncluder) {
|
||||
matc::DirIncluder includer;
|
||||
DirIncluder includer;
|
||||
includer.setIncludeDirectory(root);
|
||||
|
||||
utils::CString includerFile((root + "Dir/Baz.h").c_str());
|
||||
@@ -77,8 +77,3 @@ TEST(DirIncluder, DISABLED_IncludeFileFromIncluder) {
|
||||
|
||||
EXPECT_STREQ((root + "Dir/Bar.h").c_str(), result.name.c_str());
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Includes.h"
|
||||
#include "MockIncluder.h"
|
||||
|
||||
#include <matc/Includes.h>
|
||||
#include <filamat/MaterialBuilder.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
@@ -27,7 +27,7 @@
|
||||
#include <memory>
|
||||
|
||||
using namespace utils;
|
||||
using namespace matc;
|
||||
using namespace matp;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -211,8 +211,8 @@ TEST(IncludeResolver, NoIncludes) {
|
||||
IncludeResult source {
|
||||
.text = code
|
||||
};
|
||||
bool result = resolveIncludes(source, includer, {});
|
||||
EXPECT_TRUE(result);
|
||||
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
|
||||
EXPECT_TRUE(resolved.isOk());
|
||||
EXPECT_STREQ("no includes", source.text.c_str());
|
||||
}
|
||||
|
||||
@@ -224,8 +224,8 @@ TEST(IncludeResolver, SingleInclude) {
|
||||
IncludeResult source {
|
||||
.text = code
|
||||
};
|
||||
bool result = resolveIncludes(source, includer, {});
|
||||
EXPECT_TRUE(result);
|
||||
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
|
||||
EXPECT_TRUE(resolved.isOk());
|
||||
EXPECT_STREQ("include", source.text.c_str());
|
||||
}
|
||||
|
||||
@@ -245,8 +245,8 @@ TEST(IncludeResolver, MultipleIncludes) {
|
||||
IncludeResult source {
|
||||
.text = code
|
||||
};
|
||||
bool result = resolveIncludes(source, includer, {});
|
||||
EXPECT_TRUE(result);
|
||||
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
|
||||
EXPECT_TRUE(resolved.isOk());
|
||||
EXPECT_STREQ(utils::CString(R"(
|
||||
1
|
||||
2
|
||||
@@ -270,8 +270,8 @@ TEST(IncludeResolver, IncludeWithinInclude) {
|
||||
IncludeResult source {
|
||||
.text = code
|
||||
};
|
||||
bool result = resolveIncludes(source, includer, {});
|
||||
EXPECT_TRUE(result);
|
||||
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
|
||||
EXPECT_TRUE(resolved.isOk());
|
||||
EXPECT_STREQ(utils::CString(R"(
|
||||
1
|
||||
3
|
||||
@@ -293,8 +293,8 @@ TEST(IncludeResolver, Includers) {
|
||||
IncludeResult source {
|
||||
.text = code
|
||||
};
|
||||
bool result = resolveIncludes(source, includer, {});
|
||||
EXPECT_TRUE(result);
|
||||
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
|
||||
EXPECT_TRUE(resolved.isOk());
|
||||
EXPECT_STREQ(utils::CString(R"(
|
||||
3
|
||||
)").c_str(), source.text.c_str());
|
||||
@@ -313,8 +313,8 @@ TEST(IncludeResolver, IncludeFailure) {
|
||||
IncludeResult source {
|
||||
.text = code
|
||||
};
|
||||
bool result = resolveIncludes(source, includer, {});
|
||||
EXPECT_FALSE(result);
|
||||
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
|
||||
EXPECT_FALSE(resolved.isOk());
|
||||
}
|
||||
{
|
||||
utils::CString code(R"(
|
||||
@@ -326,8 +326,8 @@ TEST(IncludeResolver, IncludeFailure) {
|
||||
IncludeResult source {
|
||||
.text = code
|
||||
};
|
||||
bool result = resolveIncludes(source, includer, {});
|
||||
EXPECT_FALSE(result);
|
||||
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
|
||||
EXPECT_FALSE(resolved.isOk());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,9 +344,9 @@ TEST(IncludeResolver, Cycle) {
|
||||
IncludeResult source {
|
||||
.text = code
|
||||
};
|
||||
bool result = resolveIncludes(source, includer, {});
|
||||
utils::Status resolved = resolveIncludesRecursively(source, includer, {});
|
||||
// Include cycles are disallowed. We should still terminate in finite time and report false.
|
||||
EXPECT_FALSE(result);
|
||||
EXPECT_FALSE(resolved.isOk());
|
||||
}
|
||||
|
||||
// Helper function that lets us write comparison cases with line breaks.
|
||||
@@ -395,8 +395,8 @@ TEST(IncludeResolver, SingleIncludeLineDirective) {
|
||||
.includeName = utils::CString("root.h"),
|
||||
.text = code
|
||||
};
|
||||
bool result = resolveIncludes(source, includer, options);
|
||||
EXPECT_TRUE(result);
|
||||
utils::Status resolved = resolveIncludesRecursively(source, includer, options);
|
||||
EXPECT_TRUE(resolved.isOk());
|
||||
EXPECT_STREQ_TRIMMARGIN(R"(
|
||||
| #line 1 "root.h"
|
||||
| #line 1 "test.h"
|
||||
@@ -421,8 +421,8 @@ TEST(IncludeResolver, MultipleIncludesLineDirective) {
|
||||
.includeName = utils::CString("root.h"),
|
||||
.text = code,
|
||||
};
|
||||
bool result = resolveIncludes(source, includer, options);
|
||||
EXPECT_TRUE(result);
|
||||
utils::Status resolved = resolveIncludesRecursively(source, includer, options);
|
||||
EXPECT_TRUE(resolved.isOk());
|
||||
|
||||
EXPECT_STREQ_TRIMMARGIN(R"(
|
||||
| #line 1 "root.h"
|
||||
@@ -453,8 +453,8 @@ TEST(IncludeResolver, MultipleIncludesSameLineLineDirective) {
|
||||
.includeName = utils::CString("root.h"),
|
||||
.text = code
|
||||
};
|
||||
bool result = resolveIncludes(source, includer, options);
|
||||
EXPECT_TRUE(result);
|
||||
utils::Status resolved = resolveIncludesRecursively(source, includer, options);
|
||||
EXPECT_TRUE(resolved.isOk());
|
||||
|
||||
EXPECT_STREQ_TRIMMARGIN(R"(
|
||||
| #line 1 "root.h"
|
||||
@@ -518,7 +518,8 @@ TEST_F(MaterialBuilder, Include) {
|
||||
.text = utils::CString(shaderCode.c_str(), shaderCode.size()),
|
||||
};
|
||||
|
||||
EXPECT_TRUE(resolveIncludes(source, std::move(includer), options));
|
||||
utils::Status resolved = resolveIncludesRecursively(source, std::move(includer), options);
|
||||
EXPECT_TRUE(resolved.isOk());
|
||||
|
||||
// Set the material with the resolved material.
|
||||
mBuilder.material(source.text.c_str());
|
||||
@@ -545,8 +546,8 @@ TEST_F(MaterialBuilder, IncludeVertex) {
|
||||
IncludeResult source {
|
||||
.text = utils::CString(shaderCode.c_str(), shaderCode.size()),
|
||||
};
|
||||
|
||||
EXPECT_TRUE(resolveIncludes(source, std::move(includer), options));
|
||||
utils::Status resolved = resolveIncludesRecursively(source, std::move(includer), options);
|
||||
EXPECT_TRUE(resolved.isOk());
|
||||
|
||||
// Set the materialVertex with the resolved material.
|
||||
mBuilder.materialVertex(source.text.c_str());
|
||||
@@ -572,7 +573,8 @@ TEST_F(MaterialBuilder, IncludeWithinFunction) {
|
||||
.text = utils::CString(shaderCode.c_str(), shaderCode.size()),
|
||||
};
|
||||
|
||||
EXPECT_TRUE(resolveIncludes(source, std::move(includer), options));
|
||||
utils::Status resolved = resolveIncludesRecursively(source, std::move(includer), options);
|
||||
EXPECT_TRUE(resolved.isOk());
|
||||
|
||||
// Set the material with the resolved material.
|
||||
mBuilder.material(source.text.c_str());
|
||||
@@ -592,7 +594,8 @@ TEST_F(MaterialBuilder, IncludeFailure) {
|
||||
.text = utils::CString(shaderCode.c_str(), shaderCode.size()),
|
||||
};
|
||||
|
||||
EXPECT_FALSE(resolveIncludes(source, std::move(includer), options));
|
||||
utils::Status resolved = resolveIncludesRecursively(source, std::move(includer), options);
|
||||
EXPECT_FALSE(resolved.isOk());
|
||||
|
||||
mBuilder.material(source.text.c_str());
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "TestMaterialParser.h"
|
||||
|
||||
#include <filamat/MaterialBuilder.h>
|
||||
|
||||
#include <filament-matp/MaterialParser.h>
|
||||
#include "JsonishLexer.h"
|
||||
#include "JsonishParser.h"
|
||||
#include "MaterialLexer.h"
|
||||
#include "MockIncluder.h"
|
||||
|
||||
#include <filamat/MaterialBuilder.h>
|
||||
#include <filament-matp/MaterialParser.h>
|
||||
|
||||
class MaterialLexer: public ::testing::Test {
|
||||
protected:
|
||||
|
||||
@@ -37,6 +37,7 @@ set(SRCS
|
||||
src/IcoSphere.cpp
|
||||
src/MeshAssimp.cpp
|
||||
src/Sphere.cpp
|
||||
src/VulkanPlatformHelper.cpp
|
||||
)
|
||||
|
||||
set(LIBS
|
||||
@@ -67,18 +68,25 @@ set(MATERIAL_SRCS
|
||||
|
||||
if (APPLE)
|
||||
list(APPEND SRCS src/NativeWindowHelperCocoa.mm)
|
||||
list(APPEND SRCS src/VulkanPlatformHelperApple.mm)
|
||||
list(APPEND LIBS "-framework Cocoa -framework QuartzCore")
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
list(APPEND SRCS src/NativeWindowHelperLinux.cpp)
|
||||
list(APPEND SRCS src/VulkanPlatformHelperLinux.cpp)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
list(APPEND SRCS src/NativeWindowHelperWindows.cpp)
|
||||
list(APPEND SRCS src/VulkanPlatformHelperWindows.cpp)
|
||||
list(APPEND LIBS sdl2main)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
list(APPEND SRCS src/VulkanPlatformHelperAndroid.cpp)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Compile resources
|
||||
# ==================================================================================================
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 TNT_FILAMENTAPP_VULKAN_PLATFORM_HELPER_H
|
||||
#define TNT_FILAMENTAPP_VULKAN_PLATFORM_HELPER_H
|
||||
|
||||
namespace filament::backend {
|
||||
class VulkanPlatform;
|
||||
} // namespace filament::backend
|
||||
|
||||
namespace filament::filamentapp {
|
||||
|
||||
filament::backend::VulkanPlatform* createVulkanPlatform(char const* gpuHintCstr);
|
||||
void destroyVulkanPlatform(filament::backend::VulkanPlatform* platform);
|
||||
|
||||
} // namespace filament::filamentapp
|
||||
|
||||
#endif // TNT_FILAMENTAPP_VULKAN_PLATFORM_HELPER_H
|
||||
@@ -40,12 +40,15 @@
|
||||
#include <filament/SwapChain.h>
|
||||
#include <filament/View.h>
|
||||
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include <filament/DebugRegistry.h>
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
#include <filamentapp/VulkanPlatformHelper.h>
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
@@ -78,36 +81,6 @@ namespace {
|
||||
|
||||
using namespace filament::backend;
|
||||
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
class FilamentAppVulkanPlatform : public VulkanPlatform {
|
||||
public:
|
||||
FilamentAppVulkanPlatform(char const* gpuHintCstr) {
|
||||
utils::CString gpuHint{ gpuHintCstr };
|
||||
if (gpuHint.empty()) {
|
||||
return;
|
||||
}
|
||||
VulkanPlatform::Customization::GPUPreference pref;
|
||||
// Check to see if it is an integer, if so turn it into an index.
|
||||
if (std::all_of(gpuHint.begin(), gpuHint.end(), ::isdigit)) {
|
||||
char* p_end {};
|
||||
pref.index = static_cast<int8_t>(std::strtol(gpuHint.c_str(), &p_end, 10));
|
||||
} else {
|
||||
pref.deviceName = gpuHint;
|
||||
}
|
||||
mCustomization = {
|
||||
.gpu = pref
|
||||
};
|
||||
}
|
||||
|
||||
virtual VulkanPlatform::Customization getCustomization() const noexcept override {
|
||||
return mCustomization;
|
||||
}
|
||||
|
||||
private:
|
||||
VulkanPlatform::Customization mCustomization;
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
class FilamentAppWebGPUPlatform : public WebGPUPlatform {
|
||||
public:
|
||||
@@ -138,7 +111,7 @@ private:
|
||||
};
|
||||
#endif
|
||||
|
||||
} // anonymous namespace
|
||||
}
|
||||
|
||||
FilamentApp& FilamentApp::get() {
|
||||
static FilamentApp filamentApp;
|
||||
@@ -611,7 +584,7 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
|
||||
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
if (mVulkanPlatform) {
|
||||
delete mVulkanPlatform;
|
||||
filamentapp::destroyVulkanPlatform(mVulkanPlatform);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -775,11 +748,11 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
|
||||
engineConfig.stereoscopicType = Engine::StereoscopicType::NONE;
|
||||
#endif
|
||||
|
||||
Platform* platform = nullptr;
|
||||
backend::Platform* platform = nullptr;
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
if (backend == Engine::Backend::VULKAN) {
|
||||
platform = mFilamentApp->mVulkanPlatform =
|
||||
new FilamentAppVulkanPlatform(config.vulkanGPUHint.c_str());
|
||||
filamentapp::createVulkanPlatform(config.vulkanGPUHint.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 <filamentapp/VulkanPlatformHelper.h>
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace filament::filamentapp {
|
||||
|
||||
using namespace filament::backend;
|
||||
|
||||
VulkanPlatform::Customization parseGpuHint(char const* gpuHintCstr) {
|
||||
utils::CString gpuHint{ gpuHintCstr };
|
||||
if (gpuHint.empty()) {
|
||||
return {};
|
||||
}
|
||||
VulkanPlatform::Customization::GPUPreference pref;
|
||||
// Check to see if it is an integer, if so turn it into an index.
|
||||
if (std::all_of(gpuHint.begin(), gpuHint.end(), ::isdigit)) {
|
||||
char* p_end{};
|
||||
pref.index = static_cast<int8_t>(std::strtol(gpuHint.c_str(), &p_end, 10));
|
||||
} else {
|
||||
pref.deviceName = gpuHint;
|
||||
}
|
||||
return { .gpu = pref };
|
||||
}
|
||||
|
||||
void destroyVulkanPlatform(VulkanPlatform* platform) {
|
||||
delete platform;
|
||||
}
|
||||
|
||||
} // namespace filament::filamentapp
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 <backend/platforms/VulkanPlatformAndroid.h>
|
||||
|
||||
#include "VulkanPlatformHelperCommon.h"
|
||||
|
||||
namespace filament::filamentapp {
|
||||
|
||||
using namespace filament::backend;
|
||||
|
||||
class FilamentAppVulkanPlatform : public VulkanPlatformAndroid {
|
||||
public:
|
||||
FilamentAppVulkanPlatform(char const* gpuHintCstr) : mCustomization(parseGpuHint(gpuHintCstr)) {}
|
||||
|
||||
VulkanPlatform::Customization getCustomization() const noexcept override {
|
||||
return mCustomization;
|
||||
}
|
||||
|
||||
private:
|
||||
VulkanPlatform::Customization mCustomization;
|
||||
};
|
||||
|
||||
VulkanPlatform* createVulkanPlatform(char const* gpuHintCstr) {
|
||||
return new FilamentAppVulkanPlatform(gpuHintCstr);
|
||||
}
|
||||
|
||||
} // namespace filament::filamentapp
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 <backend/platforms/VulkanPlatformApple.h>
|
||||
|
||||
#include "VulkanPlatformHelperCommon.h"
|
||||
|
||||
namespace filament::filamentapp {
|
||||
|
||||
using namespace filament::backend;
|
||||
|
||||
class FilamentAppVulkanPlatform : public VulkanPlatformApple {
|
||||
public:
|
||||
FilamentAppVulkanPlatform(char const* gpuHintCstr) : mCustomization(parseGpuHint(gpuHintCstr)) {}
|
||||
|
||||
VulkanPlatform::Customization getCustomization() const noexcept override {
|
||||
return mCustomization;
|
||||
}
|
||||
|
||||
private:
|
||||
VulkanPlatform::Customization mCustomization;
|
||||
};
|
||||
|
||||
VulkanPlatform* createVulkanPlatform(char const* gpuHintCstr) {
|
||||
return new FilamentAppVulkanPlatform(gpuHintCstr);
|
||||
}
|
||||
|
||||
} // namespace filament::filamentapp
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user