Compare commits

..
Author SHA1 Message Date
Serge Metral 531bfd6f6e Fix Android build. 2025-11-19 17:41:17 +00:00
Serge Metral db272484f9 Merge branch 'main' into serge/vulkan_pipeline_cache_json 2025-11-19 09:05:45 -08:00
Serge Metral 9dadf400d2 Adding render pass key. 2025-11-19 08:47:03 -08:00
Serge Metral 16c7a3c968 Adding render pass serialization. 2025-11-18 16:00:15 -08:00
Serge Metral 626f338cba First commit. 2025-11-18 07:06:46 -08:00
Doris Wu ad27d48fd3 buffer update opt: refactor UboManager and introduce FenceManager (#9418) 2025-11-11 16:56:06 +08:00
Mathias Agopian 3a503976c8 add View::getLastDynamicResolutionScale() (#9419)
* add View::getLastDynamicResolutionScale()

This method return the last dynamic resolution scaling factor used
by the view.

FIXES=[457753622]
2025-11-10 14:49:23 -08:00
Mathias Agopian ce6fa82026 improve camera precondition checks and default state (#9420)
* improve camera precondition checks and default state

- Camera is now initialized with a default projection matrix with
left/right, top/bottom set to (-1,1) and near/far set to (0.1,1).

- setLensProjection() and setProjection(FOV) now check the near/far
precondition (near>0 and far>near).

- sanitize user's light near/far to avoid infs and nans during
  froxelization

- finally froxelizer asserts that near>0 and far>near on debug builds.

FIXES=[458030186]
2025-11-10 14:48:48 -08:00
Mathias Agopian e8349ab5cc AndroidNativeWindow must also work on 32-bits architectures (#9424) 2025-11-10 14:44:52 -08:00
yein 4773fc4647 Move the include resolution functionality to matp (#9414)
* Move the include resolution functionality to matp

- matp::MaterialParser now has a function resolveIncludes that returns a pair of status and resolve string.
- all the include resolution classes are moved to matp private src.
- matp::resolveIncludes is renamed to matp::resolveIncludesRecursively and only used internally.
- added insertLineDirectives and insertLineDirectiveChecks in Config; add those in the CommandlineConfig.
- moved output format to public use.

Note: MaterialParser::resolveIncludes could take a includer instead of the materialFilePath, but i decided
to go with the materialFilePath because the most common use case is resolving from the file's directory.
This allows the parser to just create the default DirIncluder internally, and we don't need to expose it publicly.

* add const to the buffer param
2025-11-07 15:23:54 -08:00
Rafael Marinho de Araújoandrma6 d47a69a529 add overload of loadTexture that loads bitmap from ByteArray (#9416)
Co-authored-by: rma6 <rafaelmarinhoa@gmail.com>
2025-11-07 10:11:23 -08:00
Powei Feng d3b74e96b4 vk: fix thread-safety wrt queryFrameTimestamps() (#9415)
queryFrameTimestamps() and queryCompositorTiming() are both
synchronous APIs, but VuklanSwapchain is not a thread-safe
ref-counted handle (i.e. fvkmemory::ThreadSafeResource). We move
the Platform::SwapChain* pointers to a thread-safe map that is
mapped by the handle id.
2025-11-07 18:00:01 +00:00
Mathias Agopian 209d3f7550 Fix a race condition when tearing down FrameInfo (#9413)
* HandleAllocator::deallocate() was unsafe

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

* add AsyncJobQueue::cancelAll()

* Fix a race condition when tearing down FrameInfo

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

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

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

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

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

In this PR we chose the strategy at compile time; either the more
modern weak symbol or the old way with dlsym based on __ANDROID_API__.
2025-11-06 16:49:28 -08:00
Powei Feng a4945939de vk: fix leaking swapchains again (#9410)
- Adjust order of destroy call in both headless and platform
   swapchains.  We need to be careful of the order due to
   assumptions made by the base class's destroy().
 - remove `=0` since VulkanPlatformSwapChainBase::destroy() has
   an implementation.

Fixes #9403
2025-11-05 22:10:05 -08:00
Benjamin Doherty 086760b307 Add missing mutex header 2025-11-05 15:35:29 -08:00
Powei Feng ce1b63ce38 gltf_viewer: fix accidentally removed FilamentAppVulkanPlatform 2025-11-05 15:26:47 -08:00
Mathias Agopian b7b8983653 add a feature flag for FrameInfo::gpuFrameComplete (#9409)
There are several race conditions caused by this feature. So we add
this flag and disable it by default, for now.
2025-11-05 15:22:43 -08:00
Powei Feng d52fb1f4fd vk: refactor to use polymorphism for platform specialization (#9398)
We refactor VulkanPlatform so that getSwapchainInstanceExtensions()
and createVkSurfaceKHR() are virtual functions that are implemented
by each platform. This will enable the use of polymorphism for
platform-dependent bits.
2025-11-05 19:44:39 +00:00
Powei Feng 901c87761b gltfio: add null check for when image is not provided (#9407)
Fixes #9402
2025-11-05 19:26:24 +00:00
Benjamin Doherty 081fe6a434 Bump MATERIAL_VERSION to 67 2025-11-05 10:35:14 -08:00
Serge Metral 4e05cd73c6 Merge branch 'google:main' into main 2025-11-04 11:20:17 -08:00
Serge Metral 49f9708312 Merge branch 'google:main' into main 2025-10-29 17:00:00 +00:00
Serge Metral 2791195a92 Merge branch 'google:main' into main 2025-10-22 10:50:16 -07:00
Serge Metral 65f78c06bb Merge branch 'google:main' into main 2025-10-21 11:32:23 -07:00
Serge Metral 71175ee410 Merge branch 'google:main' into main 2025-10-13 10:06:53 -07:00
Serge Metral c9b310450c Merge branch 'google:main' into main 2025-09-19 14:47:15 +00:00
Serge Metral 54c636d54c Merge branch 'google:main' into main 2025-09-04 03:15:27 +00:00
Serge Metral f503205be2 Merge branch 'google:main' into main 2025-08-29 17:25:01 +00:00
Serge Metral 02106110fe Merge branch 'google:main' into main 2025-08-25 16:31:27 +00:00
Serge Metral 5a0f78d915 Merge branch 'google:main' into main 2025-08-18 11:23:24 -07:00
Serge Metral f548c588d9 Merge branch 'google:main' into main 2025-08-13 18:17:53 +00:00
Serge Metral 487fb273f1 Merge branch 'google:main' into main 2025-08-12 15:25:09 +00:00
Serge Metral 111ac62d2f Merge branch 'google:main' into main 2025-08-07 11:24:34 -07:00
Serge Metral 2fac13e154 Merge branch 'google:main' into main 2025-07-31 20:45:26 -07:00
Serge Metral bcae04c9ef Merge branch 'google:main' into main 2025-05-12 14:15:56 -07:00
Serge Metral 55fc91b35f Merge branch 'google:main' into main 2025-05-08 18:25:41 +00:00
Serge Metral ff2827b226 Merge branch 'google:main' into main 2025-03-28 14:40:50 +00:00
Serge Metral 13e1d2a042 Merge branch 'google:main' into main 2025-03-21 23:32:55 +00:00
Serge Metral b09cbaf0db Merge branch 'google:main' into main 2025-03-18 19:47:10 +00:00
Serge Metral b570d489e3 Merge branch 'google:main' into main 2025-03-17 16:09:21 +00:00
Serge Metral 7f7daddbfa Merge branch 'google:main' into main 2025-03-14 17:28:06 +00:00
Serge Metral cfe6a423d3 Merge branch 'google:main' into main 2025-03-06 10:48:37 -08:00
Serge Metral ba99844f22 Merge branch 'google:main' into main 2025-03-05 16:00:28 -08:00
Serge Metral ea4c20ec2c Merge branch 'google:main' into main 2025-01-29 20:49:02 -08:00
Serge Metral 0a661c1f3c Merge branch 'google:main' into main 2025-01-27 08:55:26 -08:00
Serge Metral 021c4f6691 Merge branch 'google:main' into main 2025-01-13 12:07:40 -08:00
Serge Metral be6b6ae7be Merge branch 'google:main' into main 2024-12-18 20:56:10 -08:00
Serge Metral ad3a5b70b6 Merge branch 'google:main' into main 2024-12-03 10:05:08 -08:00
Serge Metral 80803f9693 Merge branch 'google:main' into main 2024-11-06 02:54:52 +00:00
Serge Metral 3b0e44c9a3 Merge branch 'google:main' into main 2024-11-05 22:20:25 +00:00
Serge Metral c5c2d42467 Merge branch 'google:main' into main 2024-11-05 05:25:51 +00:00
Serge Metral ff145f6ccf Merge branch 'google:main' into main 2024-10-31 15:55:28 +00:00
Serge Metral 48bb10a4b5 Merge branch 'google:main' into main 2024-10-30 04:14:21 +00:00
Serge Metral 01a2480175 Merge branch 'google:main' into main 2024-10-26 05:37:19 +00:00
Serge Metral 9e921936c6 Merge branch 'google:main' into main 2024-10-18 13:29:27 +00:00
Serge Metral 8bc4d3a9df Merge branch 'google:main' into main 2024-10-09 17:16:55 +00:00
Serge Metral 0fdf1cac1d Merge branch 'google:main' into main 2024-10-03 09:10:04 -07:00
Serge Metral 387942cc39 Merge branch 'google:main' into main 2024-09-20 20:48:06 -07:00
Serge Metral c81cedd887 Merge branch 'google:main' into main 2024-09-04 08:36:42 -07:00
Serge Metral abe8a49d17 Merge branch 'google:main' into main 2024-06-26 17:26:00 -07:00
Serge Metral 96cba96058 Merge branch 'google:main' into main 2024-06-18 09:38:05 -07:00
Serge Metral 4fccd957db Merge branch 'google:main' into main 2024-06-11 16:54:22 -07:00
Powei Feng d49554d5ce Merge branch 'main' into main 2024-06-05 14:36:17 -07:00
Powei Feng 867161773e Merge branch 'main' into main 2024-06-05 11:53:35 -07:00
Serge Metral e2658506f6 Merge branch 'main' into main 2024-06-04 15:39:46 -07:00
Serge Metral 6218164f79 Adding the begin frame message for later xtrace post processing. 2024-06-04 12:35:17 -07:00
87 changed files with 2355 additions and 459 deletions
+1 -1
View File
@@ -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*
+2
View File
@@ -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)
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.67.1'
implementation 'com.google.android.filament:filament-android:1.67.0'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.67.1'
pod 'Filament', '~> 1.67.0'
```
## Documentation
@@ -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);
@@ -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)
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.67.1
VERSION_NAME=1.67.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+22 -3
View File
@@ -214,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
@@ -252,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
@@ -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;
};
@@ -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
+13 -3
View File
@@ -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;
@@ -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
+7 -5
View File
@@ -230,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,
@@ -1666,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();
@@ -1785,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) {
@@ -2160,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);
}
+1 -1
View File
@@ -148,7 +148,6 @@ private:
VulkanSemaphoreManager mSemaphoreManager;
VulkanCommands mCommands;
VulkanPipelineLayoutCache mPipelineLayoutCache;
VulkanPipelineCache mPipelineCache;
VulkanStagePool mStagePool;
VulkanBufferCache mBufferCache;
@@ -158,6 +157,7 @@ private:
VulkanBlitter mBlitter;
VulkanReadPixels mReadPixels;
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
VulkanPipelineLayoutCache mPipelineLayoutCache;
VulkanDescriptorSetCache mDescriptorSetCache;
VulkanQueryManager mQueryManager;
VulkanExternalImageManager mExternalImageManager;
@@ -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
@@ -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.";
@@ -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);
}
@@ -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
+11 -11
View File
@@ -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;
+8
View File
@@ -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
View File
@@ -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);
}
+24 -2
View File
@@ -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;
+4
View File
@@ -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);
}
+31 -14
View File
@@ -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());
}
+149 -139
View File
@@ -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,
+55 -21
View File
@@ -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;
};
+4
View File
@@ -338,6 +338,10 @@ public:
return mDynamicResolution;
}
math::float2 getLastDynamicResolutionScale() const noexcept {
return mScale;
}
void setRenderQuality(RenderQuality const& renderQuality) noexcept {
mRenderQuality = renderQuality;
}
+2
View File
@@ -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
+149
View File
@@ -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
+158
View File
@@ -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
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.67.1"
spec.version = "1.67.0"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.67.1/filament-v1.67.1-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.67.0/filament-v1.67.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
+7
View File
@@ -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
+31
View File
@@ -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());
+4 -4
View File
@@ -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:
+8
View File
@@ -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
+7 -34
View File
@@ -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
@@ -0,0 +1,28 @@
/*
* 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_COMMON_H
#define TNT_FILAMENTAPP_VULKAN_PLATFORM_HELPER_COMMON_H
#include <backend/platforms/VulkanPlatform.h>
namespace filament::filamentapp {
filament::backend::VulkanPlatform::Customization parseGpuHint(char const* gpuHintCstr);
} // namespace filament::filamentapp
#endif // TNT_FILAMENTAPP_VULKAN_PLATFORM_HELPER_COMMON_H
@@ -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/VulkanPlatformLinux.h>
#include "VulkanPlatformHelperCommon.h"
namespace filament::filamentapp {
using namespace filament::backend;
class FilamentAppVulkanPlatform : public VulkanPlatformLinux {
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/VulkanPlatformWindows.h>
#include "VulkanPlatformHelperCommon.h"
namespace filament::filamentapp {
using namespace filament::backend;
class FilamentAppVulkanPlatform : public VulkanPlatformWindows {
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
+11 -5
View File
@@ -38,7 +38,7 @@
#include <utils/compiler.h>
#include <utils/JobSystem.h>
#include <utils/Log.h>
#include <utils/Logger.h>
#include <utils/Path.h>
#include <cgltf.h>
@@ -160,7 +160,7 @@ uint8_t const* parseDataUri(const char* uri, std::string* mimeType, size_t* psiz
inline void normalizeSkinningWeights(cgltf_data const* gltf) {
auto normalize = [](cgltf_accessor* data) {
if (data->type != cgltf_type_vec4 || data->component_type != cgltf_component_type_r_32f) {
slog.w << "Cannot normalize weights, unsupported attribute type." << io::endl;
LOG(WARNING) << "Cannot normalize weights, unsupported attribute type.";
return;
}
uint8_t* bytes = (uint8_t*) data->buffer_view->buffer->data;
@@ -527,6 +527,12 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
const cgltf_texture& srcTexture = asset->mSourceAsset->hierarchy->textures[textureIndex];
const cgltf_image* image = srcTexture.basisu_image ?
srcTexture.basisu_image : srcTexture.image;
if (!image) {
LOG(ERROR) << "Missing texture";
return {};
}
const cgltf_buffer_view* bv = image->buffer_view;
const char* uri = image->uri;
@@ -542,7 +548,7 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
auto foundProvider = mTextureProviders.find(mime);
if (foundProvider == mTextureProviders.end()) {
slog.e << "Missing texture provider for " << mime << io::endl;
LOG(ERROR) << "Missing texture provider for " << mime;
return {};
}
TextureProvider* provider = foundProvider->second;
@@ -598,7 +604,7 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
}
Path fullpath = Path(mGltfPath).getParent() + uri;
if (!fullpath.exists()) {
slog.e << "Unable to open " << fullpath << io::endl;
LOG(ERROR) << "Unable to open " << fullpath;
return {};
}
using namespace std;
@@ -620,7 +626,7 @@ std::pair<Texture*, CacheResult> ResourceLoader::Impl::getOrCreateTexture(FFilam
}
const char* name = srcTexture.name ? srcTexture.name : uri;
slog.e << "Unable to create texture " << name << ": " << provider->getPushMessage() << io::endl;
LOG(ERROR) << "Unable to create texture " << name << ": " << provider->getPushMessage();
return {};
}
-19
View File
@@ -12,17 +12,12 @@ set(TARGET matlang)
set(HDRS
src/matc/CommandlineConfig.h
src/matc/Compiler.h
src/matc/DirIncluder.h
src/matc/Includes.h
src/matc/IncludeCallback.h
src/matc/MaterialCompiler.h
)
set(SRCS
src/matc/Compiler.cpp
src/matc/CommandlineConfig.cpp
src/matc/DirIncluder.cpp
src/matc/Includes.cpp
src/matc/MaterialCompiler.cpp
)
@@ -68,17 +63,3 @@ set_target_properties(${TARGET} PROPERTIES FOLDER Tools)
# ==================================================================================================
install(TARGETS ${TARGET} RUNTIME DESTINATION bin)
# ==================================================================================================
# Tests
# ==================================================================================================
project(test_matc)
set(TARGET test_matc)
set(SRCS tests/test_includer.cpp tests/test_includes.cpp)
add_executable(${TARGET} ${SRCS})
target_link_libraries(${TARGET} matlang gtest)
target_include_directories(${TARGET} PRIVATE ${filamat_SOURCE_DIR}/src)
set_target_properties(test_matc PROPERTIES FOLDER Tests)
+23 -5
View File
@@ -68,6 +68,8 @@ static const option OPTIONS[] = {
{ "no-sampler-validation", no_argument, nullptr, 'F' },
{ "save-raw-variants", no_argument, nullptr, 'R' },
{ "workarounds", required_argument, nullptr, 'W' },
{ "no-insert-line-directives", no_argument, nullptr, 'n' },
{ "no-insert-line-directive-check", no_argument, nullptr, 'N' },
{ nullptr, 0, nullptr, 0 } // termination of the option list
};
@@ -142,13 +144,26 @@ static void usage(char* name) {
" Reflect the specified metadata as JSON: parameters\n\n"
" --variant-filter=<filter>, -V <filter>\n"
" Filter out specified comma-separated variants:\n"
" directionalLighting, dynamicLighting, shadowReceiver, skinning, vsm, fog,"
" directionalLighting, dynamicLighting, shadowReceiver, skinning, vsm, fog,\n"
" ssr (screen-space reflections), stereo\n"
" This variant filter is merged with the filter from the material, if any\n\n"
" This variant filter is merged with the filter from the material, if any.\n\n"
" --workarounds, -W\n"
" Workarounds to apply: all or none. (default is none).\n\n"
" --version, -v\n"
" Print the material version number\n\n"
" --output-format, -f\n"
" Specify output format: blob (default), header or mat.\n"
" When specifying the output as mat, it will return a mat\n"
" with #include directives resolved.\n\n"
" --no-insert-line-directives\n"
" By default insert #line directives before / after each #include.\n"
" Add this to omit line directives.\n\n"
" --no-insert-line-directive-check\n"
" By default it surrounds line directives with\n"
" #if defined(GL_GOOGLE_cpp_style_line_directive)\n"
" Some drivers may complain about the use of cpp style #line directives\n"
" if they don't support it.\n\n"
"Internal use and debugging only:\n"
" --optimize-none, -g, -O0\n"
" Disable all shader optimizations, for debugging\n\n"
@@ -156,9 +171,6 @@ static void usage(char* name) {
" Optimize shaders by running only the preprocessor\n\n"
" --raw, -w\n"
" Compile a raw GLSL shader into a SPIRV binary chunk\n\n"
" --output-format, -f\n"
" Specify output format: blob (default), header or mat. When specifying the "
" output as mat, it will return a mat with #include directives resolved.\n\n"
" --debug, -d\n"
" Generate extra data for debugging\n\n"
" --no-sampler-validation, -F\n"
@@ -388,6 +400,12 @@ bool CommandlineConfig::parse() {
case 'R':
mSaveRawVariants = true;
break;
case 'n':
mInsertLineDirectives = false;
break;
case 'N':
mInsertLineDirectiveChecks = false;
break;
}
}
+9 -22
View File
@@ -16,9 +16,6 @@
#include "MaterialCompiler.h"
#include "DirIncluder.h"
#include "Includes.h"
#include <memory>
#include <iostream>
#include <utility>
@@ -27,7 +24,6 @@
#include <filament-matp/Config.h>
#include <utils/CString.h>
#include <utils/JobSystem.h>
#include <utils/Log.h>
#include <utils/Path.h>
@@ -69,31 +65,22 @@ bool MaterialCompiler::run(const matp::Config& config) {
glslang::FinalizeProcess();
return success;
}
// Inline material (i.e. resolve #include directives)
DirIncluder includer;
includer.setIncludeDirectory(materialFilePath.getParent());
utils::CString fileName = utils::CString(materialFilePath.getName().c_str());
auto [resolvedStatus, resolvedString] =
mParser.resolveIncludes(buffer, size, materialFilePath,
config.getInsertLineDirectives(),
config.getInsertLineDirectiveChecks());
ResolveOptions const options {
.insertLineDirectives = true,
.insertLineDirectiveCheck = true
};
// This is both source and result, this will be mutated by `resolveIncludes` call.
IncludeResult result {
.includeName = fileName,
.text = utils::CString(buffer.get(), size),
.name = CString("")
};
if (!resolveIncludes(result, std::move(includer), options)) {
// It has failed to resolve the include directives.
if (!resolvedStatus.isOk()) {
std::cerr << resolvedStatus.getMessage() << std::endl;
return false;
}
// Now that the buffer is mutated, we need to update the buffer pointer and the size
// before parsing.
size = result.text.size();
size = resolvedString.size();
auto modifiedBuffer = std::make_unique<char[]>(size);
std::strncpy(modifiedBuffer.get(), result.text.c_str(), size);
std::strncpy(modifiedBuffer.get(), resolvedString.c_str(), size);
buffer = std::move(modifiedBuffer);
if (config.getOutputFormat() == matp::Config::OutputFormat::MAT) {
+1
View File
@@ -694,6 +694,7 @@ class_<View>("View")
.function("isStencilBufferEnabled", &View::isStencilBufferEnabled)
.function("setMaterialGlobal", &View::setMaterialGlobal)
.function("getMaterialGlobal", &View::getMaterialGlobal)
.function("getLastDynamicResolutionScale", &View::getLastDynamicResolutionScale)
.function("getFogEntity", &View::getFogEntity)
.function("clearFrameHistory", &View::clearFrameHistory);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.67.1",
"version": "1.67.0",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",