Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52b8d8cfbd |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.70.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.69.5'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -50,7 +50,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.70.1'
|
||||
pod 'Filament', '~> 1.69.5'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,9 +7,6 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.70.1
|
||||
|
||||
|
||||
## v1.70.0
|
||||
|
||||
- engine: fix crash when using variance shadow maps
|
||||
|
||||
@@ -200,7 +200,7 @@ subprojects {
|
||||
google()
|
||||
}
|
||||
|
||||
if (!name.startsWith("sample") && name != "filament-tools" && name != "gradle-plugin") {
|
||||
if (!name.startsWith("sample") && name != "filament-tools") {
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
|
||||
@@ -25,7 +25,7 @@ configured, the corresponding task will be disabled.
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
filament {
|
||||
@@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
id 'groovy-gradle-plugin'
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("filament-plugin") {
|
||||
id = "filament-plugin"
|
||||
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
|
||||
}
|
||||
+2
-3
@@ -60,10 +60,9 @@ class ToolsLocator {
|
||||
def classifier =
|
||||
project.extensions.getByType(com.google.gradle.osdetector.OsDetector).classifier
|
||||
|
||||
// If com.google.android.filament.tools-dir is set to a non-empty string, we'll use it as
|
||||
// the tool's base path.
|
||||
// If com.google.android.filament.tools-dir is set, we'll use it as the tool's base path.
|
||||
def toolsDirProp = project.providers.gradleProperty("com.google.android.filament.tools-dir")
|
||||
if (toolsDirProp.isPresent() && !toolsDirProp.get().trim().isEmpty()) {
|
||||
if (toolsDirProp.isPresent()) {
|
||||
def toolsDir = toolsDirProp.get()
|
||||
def path = OperatingSystem.current().isWindows() ?
|
||||
"${toolsDir}/bin/${name}.exe" :
|
||||
+20
-25
@@ -106,8 +106,8 @@ class ModelViewer(
|
||||
var skyboxCubemap: Texture? = null
|
||||
|
||||
private lateinit var displayHelper: DisplayHelper
|
||||
private var cameraManipulator: Manipulator? = null
|
||||
private var gestureDetector: GestureDetector? = null
|
||||
private lateinit var cameraManipulator: Manipulator
|
||||
private lateinit var gestureDetector: GestureDetector
|
||||
private var surfaceView: SurfaceView? = null
|
||||
private var textureView: TextureView? = null
|
||||
|
||||
@@ -157,13 +157,15 @@ class ModelViewer(
|
||||
surfaceView: SurfaceView,
|
||||
engine: Engine = Engine.create(),
|
||||
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
|
||||
manipulator: Manipulator? = defaultCameraManipulator(surfaceView.width, surfaceView.height)
|
||||
manipulator: Manipulator? = null
|
||||
) : this(engine, uiHelper) {
|
||||
cameraManipulator = manipulator ?: Manipulator.Builder()
|
||||
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
|
||||
.viewport(surfaceView.width, surfaceView.height)
|
||||
.build(Manipulator.Mode.ORBIT)
|
||||
|
||||
this.surfaceView = surfaceView
|
||||
cameraManipulator = manipulator
|
||||
cameraManipulator?.let { c ->
|
||||
gestureDetector = GestureDetector(surfaceView, c)
|
||||
}
|
||||
gestureDetector = GestureDetector(surfaceView, cameraManipulator)
|
||||
displayHelper = DisplayHelper(surfaceView.context)
|
||||
uiHelper.renderCallback = SurfaceCallback()
|
||||
uiHelper.attachTo(surfaceView)
|
||||
@@ -175,14 +177,15 @@ class ModelViewer(
|
||||
textureView: TextureView,
|
||||
engine: Engine = Engine.create(),
|
||||
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
|
||||
manipulator: Manipulator? = defaultCameraManipulator(textureView.width, textureView.height)
|
||||
manipulator: Manipulator? = null
|
||||
) : this(engine, uiHelper) {
|
||||
cameraManipulator = manipulator
|
||||
cameraManipulator = manipulator ?: Manipulator.Builder()
|
||||
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
|
||||
.viewport(textureView.width, textureView.height)
|
||||
.build(Manipulator.Mode.ORBIT)
|
||||
|
||||
this.textureView = textureView
|
||||
cameraManipulator = manipulator
|
||||
cameraManipulator?.let { c ->
|
||||
gestureDetector = GestureDetector(textureView, c)
|
||||
}
|
||||
gestureDetector = GestureDetector(textureView, cameraManipulator)
|
||||
displayHelper = DisplayHelper(textureView.context)
|
||||
uiHelper.renderCallback = SurfaceCallback()
|
||||
uiHelper.attachTo(textureView)
|
||||
@@ -299,13 +302,11 @@ class ModelViewer(
|
||||
asset?.let { populateScene(it) }
|
||||
|
||||
// Extract the camera basis from the helper and push it to the Filament camera.
|
||||
cameraManipulator?.let { cm ->
|
||||
cm.getLookAt(eyePos, target, upward)
|
||||
camera.lookAt(
|
||||
cameraManipulator.getLookAt(eyePos, target, upward)
|
||||
camera.lookAt(
|
||||
eyePos[0], eyePos[1], eyePos[2],
|
||||
target[0], target[1], target[2],
|
||||
upward[0], upward[1], upward[2])
|
||||
}
|
||||
|
||||
// Render the scene, unless the renderer wants to skip the frame.
|
||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
||||
@@ -397,7 +398,7 @@ class ModelViewer(
|
||||
* Handles a [MotionEvent] to enable one-finger orbit, two-finger pan, and pinch-to-zoom.
|
||||
*/
|
||||
fun onTouchEvent(event: MotionEvent) {
|
||||
gestureDetector?.onTouchEvent(event)
|
||||
gestureDetector.onTouchEvent(event)
|
||||
}
|
||||
|
||||
@SuppressWarnings("ClickableViewAccessibility")
|
||||
@@ -450,7 +451,7 @@ class ModelViewer(
|
||||
|
||||
override fun onResized(width: Int, height: Int) {
|
||||
view.viewport = Viewport(0, 0, width, height)
|
||||
cameraManipulator?.setViewport(width, height)
|
||||
cameraManipulator.setViewport(width, height)
|
||||
updateCameraProjection()
|
||||
synchronizePendingFrames(engine)
|
||||
}
|
||||
@@ -467,11 +468,5 @@ class ModelViewer(
|
||||
|
||||
companion object {
|
||||
private val kDefaultObjectPosition = Float3(0.0f, 0.0f, -4.0f)
|
||||
private fun defaultCameraManipulator(width: Int, height: Int) : Manipulator {
|
||||
return Manipulator.Builder()
|
||||
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
|
||||
.viewport(width, height)
|
||||
.build(Manipulator.Mode.ORBIT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
plugins {
|
||||
id 'groovy-gradle-plugin'
|
||||
id 'com.gradle.plugin-publish' version '2.1.0'
|
||||
}
|
||||
|
||||
group = "com.google.android.filament"
|
||||
version = "0.1.0"
|
||||
|
||||
gradlePlugin {
|
||||
website = "https://github.com/google/filament/tree/main/android/gradle-plugin"
|
||||
vcsUrl = "https://github.com/google/filament/tree/main/android/gradle-plugin"
|
||||
|
||||
plugins {
|
||||
create("filament-tools") {
|
||||
id = "com.google.android.filament-tools"
|
||||
displayName = "Filament Tools Gradle Plugin"
|
||||
description = "A plugin that helps integrate Filament into Android projects"
|
||||
tags.addAll('android', 'graphics', 'rendering', 'filament', '3d', 'gltf', 'native')
|
||||
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.70.1
|
||||
VERSION_NAME=1.69.5
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
// Filament tools plugin
|
||||
pluginManagement {
|
||||
includeBuild 'gradle-plugin'
|
||||
}
|
||||
|
||||
// Libraries
|
||||
include ':filament-android'
|
||||
include ':filamat-android'
|
||||
|
||||
@@ -93,7 +93,6 @@ set(SRCS
|
||||
src/MorphTargetBuffer.cpp
|
||||
src/PostProcessManager.cpp
|
||||
src/ProgramSpecialization.cpp
|
||||
src/LocalProgramCache.cpp
|
||||
src/RenderPass.cpp
|
||||
src/RenderPrimitive.cpp
|
||||
src/RenderTarget.cpp
|
||||
@@ -187,7 +186,6 @@ set(PRIVATE_HDRS
|
||||
src/MaterialCache.h
|
||||
src/MaterialDefinition.h
|
||||
src/MaterialParser.h
|
||||
src/LocalProgramCache.h
|
||||
src/MaterialInstanceManager.h
|
||||
src/PIDController.h
|
||||
src/PostProcessManager.h
|
||||
|
||||
@@ -600,7 +600,6 @@ if (FILAMENT_BUILD_TESTING)
|
||||
test/test_CircularBuffer.cpp
|
||||
test/test_CommandBufferQueue.cpp
|
||||
test/test_Template.cpp
|
||||
test/test_Platform.cpp
|
||||
)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
@@ -731,4 +730,4 @@ if (FILAMENT_BUILD_TESTING)
|
||||
|
||||
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
@@ -216,17 +216,6 @@ public:
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
/**
|
||||
* Types of device/driver information that can be queried from the platform.
|
||||
*/
|
||||
enum class DeviceInfoType {
|
||||
OPENGL_RENDERER, //!< glGetString(GL_RENDERER)
|
||||
OPENGL_VENDOR, //!< glGetString(GL_VENDOR)
|
||||
VULKAN_DEVICE_NAME, //!< VkPhysicalDeviceProperties::deviceName
|
||||
VULKAN_DRIVER_NAME, //!< VkPhysicalDeviceDriverProperties::driverName
|
||||
VULKAN_DRIVER_INFO, //!< VkPhysicalDeviceDriverProperties::driverInfo
|
||||
};
|
||||
|
||||
/**
|
||||
* This controls the priority level for GPU work scheduling, which helps prioritize the
|
||||
* submitted GPU work and enables preemption.
|
||||
@@ -327,7 +316,7 @@ public:
|
||||
*/
|
||||
StereoscopicType stereoscopicType = StereoscopicType::NONE;
|
||||
|
||||
/**
|
||||
/*
|
||||
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
|
||||
* between 1 and Engine::getMaxStereoscopicEyes() (inclusive).
|
||||
*/
|
||||
@@ -388,16 +377,6 @@ public:
|
||||
*/
|
||||
virtual int getOSVersion() const noexcept = 0;
|
||||
|
||||
/**
|
||||
* Queries device/driver information of the graphics API.
|
||||
* @param infoType the type of information to query.
|
||||
* @param driver a pointer to the current driver.
|
||||
* @return a CString containing the requested information.
|
||||
*/
|
||||
virtual utils::CString getDeviceInfo(DeviceInfoType infoType,
|
||||
Driver* UTILS_NULLABLE driver) const noexcept = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Creates and initializes the low-level API (e.g. an OpenGL context or Vulkan instance),
|
||||
* then creates the concrete Driver.
|
||||
|
||||
@@ -52,9 +52,6 @@ protected:
|
||||
|
||||
~OpenGLPlatform() noexcept override;
|
||||
|
||||
utils::CString getDeviceInfo(DeviceInfoType infoType,
|
||||
Driver* UTILS_NULLABLE driver) const noexcept override;
|
||||
|
||||
public:
|
||||
struct ExternalTexture {
|
||||
unsigned int target; // GLenum target
|
||||
|
||||
@@ -38,7 +38,6 @@ public:
|
||||
|
||||
Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) override;
|
||||
int getOSVersion() const noexcept override { return 0; }
|
||||
utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override { return {}; }
|
||||
|
||||
/**
|
||||
* Optionally initializes the Metal platform by acquiring resources necessary for rendering.
|
||||
|
||||
@@ -143,8 +143,6 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
utils::CString getDeviceInfo(DeviceInfoType infoType, Driver* driver) const noexcept override;
|
||||
|
||||
// ----------------------------------------------------
|
||||
// ---------- Platform Customization options ----------
|
||||
struct Customization {
|
||||
@@ -177,12 +175,6 @@ public:
|
||||
* presentation. Default is true.
|
||||
*/
|
||||
bool transitionSwapChainImageLayoutForPresent = true;
|
||||
|
||||
/**
|
||||
* The number of frames before an unused framebuffer is evicted from the cache.
|
||||
* Default is 3.
|
||||
*/
|
||||
uint32_t timeBeforeEvictionFbo = 3;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,9 +46,6 @@ public:
|
||||
~WebGPUPlatform() override = default;
|
||||
|
||||
[[nodiscard]] int getOSVersion() const noexcept final { return 0; }
|
||||
[[nodiscard]] utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override {
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; }
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ class PlatformNoop final : public Platform {
|
||||
public:
|
||||
|
||||
int getOSVersion() const noexcept final { return 0; }
|
||||
utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override { return {}; }
|
||||
|
||||
~PlatformNoop() noexcept override = default;
|
||||
|
||||
|
||||
@@ -341,44 +341,7 @@ void GLDescriptorSet::bind(
|
||||
if (arg.handle) {
|
||||
GLTexture const* const t = handleAllocator.handle_cast<GLTexture*>(arg.handle);
|
||||
gl.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
|
||||
SamplerParams params = arg.params;
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
// From https://registry.khronos.org/OpenGL-Refpages/es2.0/
|
||||
// GLES 2.0 will draw mipmapped samplers as black if the following conditions are not met:
|
||||
//
|
||||
// "...if the width or height of a texture image are not powers of two and either the
|
||||
// GL_TEXTURE_MIN_FILTER is set to one of the functions that requires mipmaps or the
|
||||
// GL_TEXTURE_WRAP_S or GL_TEXTURE_WRAP_T is not set to GL_CLAMP_TO_EDGE, then the
|
||||
// texture image unit will return (R, G, B, A) = (0, 0, 0, 1)."
|
||||
//
|
||||
// "If the texture has dimensions w × h , there are [floor(log2(max(w, h))) + 1] mipmap levels.
|
||||
// Level 0 is the original texture; level [floor(log2(max(w, h)))] is the final 1 × 1 mipmap."
|
||||
// "Suppose that a texture is accessed from a fragment shader or vertex shader and has set GL_TEXTURE_MIN_FILTER
|
||||
// to one of the functions that requires mipmaps. If either the dimensions of the texture images currently
|
||||
// defined (with previous calls to glTexImage2D, glCompressedTexImage2D, or glCopyTexImage2D) do not follow
|
||||
// the proper sequence for mipmaps (described above), or there are fewer texture images defined than are needed,
|
||||
// or the set of texture images were defined with different formats or types, then the texture image unit
|
||||
// will return (R, G, B, A) = (0, 0, 0, 1)."
|
||||
//
|
||||
// So we force the sampler to a valid state in those cases.
|
||||
auto isPowerOfTwo = [](uint32_t x) -> bool { return (x & (x - 1)) == 0 && x > 0; };
|
||||
bool textureIsPowerOfTwo = isPowerOfTwo(t->width) && isPowerOfTwo(t->height);
|
||||
|
||||
uint32_t requiredMipLevels = (std::floor(std::log2(std::max(t->width, std::max(t->height, t->depth)))) + 1);
|
||||
bool textureHasRequiredMipLevels = t->levels == requiredMipLevels;
|
||||
|
||||
bool samplerCanBeInvalid = params.filterMin > SamplerMinFilter::LINEAR;
|
||||
if (samplerCanBeInvalid && (!textureIsPowerOfTwo || !textureHasRequiredMipLevels)) {
|
||||
if (params.filterMin == SamplerMinFilter::NEAREST_MIPMAP_NEAREST
|
||||
|| params.filterMin == SamplerMinFilter::LINEAR_MIPMAP_NEAREST) {
|
||||
params.filterMin = SamplerMinFilter::NEAREST;
|
||||
} else {
|
||||
params.filterMin = SamplerMinFilter::LINEAR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SamplerParams const params = arg.params;
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER,
|
||||
(GLint)GLUtils::getTextureFilter(params.filterMin));
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER,
|
||||
|
||||
@@ -45,19 +45,6 @@ Driver* OpenGLPlatform::createDefaultDriver(OpenGLPlatform* platform,
|
||||
|
||||
OpenGLPlatform::~OpenGLPlatform() noexcept = default;
|
||||
|
||||
utils::CString OpenGLPlatform::getDeviceInfo(DeviceInfoType infoType,
|
||||
Driver* driver) const noexcept {
|
||||
switch (infoType) {
|
||||
case DeviceInfoType::OPENGL_RENDERER:
|
||||
return getRendererString(driver);
|
||||
case DeviceInfoType::OPENGL_VENDOR:
|
||||
return getVendorString(driver);
|
||||
default:
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Unsupported DeviceInfoType for OpenGLPlatform";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
utils::CString OpenGLPlatform::getVendorString(Driver const* driver) {
|
||||
auto const p = static_cast<OpenGLDriverBase const*>(driver);
|
||||
#if UTILS_HAS_RTTI
|
||||
|
||||
@@ -706,11 +706,10 @@ bool PlatformEGL::setExternalImage(ExternalImageHandleRef externalImage,
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
void PlatformEGL::initializeGlExtensions() noexcept {
|
||||
// We're guaranteed to be on an ES platform, since we're using EGL
|
||||
const char* const extensions = (const char*)glGetString(GL_EXTENSIONS);
|
||||
if (extensions) {
|
||||
GLUtils::unordered_string_set const glExtensions = GLUtils::split(extensions);
|
||||
ext.gl.OES_EGL_image_external_essl3 = glExtensions.has("GL_OES_EGL_image_external_essl3");
|
||||
}
|
||||
GLUtils::unordered_string_set const glExtensions = GLUtils::split(extensions);
|
||||
ext.gl.OES_EGL_image_external_essl3 = glExtensions.has("GL_OES_EGL_image_external_essl3");
|
||||
}
|
||||
|
||||
EGLContext PlatformEGL::getContextForType(ContextType const type) const noexcept {
|
||||
|
||||
@@ -118,6 +118,14 @@ void VulkanBufferCache::terminate() noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
size_t VulkanBufferCache::getSize() const noexcept {
|
||||
size_t size = 0;
|
||||
for (int i = 0; i < MAX_POOL_COUNT; i++) {
|
||||
size += mGpuBufferPools[i].size();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void VulkanBufferCache::release(VulkanGpuBuffer const* gpuBuffer) noexcept {
|
||||
assert_invariant(gpuBuffer != nullptr);
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ public:
|
||||
// This should be called while the context's VkDevice is still alive.
|
||||
void terminate() noexcept;
|
||||
|
||||
size_t getSize() const noexcept;
|
||||
|
||||
private:
|
||||
struct UnusedGpuBuffer {
|
||||
uint64_t lastAccessed;
|
||||
|
||||
@@ -158,6 +158,8 @@ struct CommandBufferPool {
|
||||
|
||||
inline bool isRecording() const { return mRecording != INVALID; }
|
||||
|
||||
size_t getSize() const noexcept { return mBuffers.size(); }
|
||||
|
||||
private:
|
||||
static constexpr int CAPACITY = FVK_MAX_COMMAND_BUFFERS;
|
||||
// int8 only goes up to 127, therefore capacity must be less than that.
|
||||
@@ -255,6 +257,17 @@ public:
|
||||
// Updates the atomic "status" variable in every extant fence.
|
||||
void updateFences();
|
||||
|
||||
struct SizeInfo {
|
||||
size_t regular;
|
||||
size_t protectedPool;
|
||||
};
|
||||
SizeInfo getSize() const noexcept {
|
||||
return {
|
||||
mPool ? mPool->getSize() : 0,
|
||||
mProtectedPool ? mProtectedPool->getSize() : 0
|
||||
};
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {});
|
||||
void popGroupMarker();
|
||||
|
||||
@@ -137,14 +137,6 @@ public:
|
||||
return mFenceExportFlags;
|
||||
}
|
||||
|
||||
inline const char* getPhysicalDeviceName() const noexcept {
|
||||
return mPhysicalDeviceProperties.properties.deviceName;
|
||||
}
|
||||
|
||||
inline const char* getDriverName() const noexcept { return mDriverProperties.driverName; }
|
||||
|
||||
inline const char* getDriverInfo() const noexcept { return mDriverProperties.driverInfo; }
|
||||
|
||||
inline bool isImageCubeArraySupported() const noexcept {
|
||||
return mPhysicalDeviceFeatures.features.imageCubeArray == VK_TRUE;
|
||||
}
|
||||
@@ -220,16 +212,11 @@ public:
|
||||
return mGlobalPrioritySupported;
|
||||
}
|
||||
|
||||
inline bool isDriverPropertiesSupported() const noexcept { return mDriverPropertiesSupported; }
|
||||
|
||||
private:
|
||||
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
|
||||
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
||||
};
|
||||
VkPhysicalDeviceDriverProperties mDriverProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES,
|
||||
};
|
||||
VkPhysicalDeviceVulkan11Features mPhysicalDeviceVk11Features = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
|
||||
};
|
||||
@@ -259,7 +246,6 @@ private:
|
||||
bool mProtectedMemorySupported = false;
|
||||
bool mVertexInputDynamicStateSupported = false;
|
||||
bool mGlobalPrioritySupported = false;
|
||||
bool mDriverPropertiesSupported = false;
|
||||
|
||||
// These are options that can be enabled or disabled at an application level.
|
||||
bool mAsyncPipelineCachePrewarmingEnabled = false;
|
||||
|
||||
@@ -119,6 +119,9 @@ public:
|
||||
return mCapacity;
|
||||
}
|
||||
|
||||
uint16_t size() const { return mSize; }
|
||||
uint16_t unusedCount() const { return mUnusedCount; }
|
||||
|
||||
// A convenience method for checking if this pool can allocate sets for a given layout.
|
||||
inline bool canAllocate(DescriptorCount const& count) {
|
||||
return count == mCount;
|
||||
@@ -255,6 +258,16 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
VulkanDescriptorSetCache::SizeInfo getSize() const {
|
||||
VulkanDescriptorSetCache::SizeInfo info = {};
|
||||
info.poolCount = mPools.size();
|
||||
for (auto& pool : mPools) {
|
||||
info.totalSize += pool->size();
|
||||
info.totalUnusedCount += pool->unusedCount();
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
std::vector<std::unique_ptr<DescriptorPool>> mPools;
|
||||
@@ -355,8 +368,35 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
||||
VkSampler sampler, VkDescriptorSetLayout externalSamplerLayout) noexcept {
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler,
|
||||
VkDescriptorSetLayout externalSamplerLayout) noexcept {
|
||||
|
||||
// We have to update a bound set for two use cases
|
||||
// - streaming API (a changing feed of AHardwareBuffer)
|
||||
// - external samplers - potential changing of dataspace per-frame
|
||||
|
||||
// TODO: Fix the stream flow case base on the above comment!!
|
||||
if (set->isAnExternalSamplerBound) {
|
||||
auto layout = set->getLayout();
|
||||
// Build a new descriptor set from the new layout
|
||||
VkDescriptorSetLayout const genLayout = set->boundLayout;
|
||||
VkDescriptorSet const newSet = getVkSet(layout->count, genLayout);
|
||||
Bitmask const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
||||
Bitmask samplers = layout->bitmask.sampler;
|
||||
samplers.unset(binding);
|
||||
|
||||
// Each bitmask denotes a binding index, and separated into two stages - vertex and buffer
|
||||
// We fold the two stages into just the lower half of the bits to denote a combined set of
|
||||
// bindings.
|
||||
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
|
||||
VkDescriptorSet const srcSet = set->getVkSet();
|
||||
copySet(srcSet, newSet, copyBindings);
|
||||
set->addNewSet(newSet,
|
||||
[this, layoutCount = layout->count, genLayout, newSet](VulkanDescriptorSet*) {
|
||||
this->manualRecycle(layoutCount, genLayout, newSet);
|
||||
});
|
||||
}
|
||||
|
||||
VkDescriptorSet const vkset = set->getVkSet();
|
||||
VkImageSubresourceRange range = texture->getPrimaryViewRange();
|
||||
VkImageViewType const expectedType = texture->getViewType();
|
||||
@@ -405,28 +445,6 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
|
||||
return set;
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::cloneSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
fvkutils::SamplerBitmask samplerMask) noexcept {
|
||||
auto const& layout = set->getLayout();
|
||||
// Build a new descriptor set from layout
|
||||
VkDescriptorSetLayout const genLayout = set->boundLayout;
|
||||
VkDescriptorSet const newSet = getVkSet(layout->count, genLayout);
|
||||
|
||||
// Each bitmask denotes a binding index, and separated into two stages - vertex and buffer
|
||||
// We fold the two stages into just the lower half of the bits to denote a combined set of
|
||||
// bindings.
|
||||
Bitmask const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
||||
// Don't copy the samplers in the mask
|
||||
Bitmask samplers = layout->bitmask.sampler ^ samplerMask;
|
||||
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
|
||||
|
||||
VkDescriptorSet const srcSet = set->getVkSet();
|
||||
copySet(srcSet, newSet, copyBindings);
|
||||
set->addNewSet(newSet,
|
||||
[this, layoutCount = layout->count, genLayout, newSet](
|
||||
VulkanDescriptorSet*) { this->manualRecycle(layoutCount, genLayout, newSet); });
|
||||
}
|
||||
|
||||
VkDescriptorSet VulkanDescriptorSetCache::getVkSet(DescriptorCount const& count,
|
||||
VkDescriptorSetLayout vklayout) {
|
||||
return mDescriptorPool->obtainSet(count, vklayout);
|
||||
@@ -443,6 +461,10 @@ void VulkanDescriptorSetCache::manualRecycle(VulkanDescriptorSetLayout::Count co
|
||||
|
||||
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
|
||||
|
||||
VulkanDescriptorSetCache::SizeInfo VulkanDescriptorSetCache::getSize() const noexcept {
|
||||
return mDescriptorPool->getSize();
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::copySet(VkDescriptorSet srcSet, VkDescriptorSet dstSet,
|
||||
fvkutils::SamplerBitmask bindings) const {
|
||||
// TODO: fix the size for better memory management
|
||||
|
||||
@@ -76,11 +76,6 @@ public:
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
// Create and set as current a new VkDescriptorSet using the `set` currently bound layout and
|
||||
// copy all the bindings and ignoring the samplers bindings in the `samplerMask`.
|
||||
void cloneSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
fvkutils::SamplerBitmask samplerMask) noexcept;
|
||||
|
||||
// This method is meant to be used with external samplers
|
||||
VkDescriptorSet getVkSet(DescriptorCount const& count, VkDescriptorSetLayout vklayout);
|
||||
|
||||
@@ -94,6 +89,13 @@ public:
|
||||
|
||||
void resetCachedState() noexcept { mLastBoundInfo = {}; }
|
||||
|
||||
struct SizeInfo {
|
||||
size_t poolCount;
|
||||
size_t totalSize;
|
||||
size_t totalUnusedCount;
|
||||
};
|
||||
SizeInfo getSize() const noexcept;
|
||||
|
||||
private:
|
||||
void copySet(VkDescriptorSet srcSet, VkDescriptorSet destSet,
|
||||
fvkutils::SamplerBitmask copyBindings) const;
|
||||
|
||||
@@ -49,6 +49,8 @@ public:
|
||||
fvkutils::SamplerBitmask externalSamplers,
|
||||
utils::FixedCapacityVector<std::pair<uint64_t, VkSampler>> immutableSamplers = {});
|
||||
|
||||
size_t getSize() const noexcept { return mVkLayouts.size(); }
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
fvkmemory::ResourceManager* mResourceManager;
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -260,8 +261,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
|
||||
mPipelineCache(*this, mPlatform->getDevice(), mContext),
|
||||
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
|
||||
mBufferCache(mContext, mResourceManager, mAllocator),
|
||||
mFramebufferCache(mPlatform->getDevice(),
|
||||
mPlatform->getCustomization().timeBeforeEvictionFbo),
|
||||
mFramebufferCache(mPlatform->getDevice()),
|
||||
mYcbcrConversionCache(mPlatform->getDevice()),
|
||||
mSamplerCache(mPlatform->getDevice()),
|
||||
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
|
||||
@@ -446,6 +446,29 @@ void VulkanDriver::collectGarbage() {
|
||||
|
||||
mResourceManager.gc();
|
||||
|
||||
auto dsSize = mDescriptorSetCache.getSize();
|
||||
auto stageSize = mStagePool.getSize();
|
||||
auto externalSize = mExternalImageManager.getSize();
|
||||
auto commandsSize = mCommands.getSize();
|
||||
|
||||
fprintf(stderr, "Vulkan Cache Sizes:\n");
|
||||
fprintf(stderr, " Pipelines: %zu\n", mPipelineCache.getSize());
|
||||
fprintf(stderr, " Pipeline Layouts: %zu\n", mPipelineLayoutCache.getSize());
|
||||
fprintf(stderr, " Descriptor Set Layouts: %zu\n", mDescriptorSetLayoutCache.getSize());
|
||||
fprintf(stderr, " Descriptor Sets: %zu (pools: %zu, unused: %zu)\n", dsSize.totalSize, dsSize.poolCount, dsSize.totalUnusedCount);
|
||||
fprintf(stderr, " FBOs: %zu\n", mFramebufferCache.getFboCacheSize());
|
||||
fprintf(stderr, " Render Passes: %zu (refcount: %zu)\n",
|
||||
mFramebufferCache.getRenderPassCacheSize(), mFramebufferCache.getRenderPassRefCountSize());
|
||||
fprintf(stderr, " Samplers: %zu\n", mSamplerCache.getSize());
|
||||
fprintf(stderr, " Buffers: %zu\n", mBufferCache.getSize());
|
||||
fprintf(stderr, " Stages: %zu, Free Images: %zu\n", stageSize.stages, stageSize.freeImages);
|
||||
fprintf(stderr, " YCbCr Conversions: %zu\n", mYcbcrConversionCache.getSize());
|
||||
fprintf(stderr, " External Images: %zu, Set Bindings: %zu\n", externalSize.images, externalSize.setBindings);
|
||||
fprintf(stderr, " Streamed Bindings: %zu\n", mStreamedImageManager.getSize());
|
||||
fprintf(stderr, " Command Buffers: %zu (regular: %zu, protected: %zu)\n",
|
||||
commandsSize.regular + commandsSize.protectedPool, commandsSize.regular, commandsSize.protectedPool);
|
||||
fprintf(stderr, " Semaphores: %zu\n", mSemaphoreManager.getSize());
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
mResourceManager.print();
|
||||
#endif
|
||||
@@ -1990,7 +2013,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
rt->emitBarriersBeginRenderPass(*commandBuffer);
|
||||
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> vkfb =
|
||||
mFramebufferCache.getFramebuffer(fbkey, &mResourceManager, rt);
|
||||
mFramebufferCache.getFramebuffer(fbkey, &mResourceManager);
|
||||
|
||||
// Assign a label to the framebuffer for debugging purposes.
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS | FVK_DEBUG_DEBUG_UTILS)
|
||||
|
||||
@@ -94,9 +94,6 @@ void VulkanExternalImageManager::updateSetAndLayout(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> const& layout = set->getLayout();
|
||||
set->boundLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
|
||||
actualExternalSamplers, outSamplers);
|
||||
|
||||
mDescriptorSetCache->cloneSet(set, actualExternalSamplers);
|
||||
|
||||
// Update the external samplers in the set
|
||||
for (auto& [binding, sampler, image]: samplerAndBindings) {
|
||||
// We cannot call updateSamplerForExternalSamplerSet because some samplers are non NULL
|
||||
|
||||
@@ -85,6 +85,12 @@ public:
|
||||
// - Update the bindings that use external samplers.
|
||||
void updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||
|
||||
struct SizeInfo {
|
||||
size_t setBindings;
|
||||
size_t images;
|
||||
};
|
||||
SizeInfo getSize() const noexcept { return { mSetBindings.size(), mImages.size() }; }
|
||||
|
||||
private:
|
||||
|
||||
VulkanSamplerCache* mSamplerCache;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "VulkanHandles.h"
|
||||
#include "vulkan/utils/Image.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
// If any VkRenderPass or VkFramebuffer is unused for more than TIME_BEFORE_EVICTION frames, it
|
||||
@@ -63,18 +62,16 @@ bool VulkanFboCache::FboKeyEqualFn::operator()(const FboKey& k1, const FboKey& k
|
||||
return true;
|
||||
}
|
||||
|
||||
VulkanFboCache::VulkanFboCache(VkDevice device, uint32_t timeBeforeEvictionFbo)
|
||||
: mDevice(device),
|
||||
mTimeBeforeEvictionFbo(timeBeforeEvictionFbo) {}
|
||||
VulkanFboCache::VulkanFboCache(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VulkanFboCache::~VulkanFboCache() {
|
||||
FILAMENT_CHECK_POSTCONDITION(mFramebufferCache.empty() && mRenderPassCache.empty())
|
||||
<< "Please explicitly call terminate() while the VkDevice is still alive.";
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(FboKey const& config,
|
||||
fvkmemory::ResourceManager* resManager,
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget) noexcept {
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(
|
||||
FboKey const& config, fvkmemory::ResourceManager* resManager) noexcept {
|
||||
FboMap::iterator iter = mFramebufferCache.find(config);
|
||||
if (UTILS_LIKELY(iter != mFramebufferCache.end())) {
|
||||
iter.value().timestamp = mCurrentTime;
|
||||
@@ -123,9 +120,8 @@ fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(FboKey
|
||||
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create framebuffer."
|
||||
<< " error=" << static_cast<int32_t>(error);
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> fbh =
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer>::construct(resManager, mDevice, framebuffer,
|
||||
renderTarget);
|
||||
mFramebufferCache[config] = { fbh, mCurrentTime };
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer>::construct(resManager, mDevice, framebuffer);
|
||||
mFramebufferCache[config] = {fbh, mCurrentTime};
|
||||
return fbh;
|
||||
}
|
||||
|
||||
@@ -378,36 +374,31 @@ void VulkanFboCache::gc() noexcept {
|
||||
FVK_SYSTRACE_START("fbocache::gc");
|
||||
|
||||
// If this is one of the first few frames, return early to avoid wrapping unsigned integers.
|
||||
++mCurrentTime;
|
||||
if (++mCurrentTime <= TIME_BEFORE_EVICTION) {
|
||||
return;
|
||||
}
|
||||
const uint32_t evictTime = mCurrentTime - TIME_BEFORE_EVICTION;
|
||||
|
||||
if (UTILS_UNLIKELY(mCurrentTime > mTimeBeforeEvictionFbo)) {
|
||||
const uint32_t evictTimeFbo = mCurrentTime - mTimeBeforeEvictionFbo;
|
||||
for (FboMap::iterator iter = mFramebufferCache.begin(); iter != mFramebufferCache.end();) {
|
||||
const FboVal fbo = iter->second;
|
||||
if (fbo.timestamp < evictTimeFbo && fbo.handle) {
|
||||
mRenderPassRefCount[iter->first.renderPass]--;
|
||||
for (FboMap::iterator iter = mFramebufferCache.begin(); iter != mFramebufferCache.end(); ) {
|
||||
const FboVal fbo = iter->second;
|
||||
if (fbo.timestamp < evictTime && fbo.handle) {
|
||||
mRenderPassRefCount[iter->first.renderPass]--;
|
||||
|
||||
// erase(iterator) returns the iterator to the next element.
|
||||
iter = mFramebufferCache.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
// erase(iterator) returns the iterator to the next element.
|
||||
iter = mFramebufferCache.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(mCurrentTime > TIME_BEFORE_EVICTION)) {
|
||||
const uint32_t evictTimeRp = mCurrentTime - TIME_BEFORE_EVICTION;
|
||||
for (RenderPassMap::iterator iter = mRenderPassCache.begin();
|
||||
iter != mRenderPassCache.end();) {
|
||||
const VkRenderPass handle = iter->second.handle->getVkRenderPass();
|
||||
if (iter->second.timestamp < evictTimeRp && handle &&
|
||||
mRenderPassRefCount[handle] == 0) {
|
||||
// erase(iterator) returns the iterator to the next element.
|
||||
iter = mRenderPassCache.erase(iter);
|
||||
mRenderPassRefCount.erase(handle);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
for (RenderPassMap::iterator iter = mRenderPassCache.begin(); iter != mRenderPassCache.end(); ) {
|
||||
const VkRenderPass handle = iter->second.handle->getVkRenderPass();
|
||||
if (iter->second.timestamp < evictTime && handle && mRenderPassRefCount[handle] == 0) {
|
||||
// erase(iterator) returns the iterator to the next element.
|
||||
iter = mRenderPassCache.erase(iter);
|
||||
mRenderPassRefCount.erase(handle);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,13 +100,12 @@ public:
|
||||
bool operator()(const FboKey& k1, const FboKey& k2) const;
|
||||
};
|
||||
|
||||
explicit VulkanFboCache(VkDevice device, uint32_t timeBeforeEvictionFbo);
|
||||
explicit VulkanFboCache(VkDevice device);
|
||||
~VulkanFboCache();
|
||||
|
||||
// Retrieves or creates a VkFramebuffer handle.
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> getFramebuffer(FboKey const& config,
|
||||
fvkmemory::ResourceManager* resManager,
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget) noexcept;
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> getFramebuffer(
|
||||
FboKey const& config, fvkmemory::ResourceManager* resManager) noexcept;
|
||||
|
||||
// Retrieves or creates a VkRenderPass handle.
|
||||
fvkmemory::resource_ptr<VulkanRenderPass> getRenderPass(
|
||||
@@ -121,6 +120,10 @@ public:
|
||||
// Frees all Vulkan objects. Call this during shutdown before the device is destroyed.
|
||||
void terminate() noexcept;
|
||||
|
||||
size_t getFboCacheSize() const noexcept { return mFramebufferCache.size(); }
|
||||
size_t getRenderPassCacheSize() const noexcept { return mRenderPassCache.size(); }
|
||||
size_t getRenderPassRefCountSize() const noexcept { return mRenderPassRefCount.size(); }
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
using FboMap = tsl::robin_map<FboKey, FboVal, FboKeyHashFn, FboKeyEqualFn>;
|
||||
@@ -130,7 +133,6 @@ private:
|
||||
RenderPassMap mRenderPassCache;
|
||||
tsl::robin_map<VkRenderPass, uint32_t> mRenderPassRefCount;
|
||||
uint32_t mCurrentTime = 0;
|
||||
uint32_t mTimeBeforeEvictionFbo;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -715,11 +715,8 @@ VulkanRenderPrimitive::VulkanRenderPrimitive(PrimitiveType pt,
|
||||
vertexBuffer(vb),
|
||||
indexBuffer(ib) {}
|
||||
|
||||
VulkanFramebuffer::VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer,
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget)
|
||||
: mDevice(device),
|
||||
mFramebuffer(framebuffer),
|
||||
mRenderTarget(renderTarget) {}
|
||||
VulkanFramebuffer::VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer)
|
||||
: mDevice(device), mFramebuffer(framebuffer) {}
|
||||
|
||||
VulkanFramebuffer::~VulkanFramebuffer() {
|
||||
vkDestroyFramebuffer(mDevice, mFramebuffer, VKALLOC);
|
||||
|
||||
@@ -195,7 +195,7 @@ public:
|
||||
}
|
||||
|
||||
// The current layout used by the descriptor set. This one will match the bindings, including
|
||||
// external samplers data.
|
||||
// external samplers data.
|
||||
// This will not necessarilly be the same as `mLayout`.
|
||||
VkDescriptorSetLayout boundLayout = VK_NULL_HANDLE;
|
||||
|
||||
@@ -203,7 +203,7 @@ public:
|
||||
|
||||
fvkutils::UniformBufferBitmask const& dynamicUboMask;
|
||||
uint8_t const uniqueDynamicUboCount;
|
||||
|
||||
|
||||
// Flag to indicate if the current layout needs to be recreated or not.
|
||||
// This should only set to `true` when a external sampler image is bound to the descriptor set.
|
||||
bool isLayoutDirty = false;
|
||||
@@ -586,9 +586,7 @@ struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource {
|
||||
};
|
||||
|
||||
struct VulkanFramebuffer : public fvkmemory::Resource {
|
||||
VulkanFramebuffer(VkDevice device,
|
||||
VkFramebuffer framebuffer,
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget);
|
||||
VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer);
|
||||
~VulkanFramebuffer();
|
||||
|
||||
inline VkFramebuffer getVkFramebuffer() const noexcept {
|
||||
@@ -598,11 +596,6 @@ struct VulkanFramebuffer : public fvkmemory::Resource {
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
VkFramebuffer mFramebuffer;
|
||||
|
||||
// We need to keep a reference to the renderTarget because the key of the framebuffer in the
|
||||
// cache has references to the image views that are derived from the textures of the render
|
||||
// target.
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> mRenderTarget;
|
||||
};
|
||||
|
||||
struct VulkanRenderPass : public fvkmemory::Resource {
|
||||
|
||||
@@ -136,6 +136,8 @@ public:
|
||||
void terminate() noexcept;
|
||||
void gc() noexcept;
|
||||
|
||||
size_t getSize() const noexcept { return mPipelines.size(); }
|
||||
|
||||
private:
|
||||
// PIPELINE CACHE KEY
|
||||
// ------------------
|
||||
|
||||
@@ -59,6 +59,8 @@ public:
|
||||
VkPipelineLayout getLayout(DescriptorSetLayoutArray const& descriptorSetLayouts,
|
||||
fvkmemory::resource_ptr<VulkanProgram> program);
|
||||
|
||||
size_t getSize() const noexcept { return mPipelineLayouts.size(); }
|
||||
|
||||
private:
|
||||
using Timestamp = uint64_t;
|
||||
struct PipelineLayoutCacheEntry {
|
||||
|
||||
@@ -40,6 +40,8 @@ public:
|
||||
explicit VulkanSamplerCache(VkDevice device);
|
||||
VkSampler getSampler(Params params);
|
||||
void terminate() noexcept;
|
||||
|
||||
size_t getSize() const noexcept { return mCache.size(); }
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ public:
|
||||
void terminate();
|
||||
Semaphore acquire();
|
||||
|
||||
size_t getSize() const noexcept { return mPool.size(); }
|
||||
|
||||
private:
|
||||
friend struct VulkanSemaphore;
|
||||
void recycle(VkSemaphore semaphore);
|
||||
|
||||
@@ -223,6 +223,12 @@ public:
|
||||
// resource_ptrs, as this would lead to undefined behavior.
|
||||
void terminate() noexcept;
|
||||
|
||||
struct SizeInfo {
|
||||
size_t stages;
|
||||
size_t freeImages;
|
||||
};
|
||||
SizeInfo getSize() const noexcept { return { mStages.size(), mFreeImages.size() }; }
|
||||
|
||||
private:
|
||||
VmaAllocator mAllocator;
|
||||
fvkmemory::ResourceManager* mResManager;
|
||||
|
||||
@@ -46,6 +46,8 @@ public:
|
||||
void onStreamAcquireImage(fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
fvkmemory::resource_ptr<VulkanStream> stream);
|
||||
|
||||
size_t getSize() const noexcept { return mStreamedTexturesBindings.size(); }
|
||||
|
||||
private:
|
||||
struct StreamedTextureBinding {
|
||||
uint8_t binding = 0;
|
||||
|
||||
@@ -42,6 +42,8 @@ public:
|
||||
VkSamplerYcbcrConversion getConversion(Params params);
|
||||
void terminate() noexcept;
|
||||
|
||||
size_t getSize() const noexcept { return mCache.size(); }
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
|
||||
|
||||
@@ -704,30 +704,6 @@ VulkanPlatform::VulkanPlatform() = default;
|
||||
|
||||
VulkanPlatform::~VulkanPlatform() = default;
|
||||
|
||||
utils::CString VulkanPlatform::getDeviceInfo(DeviceInfoType infoType,
|
||||
Driver* driver) const noexcept {
|
||||
if (mImpl->mPhysicalDevice == VK_NULL_HANDLE) {
|
||||
return {};
|
||||
}
|
||||
auto& context = mImpl->mContext;
|
||||
switch (infoType) {
|
||||
case DeviceInfoType::VULKAN_DEVICE_NAME: {
|
||||
return utils::CString(context.getPhysicalDeviceName());
|
||||
}
|
||||
case DeviceInfoType::VULKAN_DRIVER_NAME: {
|
||||
return context.isDriverPropertiesSupported() ? utils::CString(context.getDriverName())
|
||||
: utils::CString();
|
||||
}
|
||||
case DeviceInfoType::VULKAN_DRIVER_INFO: {
|
||||
return context.isDriverPropertiesSupported() ? utils::CString(context.getDriverInfo())
|
||||
: utils::CString();
|
||||
}
|
||||
default:
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Unsupported DeviceInfoType for VulkanPlatform";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
VulkanPlatform::SwapChainBundle VulkanPlatform::getSwapChainBundle(SwapChainPtr handle) {
|
||||
return static_cast<VulkanPlatformSwapChainBase*>(handle)->getSwapChainBundle();
|
||||
}
|
||||
@@ -976,11 +952,6 @@ void VulkanPlatform::queryAndSetDeviceFeatures(Platform::DriverConfig const& dri
|
||||
chainStruct(&context.mPhysicalDeviceFeatures, &globalPriorityFeatures);
|
||||
}
|
||||
|
||||
if (vkGetPhysicalDeviceProperties2) {
|
||||
chainStruct(&context.mPhysicalDeviceProperties, &context.mDriverProperties);
|
||||
context.mDriverPropertiesSupported = true;
|
||||
}
|
||||
|
||||
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
|
||||
// physicalDeviceFeatures.
|
||||
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
|
||||
|
||||
@@ -192,15 +192,8 @@ void WebGPUDriver::flush(int) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::finish(int /* dummy */) {
|
||||
mReadPixelMapsCounter.waitForAllToFinish();
|
||||
mQueueManager.finish();
|
||||
|
||||
// We use polling to advance webgpu's callback counter until all the read backs have been
|
||||
// processed. Note that blocking with mReadPixelMapsCounter.waitForAllToFinish will only
|
||||
// deadlock since we could not advance the counter.
|
||||
while (!mReadPixelMapsCounter.isIdle()) {
|
||||
mAdapter.GetInstance().ProcessEvents();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
@@ -947,6 +940,10 @@ size_t WebGPUDriver::getUniformBufferOffsetAlignment(){
|
||||
|
||||
void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
|
||||
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
|
||||
// make sure command elements (draws, etc.) prior to the buffer update are processed before the
|
||||
// update on the GPU, otherwise the expected data may not be available at the time certain
|
||||
// draw calls are made.
|
||||
flush();
|
||||
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
@@ -961,6 +958,10 @@ void WebGPUDriver::updateIndexBufferAsyncR(AsyncCallId jobId,
|
||||
|
||||
void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
|
||||
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
|
||||
// make sure command elements (draws, etc.) prior to the buffer update are processed before the
|
||||
// update on the GPU, otherwise the expected data may not be available at the time certain
|
||||
// draw calls are made.
|
||||
flush();
|
||||
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
@@ -1007,6 +1008,10 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
const uint32_t xoffset, const uint32_t yoffset, const uint32_t zoffset,
|
||||
const uint32_t width, const uint32_t height, const uint32_t depth,
|
||||
PixelBufferDescriptor&& pixelBufferDescriptor) {
|
||||
// one way or another this function writes texture(s), thus any commands (draw calls etc.) should
|
||||
// get submitted prior to these updates so that subsequent commands/draws run with the
|
||||
// image/texture(s) updated as expected.
|
||||
flush();
|
||||
PixelBufferDescriptor* inputData{ &pixelBufferDescriptor };
|
||||
PixelBufferDescriptor reshapedData;
|
||||
if (reshape(pixelBufferDescriptor, reshapedData)) {
|
||||
@@ -1178,6 +1183,11 @@ void WebGPUDriver::generateMipmaps(Handle<HwTexture> textureHandle) {
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
// make sure command elements (draws, etc.) prior to the texture update are processed before the
|
||||
// update on the GPU.
|
||||
// this ensures subsequent draw calls are run after the mipmaps have been generated as expected
|
||||
flush();
|
||||
|
||||
const auto usage = wgpuTexture.GetUsage();
|
||||
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
|
||||
<< "Texture for mipmap generation must have TextureBinding usage.";
|
||||
@@ -1366,9 +1376,6 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
customDepthStencilMsaaSidecarTextureView);
|
||||
|
||||
mRenderPassEncoder = commandEncoder.BeginRenderPass(&renderPassDescriptor);
|
||||
|
||||
// TODO: there's a bug here because the webgpu viewport has the origin at top-left, whereas
|
||||
// filament expects it to be bottom left.
|
||||
mRenderPassEncoder.SetViewport(
|
||||
static_cast<float>(params.viewport.left),
|
||||
static_cast<float>(params.viewport.bottom),
|
||||
@@ -1456,6 +1463,7 @@ void WebGPUDriver::stopCapture(int /* dummy */) {
|
||||
void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, const uint32_t x,
|
||||
const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
PixelBufferDescriptor&& pixelBufferDescriptor) {
|
||||
flush();
|
||||
const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
|
||||
assert_invariant(srcTarget);
|
||||
|
||||
@@ -1513,7 +1521,10 @@ void WebGPUDriver::readTextureToBuffer(wgpu::Texture srcTexture, uint32_t level,
|
||||
return;
|
||||
}
|
||||
|
||||
auto commandEncoder = mQueueManager.getCommandEncoder();
|
||||
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
|
||||
.label = "read_texture_to_buffer_command",
|
||||
};
|
||||
auto commandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
|
||||
<< "Failed to create command encoder for readTextureToBuffer?";
|
||||
|
||||
@@ -1635,6 +1646,9 @@ void WebGPUDriver::readTextureToBuffer(wgpu::Texture srcTexture, uint32_t level,
|
||||
.depthOrArrayLayers = 1,
|
||||
};
|
||||
commandEncoder.CopyTextureToBuffer(&source, &destination, ©Size);
|
||||
wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
|
||||
assert_invariant(commandBuffer);
|
||||
mDevice.GetQueue().Submit(1, &commandBuffer);
|
||||
|
||||
// Map the buffer to read the data
|
||||
struct UserData final {
|
||||
@@ -1655,9 +1669,6 @@ void WebGPUDriver::readTextureToBuffer(wgpu::Texture srcTexture, uint32_t level,
|
||||
});
|
||||
|
||||
mReadPixelMapsCounter.startTask();
|
||||
// We need to flush here before we can readback from the staging buffer since the copy op is
|
||||
// pending to be submitted.
|
||||
mQueueManager.flush();
|
||||
userData->buffer.MapAsync(
|
||||
wgpu::MapMode::Read, 0, bufferSize, wgpu::CallbackMode::AllowSpontaneous,
|
||||
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
|
||||
@@ -1750,6 +1761,7 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
const wgpu::Extent2D destinationSize{ static_cast<uint32_t>(destinationViewport.width),
|
||||
static_cast<uint32_t>(destinationViewport.height) };
|
||||
|
||||
mQueueManager.flush();
|
||||
wgpu::CommandEncoder commandEncoder = mQueueManager.getCommandEncoder();
|
||||
const WebGPUBlitter::BlitArgs blitArgs{
|
||||
.source = { .texture = sourceTexture->getTexture(),
|
||||
@@ -1769,6 +1781,8 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
.filter = filter,
|
||||
};
|
||||
mBlitter.blit(mDevice.GetQueue(), commandEncoder, blitArgs);
|
||||
|
||||
mQueueManager.flush();
|
||||
}
|
||||
|
||||
void WebGPUDriver::resolve(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
|
||||
@@ -1808,6 +1822,7 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
|
||||
Handle<HwTexture> sourceTextureHandle, const uint8_t destinationLevel,
|
||||
const uint8_t destinationLayer, const math::uint2 sourceOrigin, const math::uint2 size) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
mQueueManager.flush();
|
||||
wgpu::CommandEncoder commandEncoder = mQueueManager.getCommandEncoder();
|
||||
const auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
|
||||
const auto destinationTexture{ handleCast<WebGPUTexture>(destinationTextureHandle) };
|
||||
@@ -1831,6 +1846,7 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
|
||||
.filter = SamplerMagFilter::NEAREST,
|
||||
};
|
||||
mBlitter.blit(mDevice.GetQueue(), commandEncoder, blitArgs);
|
||||
mQueueManager.flush();
|
||||
}
|
||||
|
||||
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
|
||||
@@ -50,9 +50,6 @@ public:
|
||||
|
||||
[[nodiscard]] bool isHeadless() const { return mType == SwapChainType::HEADLESS; }
|
||||
|
||||
[[nodiscard]] uint32_t getWidth() const { return mConfig.width; }
|
||||
[[nodiscard]] uint32_t getHeight() const { return mConfig.height; }
|
||||
|
||||
void present(DriverBase& driver);
|
||||
|
||||
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
|
||||
@@ -41,10 +41,5 @@ void AsyncTaskCounter::waitForAllToFinish() {
|
||||
mFinishedCondition.wait(lock, [this] { return mTasksInProgress == 0; });
|
||||
}
|
||||
|
||||
bool AsyncTaskCounter::isIdle() {
|
||||
std::lock_guard<std::mutex> lock{ mMutex };
|
||||
return mTasksInProgress == 0;
|
||||
}
|
||||
|
||||
} // namespace filament::backend::webgpuutils
|
||||
|
||||
|
||||
@@ -51,11 +51,6 @@ public:
|
||||
*/
|
||||
void waitForAllToFinish();
|
||||
|
||||
/**
|
||||
* Check if all tasks have finished (work counter is 0) (thread-safe)
|
||||
*/
|
||||
bool isIdle();
|
||||
|
||||
private:
|
||||
std::mutex mMutex;
|
||||
std::condition_variable mFinishedCondition;
|
||||
|
||||
@@ -35,10 +35,9 @@ test::NativeView getNativeView() {
|
||||
|
||||
namespace {
|
||||
|
||||
std::array<test::Backend, 3> const VALID_BACKENDS{
|
||||
test::Backend::OPENGL,
|
||||
test::Backend::VULKAN,
|
||||
test::Backend::WEBGPU
|
||||
std::array<test::Backend, 2> const VALID_BACKENDS{
|
||||
test::Backend::OPENGL,
|
||||
test::Backend::VULKAN,
|
||||
};
|
||||
|
||||
}// namespace
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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/Platform.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <private/backend/PlatformFactory.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
TEST(PlatformTest, GetDeviceInfo) {
|
||||
Backend backend = Backend::DEFAULT;
|
||||
Platform* platform = PlatformFactory::create(&backend);
|
||||
ASSERT_NE(platform, nullptr);
|
||||
|
||||
// Test valid queries for the current platform (will be either GL, Vulkan, or Metal depending on
|
||||
// host)
|
||||
if (backend == Backend::OPENGL) {
|
||||
platform->getDeviceInfo(Platform::DeviceInfoType::OPENGL_RENDERER, nullptr);
|
||||
platform->getDeviceInfo(Platform::DeviceInfoType::OPENGL_VENDOR, nullptr);
|
||||
|
||||
// Death tests for Vulkan info on OpenGL platform
|
||||
EXPECT_DEATH(platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DEVICE_NAME, nullptr),
|
||||
"Unsupported DeviceInfoType");
|
||||
} else if (backend == Backend::VULKAN) {
|
||||
platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DEVICE_NAME, nullptr);
|
||||
platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DRIVER_NAME, nullptr);
|
||||
platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DRIVER_INFO, nullptr);
|
||||
|
||||
// Death tests for OpenGL info on Vulkan platform
|
||||
EXPECT_DEATH(platform->getDeviceInfo(Platform::DeviceInfoType::OPENGL_RENDERER, nullptr),
|
||||
"Unsupported DeviceInfoType");
|
||||
}
|
||||
|
||||
PlatformFactory::destroy(&platform);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -227,6 +227,7 @@ bool Froxelizer::prepare(
|
||||
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);
|
||||
|
||||
|
||||
@@ -1,265 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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 "LocalProgramCache.h"
|
||||
#include "MaterialParser.h"
|
||||
|
||||
#include "details/Engine.h"
|
||||
#include "details/Material.h"
|
||||
|
||||
#include <backend/DriverApiForward.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace backend;
|
||||
using namespace utils;
|
||||
|
||||
LocalProgramCache::LocalProgramCache(LocalProgramCache const& other)
|
||||
: mMaterial(other.mMaterial),
|
||||
mCachedPrograms(other.mCachedPrograms.size()),
|
||||
mSpecializationConstants((other.mMaterial != nullptr)
|
||||
? other.mMaterial->getEngine()
|
||||
.getMaterialCache()
|
||||
.getSpecializationConstantsInternPool()
|
||||
.acquire(other.mSpecializationConstants)
|
||||
: SpecializationConstants()) {}
|
||||
|
||||
LocalProgramCache& LocalProgramCache::operator=(LocalProgramCache const& other) {
|
||||
assert_invariant(mMaterial == nullptr);
|
||||
assert_invariant(mCachedPrograms.empty());
|
||||
assert_invariant(mSpecializationConstants.empty());
|
||||
|
||||
mMaterial = other.mMaterial;
|
||||
if (mMaterial != nullptr) {
|
||||
mCachedPrograms = FixedCapacityVector<Handle<HwProgram>>(other.mCachedPrograms.size());
|
||||
mSpecializationConstants = other.mMaterial->getEngine()
|
||||
.getMaterialCache()
|
||||
.getSpecializationConstantsInternPool()
|
||||
.acquire(other.mSpecializationConstants);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void LocalProgramCache::initializeForMaterial(FEngine& engine, FMaterial const& material,
|
||||
utils::FixedCapacityVector<backend::Program::SpecializationConstant>
|
||||
specializationConstants) {
|
||||
assert_invariant(mMaterial == nullptr);
|
||||
assert_invariant(mCachedPrograms.empty());
|
||||
assert_invariant(mSpecializationConstants.empty());
|
||||
|
||||
mMaterial = &material;
|
||||
|
||||
mSpecializationConstants =
|
||||
engine.getMaterialCache().getSpecializationConstantsInternPool().acquire(
|
||||
std::move(specializationConstants));
|
||||
|
||||
size_t cachedProgramsSize;
|
||||
switch (material.getMaterialDomain()) {
|
||||
case filament::MaterialDomain::SURFACE:
|
||||
cachedProgramsSize = 1 << VARIANT_BITS;
|
||||
break;
|
||||
case filament::MaterialDomain::POST_PROCESS:
|
||||
cachedProgramsSize = 1 << POST_PROCESS_VARIANT_BITS;
|
||||
break;
|
||||
case filament::MaterialDomain::COMPUTE:
|
||||
cachedProgramsSize = 1;
|
||||
break;
|
||||
}
|
||||
mCachedPrograms = FixedCapacityVector<Handle<HwProgram>>(cachedProgramsSize);
|
||||
|
||||
material.getDefinition().acquirePrograms(engine, mCachedPrograms.as_slice(),
|
||||
material.getMaterialParser(), mSpecializationConstants, material.isDefaultMaterial());
|
||||
}
|
||||
|
||||
void LocalProgramCache::initializeForMaterialInstance(FEngine& engine, FMaterial const& material) {
|
||||
assert_invariant(mMaterial == nullptr);
|
||||
assert_invariant(mCachedPrograms.empty());
|
||||
assert_invariant(mSpecializationConstants.empty());
|
||||
|
||||
mMaterial = &material;
|
||||
LocalProgramCache const& programs = material.getPrograms();
|
||||
|
||||
mSpecializationConstants =
|
||||
engine.getMaterialCache().getSpecializationConstantsInternPool().acquire(
|
||||
programs.getSpecializationConstants());
|
||||
|
||||
mCachedPrograms =
|
||||
FixedCapacityVector<Handle<HwProgram>>(material.getPrograms().mCachedPrograms.size());
|
||||
}
|
||||
|
||||
Handle<HwProgram> LocalProgramCache::prepareProgramSlow(DriverApi& driver, Variant const variant,
|
||||
CompilerPriorityQueue const priorityQueue) const noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
FEngine& engine = mMaterial->getEngine();
|
||||
if (mMaterial->isSharedVariant(variant)) {
|
||||
FMaterial const* defaultMaterial = engine.getDefaultMaterial();
|
||||
assert_invariant(defaultMaterial);
|
||||
LocalProgramCache const& defaultPrograms = defaultMaterial->getPrograms();
|
||||
Handle<HwProgram> program = defaultPrograms.mCachedPrograms[variant.key];
|
||||
if (program) {
|
||||
return mCachedPrograms[variant.key] = program;
|
||||
}
|
||||
return mCachedPrograms[variant.key] =
|
||||
defaultPrograms.prepareProgram(driver, variant, priorityQueue);
|
||||
}
|
||||
return mCachedPrograms[variant.key] = mMaterial->getDefinition().prepareProgram(engine, driver,
|
||||
mMaterial->getMaterialParser(), getProgramSpecialization(variant), priorityQueue);
|
||||
}
|
||||
|
||||
ProgramSpecialization LocalProgramCache::getProgramSpecialization(Variant variant) const noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
return ProgramSpecialization {
|
||||
.materialCrc32 = mMaterial->getMaterialParser().getCrc32(),
|
||||
.variant = variant,
|
||||
.specializationConstants = mSpecializationConstants,
|
||||
};
|
||||
}
|
||||
|
||||
void LocalProgramCache::terminate(FEngine& engine) {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
mMaterial->getDefinition().releasePrograms(engine, mCachedPrograms.as_slice(),
|
||||
mMaterial->getMaterialParser(), mSpecializationConstants,
|
||||
mMaterial->isDefaultMaterial());
|
||||
engine.getMaterialCache().releaseMaterial(engine, mMaterial->getDefinition());
|
||||
engine.getMaterialCache().getSpecializationConstantsInternPool().release(
|
||||
mSpecializationConstants);
|
||||
}
|
||||
|
||||
void LocalProgramCache::clear(FEngine& engine) {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
mMaterial->getDefinition().releasePrograms(engine, mCachedPrograms.as_slice(),
|
||||
mMaterial->getMaterialParser(), mSpecializationConstants,
|
||||
mMaterial->isDefaultMaterial());
|
||||
}
|
||||
|
||||
Variant LocalProgramCache::filterVariantForGetProgram(Variant variant) const noexcept {
|
||||
if (UTILS_UNLIKELY(mMaterial->getEngine().features.material.enable_fog_as_postprocess)) {
|
||||
// if the fog as post-process feature is enabled, we need to proceed "as-if" the material
|
||||
// didn't have the FOG variant bit.
|
||||
if (mMaterial->getMaterialDomain() == MaterialDomain::SURFACE) {
|
||||
BlendingMode const blendingMode = mMaterial->getBlendingMode();
|
||||
bool const hasScreenSpaceRefraction =
|
||||
mMaterial->getRefractionMode() == RefractionMode::SCREEN_SPACE;
|
||||
bool const isBlendingCommand = !hasScreenSpaceRefraction &&
|
||||
(blendingMode != BlendingMode::OPAQUE && blendingMode != BlendingMode::MASKED);
|
||||
if (!isBlendingCommand) {
|
||||
variant.setFog(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return variant;
|
||||
}
|
||||
|
||||
Program::SpecializationConstant LocalProgramCache::getConstantImpl(uint32_t id) const noexcept {
|
||||
return mSpecializationConstants[id];
|
||||
}
|
||||
|
||||
Program::SpecializationConstant LocalProgramCache::getConstantImpl(
|
||||
std::string_view name) const noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
MaterialDefinition const& definition = mMaterial->getDefinition();
|
||||
auto it = definition.specializationConstantsNameToIndex.find(name);
|
||||
if (it != definition.specializationConstantsNameToIndex.cend()) {
|
||||
return getConstantImpl(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
|
||||
}
|
||||
|
||||
std::string name_cstring(name);
|
||||
PANIC_PRECONDITION("No such constant exists: %s", name_cstring.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
void LocalProgramCache::setConstants(
|
||||
std::initializer_list<std::pair<uint32_t, Program::SpecializationConstant>>
|
||||
constants) noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
auto newSpecializationConstants =
|
||||
FixedCapacityVector<Program::SpecializationConstant>(mSpecializationConstants);
|
||||
|
||||
bool hasChanged = false;
|
||||
for (const auto& [id, value] : constants) {
|
||||
if (newSpecializationConstants[id] != value) {
|
||||
newSpecializationConstants[id] = value;
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanged) {
|
||||
setConstantsImpl(std::move(newSpecializationConstants));
|
||||
}
|
||||
}
|
||||
|
||||
void LocalProgramCache::setConstants(
|
||||
std::initializer_list<std::pair<std::string_view, Program::SpecializationConstant>>
|
||||
constants) noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
auto newSpecializationConstants =
|
||||
FixedCapacityVector<Program::SpecializationConstant>(mSpecializationConstants);
|
||||
|
||||
bool hasChanged = false;
|
||||
for (const auto& [name, value] : constants) {
|
||||
MaterialDefinition const& definition = mMaterial->getDefinition();
|
||||
auto it = definition.specializationConstantsNameToIndex.find(name);
|
||||
if (it != definition.specializationConstantsNameToIndex.cend()) {
|
||||
uint32_t id = it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
|
||||
if (newSpecializationConstants[id] != value) {
|
||||
newSpecializationConstants[id] = value;
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanged) {
|
||||
setConstantsImpl(std::move(newSpecializationConstants));
|
||||
}
|
||||
}
|
||||
|
||||
void LocalProgramCache::setConstantsImpl(
|
||||
FixedCapacityVector<Program::SpecializationConstant> constants) noexcept {
|
||||
FEngine& engine = mMaterial->getEngine();
|
||||
|
||||
auto& internPool = engine.getMaterialCache().getSpecializationConstantsInternPool();
|
||||
MaterialParser const& materialParser = mMaterial->getMaterialParser();
|
||||
MaterialDefinition const& definition = mMaterial->getDefinition();
|
||||
const bool isDefaultMaterial = mMaterial->isDefaultMaterial();
|
||||
|
||||
// Release old resources...
|
||||
definition.releasePrograms(engine, mCachedPrograms.as_slice(), materialParser,
|
||||
mSpecializationConstants, isDefaultMaterial);
|
||||
internPool.release(mSpecializationConstants);
|
||||
|
||||
// Then acquire new ones.
|
||||
mSpecializationConstants = internPool.acquire(std::move(constants));
|
||||
definition.acquirePrograms(engine, mCachedPrograms.as_slice(), materialParser,
|
||||
mSpecializationConstants, isDefaultMaterial);
|
||||
}
|
||||
|
||||
template int32_t LocalProgramCache::getConstant<int32_t>(uint32_t id) const noexcept;
|
||||
template float LocalProgramCache::getConstant<float>(uint32_t id) const noexcept;
|
||||
template bool LocalProgramCache::getConstant<bool>(uint32_t id) const noexcept;
|
||||
|
||||
template int32_t LocalProgramCache::getConstant<int32_t>(std::string_view name) const noexcept;
|
||||
template float LocalProgramCache::getConstant<float>(std::string_view name) const noexcept;
|
||||
template bool LocalProgramCache::getConstant<bool>(std::string_view name) const noexcept;
|
||||
|
||||
} // namespace filament
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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_LOCALPROGRAMCACHE_H
|
||||
#define TNT_FILAMENT_LOCALPROGRAMCACHE_H
|
||||
|
||||
#include "MaterialDefinition.h"
|
||||
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <private/filament/Variant.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace filament {
|
||||
|
||||
class FMaterial;
|
||||
|
||||
// L0 cache for material programs. Manages recompiling them on-demand; owned by Material and
|
||||
// MaterialInstance.
|
||||
class LocalProgramCache {
|
||||
template<typename T>
|
||||
using is_supported_constant_parameter_t =
|
||||
std::enable_if_t<std::is_same_v<int32_t, T> || std::is_same_v<float, T> ||
|
||||
std::is_same_v<bool, T>>;
|
||||
|
||||
public:
|
||||
using Programs = utils::Slice<const backend::Handle<backend::HwProgram>>;
|
||||
using SpecializationConstants = utils::Slice<const backend::Program::SpecializationConstant>;
|
||||
|
||||
LocalProgramCache() = default;
|
||||
LocalProgramCache(LocalProgramCache const& other);
|
||||
|
||||
LocalProgramCache& operator=(LocalProgramCache const& other);
|
||||
|
||||
// Initialize for use in a Material.
|
||||
void initializeForMaterial(FEngine& engine, FMaterial const& material,
|
||||
utils::FixedCapacityVector<backend::Program::SpecializationConstant>
|
||||
specializationConstants);
|
||||
|
||||
// Initialize for use in a MaterialInstance. Copies the set of spec constants currently in use
|
||||
// from its Material.
|
||||
void initializeForMaterialInstance(FEngine& engine, FMaterial const& material);
|
||||
|
||||
bool isInitialized() const noexcept { return mMaterial != nullptr; }
|
||||
|
||||
// prepareProgram creates the program for the material's given variant at the backend level.
|
||||
// Must be called outside of backend render pass.
|
||||
// Must be called before getProgram() below.
|
||||
backend::Handle<backend::HwProgram> prepareProgram(backend::DriverApi& driver,
|
||||
Variant const variant,
|
||||
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
|
||||
backend::Handle<backend::HwProgram> program = mCachedPrograms[variant.key];
|
||||
if (UTILS_LIKELY(program)) {
|
||||
return program;
|
||||
}
|
||||
return prepareProgramSlow(driver, variant, priorityQueue);
|
||||
}
|
||||
|
||||
// getProgram returns the backend program for the material's given variant.
|
||||
// Must be called after prepareProgram().
|
||||
[[nodiscard]]
|
||||
backend::Handle<backend::HwProgram> getProgram(Variant variant) const noexcept {
|
||||
variant = filterVariantForGetProgram(variant);
|
||||
backend::Handle<backend::HwProgram> program = mCachedPrograms[variant.key];
|
||||
assert_invariant(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
SpecializationConstants getSpecializationConstants() const noexcept {
|
||||
return mSpecializationConstants;
|
||||
}
|
||||
|
||||
Programs getPrograms() const noexcept { return mCachedPrograms.as_slice(); }
|
||||
|
||||
// Free all engine resources associated with this instance.
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
// Clear all cached programs. Used primarily by matdbg to "sever" a Material's connection to the
|
||||
// global material cache.
|
||||
void clear(FEngine& engine);
|
||||
|
||||
// Get constant by ID.
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
T getConstant(uint32_t id) const noexcept {
|
||||
return std::get<T>(getConstantImpl(id));
|
||||
}
|
||||
|
||||
// Get constant by name.
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
T getConstant(std::string_view name) const noexcept {
|
||||
return std::get<T>(getConstantImpl(name));
|
||||
}
|
||||
|
||||
// Set constants by ID.
|
||||
void setConstants(
|
||||
std::initializer_list<std::pair<uint32_t, backend::Program::SpecializationConstant>>
|
||||
constants) noexcept;
|
||||
|
||||
// Set constants by name.
|
||||
void setConstants(std::initializer_list<
|
||||
std::pair<std::string_view, backend::Program::SpecializationConstant>>
|
||||
constants) noexcept;
|
||||
|
||||
private:
|
||||
backend::Handle<backend::HwProgram> prepareProgramSlow(backend::DriverApi& driver,
|
||||
Variant const variant,
|
||||
backend::CompilerPriorityQueue const priorityQueue) const noexcept;
|
||||
|
||||
ProgramSpecialization getProgramSpecialization(Variant variant) const noexcept;
|
||||
|
||||
Variant filterVariantForGetProgram(Variant const variant) const noexcept;
|
||||
|
||||
void setConstantsImpl(utils::FixedCapacityVector<backend::Program::SpecializationConstant>
|
||||
constants) noexcept;
|
||||
|
||||
backend::Program::SpecializationConstant getConstantImpl(uint32_t id) const noexcept;
|
||||
backend::Program::SpecializationConstant getConstantImpl(std::string_view name) const noexcept;
|
||||
|
||||
FMaterial const* mMaterial = nullptr;
|
||||
mutable utils::FixedCapacityVector<backend::Handle<backend::HwProgram>> mCachedPrograms;
|
||||
SpecializationConstants mSpecializationConstants;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_FILAMENT_LOCALPROGRAMCACHE_H
|
||||
@@ -39,25 +39,12 @@ bool MaterialCache::MaterialKey::operator==(MaterialKey const& rhs) const noexce
|
||||
return parser == rhs.parser;
|
||||
}
|
||||
|
||||
MaterialCache::MaterialCache()
|
||||
: mDefinitions("MaterialCache::mDefinitions", 0),
|
||||
mPrograms("MaterialCache::mPrograms", 0) {}
|
||||
|
||||
MaterialCache::~MaterialCache() {
|
||||
assert_invariant(mDefinitions.empty());
|
||||
assert_invariant(mPrograms.empty());
|
||||
assert_invariant(mSpecializationConstantsInternPool.empty());
|
||||
}
|
||||
|
||||
void MaterialCache::terminate(FEngine& engine) {
|
||||
mPrograms.clearLruCache([&engine](backend::Handle<backend::HwProgram>& program) {
|
||||
engine.getDriverApi().destroyProgram(program);
|
||||
});
|
||||
mDefinitions.clearLruCache([&engine](MaterialDefinition& definition) {
|
||||
definition.terminate(engine);
|
||||
});
|
||||
}
|
||||
|
||||
MaterialDefinition* UTILS_NULLABLE MaterialCache::acquireMaterial(FEngine& engine,
|
||||
const void* UTILS_NONNULL data, size_t size) noexcept {
|
||||
std::unique_ptr<MaterialParser> parser = MaterialDefinition::createParser(engine.getBackend(),
|
||||
|
||||
@@ -56,13 +56,8 @@ public:
|
||||
using ProgramCache =
|
||||
utils::RefCountedMap<ProgramSpecialization, backend::Handle<backend::HwProgram>>;
|
||||
|
||||
MaterialCache();
|
||||
~MaterialCache();
|
||||
|
||||
// All reference-counted resources should be freed by the time MaterialCache is destructed, but
|
||||
// the LRU cache needs to be explicitly freed in addition.
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
SpecializationConstantInternPool& getSpecializationConstantsInternPool() {
|
||||
return mSpecializationConstantsInternPool;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
#include "fsr.h"
|
||||
#include "FrameHistory.h"
|
||||
#include "RenderPass.h"
|
||||
#include "ShadowMapManager.h"
|
||||
|
||||
#include "details/Camera.h"
|
||||
#include "details/ColorGrading.h"
|
||||
@@ -999,10 +998,13 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
|
||||
auto& material = getPostProcessMaterial(materialName);
|
||||
|
||||
FMaterial* ma = material.getMaterial(mEngine, driver);
|
||||
ma->getPrograms().setConstants({
|
||||
{ "useVisibilityBitmasks", options.gtao.useVisibilityBitmasks },
|
||||
{ "linearThickness", options.gtao.linearThickness },
|
||||
});
|
||||
{
|
||||
FMaterial::SpecializationConstantsBuilder maConstants =
|
||||
ma->getSpecializationConstantsBuilder();
|
||||
maConstants.set("useVisibilityBitmasks", options.gtao.useVisibilityBitmasks);
|
||||
maConstants.set("linearThickness", options.gtao.linearThickness);
|
||||
ma->setSpecializationConstants(std::move(maConstants));
|
||||
}
|
||||
|
||||
ma = material.getMaterial(mEngine, driver);
|
||||
FMaterialInstance* const mi = getMaterialInstance(ma);
|
||||
@@ -2915,18 +2917,20 @@ void PostProcessManager::configureTemporalAntiAliasingMaterial(backend::DriverAp
|
||||
TemporalAntiAliasingOptions const& taaOptions) noexcept {
|
||||
|
||||
FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine, driver);
|
||||
ma->getPrograms().setConstants({
|
||||
{ "upscaling", taaOptions.upscaling > 1.0f },
|
||||
{ "historyReprojection", taaOptions.historyReprojection },
|
||||
{ "filterHistory", taaOptions.filterHistory },
|
||||
{ "filterInput", taaOptions.filterInput },
|
||||
{ "useYCoCg", taaOptions.useYCoCg },
|
||||
{ "hdr", taaOptions.hdr },
|
||||
{ "preventFlickering", taaOptions.preventFlickering },
|
||||
{ "boxType", int32_t(taaOptions.boxType) },
|
||||
{ "boxClipping", int32_t(taaOptions.boxClipping) },
|
||||
{ "varianceGamma", taaOptions.varianceGamma },
|
||||
});
|
||||
FMaterial::SpecializationConstantsBuilder maConstants = ma->getSpecializationConstantsBuilder();
|
||||
|
||||
maConstants.set("upscaling", taaOptions.upscaling > 1.0f);
|
||||
maConstants.set("historyReprojection", taaOptions.historyReprojection);
|
||||
maConstants.set("filterHistory", taaOptions.filterHistory);
|
||||
maConstants.set("filterInput", taaOptions.filterInput);
|
||||
maConstants.set("useYCoCg", taaOptions.useYCoCg);
|
||||
maConstants.set("hdr", taaOptions.hdr);
|
||||
maConstants.set("preventFlickering", taaOptions.preventFlickering);
|
||||
maConstants.set("boxType", int32_t(taaOptions.boxType));
|
||||
maConstants.set("boxClipping", int32_t(taaOptions.boxClipping));
|
||||
maConstants.set("varianceGamma", taaOptions.varianceGamma);
|
||||
|
||||
ma->setSpecializationConstants(std::move(maConstants));
|
||||
}
|
||||
|
||||
FMaterialInstance* PostProcessManager::configureColorGradingMaterial(backend::DriverApi& driver,
|
||||
@@ -2934,10 +2938,13 @@ FMaterialInstance* PostProcessManager::configureColorGradingMaterial(backend::Dr
|
||||
ColorGradingConfig const& colorGradingConfig, VignetteOptions const& vignetteOptions,
|
||||
uint32_t const width, uint32_t const height) noexcept {
|
||||
FMaterial* ma = material.getMaterial(mEngine, driver);
|
||||
ma->getPrograms().setConstants({
|
||||
{ "isOneDimensional", colorGrading->isOneDimensional() },
|
||||
{ "isLDR", colorGrading->isLDR() },
|
||||
});
|
||||
{
|
||||
FMaterial::SpecializationConstantsBuilder maConstants =
|
||||
ma->getSpecializationConstantsBuilder();
|
||||
maConstants.set("isOneDimensional", colorGrading->isOneDimensional());
|
||||
maConstants.set("isLDR", colorGrading->isLDR());
|
||||
ma->setSpecializationConstants(std::move(maConstants));
|
||||
}
|
||||
|
||||
PostProcessVariant const variant = colorGradingConfig.translucent
|
||||
? PostProcessVariant::TRANSLUCENT
|
||||
@@ -3673,12 +3680,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
|
||||
}
|
||||
|
||||
// The Metal / Vulkan backends currently don't support depth/stencil resolve.
|
||||
// TODO: Stencil resolve is actually *not* supported. Trying to resolve a stencil texture will
|
||||
// trigger an assert on debug builds. We need to investigate how this can be accomplished
|
||||
// through shaders or some other manipulation.
|
||||
if ((isDepthFormat(inDesc.format) || isStencilFormat(inDesc.format)) &&
|
||||
(!mDepthStencilResolveSupported)) {
|
||||
return resolveDepthWithShader(fg, outputBufferName, input, outDesc);
|
||||
if (isDepthFormat(inDesc.format) && (!mDepthStencilResolveSupported)) {
|
||||
return resolveDepth(fg, outputBufferName, input, outDesc);
|
||||
}
|
||||
|
||||
outDesc.width = inDesc.width;
|
||||
@@ -3693,6 +3696,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
|
||||
|
||||
auto const& ppResolve = fg.addPass<ResolveData>("resolve",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
// we currently don't support stencil resolve.
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
data.input = builder.read(input, FrameGraphTexture::Usage::BLIT_SRC);
|
||||
data.output = builder.createTexture(outputBufferName, outDesc);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::BLIT_DST);
|
||||
@@ -3717,7 +3723,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
|
||||
return ppResolve->output;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepthWithShader(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepth(FrameGraph& fg,
|
||||
utils::StaticString outputBufferName, FrameGraphId<FrameGraphTexture> const input,
|
||||
FrameGraphTexture::Descriptor outDesc) noexcept {
|
||||
|
||||
@@ -3742,8 +3748,11 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepthWithShader(Frame
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
};
|
||||
|
||||
auto const& ppResolve = fg.addPass<ResolveData>("resolveDepthWithShader",
|
||||
auto const& ppResolve = fg.addPass<ResolveData>("resolveDepth",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
// we currently don't support stencil resolve
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
data.input = builder.sample(input);
|
||||
data.output = builder.createTexture(outputBufferName, outDesc);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
@@ -3752,9 +3761,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepthWithShader(Frame
|
||||
.clearFlags = TargetBufferFlags::DEPTH });
|
||||
},
|
||||
[=, this](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
// we currently don't support stencil resolve
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
bindPostProcessDescriptorSet(driver);
|
||||
bindPerRenderableDescriptorSet(driver);
|
||||
auto const& input = resources.getTexture(data.input);
|
||||
@@ -3831,7 +3837,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::debugShadowCascades(FrameGraph& fg,
|
||||
ShadowMapManager const& smm,
|
||||
FrameGraphId<FrameGraphTexture> const input,
|
||||
FrameGraphId<FrameGraphTexture> const depth) noexcept {
|
||||
|
||||
@@ -3849,31 +3854,17 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugShadowCascades(FrameGra
|
||||
data.output = builder.createTexture("Shadow Cascade Debug", desc);
|
||||
builder.declareRenderPass(data.output);
|
||||
},
|
||||
[=, &smm, this](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
[=, this](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
bindPostProcessDescriptorSet(driver);
|
||||
bindPerRenderableDescriptorSet(driver);
|
||||
auto color = resources.getTexture(data.color);
|
||||
auto depth = resources.getTexture(data.depth);
|
||||
auto const out = resources.getRenderPassInfo();
|
||||
|
||||
auto const& smu = smm.getShadowMappingUniforms();
|
||||
mat4f lightFromWorldMatrix[4] = {};
|
||||
float4 scissorNormalized[4] = {};
|
||||
for (size_t i = 0, c = std::max(4u, (smu.cascades & 0xF)) ; i < c; i++) {
|
||||
auto const& csp = smm.getCascadeShaderParameters(i);
|
||||
lightFromWorldMatrix[i] = csp.lightSpace;
|
||||
scissorNormalized[i] = csp.scissorNormalized;
|
||||
}
|
||||
|
||||
auto const& material = getPostProcessMaterial("debugShadowCascades");
|
||||
FMaterialInstance* const mi = getMaterialInstance(mEngine, driver, material);
|
||||
FMaterialInstance* const mi =
|
||||
getMaterialInstance(mEngine, driver, material);
|
||||
mi->setParameter("color", color, SamplerParams{}); // nearest
|
||||
mi->setParameter("depth", depth, SamplerParams{}); // nearest
|
||||
mi->setParameter("cascadeSplits", smu.cascadeSplits);
|
||||
mi->setParameter("cascadeCount", smu.cascades & 0xF);
|
||||
mi->setParameter("shadowAtlasResolution", smu.atlasResolution);
|
||||
mi->setParameter("lightFromWorldMatrix", lightFromWorldMatrix, 4);
|
||||
mi->setParameter("scissorNormalized", scissorNormalized, 4);
|
||||
commitAndRenderFullScreenQuad(driver, out, mi);
|
||||
unbindAllDescriptorSets(driver);
|
||||
});
|
||||
|
||||
@@ -66,7 +66,6 @@ class FMaterialInstance;
|
||||
class FrameGraph;
|
||||
class RenderPass;
|
||||
class RenderPassBuilder;
|
||||
class ShadowMapManager;
|
||||
class UboManager;
|
||||
struct CameraInfo;
|
||||
|
||||
@@ -305,10 +304,9 @@ public:
|
||||
utils::StaticString outputBufferName, FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor outDesc) noexcept;
|
||||
|
||||
// Resolves base level of input and outputs a texture from outDesc using a shader instead of
|
||||
// driver-implemented API.
|
||||
// Resolves base level of input and outputs a texture from outDesc.
|
||||
// outDesc with, height, format and samples will be overridden.
|
||||
FrameGraphId<FrameGraphTexture> resolveDepthWithShader(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> resolveDepth(FrameGraph& fg,
|
||||
utils::StaticString outputBufferName, FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor outDesc) noexcept;
|
||||
|
||||
@@ -323,7 +321,6 @@ public:
|
||||
bool reinhard, size_t kernelWidth, float sigma) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> debugShadowCascades(FrameGraph& fg,
|
||||
ShadowMapManager const& smm,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> depth) noexcept;
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ RenderableManager::getInstance(Entity const e) const noexcept {
|
||||
}
|
||||
|
||||
void RenderableManager::destroy(Entity const e) noexcept {
|
||||
return downcast(this)->clientDestroy(e);
|
||||
return downcast(this)->destroy(e);
|
||||
}
|
||||
|
||||
void RenderableManager::setAxisAlignedBoundingBox(Instance const instance, const Box& aabb) {
|
||||
|
||||
@@ -681,7 +681,6 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
ShadowTechnique shadowTechnique{};
|
||||
uint32_t directionalShadowsMask = 0;
|
||||
uint32_t cascadeHasVisibleShadows = 0;
|
||||
float4 wsSplitPositionUniform = { -std::numeric_limits<float>::infinity() };
|
||||
|
||||
if (hasVisibleShadows) {
|
||||
uint32_t const cascadeCount = cascadedShadowMaps.size();
|
||||
@@ -705,8 +704,11 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
// (which is the near plane, and doesn't need to be communicated to the shaders).
|
||||
static_assert(CONFIG_MAX_SHADOW_CASCADES <= 5,
|
||||
"At most, a float4 can fit 4 split positions for 5 shadow cascades");
|
||||
float4 wsSplitPositionUniform{ -std::numeric_limits<float>::infinity() };
|
||||
std::copy(splits.begin() + 1, splits.end(), &wsSplitPositionUniform[0]);
|
||||
|
||||
mShadowMappingUniforms.cascadeSplits = wsSplitPositionUniform;
|
||||
|
||||
// When computing the required bias we need a half-texel size, so we multiply by 0.5 here.
|
||||
// note: normalBias is set to zero for VSM
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : 0.5f * lcm.getShadowNormalBias(0);
|
||||
@@ -731,8 +733,6 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo,
|
||||
canUseDepthClamp);
|
||||
|
||||
mCascadesShaderParameters[i] = shaderParameters;
|
||||
|
||||
if (shadowMap.hasVisibleShadows()) {
|
||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||
assert_invariant(shadowIndex == i);
|
||||
@@ -775,14 +775,10 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
cascades |= uint32_t(cascadedShadowMaps.size());
|
||||
cascades |= cascadeHasVisibleShadows << 8u;
|
||||
|
||||
mShadowMappingUniforms = {
|
||||
.cascadeSplits = wsSplitPositionUniform,
|
||||
.ssContactShadowDistance = screenSpaceShadowDistance,
|
||||
.directionalShadows = directionalShadowsMask,
|
||||
.cascades = cascades,
|
||||
.atlasResolution = { mTextureAtlasRequirements.size, 1.0f / float(mTextureAtlasRequirements.size) }
|
||||
};
|
||||
|
||||
mShadowMappingUniforms.directionalShadows = directionalShadowsMask;
|
||||
mShadowMappingUniforms.ssContactShadowDistance = screenSpaceShadowDistance;
|
||||
mShadowMappingUniforms.cascades = cascades;
|
||||
|
||||
return shadowTechnique;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
|
||||
#include <math/mat4.h>
|
||||
#include <math/half.h>
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -76,7 +75,6 @@ struct ShadowMappingUniforms {
|
||||
float ssContactShadowDistance;
|
||||
uint32_t directionalShadows;
|
||||
uint32_t cascades;
|
||||
math::float2 atlasResolution;
|
||||
};
|
||||
|
||||
class ShadowMapManager {
|
||||
@@ -172,10 +170,6 @@ public:
|
||||
return std::min(targetExponent, filterCeiling);
|
||||
}
|
||||
|
||||
ShadowMap::ShaderParameters const& getCascadeShaderParameters(size_t index) const noexcept {
|
||||
return mCascadesShaderParameters[index];
|
||||
}
|
||||
|
||||
private:
|
||||
explicit ShadowMapManager(FEngine& engine);
|
||||
|
||||
@@ -259,8 +253,6 @@ private:
|
||||
|
||||
ShadowMap::SceneInfo mSceneInfo;
|
||||
|
||||
ShadowMap::ShaderParameters mCascadesShaderParameters[4]{};
|
||||
|
||||
// Inline storage for all our ShadowMap objects, we can't easily use a std::array<> directly.
|
||||
// Because ShadowMap doesn't have a default ctor, and we avoid out-of-line allocations.
|
||||
// Each ShadowMap is currently 88 bytes (total of ~12KB for 128 shadow maps)
|
||||
|
||||
@@ -85,6 +85,7 @@ void FCameraManager::destroy(FEngine& engine, Entity const e) noexcept {
|
||||
{ // scope for camera -- it's invalid after this scope.
|
||||
FCamera* const camera = manager.elementAt<CAMERA>(i);
|
||||
assert_invariant(camera);
|
||||
camera->terminate(engine);
|
||||
engine.getHeapAllocator().destroy(camera);
|
||||
|
||||
// Remove the camera component
|
||||
|
||||
@@ -577,7 +577,7 @@ void FRenderableManager::create(
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
if (UTILS_UNLIKELY(manager.hasComponent(entity))) {
|
||||
destroy(entity, driver);
|
||||
destroy(entity);
|
||||
}
|
||||
Instance const ci = manager.addComponent(entity);
|
||||
assert_invariant(ci);
|
||||
@@ -733,42 +733,41 @@ void FRenderableManager::create(
|
||||
}
|
||||
|
||||
// this destroys a single component from an entity
|
||||
void FRenderableManager::destroy(Entity const e, DriverApi& driver) noexcept {
|
||||
void FRenderableManager::destroy(Entity const e) noexcept {
|
||||
Instance const ci = getInstance(e);
|
||||
if (ci) {
|
||||
destroyComponent(ci, driver);
|
||||
destroyComponent(ci);
|
||||
mManager.removeComponent(e);
|
||||
}
|
||||
}
|
||||
|
||||
void FRenderableManager::clientDestroy(utils::Entity e) noexcept {
|
||||
destroy(e, mEngine.getDriverApi());
|
||||
}
|
||||
|
||||
// this destroys all components in this manager
|
||||
void FRenderableManager::terminate(DriverApi& driver) noexcept {
|
||||
void FRenderableManager::terminate() noexcept {
|
||||
auto& manager = mManager;
|
||||
if (!manager.empty()) {
|
||||
DLOG(INFO) << "cleaning up " << manager.getComponentCount()
|
||||
<< " leaked Renderable components";
|
||||
while (!manager.empty()) {
|
||||
Instance const ci = manager.end() - 1;
|
||||
destroyComponent(ci, driver);
|
||||
destroyComponent(ci);
|
||||
manager.removeComponent(manager.getEntity(ci));
|
||||
}
|
||||
}
|
||||
mHwRenderPrimitiveFactory.terminate(driver);
|
||||
mHwRenderPrimitiveFactory.terminate(mEngine.getDriverApi());
|
||||
}
|
||||
|
||||
void FRenderableManager::gc(EntityManager& em, DriverApi& driver) noexcept {
|
||||
mManager.gc(em, [this, &driver](Entity const e) {
|
||||
destroy(e, driver);
|
||||
void FRenderableManager::gc(EntityManager& em) noexcept {
|
||||
mManager.gc(em, [this](Entity const e) {
|
||||
destroy(e);
|
||||
});
|
||||
}
|
||||
|
||||
// This is basically a Renderable's destructor.
|
||||
void FRenderableManager::destroyComponent(Instance const ci, DriverApi& driver) noexcept {
|
||||
void FRenderableManager::destroyComponent(Instance const ci) noexcept {
|
||||
auto& manager = mManager;
|
||||
FEngine& engine = mEngine;
|
||||
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
// See create(RenderableManager::Builder&, Entity)
|
||||
destroyComponentPrimitives(mHwRenderPrimitiveFactory, driver, manager[ci].primitives);
|
||||
|
||||
@@ -91,9 +91,9 @@ public:
|
||||
~FRenderableManager();
|
||||
|
||||
// free-up all resources
|
||||
void terminate(backend::DriverApi& driver) noexcept;
|
||||
void terminate() noexcept;
|
||||
|
||||
void gc(utils::EntityManager& em, backend::DriverApi& driver) noexcept;
|
||||
void gc(utils::EntityManager& em) noexcept;
|
||||
|
||||
/*
|
||||
* Component Manager APIs
|
||||
@@ -125,11 +125,7 @@ public:
|
||||
|
||||
void create(const Builder& builder, utils::Entity entity);
|
||||
|
||||
void destroy(utils::Entity e, backend::DriverApi& driver) noexcept;
|
||||
|
||||
// The client API should have taken an Engine&, but it doesn't, so that's a workaround
|
||||
// so we can keep the API as it is.
|
||||
void clientDestroy(utils::Entity e) noexcept;
|
||||
void destroy(utils::Entity e) noexcept;
|
||||
|
||||
inline void setAxisAlignedBoundingBox(Instance instance, const Box& aabb);
|
||||
|
||||
@@ -243,7 +239,7 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
void destroyComponent(Instance ci, backend::DriverApi& driver) noexcept;
|
||||
void destroyComponent(Instance ci) noexcept;
|
||||
static void destroyComponentPrimitives(
|
||||
HwRenderPrimitiveFactory& factory, backend::DriverApi& driver,
|
||||
utils::Slice<FRenderPrimitive> primitives) noexcept;
|
||||
|
||||
@@ -46,6 +46,9 @@ public:
|
||||
|
||||
FCamera(FEngine& engine, utils::Entity e);
|
||||
|
||||
void terminate(FEngine&) noexcept { }
|
||||
|
||||
|
||||
// Sets the projection matrices (viewing and culling). The viewing matrice has infinite far.
|
||||
void setProjection(Projection projection,
|
||||
double left, double right, double bottom, double top,
|
||||
|
||||
@@ -20,11 +20,6 @@
|
||||
#include "TextureCache.h"
|
||||
#include "RenderPrimitive.h"
|
||||
|
||||
#include "components/CameraManager.h"
|
||||
#include "components/LightManager.h"
|
||||
#include "components/RenderableManager.h"
|
||||
#include "components/TransformManager.h"
|
||||
|
||||
#include "details/BufferObject.h"
|
||||
#include "details/Camera.h"
|
||||
#include "details/Fence.h"
|
||||
@@ -63,7 +58,6 @@
|
||||
#include <utils/CallStack.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Logger.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/PrivateImplementation-impl.h>
|
||||
@@ -544,10 +538,12 @@ void FEngine::init() {
|
||||
&debug.shadowmap.debug_directional_shadowmap, [this] {
|
||||
mMaterials.forEach([this](FMaterial* material) {
|
||||
if (material->getMaterialDomain() == MaterialDomain::SURFACE) {
|
||||
material->getPrograms().setConstants({
|
||||
{ +ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP,
|
||||
debug.shadowmap.debug_directional_shadowmap },
|
||||
});
|
||||
FMaterial::SpecializationConstantsBuilder constants =
|
||||
material->getSpecializationConstantsBuilder();
|
||||
constants.set(+ReservedSpecializationConstants::
|
||||
CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP,
|
||||
debug.shadowmap.debug_directional_shadowmap);
|
||||
material->setSpecializationConstants(std::move(constants));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -556,10 +552,12 @@ void FEngine::init() {
|
||||
&debug.lighting.debug_froxel_visualization, [this] {
|
||||
mMaterials.forEach([this](FMaterial* material) {
|
||||
if (material->getMaterialDomain() == MaterialDomain::SURFACE) {
|
||||
material->getPrograms().setConstants({
|
||||
{ +ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION,
|
||||
debug.lighting.debug_froxel_visualization },
|
||||
});
|
||||
FMaterial::SpecializationConstantsBuilder constants =
|
||||
material->getSpecializationConstantsBuilder();
|
||||
constants.set(+ReservedSpecializationConstants::
|
||||
CONFIG_DEBUG_FROXEL_VISUALIZATION,
|
||||
debug.lighting.debug_froxel_visualization);
|
||||
material->setSpecializationConstants(std::move(constants));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -592,9 +590,6 @@ void FEngine::shutdown() {
|
||||
DLOG(INFO) << "CircularBuffer: High watermark " << wm / 1024 << " KiB (" << wmpct << "%)";
|
||||
#endif
|
||||
|
||||
/* Destroy any leftover items in the cache. */
|
||||
mMaterialCache.terminate(*this);
|
||||
|
||||
DriverApi& driver = getDriverApi();
|
||||
|
||||
/*
|
||||
@@ -605,7 +600,7 @@ void FEngine::shutdown() {
|
||||
mResourceAllocatorDisposer->terminate();
|
||||
mResourceAllocatorDisposer.reset();
|
||||
mDFG.terminate(*this); // free-up the DFG
|
||||
mRenderableManager.terminate(driver); // free-up all renderables
|
||||
mRenderableManager.terminate(); // free-up all renderables
|
||||
mLightManager.terminate(); // free-up all lights
|
||||
mCameraManager.terminate(*this); // free-up all cameras
|
||||
|
||||
@@ -769,30 +764,13 @@ void FEngine::prepare(DriverApi& driver) {
|
||||
});
|
||||
}
|
||||
|
||||
void FEngine::gc() {
|
||||
JobSystem& js = getJobSystem();
|
||||
auto *rootJob = js.createJob();
|
||||
|
||||
js.run(
|
||||
jobs::createJob(js, rootJob, [this] {
|
||||
// These are safe to GC *sequentially* from a worker thread.
|
||||
// The JobSystem acts as a release/acquire operation.
|
||||
mLightManager.gc(mEntityManager);
|
||||
mTransformManager.gc(mEntityManager);
|
||||
mCameraManager.gc(*this, mEntityManager);
|
||||
}));
|
||||
|
||||
if (isAsynchronousModeEnabled()) {
|
||||
// gcDeferredAsyncObjectDestruction() is thread-safe in this context because
|
||||
// 1. JobSystem provides the release/acquire operation
|
||||
// 2. it's only used from the main thread, which we are blocking during the gc
|
||||
js.run(jobs::createJob(js, rootJob, &FEngine::gcDeferredAsyncObjectDestruction, this));
|
||||
}
|
||||
|
||||
// RenderableManager cannot be gc'ed from a different thread, because it needs the DriverAPI
|
||||
mRenderableManager.gc(mEntityManager, getDriverApi());
|
||||
|
||||
js.runAndWait(rootJob);
|
||||
void FEngine::gcManagers() {
|
||||
// Note: this runs in a Job
|
||||
auto& em = mEntityManager;
|
||||
mRenderableManager.gc(em);
|
||||
mLightManager.gc(em);
|
||||
mTransformManager.gc(em);
|
||||
mCameraManager.gc(*this, em);
|
||||
}
|
||||
|
||||
void FEngine::gcDeferredAsyncObjectDestruction() {
|
||||
@@ -820,7 +798,7 @@ void FEngine::submitFrame() {
|
||||
void FEngine::flush() {
|
||||
// flush the command buffer
|
||||
flushCommandBuffer(mCommandBufferQueue);
|
||||
|
||||
|
||||
// In single-threaded mode, we have to call execute() to drain the command
|
||||
// buffer to really free up space
|
||||
if constexpr (!UTILS_HAS_THREADING) {
|
||||
@@ -889,14 +867,8 @@ int FEngine::loop() {
|
||||
#endif
|
||||
if (portString != nullptr) {
|
||||
const int port = atoi(portString);
|
||||
|
||||
ShaderLanguage preferredLanguage = ShaderLanguage::UNSPECIFIED;
|
||||
if (mBackend == Backend::METAL) {
|
||||
preferredLanguage = ShaderLanguage::MSL;
|
||||
}
|
||||
|
||||
debug.server = new matdbg::DebugServer(mBackend,
|
||||
mDriver->getShaderLanguages(preferredLanguage).front(),
|
||||
mDriver->getShaderLanguages(ShaderLanguage::UNSPECIFIED).front(),
|
||||
matdbg::DbgShaderModel((uint8_t) mDriver->getShaderModel()), port);
|
||||
|
||||
// Sometimes the server can fail to spin up (e.g. if the above port is already in use).
|
||||
@@ -1427,7 +1399,7 @@ bool FEngine::destroy(const FMaterialInstance* p) {
|
||||
|
||||
UTILS_NOINLINE
|
||||
void FEngine::destroy(Entity const e) {
|
||||
mRenderableManager.destroy(e, getDriverApi());
|
||||
mRenderableManager.destroy(e);
|
||||
mLightManager.destroy(e);
|
||||
mTransformManager.destroy(e);
|
||||
mCameraManager.destroy(*this, e);
|
||||
|
||||
@@ -456,7 +456,8 @@ public:
|
||||
}
|
||||
|
||||
void prepare(backend::DriverApi& driver);
|
||||
void gc();
|
||||
void gcManagers();
|
||||
void gcDeferredAsyncObjectDestruction();
|
||||
void submitFrame();
|
||||
|
||||
using ShaderContent = utils::FixedCapacityVector<uint8_t>;
|
||||
@@ -582,8 +583,6 @@ private:
|
||||
int loop();
|
||||
void flushCommandBuffer(backend::CommandBufferQueue& commandBufferQueue) const;
|
||||
|
||||
void gcDeferredAsyncObjectDestruction();
|
||||
|
||||
template<typename T>
|
||||
bool isValid(const T* ptr, ResourceList<T> const& list) const;
|
||||
|
||||
|
||||
@@ -176,7 +176,28 @@ FMaterial::FMaterial(FEngine& engine, const Builder& builder, MaterialDefinition
|
||||
driver.isWorkaroundNeeded(Workaround::DISABLE_DEPTH_PRECACHE_FOR_DEFAULT_MATERIAL);
|
||||
mDefaultMaterial = engine.getDefaultMaterial();
|
||||
|
||||
mPrograms.initializeForMaterial(engine, *this, processSpecializationConstants(builder));
|
||||
FixedCapacityVector<Program::SpecializationConstant> specializationConstants =
|
||||
processSpecializationConstants(builder);
|
||||
mSpecializationConstants =
|
||||
engine.getMaterialCache().getSpecializationConstantsInternPool().acquire(
|
||||
std::move(specializationConstants));
|
||||
|
||||
size_t cachedProgramsSize;
|
||||
switch (mDefinition.materialDomain) {
|
||||
case filament::MaterialDomain::SURFACE:
|
||||
cachedProgramsSize = 1 << VARIANT_BITS;
|
||||
break;
|
||||
case filament::MaterialDomain::POST_PROCESS:
|
||||
cachedProgramsSize = 1 << POST_PROCESS_VARIANT_BITS;
|
||||
break;
|
||||
case filament::MaterialDomain::COMPUTE:
|
||||
cachedProgramsSize = 1;
|
||||
break;
|
||||
}
|
||||
mCachedPrograms = FixedCapacityVector<backend::Handle<backend::HwProgram>>(cachedProgramsSize);
|
||||
|
||||
mDefinition.acquirePrograms(engine, mCachedPrograms.as_slice(), getMaterialParser(),
|
||||
mSpecializationConstants, mIsDefaultMaterial);
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
// Register the material with matdbg.
|
||||
@@ -216,7 +237,11 @@ void FMaterial::terminate(FEngine& engine) {
|
||||
}
|
||||
#endif
|
||||
|
||||
mPrograms.terminate(engine);
|
||||
mDefinition.releasePrograms(engine, mCachedPrograms.as_slice(),
|
||||
getMaterialParser(), mSpecializationConstants, mIsDefaultMaterial);
|
||||
engine.getMaterialCache().releaseMaterial(engine, mDefinition);
|
||||
engine.getMaterialCache().getSpecializationConstantsInternPool().release(
|
||||
mSpecializationConstants);
|
||||
}
|
||||
|
||||
filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout(
|
||||
@@ -281,6 +306,22 @@ void FMaterial::compile(CompilerPriorityQueue const priority,
|
||||
}
|
||||
}
|
||||
|
||||
Handle<HwProgram> FMaterial::prepareProgramSlow(DriverApi& driver, Variant const variant,
|
||||
CompilerPriorityQueue const priorityQueue) const noexcept {
|
||||
if (isSharedVariant(variant)) {
|
||||
FMaterial const* defaultMaterial = mEngine.getDefaultMaterial();
|
||||
FILAMENT_CHECK_PRECONDITION(defaultMaterial);
|
||||
Handle<HwProgram> program = defaultMaterial->mCachedPrograms[variant.key];
|
||||
if (program) {
|
||||
return mCachedPrograms[variant.key] = program;
|
||||
}
|
||||
return mCachedPrograms[variant.key] =
|
||||
defaultMaterial->prepareProgram(driver, variant, priorityQueue);
|
||||
}
|
||||
return mCachedPrograms[variant.key] = mDefinition.prepareProgram(mEngine, driver,
|
||||
getMaterialParser(), getProgramSpecialization(variant), priorityQueue);
|
||||
}
|
||||
|
||||
FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept {
|
||||
if (mDefaultMaterialInstance) {
|
||||
// if we have a default instance, use it to create a new one
|
||||
@@ -315,6 +356,14 @@ BufferInterfaceBlock::FieldInfo const* FMaterial::reflect(
|
||||
return mDefinition.uniformInterfaceBlock.getFieldInfo(name);
|
||||
}
|
||||
|
||||
ProgramSpecialization FMaterial::getProgramSpecialization(Variant const variant) const noexcept {
|
||||
return ProgramSpecialization {
|
||||
.materialCrc32 = mDefinition.getMaterialParser().getCrc32(),
|
||||
.variant = variant,
|
||||
.specializationConstants = mSpecializationConstants,
|
||||
};
|
||||
}
|
||||
|
||||
size_t FMaterial::getParameters(ParameterInfo* parameters, size_t count) const noexcept {
|
||||
count = std::min(count, getParameterCount());
|
||||
|
||||
@@ -381,7 +430,8 @@ void FMaterial::updateActiveProgramsForMatdbg(Variant const variant) const noexc
|
||||
void FMaterial::applyPendingEdits() noexcept {
|
||||
const char* name = mDefinition.name.c_str();
|
||||
DLOG(INFO) << "Applying edits to " << (name ? name : "(untitled)");
|
||||
mPrograms.clear(mEngine);
|
||||
mDefinition.releasePrograms(mEngine, mCachedPrograms.as_slice(), getMaterialParser(),
|
||||
mSpecializationConstants, mIsDefaultMaterial);
|
||||
latchPendingEdits();
|
||||
}
|
||||
|
||||
@@ -430,6 +480,26 @@ void FMaterial::onQueryCallback(void* userdata, VariantList* pActiveVariants) {
|
||||
|
||||
#endif // FILAMENT_ENABLE_MATDBG
|
||||
|
||||
void FMaterial::setSpecializationConstants(SpecializationConstantsBuilder&& builder) noexcept {
|
||||
if (builder.mConstants.empty()) {
|
||||
// Nothing was changed.
|
||||
return;
|
||||
}
|
||||
|
||||
auto& internPool = mEngine.getMaterialCache().getSpecializationConstantsInternPool();
|
||||
MaterialParser const& materialParser = getMaterialParser();
|
||||
|
||||
// Release old resources...
|
||||
mDefinition.releasePrograms(mEngine, mCachedPrograms.as_slice(), materialParser,
|
||||
mSpecializationConstants, mIsDefaultMaterial);
|
||||
internPool.release(mSpecializationConstants);
|
||||
|
||||
// Then acquire new ones.
|
||||
mSpecializationConstants = internPool.acquire(std::move(builder.mConstants));
|
||||
mDefinition.acquirePrograms(mEngine, mCachedPrograms.as_slice(), materialParser,
|
||||
mSpecializationConstants, mIsDefaultMaterial);
|
||||
}
|
||||
|
||||
FixedCapacityVector<Program::SpecializationConstant> FMaterial::processSpecializationConstants(
|
||||
Builder const& builder) {
|
||||
FixedCapacityVector<Program::SpecializationConstant> specializationConstants =
|
||||
@@ -490,4 +560,12 @@ const char* FMaterial::getParameterTransformName(std::string_view samplerName) c
|
||||
return info->transformName.c_str();
|
||||
}
|
||||
|
||||
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<int32_t>(uint32_t id, int32_t value) noexcept;
|
||||
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<float>(uint32_t id, float value) noexcept;
|
||||
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<bool>(uint32_t id, bool value) noexcept;
|
||||
|
||||
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<int32_t>(std::string_view name, int32_t value) noexcept;
|
||||
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<float>(std::string_view name, float value) noexcept;
|
||||
template FMaterial::SpecializationConstantsBuilder& FMaterial::SpecializationConstantsBuilder::set<bool>(std::string_view name, bool value) noexcept;
|
||||
|
||||
} // namespace filament
|
||||
|
||||
+107
-32
@@ -18,7 +18,6 @@
|
||||
#define TNT_FILAMENT_DETAILS_MATERIAL_H
|
||||
|
||||
#include "downcast.h"
|
||||
#include "LocalProgramCache.h"
|
||||
|
||||
#include "details/MaterialInstance.h"
|
||||
|
||||
@@ -46,6 +45,7 @@
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -72,6 +72,62 @@ public:
|
||||
DefaultMaterialBuilder();
|
||||
};
|
||||
|
||||
// Used to change specialization constants at runtime internally by Filament.
|
||||
//
|
||||
// Call Material::getSpecializationConstantsBuilder() to return an instance of
|
||||
// SpecializationConstantsBuilder. The lifetime of this object must not exceed the lifetime of
|
||||
// the corresponding Material. After spec constants are changed with calls to set(), apply the
|
||||
// changes to the original Material via setSpecializationConstants().
|
||||
//
|
||||
// It would be nice to reuse Builder for this, but Builder allocates an entire map and asserts
|
||||
// if specified constants don't exist. In constrast, SpecializationConstantsBuilder is
|
||||
// copy-on-write and allows non-existent specialization constants to be named.
|
||||
class SpecializationConstantsBuilder {
|
||||
friend class FMaterial;
|
||||
|
||||
public:
|
||||
SpecializationConstantsBuilder(SpecializationConstantsBuilder const& rhs) = delete;
|
||||
SpecializationConstantsBuilder& operator=(
|
||||
SpecializationConstantsBuilder const& rhs) = delete;
|
||||
|
||||
SpecializationConstantsBuilder(SpecializationConstantsBuilder&& rhs) = default;
|
||||
SpecializationConstantsBuilder& operator=(SpecializationConstantsBuilder&& rhs) = default;
|
||||
|
||||
template<typename T, typename = Builder::is_supported_constant_parameter_t<T>>
|
||||
SpecializationConstantsBuilder& set(uint32_t id, T value) noexcept {
|
||||
// Don't allocate if we can help it.
|
||||
if (mConstants.empty()) {
|
||||
if (std::get<T>(mDefaultConstants[id]) == value) {
|
||||
return *this;
|
||||
}
|
||||
mConstants = utils::FixedCapacityVector<backend::Program::SpecializationConstant>(
|
||||
mDefaultConstants);
|
||||
}
|
||||
mConstants[id] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, typename = Builder::is_supported_constant_parameter_t<T>>
|
||||
SpecializationConstantsBuilder& set(std::string_view name, T value) noexcept {
|
||||
auto it = mDefinition->specializationConstantsNameToIndex.find(name);
|
||||
if (it != mDefinition->specializationConstantsNameToIndex.cend()) {
|
||||
set(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS, value);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
SpecializationConstantsBuilder(MaterialDefinition const& definition,
|
||||
utils::Slice<const backend::Program::SpecializationConstant> defaultConstants)
|
||||
: mDefinition(&definition),
|
||||
mDefaultConstants(defaultConstants) {}
|
||||
|
||||
MaterialDefinition const* mDefinition;
|
||||
utils::Slice<const backend::Program::SpecializationConstant> mDefaultConstants;
|
||||
// Copy-on-write vector.
|
||||
utils::FixedCapacityVector<backend::Program::SpecializationConstant> mConstants;
|
||||
};
|
||||
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
// return the uniform interface block for this material
|
||||
@@ -131,7 +187,11 @@ public:
|
||||
backend::Handle<backend::HwProgram> prepareProgram(backend::DriverApi& driver,
|
||||
Variant const variant,
|
||||
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
|
||||
return mPrograms.prepareProgram(driver, variant, priorityQueue);
|
||||
backend::Handle<backend::HwProgram> program = mCachedPrograms[variant.key];
|
||||
if (UTILS_LIKELY(program)) {
|
||||
return program;
|
||||
}
|
||||
return prepareProgramSlow(driver, variant, priorityQueue);
|
||||
}
|
||||
|
||||
// getProgram returns the backend program for the material's given variant.
|
||||
@@ -156,7 +216,9 @@ public:
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
updateActiveProgramsForMatdbg(variant);
|
||||
#endif
|
||||
return mPrograms.getProgram(variant);
|
||||
backend::Handle<backend::HwProgram> program = mCachedPrograms[variant.key];
|
||||
assert_invariant(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
// MaterialInstance::use() binds descriptor sets before drawing. For shared variants,
|
||||
@@ -213,8 +275,6 @@ public:
|
||||
float getSpecularAntiAliasingVariance() const noexcept { return mDefinition.specularAntiAliasingVariance; }
|
||||
float getSpecularAntiAliasingThreshold() const noexcept { return mDefinition.specularAntiAliasingThreshold; }
|
||||
|
||||
bool isDefaultMaterial() const noexcept { return mIsDefaultMaterial; }
|
||||
|
||||
backend::descriptor_binding_t getSamplerBinding(
|
||||
std::string_view const& name) const;
|
||||
|
||||
@@ -237,7 +297,12 @@ public:
|
||||
|
||||
uint32_t generateMaterialInstanceId() const noexcept { return mMaterialInstanceId++; }
|
||||
|
||||
LocalProgramCache& getPrograms() noexcept { return mPrograms; }
|
||||
// Update specialization constants.
|
||||
SpecializationConstantsBuilder getSpecializationConstantsBuilder() const noexcept {
|
||||
return SpecializationConstantsBuilder(mDefinition, mSpecializationConstants);
|
||||
}
|
||||
|
||||
void setSpecializationConstants(SpecializationConstantsBuilder&& builder) noexcept;
|
||||
|
||||
uint8_t getPerViewLayoutIndex() const noexcept {
|
||||
return mDefinition.perViewLayoutIndex;
|
||||
@@ -251,30 +316,6 @@ public:
|
||||
return mDefinition.source.c_str_safe();
|
||||
}
|
||||
|
||||
inline bool isSharedVariant(Variant const variant) const {
|
||||
// HACK: The default material "should" have MNT | DEP, but then we'd have to compile it as a
|
||||
// lit material, which would increase binary size. Perhaps we could specially compile it
|
||||
// with this variant, but with the shader program cache in active development, the days of
|
||||
// the default material are numbered anyway.
|
||||
constexpr Variant::type_t vsmAndDep = Variant::MNT | Variant::DEP;
|
||||
return mDefinition.materialDomain == MaterialDomain::SURFACE && !mIsDefaultMaterial &&
|
||||
!mDefinition.hasCustomDepthShader && Variant::isValidDepthVariant(variant) &&
|
||||
(variant.key & vsmAndDep) != vsmAndDep;
|
||||
}
|
||||
|
||||
MaterialParser const& getMaterialParser() const noexcept {
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
if (mEditedMaterialParser) {
|
||||
return *mEditedMaterialParser;
|
||||
}
|
||||
#endif
|
||||
return mDefinition.getMaterialParser();
|
||||
}
|
||||
|
||||
MaterialDefinition const& getDefinition() const noexcept { return mDefinition; }
|
||||
|
||||
LocalProgramCache const& getPrograms() const noexcept { return mPrograms; }
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
void applyPendingEdits() noexcept;
|
||||
|
||||
@@ -308,6 +349,21 @@ public:
|
||||
#endif
|
||||
|
||||
private:
|
||||
MaterialParser const& getMaterialParser() const noexcept {
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
if (mEditedMaterialParser) {
|
||||
return *mEditedMaterialParser;
|
||||
}
|
||||
#endif
|
||||
return mDefinition.getMaterialParser();
|
||||
}
|
||||
|
||||
ProgramSpecialization getProgramSpecialization(Variant const variant) const noexcept;
|
||||
|
||||
backend::Handle<backend::HwProgram> prepareProgramSlow(backend::DriverApi& driver,
|
||||
Variant const variant,
|
||||
backend::CompilerPriorityQueue const priorityQueue) const noexcept;
|
||||
|
||||
utils::FixedCapacityVector<backend::Program::SpecializationConstant>
|
||||
processSpecializationConstants(Builder const& builder);
|
||||
|
||||
@@ -315,6 +371,24 @@ private:
|
||||
|
||||
void createAndCacheProgram(backend::DriverApi& driver, backend::Program&& p, Variant variant) const noexcept;
|
||||
|
||||
backend::DescriptorSetLayout const& getPerViewDescriptorSetLayoutDescription(
|
||||
Variant variant, bool useVsmDescriptorSetLayout) const noexcept;
|
||||
|
||||
backend::DescriptorSetLayout const& getDescriptorSetLayoutDescription(
|
||||
Variant variant = {}) const noexcept;
|
||||
|
||||
bool isSharedVariant(Variant const variant) const {
|
||||
// HACK: The default material "should" have MNT | DEP, but then we'd have to compile it as a
|
||||
// lit material, which would increase binary size. Perhaps we could specially compile it
|
||||
// with this variant, but with the shader program cache in active development, the days of
|
||||
// the default material are numbered anyway.
|
||||
constexpr Variant::type_t vsmAndDep = Variant::MNT | Variant::DEP;
|
||||
return mDefinition.materialDomain == MaterialDomain::SURFACE && !mIsDefaultMaterial &&
|
||||
!mDefinition.hasCustomDepthShader && Variant::isValidDepthVariant(variant) &&
|
||||
(variant.key & vsmAndDep) != vsmAndDep;
|
||||
}
|
||||
|
||||
mutable utils::FixedCapacityVector<backend::Handle<backend::HwProgram>> mCachedPrograms;
|
||||
MaterialDefinition const& mDefinition;
|
||||
|
||||
bool mIsDefaultMaterial = false;
|
||||
@@ -329,6 +403,9 @@ private:
|
||||
// reserve some space to construct the default material instance
|
||||
mutable FMaterialInstance* mDefaultMaterialInstance = nullptr;
|
||||
|
||||
// current specialization constants for the HwProgram
|
||||
utils::Slice<const backend::Program::SpecializationConstant> mSpecializationConstants;
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
matdbg::MaterialKey mDebuggerId;
|
||||
mutable utils::Mutex mActiveProgramsLock;
|
||||
@@ -346,8 +423,6 @@ private:
|
||||
FEngine& mEngine;
|
||||
const uint32_t mMaterialId;
|
||||
mutable uint32_t mMaterialInstanceId = 0;
|
||||
|
||||
LocalProgramCache mPrograms;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -310,7 +310,13 @@ void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
|
||||
engine.flush(); // flush command stream
|
||||
|
||||
// Run GC
|
||||
engine.gc();
|
||||
JobSystem& js = engine.getJobSystem();
|
||||
auto *rootJob = js.createJob();
|
||||
js.run(jobs::createJob(js, rootJob, &FEngine::gcManagers, &engine)); // gc all component managers
|
||||
if (engine.isAsynchronousModeEnabled()) {
|
||||
js.run(jobs::createJob(js, rootJob, &FEngine::gcDeferredAsyncObjectDestruction, &engine));
|
||||
}
|
||||
js.runAndWait(rootJob);
|
||||
|
||||
mFrameSkipper.frameSkipped();
|
||||
}
|
||||
@@ -512,7 +518,13 @@ void FRenderer::endFrame() {
|
||||
engine.flush(); // flush command stream
|
||||
|
||||
// Run GC
|
||||
engine.gc();
|
||||
JobSystem& js = engine.getJobSystem();
|
||||
auto *rootJob = js.createJob();
|
||||
js.run(jobs::createJob(js, rootJob, &FEngine::gcManagers, &engine)); // gc all component managers
|
||||
if (engine.isAsynchronousModeEnabled()) {
|
||||
js.run(jobs::createJob(js, rootJob, &FEngine::gcDeferredAsyncObjectDestruction, &engine));
|
||||
}
|
||||
js.runAndWait(rootJob);
|
||||
}
|
||||
|
||||
void FRenderer::readPixels(
|
||||
@@ -1362,7 +1374,7 @@ void FRenderer::renderJob(DriverApi& driver, RootArenaScope& rootArenaScope, FVi
|
||||
// Debug: CSM visualisation
|
||||
if (UTILS_UNLIKELY(engine.debug.shadowmap.visualize_cascades &&
|
||||
view.hasShadowing() && view.hasDirectionalLighting())) {
|
||||
input = ppm.debugShadowCascades(fg, view.getShadowMapManager(), input, depth);
|
||||
input = ppm.debugShadowCascades(fg, input, depth);
|
||||
}
|
||||
|
||||
// TODO: DoF should be applied here, before TAA -- but if we do this it'll result in a lot of
|
||||
|
||||
@@ -506,8 +506,6 @@ public:
|
||||
|
||||
MaterialGlobals getMaterialGlobals() const { return mMaterialGlobals; }
|
||||
|
||||
ShadowMapManager const& getShadowMapManager() const noexcept { return *mShadowMapManager; }
|
||||
|
||||
private:
|
||||
struct FPickingQuery : public PickingQuery {
|
||||
private:
|
||||
|
||||
@@ -18,24 +18,9 @@ material {
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : int,
|
||||
type : uint,
|
||||
name : cascadeCount,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
name : shadowAtlasResolution,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : mat4[4],
|
||||
name : lightFromWorldMatrix,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float4[4],
|
||||
name : scissorNormalized,
|
||||
precision: high
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
@@ -74,10 +59,9 @@ fragment {
|
||||
return vec3(0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
int getShadowCascade(highp vec4 position) {
|
||||
highp float z = position.z / position.w;
|
||||
ivec4 greaterZ = ivec4(greaterThan(materialParams.cascadeSplits, vec4(z)));
|
||||
int cascadeCount = materialParams.cascadeCount;
|
||||
int getShadowCascade(highp float z) {
|
||||
ivec4 greaterZ = ivec4(greaterThan(frameUniforms.cascadeSplits, vec4(z)));
|
||||
int cascadeCount = frameUniforms.cascades & 0xF;
|
||||
return clamp(greaterZ.x + greaterZ.y + greaterZ.z + greaterZ.w, 0, cascadeCount - 1);
|
||||
}
|
||||
|
||||
@@ -86,13 +70,11 @@ fragment {
|
||||
|
||||
// depth from the depth buffer
|
||||
highp float depth = textureLod(materialParams_depth, variable_vertex.xy, 0.0).r;
|
||||
|
||||
highp vec2 clip = variable_vertex.xy * 2.0 - vec2(1.0);
|
||||
|
||||
// convert to view-space (linear z).
|
||||
highp vec4 p = mulMat4x4Float3(getViewFromClipMatrix(), vec3(clip, depth));
|
||||
highp vec4 p = mulMat4x4Float3(getViewFromClipMatrix(), vec3(0, 0, depth));
|
||||
highp float z = p.z / p.w;
|
||||
|
||||
color.rgb *= uintToColorDebug(getShadowCascade(p));
|
||||
color.rgb *= uintToColorDebug(getShadowCascade(z));
|
||||
postProcess.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.70.1"
|
||||
spec.version = "1.69.5"
|
||||
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.70.1/filament-v1.70.1-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.69.5/filament-v1.69.5-ios.tgz" }
|
||||
|
||||
spec.libraries = 'c++'
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
namespace filament {
|
||||
|
||||
// update this when a new version of filament wouldn't work with older materials
|
||||
static constexpr size_t MATERIAL_VERSION = 70;
|
||||
static constexpr size_t MATERIAL_VERSION = 69;
|
||||
|
||||
// Those are the api levels that are used in the source material file (.mat)
|
||||
//
|
||||
|
||||
@@ -39,10 +39,6 @@ using namespace utils;
|
||||
|
||||
namespace image {
|
||||
|
||||
// Define maximum sane image dimensions to prevent integer overflows and memory exhaustion (DoS).
|
||||
constexpr size_t MAX_IMAGE_DIMENSION = 65536;
|
||||
constexpr size_t MAX_IMAGE_PIXELS = 16384 * 16384;
|
||||
|
||||
const char HDRDecoder::sigRadiance[] = { '#', '?', 'R', 'A', 'D', 'I', 'A', 'N', 'C', 'E', 0xa };
|
||||
const char HDRDecoder::sigRGBE[] = { '#', '?', 'R', 'G', 'B', 'E', 0xa };
|
||||
|
||||
@@ -63,16 +59,16 @@ HDRDecoder::HDRDecoder(std::istream& stream)
|
||||
HDRDecoder::~HDRDecoder() = default;
|
||||
|
||||
LinearImage HDRDecoder::decode() {
|
||||
float gamma = 1.0f;
|
||||
float exposure = 1.0f;
|
||||
char sy = 0, sx = 0;
|
||||
float gamma;
|
||||
float exposure;
|
||||
char sy, sx;
|
||||
unsigned int height = 0, width = 0;
|
||||
{
|
||||
char buf[1024] = {}; // Initialize buffer to prevent reading stack garbage
|
||||
// Check mStream status in the loop condition to prevent an infinite loop on EOF or read error
|
||||
while (mStream.getline(buf, sizeof(buf), 0xa)) {
|
||||
char buf[1024];
|
||||
do {
|
||||
char format[128];
|
||||
mStream.getline(buf, sizeof(buf), 0xa);
|
||||
if (buf[0] == '#') continue;
|
||||
char format[128] = {0};
|
||||
sscanf(buf, "FORMAT=%127s", format); // NOLINT
|
||||
sscanf(buf, "GAMMA=%f", &gamma); // NOLINT
|
||||
sscanf(buf, "EXPOSURE=%f", &exposure); // NOLINT
|
||||
@@ -80,29 +76,14 @@ LinearImage HDRDecoder::decode() {
|
||||
(sscanf(buf, "%cX %u %cY %u", &sx, &width, &sy, &height) == 4)) { // NOLINT
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that dimensions were successfully found before proceeding
|
||||
if (width == 0 || height == 0) {
|
||||
slog.e << "Invalid or missing image dimensions" << io::endl;
|
||||
return {};
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
// Enforce sane limits on dimensions to prevent integer overflow and massive allocations
|
||||
if (width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION ||
|
||||
(size_t(width) * size_t(height) > MAX_IMAGE_PIXELS)) {
|
||||
slog.e << "Image dimensions exceed safety limits" << io::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
LinearImage image(width, height, 3);
|
||||
|
||||
if (sx == '-') image = (image);
|
||||
if (sy == '+') image = verticalFlip(image);
|
||||
|
||||
// Allocate memory to hold one row of decoded pixel data.
|
||||
// width is now validated, so width * 4 will not integer overflow.
|
||||
std::unique_ptr<uint8_t[]> rgbe(new uint8_t[width * 4]);
|
||||
|
||||
// First, test for non-RLE images.
|
||||
@@ -146,37 +127,21 @@ LinearImage HDRDecoder::decode() {
|
||||
size_t num_bytes = 0;
|
||||
while (num_bytes < width) {
|
||||
uint8_t rle_count;
|
||||
if (!mStream.read((char*) &rle_count, 1)) {
|
||||
slog.e << "Unexpected EOF during RLE read" << io::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
mStream.read((char*) &rle_count, 1);
|
||||
if (rle_count > 128) {
|
||||
size_t run_length = rle_count - 128;
|
||||
// Prevent Heap Buffer Overflow by checking bounds
|
||||
if (num_bytes + run_length > width) {
|
||||
slog.e << "RLE buffer overflow detected" << io::endl;
|
||||
return {};
|
||||
}
|
||||
char v;
|
||||
mStream.read(&v, 1);
|
||||
memset(d, v, run_length);
|
||||
d += run_length;
|
||||
num_bytes += run_length;
|
||||
memset(d, v, size_t(rle_count - 128));
|
||||
d += rle_count - 128;
|
||||
num_bytes += rle_count - 128;
|
||||
} else {
|
||||
if (rle_count == 0) {
|
||||
slog.e << "run length is zero" << io::endl;
|
||||
return {};
|
||||
}
|
||||
size_t run_length = rle_count;
|
||||
// Prevent Heap Buffer Overflow by checking bounds
|
||||
if (num_bytes + run_length > width) {
|
||||
slog.e << "RLE buffer overflow detected" << io::endl;
|
||||
return {};
|
||||
}
|
||||
mStream.read(d, run_length);
|
||||
d += run_length;
|
||||
num_bytes += run_length;
|
||||
mStream.read(d, rle_count);
|
||||
d += rle_count;
|
||||
num_bytes += rle_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ set(DIST_HDRS
|
||||
${PUBLIC_HDR_DIR}/${TARGET}/Invocable.h
|
||||
${PUBLIC_HDR_DIR}/${TARGET}/Log.h
|
||||
${PUBLIC_HDR_DIR}/${TARGET}/Logger.h
|
||||
${PUBLIC_HDR_DIR}/${TARGET}/LruCache.h
|
||||
${PUBLIC_HDR_DIR}/${TARGET}/memalign.h
|
||||
${PUBLIC_HDR_DIR}/${TARGET}/MonotonicRingMap.h
|
||||
${PUBLIC_HDR_DIR}/${TARGET}/Mutex.h
|
||||
@@ -182,7 +181,6 @@ if (FILAMENT_BUILD_TESTING)
|
||||
test/test_FixedCircularBuffer.cpp
|
||||
test/test_Hash.cpp
|
||||
test/test_InternPool.cpp
|
||||
test/test_LruCache.cpp
|
||||
test/test_JobSystem.cpp
|
||||
test/test_MonotonicRingMap.cpp
|
||||
test/test_QuadTreeArray.cpp
|
||||
|
||||
@@ -1,322 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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_UTILS_LRUCACHE_H
|
||||
#define TNT_UTILS_LRUCACHE_H
|
||||
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace utils {
|
||||
|
||||
/**
|
||||
* A fixed-capacity Least Recently Used (LRU) cache.
|
||||
*
|
||||
* This container allows O(1) access, insertion, and eviction. It uses an Arena with an
|
||||
* ObjectPoolAllocator to store the nodes of a doubly-linked list representing the LRU order,
|
||||
* and a robin_map for fast key lookups.
|
||||
*
|
||||
* This class is not thread-safe. Additionally, the pop() and put() methods have special concerns
|
||||
* with regards to object lifetimes.
|
||||
*
|
||||
* @tparam Key The type of the keys.
|
||||
* @tparam T The type of the values.
|
||||
* @tparam Hash The hasher for the keys.
|
||||
*/
|
||||
template<typename Key,
|
||||
typename T,
|
||||
typename Hash = std::hash<Key>,
|
||||
typename KeyEqual = std::equal_to<Key>>
|
||||
class LruCache {
|
||||
// If Key is bigger than a pointer, robin map keys are references to Node::key. Otherwise,
|
||||
// they're full copies.
|
||||
using MapKey = std::conditional_t<(sizeof(Key) > sizeof(void*)),
|
||||
std::reference_wrapper<const Key>, Key>;
|
||||
|
||||
public:
|
||||
using size_type = uint32_t;
|
||||
|
||||
/**
|
||||
* Creates an LRU cache with the specified capacity.
|
||||
*
|
||||
* @param name Name to identify the backing Arena allocator.
|
||||
* @param capacity The maximum number of elements the cache can hold.
|
||||
*/
|
||||
LruCache(const char* UTILS_NONNULL name, size_type capacity)
|
||||
: mCapacity(capacity),
|
||||
// HACK: FreeList cannot handle a capacity of 0.
|
||||
mArena(name, (capacity ? capacity : 1) * sizeof(Node)),
|
||||
mHead(nullptr),
|
||||
mTail(nullptr) {
|
||||
mMap.reserve(capacity);
|
||||
}
|
||||
|
||||
~LruCache() {
|
||||
clear();
|
||||
}
|
||||
|
||||
LruCache(const LruCache&) = delete;
|
||||
LruCache& operator=(const LruCache&) = delete;
|
||||
LruCache(LruCache&&) = default;
|
||||
LruCache& operator=(LruCache&&) = default;
|
||||
|
||||
/**
|
||||
* Returns a reference to the value associated with the key.
|
||||
*
|
||||
* Additionally moves the key/value pair to the front of the most recently used (MRU) list.
|
||||
*
|
||||
* @param key The key to look up.
|
||||
* @param hash The precomputed hash of the key.
|
||||
* @return Pointer to the value, if exists; otherwise, nullptr.
|
||||
*/
|
||||
T* UTILS_NULLABLE get(Key const& key, size_t hash) {
|
||||
auto it = mMap.find(key, hash);
|
||||
if (UTILS_UNLIKELY(it == mMap.end())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Node* node = it->second;
|
||||
assert(node != nullptr);
|
||||
|
||||
moveToFront(node);
|
||||
|
||||
return &node->value;
|
||||
}
|
||||
|
||||
inline T* UTILS_NULLABLE get(Key const& key) {
|
||||
return get(key, Hash{}(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the value out of the cache, if it exists.
|
||||
*
|
||||
* Because this moves the actual value, any previous references to this object returned by a
|
||||
* call to get() or put() is invalidated by this call.
|
||||
*
|
||||
* @param key The key to look up.
|
||||
* @param hash The precomputed hash of the key.
|
||||
*/
|
||||
std::optional<T> pop(Key const& key, size_t hash) {
|
||||
auto it = mMap.find(key, hash);
|
||||
if (UTILS_UNLIKELY(it == mMap.end())) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Node* node = it->second;
|
||||
assert(node != nullptr);
|
||||
|
||||
if (node == mHead && node == mTail) {
|
||||
mHead = nullptr;
|
||||
mTail = nullptr;
|
||||
} else if (node == mHead) {
|
||||
assert(node->next != nullptr);
|
||||
mHead = node->next;
|
||||
mHead->prev = nullptr;
|
||||
} else if (node == mTail) {
|
||||
assert(node->prev != nullptr);
|
||||
mTail = node->prev;
|
||||
mTail->next = nullptr;
|
||||
} else {
|
||||
assert(node->prev != nullptr);
|
||||
assert(node->next != nullptr);
|
||||
node->prev->next = node->next;
|
||||
node->next->prev = node->prev;
|
||||
}
|
||||
|
||||
T r = std::move(node->value);
|
||||
mMap.erase(it);
|
||||
mArena.destroy(node);
|
||||
return r;
|
||||
}
|
||||
|
||||
inline std::optional<T> pop(Key const& key) {
|
||||
return pop(key, Hash{}(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new entry or updates an existing one.
|
||||
*
|
||||
* Prepends it to the front of the most recently used (MRU) list. Potentially evicts the
|
||||
* least-recently used (LRU) key/value pair by calling the releaser function.
|
||||
*
|
||||
* Due to the evicting nature of this function, any pointers to any objects within the LRU cache
|
||||
* that had been previously returned by a call to get() or another call to put() can be
|
||||
* considered INVALID and any access to them will result in undefined behavior.
|
||||
*
|
||||
* An evicted node is removed from the map before the releaser callback is called. Calls to
|
||||
* get() or pop() in the body of the releaser for the item in question will fail.
|
||||
*
|
||||
* @tparam F Callable type accepting T&&.
|
||||
* @param key The key to insert or update.
|
||||
* @param t The value to insert or update.
|
||||
* @param hash The precomputed hash of the key.
|
||||
* @param releaser Function called with the evicted value (T&&) if eviction occurs.
|
||||
* @return Reference to the inserted or updated value.
|
||||
*/
|
||||
template<typename F>
|
||||
T& put(Key key, T value, size_t hash, F releaser) {
|
||||
// Assert that we have capacity to store at least one item
|
||||
assert(mCapacity > 0);
|
||||
|
||||
auto it = mMap.find(key, hash);
|
||||
if (it != mMap.end()) {
|
||||
Node* node = it->second;
|
||||
assert(node != nullptr);
|
||||
|
||||
moveToFront(node);
|
||||
|
||||
node->value = std::move(value);
|
||||
return node->value;
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(mMap.size() >= mCapacity)) {
|
||||
evict(releaser);
|
||||
}
|
||||
|
||||
// Create new node at front of list.
|
||||
Node* node = mArena.template make<Node>(std::move(key), std::move(value), mHead);
|
||||
assert(node != nullptr);
|
||||
attach(node);
|
||||
|
||||
mMap.emplace(node->key, node);
|
||||
|
||||
return node->value;
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
inline T& put(Key key, T value, F releaser) {
|
||||
return put(std::move(key), std::move(value), Hash{}(key), std::move(releaser));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache, calling releaser on each item evicted.
|
||||
*
|
||||
* @param releaser Function called with the evicted value (T&&)
|
||||
*/
|
||||
template<typename F>
|
||||
void clear(F releaser) {
|
||||
// Clear map first, just in case it tries to dereference any keys.
|
||||
mMap.clear();
|
||||
// Destroy everything in the arena.
|
||||
Node* node = mHead;
|
||||
while (UTILS_LIKELY(node)) {
|
||||
Node* next = node->next;
|
||||
releaser(std::move(node->value));
|
||||
mArena.destroy(node);
|
||||
node = next;
|
||||
}
|
||||
mHead = nullptr;
|
||||
mTail = nullptr;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
clear([](T&&){});
|
||||
}
|
||||
|
||||
/** Returns the number of elements in the cache. */
|
||||
size_type size() const noexcept { return mMap.size(); }
|
||||
|
||||
/** Returns the capacity of the cache. */
|
||||
size_type capacity() const noexcept { return mCapacity; }
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
Key key;
|
||||
T value;
|
||||
Node* UTILS_NULLABLE prev;
|
||||
Node* UTILS_NULLABLE next;
|
||||
|
||||
Node(Key&& k, T&& v, Node* UTILS_NULLABLE next)
|
||||
: key(std::move(k)),
|
||||
value(std::move(v)),
|
||||
prev(nullptr),
|
||||
next(next) {}
|
||||
};
|
||||
|
||||
void moveToFront(Node* UTILS_NONNULL node) {
|
||||
if (node == mHead) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First, detach from list.
|
||||
if (UTILS_UNLIKELY(node == mTail)) {
|
||||
assert(node->prev != nullptr);
|
||||
mTail = node->prev;
|
||||
mTail->next = nullptr;
|
||||
} else {
|
||||
assert(node->prev != nullptr);
|
||||
assert(node->next != nullptr);
|
||||
node->prev->next = node->next;
|
||||
node->next->prev = node->prev;
|
||||
}
|
||||
|
||||
// Then attach to head.
|
||||
node->prev = nullptr;
|
||||
node->next = mHead;
|
||||
attach(node);
|
||||
}
|
||||
|
||||
// Attach node to front.
|
||||
void attach(Node* UTILS_NONNULL node) {
|
||||
if (UTILS_LIKELY(mHead != nullptr)) {
|
||||
mHead->prev = node;
|
||||
} else {
|
||||
assert(mTail == nullptr);
|
||||
mTail = node;
|
||||
}
|
||||
mHead = node;
|
||||
}
|
||||
|
||||
// Evict the least recently used node.
|
||||
template<typename F>
|
||||
void evict(F releaser) {
|
||||
Node* node = mTail;
|
||||
assert(node != nullptr);
|
||||
|
||||
if (UTILS_UNLIKELY(node == mHead)) {
|
||||
mTail = nullptr;
|
||||
mHead = nullptr;
|
||||
} else {
|
||||
// Move new tail node to mTail.
|
||||
assert(node->prev != nullptr);
|
||||
mTail = node->prev;
|
||||
mTail->next = nullptr;
|
||||
}
|
||||
|
||||
// Finally free the node.
|
||||
mMap.erase(node->key);
|
||||
releaser(std::move(node->value));
|
||||
mArena.destroy(node);
|
||||
}
|
||||
|
||||
size_type mCapacity;
|
||||
utils::Arena<utils::ObjectPoolAllocator<Node>, utils::LockingPolicy::NoLock> mArena;
|
||||
tsl::robin_map<MapKey, Node* UTILS_NONNULL, Hash, KeyEqual> mMap;
|
||||
Node* UTILS_NULLABLE mHead;
|
||||
Node* UTILS_NULLABLE mTail;
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
|
||||
#endif // TNT_UTILS_LRUCACHE_H
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/LruCache.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
@@ -76,6 +75,8 @@ class RefCountedMap {
|
||||
T value;
|
||||
};
|
||||
|
||||
using Map = tsl::robin_map<Key, Entry, Hash>;
|
||||
|
||||
static constexpr TValue& deref(T& a) {
|
||||
if constexpr (refcountedmap::IsPointer<T>) {
|
||||
return *a;
|
||||
@@ -98,9 +99,6 @@ class RefCountedMap {
|
||||
"Attempted to get missing value";
|
||||
|
||||
public:
|
||||
explicit RefCountedMap(const char* UTILS_NONNULL name = "RefCountedMap", size_t lruCapacity = 0)
|
||||
: mLruCache(name, lruCapacity) {}
|
||||
|
||||
/** Acquire and return a value by key, initializing it with F if it doesn't exist.
|
||||
*
|
||||
* If F returns NullValue{}(), this indicates a failure to create the object. If T is a value
|
||||
@@ -113,16 +111,12 @@ public:
|
||||
it.value().referenceCount++;
|
||||
return &deref(it.value().value);
|
||||
}
|
||||
|
||||
if (std::optional<T> lruValue = mLruCache.pop(key, hash)) {
|
||||
return &insert(key, std::move(*lruValue));
|
||||
}
|
||||
|
||||
T r = factory();
|
||||
if (r == NullValue{}()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &insert(key, std::move(r));
|
||||
// TODO: how to use above computed hash here?
|
||||
return &deref(mMap.insert({ key, Entry{ 1, std::move(r) } }).first.value().value);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
@@ -144,11 +138,6 @@ public:
|
||||
it.value().referenceCount++;
|
||||
return &deref(it.value().value);
|
||||
}
|
||||
|
||||
if (std::optional<T> lruValue = mLruCache.pop(key, hash)) {
|
||||
return &insert(key, std::move(*lruValue));
|
||||
}
|
||||
|
||||
// TODO: how to use above computed hash here?
|
||||
mMap.insert({ key, Entry{ 1, NullValue{}() } });
|
||||
return nullptr;
|
||||
@@ -167,14 +156,8 @@ public:
|
||||
auto it = mMap.find(key, hash);
|
||||
FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING;
|
||||
if (--it.value().referenceCount == 0) {
|
||||
if (it.value().value != NullValue{}()) {
|
||||
if (mLruCache.capacity() > 0) {
|
||||
mLruCache.put(key, std::move(it.value().value), hash, [&releaser](T&& v) {
|
||||
releaser(deref(v));
|
||||
});
|
||||
} else {
|
||||
releaser(deref(it.value().value));
|
||||
}
|
||||
if (it.value().value != NullValue{}()){
|
||||
releaser(deref(it.value().value));
|
||||
}
|
||||
// TODO: change to erase_fast
|
||||
mMap.erase(it);
|
||||
@@ -194,9 +177,6 @@ public:
|
||||
auto it = mMap.find(key, hash);
|
||||
FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING;
|
||||
if (--it.value().referenceCount == 0) {
|
||||
if (mLruCache.capacity() > 0) {
|
||||
mLruCache.put(key, std::move(it.value().value), hash, [](T&&){});
|
||||
}
|
||||
// TODO: change to erase_fast
|
||||
mMap.erase(it);
|
||||
}
|
||||
@@ -237,7 +217,7 @@ public:
|
||||
* Panics if no entry found in map.
|
||||
*/
|
||||
TValue& get(KeyRef key, size_t hash) {
|
||||
auto it = mMap.find(key, hash);
|
||||
auto it = mMap.find(key);
|
||||
FILAMENT_CHECK_PRECONDITION(it != mMap.end()) << MISSING_ENTRY_ERROR_STRING;
|
||||
FILAMENT_CHECK_PRECONDITION(it.value().value != NullValue{}())
|
||||
<< MISSING_VALUE_ERROR_STRING;
|
||||
@@ -256,33 +236,13 @@ public:
|
||||
|
||||
inline TValue const& get(KeyRef key) const noexcept { return get(key, Hash{}(key)); }
|
||||
|
||||
/** Clear the LRU cache, calling releaser on each item evicted. */
|
||||
template<typename F>
|
||||
void clearLruCache(F releaser) {
|
||||
mLruCache.clear([&releaser](T&& v) {
|
||||
releaser(deref(v));
|
||||
});
|
||||
}
|
||||
|
||||
/** Clear the LRU cache without calling any releasers. */
|
||||
void clearLruCache() {
|
||||
mLruCache.clear();
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
inline bool empty() const noexcept { return mMap.empty(); }
|
||||
|
||||
private:
|
||||
tsl::robin_map<Key, Entry, Hash> mMap;
|
||||
utils::LruCache<Key, T, Hash> mLruCache;
|
||||
|
||||
TValue& insert(KeyRef key, T value) {
|
||||
// TODO: how to use computed hash here?
|
||||
auto it = mMap.insert({ key, Entry{ 1, std::move(value) } });
|
||||
return deref(it.first.value().value);
|
||||
}
|
||||
Map mMap;
|
||||
};
|
||||
|
||||
} // utils
|
||||
}
|
||||
|
||||
#endif // TNT_UTILS_REFCOUNTEDMAP_H
|
||||
|
||||
@@ -87,7 +87,7 @@ FreeList::Node* FreeList::init(void* begin, void* end,
|
||||
void* const p = pointermath::align(begin, alignment, extra);
|
||||
void* const n = pointermath::align(pointermath::add(p, elementSize), alignment, extra);
|
||||
assert_invariant(p >= begin && p < end);
|
||||
assert_invariant(n >= begin && n <= end && n > p);
|
||||
assert_invariant(n >= begin && n < end && n > p);
|
||||
|
||||
const size_t d = uintptr_t(n) - uintptr_t(p);
|
||||
const size_t num = (uintptr_t(end) - uintptr_t(p)) / d;
|
||||
@@ -128,7 +128,7 @@ AtomicFreeList::AtomicFreeList(void* begin, void* end,
|
||||
void* const p = pointermath::align(begin, alignment, extra);
|
||||
void* const n = pointermath::align(pointermath::add(p, elementSize), alignment, extra);
|
||||
assert_invariant(p >= begin && p < end);
|
||||
assert_invariant(n >= begin && n <= end && n > p);
|
||||
assert_invariant(n >= begin && n < end && n > p);
|
||||
|
||||
const size_t d = uintptr_t(n) - uintptr_t(p);
|
||||
const size_t num = (uintptr_t(end) - uintptr_t(p)) / d;
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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 <utils/LruCache.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace utils;
|
||||
|
||||
TEST(LruCacheTest, BasicPutGet) {
|
||||
LruCache<int, std::string> cache("LruCacheTest", 3);
|
||||
|
||||
cache.put(1, "one", [](std::string&&) {});
|
||||
cache.put(2, "two", [](std::string&&) {});
|
||||
|
||||
auto* v1 = cache.get(1);
|
||||
ASSERT_NE(v1, nullptr);
|
||||
EXPECT_EQ(*v1, "one");
|
||||
|
||||
auto* v2 = cache.get(2);
|
||||
ASSERT_NE(v2, nullptr);
|
||||
EXPECT_EQ(*v2, "two");
|
||||
|
||||
EXPECT_EQ(cache.size(), 2);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, MissingKey) {
|
||||
LruCache<int, int> cache("LruCacheTest", 3);
|
||||
EXPECT_EQ(cache.get(999), nullptr);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, Eviction) {
|
||||
LruCache<int, int> cache("LruCacheTest", 3);
|
||||
std::vector<int> evicted;
|
||||
auto releaser = [&](int&& v) { evicted.push_back(v); };
|
||||
|
||||
cache.put(1, 10, releaser);
|
||||
cache.put(2, 20, releaser);
|
||||
cache.put(3, 30, releaser);
|
||||
|
||||
// Cache: 3(MRU), 2, 1(LRU)
|
||||
EXPECT_EQ(cache.size(), 3);
|
||||
EXPECT_TRUE(evicted.empty());
|
||||
|
||||
cache.put(4, 40, releaser);
|
||||
// Evicted 1. Cache: 4(MRU), 3, 2
|
||||
EXPECT_EQ(cache.size(), 3);
|
||||
ASSERT_EQ(evicted.size(), 1);
|
||||
EXPECT_EQ(evicted[0], 10);
|
||||
|
||||
// Verify contents
|
||||
EXPECT_NE(cache.get(2), nullptr);
|
||||
EXPECT_NE(cache.get(3), nullptr);
|
||||
EXPECT_NE(cache.get(4), nullptr);
|
||||
EXPECT_EQ(cache.get(1), nullptr);
|
||||
|
||||
// Accessing 2 makes it MRU. Cache: 2(MRU), 4, 3(LRU)
|
||||
cache.get(2);
|
||||
|
||||
cache.put(5, 50, releaser);
|
||||
// Should evict 3.
|
||||
ASSERT_EQ(evicted.size(), 2);
|
||||
EXPECT_EQ(evicted[1], 30);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, UpdatePromotesToMRU) {
|
||||
LruCache<int, int> cache("LruCacheTest", 3);
|
||||
auto releaser = [](int&&) {};
|
||||
|
||||
cache.put(1, 10, releaser);
|
||||
cache.put(2, 20, releaser);
|
||||
cache.put(3, 30, releaser);
|
||||
// Order: 3, 2, 1
|
||||
|
||||
cache.put(1, 11, releaser);
|
||||
// Order: 1, 3, 2
|
||||
|
||||
cache.put(4, 40, releaser);
|
||||
// Order: 4, 1, 3, 2 (evicted)
|
||||
|
||||
// Check if 1 exists
|
||||
auto* v1 = cache.get(1);
|
||||
ASSERT_NE(v1, nullptr);
|
||||
EXPECT_EQ(*v1, 11);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, GetPromotesToMRU) {
|
||||
LruCache<int, int> cache("LruCacheTest", 3);
|
||||
std::vector<int> evicted;
|
||||
auto releaser = [&](int&& v) { evicted.push_back(v); };
|
||||
|
||||
cache.put(1, 10, releaser);
|
||||
cache.put(2, 20, releaser);
|
||||
cache.put(3, 30, releaser);
|
||||
// 3, 2, 1
|
||||
|
||||
cache.get(1);
|
||||
// 1, 3, 2
|
||||
|
||||
cache.put(4, 40, releaser);
|
||||
// Evicts 2.
|
||||
ASSERT_EQ(evicted.size(), 1);
|
||||
EXPECT_EQ(evicted[0], 20);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, MoveToFrontFromTail) {
|
||||
// This specific test targets the potential bug where moving from tail doesn't update tail.
|
||||
LruCache<int, int> cache("LruCacheTest", 3);
|
||||
std::vector<int> evicted;
|
||||
auto releaser = [&](int&& v) { evicted.push_back(v); };
|
||||
|
||||
cache.put(1, 10, releaser);
|
||||
cache.put(2, 20, releaser);
|
||||
cache.put(3, 30, releaser);
|
||||
// State: 3 (MRU) -> 2 -> 1 (LRU, Tail)
|
||||
|
||||
// Move 1 to front.
|
||||
cache.get(1);
|
||||
// Expected State: 1 (MRU) -> 3 -> 2 (LRU, Tail)
|
||||
|
||||
// Add 4. Should evict 2.
|
||||
cache.put(4, 40, releaser);
|
||||
|
||||
ASSERT_EQ(evicted.size(), 1);
|
||||
EXPECT_EQ(evicted[0], 20);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, MoveToFrontFromMiddle) {
|
||||
LruCache<int, int> cache("LruCacheTest", 3);
|
||||
std::vector<int> evicted;
|
||||
auto releaser = [&](int&& v) { evicted.push_back(v); };
|
||||
|
||||
cache.put(1, 10, releaser);
|
||||
cache.put(2, 20, releaser);
|
||||
cache.put(3, 30, releaser);
|
||||
// 3, 2, 1
|
||||
|
||||
cache.get(2);
|
||||
// 2, 3, 1
|
||||
|
||||
cache.put(4, 40, releaser);
|
||||
// Evicts 1.
|
||||
ASSERT_EQ(evicted.size(), 1);
|
||||
EXPECT_EQ(evicted[0], 10);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, CapacityOne) {
|
||||
LruCache<int, int> cache("LruCacheTest", 1);
|
||||
std::vector<int> evicted;
|
||||
auto releaser = [&](int&& v) { evicted.push_back(v); };
|
||||
|
||||
cache.put(1, 10, releaser);
|
||||
EXPECT_NE(cache.get(1), nullptr);
|
||||
EXPECT_EQ(*cache.get(1), 10);
|
||||
|
||||
cache.put(2, 20, releaser);
|
||||
ASSERT_EQ(evicted.size(), 1);
|
||||
EXPECT_EQ(evicted[0], 10);
|
||||
EXPECT_NE(cache.get(2), nullptr);
|
||||
EXPECT_EQ(*cache.get(2), 20);
|
||||
}
|
||||
|
||||
#ifdef GTEST_HAS_DEATH_TEST
|
||||
TEST(LruCacheTest, CapacityZero) {
|
||||
LruCache<int, int> cache("LruCacheTest", 0);
|
||||
EXPECT_EQ(cache.capacity(), 0);
|
||||
EXPECT_EQ(cache.size(), 0);
|
||||
EXPECT_DEATH(cache.put(1, 1, [](int&&){}), "");
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(LruCacheTest, Clear) {
|
||||
LruCache<int, int> cache("LruCacheTest", 3);
|
||||
std::vector<int> evicted;
|
||||
auto releaser = [&](int&& v) { evicted.push_back(v); };
|
||||
|
||||
cache.put(1, 10, releaser);
|
||||
cache.put(2, 20, releaser);
|
||||
|
||||
EXPECT_EQ(cache.size(), 2);
|
||||
|
||||
cache.clear(releaser);
|
||||
|
||||
EXPECT_EQ(cache.size(), 0);
|
||||
ASSERT_EQ(evicted.size(), 2);
|
||||
// Implementation traverses mHead -> mTail (MRU to LRU).
|
||||
EXPECT_EQ(evicted[0], 20); // MRU
|
||||
EXPECT_EQ(evicted[1], 10); // LRU
|
||||
|
||||
// We should now be able to refill the cache.
|
||||
cache.put(3, 30, releaser);
|
||||
cache.put(4, 40, releaser);
|
||||
|
||||
EXPECT_EQ(cache.size(), 2);
|
||||
EXPECT_NE(cache.get(3), nullptr);
|
||||
EXPECT_NE(cache.get(4), nullptr);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, Pop) {
|
||||
LruCache<int, int> cache("LruCacheTest", 3);
|
||||
auto releaser = [](int&&) {};
|
||||
|
||||
cache.put(1, 10, releaser);
|
||||
cache.put(2, 20, releaser);
|
||||
|
||||
std::optional<int> val1 = cache.pop(1);
|
||||
EXPECT_TRUE(val1.has_value());
|
||||
EXPECT_EQ(*val1, 10);
|
||||
EXPECT_EQ(cache.size(), 1);
|
||||
EXPECT_EQ(cache.get(1), nullptr);
|
||||
EXPECT_NE(cache.get(2), nullptr);
|
||||
|
||||
std::optional<int> val2 = cache.pop(2);
|
||||
EXPECT_TRUE(val2.has_value());
|
||||
EXPECT_EQ(*val2, 20);
|
||||
EXPECT_EQ(cache.size(), 0);
|
||||
}
|
||||
|
||||
/* Large key tests. */
|
||||
|
||||
struct LargeKey {
|
||||
long long a, b;
|
||||
bool operator==(LargeKey const& other) const {
|
||||
return a == other.a && b == other.b;
|
||||
}
|
||||
};
|
||||
|
||||
struct LargeKeyHash {
|
||||
size_t operator()(LargeKey const& k) const {
|
||||
return std::hash<long long>{}(k.a) ^ std::hash<long long>{}(k.b);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(LruCacheTest, LargeKey) {
|
||||
static_assert(sizeof(LargeKey) > sizeof(void*), "LargeKey must be larger than pointer");
|
||||
LruCache<LargeKey, int, LargeKeyHash> cache("LruCacheTest", 2);
|
||||
|
||||
LargeKey k1{1, 1};
|
||||
LargeKey k2{2, 2};
|
||||
LargeKey k3{3, 3};
|
||||
|
||||
cache.put(k1, 100, [](int&&){});
|
||||
cache.put(k2, 200, [](int&&){});
|
||||
|
||||
EXPECT_NE(cache.get(k1), nullptr);
|
||||
EXPECT_EQ(*cache.get(k1), 100);
|
||||
|
||||
// Evict k2 (LRU because k1 was accessed)
|
||||
cache.put(k3, 300, [](int&&){});
|
||||
|
||||
EXPECT_NE(cache.get(k3), nullptr); // k3 present
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user