1560 lines
52 KiB
C++
1560 lines
52 KiB
C++
/*
|
|
* Copyright (C) 2015 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 "details/Engine.h"
|
|
|
|
#include "MaterialParser.h"
|
|
#include "ResourceAllocator.h"
|
|
#include "RenderPrimitive.h"
|
|
|
|
#include "details/BufferObject.h"
|
|
#include "details/Camera.h"
|
|
#include "details/Fence.h"
|
|
#include "details/IndexBuffer.h"
|
|
#include "details/IndirectLight.h"
|
|
#include "details/InstanceBuffer.h"
|
|
#include "details/Material.h"
|
|
#include "details/MorphTargetBuffer.h"
|
|
#include "details/Renderer.h"
|
|
#include "details/Scene.h"
|
|
#include "details/SkinningBuffer.h"
|
|
#include "details/Skybox.h"
|
|
#include "details/Stream.h"
|
|
#include "details/SwapChain.h"
|
|
#include "details/Texture.h"
|
|
#include "details/VertexBuffer.h"
|
|
#include "details/View.h"
|
|
|
|
#include <filament/ColorGrading.h>
|
|
#include <filament/Engine.h>
|
|
#include <filament/MaterialEnums.h>
|
|
|
|
#include <private/filament/DescriptorSets.h>
|
|
#include <private/filament/EngineEnums.h>
|
|
#include <private/filament/Variant.h>
|
|
|
|
#include <private/backend/PlatformFactory.h>
|
|
|
|
#include <backend/DriverEnums.h>
|
|
|
|
#include <utils/Allocator.h>
|
|
#include <utils/CallStack.h>
|
|
#include <utils/compiler.h>
|
|
#include <utils/debug.h>
|
|
#include <utils/Invocable.h>
|
|
#include <utils/Log.h>
|
|
#include <utils/ostream.h>
|
|
#include <utils/Panic.h>
|
|
#include <utils/PrivateImplementation-impl.h>
|
|
#include <utils/Systrace.h>
|
|
#include <utils/ThreadUtils.h>
|
|
|
|
#include <math/vec3.h>
|
|
#include <math/vec4.h>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <initializer_list>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <new>
|
|
#include <optional>
|
|
#include <thread>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "generated/resources/materials.h"
|
|
|
|
using namespace filament::math;
|
|
using namespace utils;
|
|
|
|
namespace filament {
|
|
|
|
using namespace backend;
|
|
using namespace filaflat;
|
|
|
|
struct Engine::BuilderDetails {
|
|
Backend mBackend = Backend::DEFAULT;
|
|
Platform* mPlatform = nullptr;
|
|
Config mConfig;
|
|
FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1;
|
|
void* mSharedContext = nullptr;
|
|
bool mPaused = false;
|
|
std::unordered_map<std::string_view, bool> mFeatureFlags;
|
|
|
|
static Config validateConfig(Config config) noexcept;
|
|
};
|
|
|
|
Engine* FEngine::create(Builder const& builder) {
|
|
SYSTRACE_ENABLE();
|
|
SYSTRACE_CALL();
|
|
|
|
FEngine* instance = new FEngine(builder);
|
|
|
|
// initialize all fields that need an instance of FEngine
|
|
// (this cannot be done safely in the ctor)
|
|
|
|
// Normally we launch a thread and create the context and Driver from there (see FEngine::loop).
|
|
// In the single-threaded case, we do so in the here and now.
|
|
if constexpr (!UTILS_HAS_THREADING) {
|
|
Platform* platform = builder->mPlatform;
|
|
void* const sharedContext = builder->mSharedContext;
|
|
|
|
if (platform == nullptr) {
|
|
platform = PlatformFactory::create(&instance->mBackend);
|
|
instance->mPlatform = platform;
|
|
instance->mOwnPlatform = true;
|
|
}
|
|
if (platform == nullptr) {
|
|
slog.e << "Selected backend not supported in this build." << io::endl;
|
|
delete instance;
|
|
return nullptr;
|
|
}
|
|
DriverConfig const driverConfig{
|
|
.handleArenaSize = instance->getRequestedDriverHandleArenaSize(),
|
|
.metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes,
|
|
.disableParallelShaderCompile = instance->features.backend.disable_parallel_shader_compile,
|
|
.disableHandleUseAfterFreeCheck = instance->features.backend.disable_handle_use_after_free_check,
|
|
.disableHeapHandleTags = instance->features.backend.disable_heap_handle_tags,
|
|
.forceGLES2Context = instance->getConfig().forceGLES2Context,
|
|
.stereoscopicType = instance->getConfig().stereoscopicType,
|
|
.assertNativeWindowIsValid = instance->features.backend.opengl.assert_native_window_is_valid,
|
|
};
|
|
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
|
|
|
|
} else {
|
|
// start the driver thread
|
|
instance->mDriverThread = std::thread(&FEngine::loop, instance);
|
|
|
|
// wait for the driver to be ready
|
|
instance->mDriverBarrier.await();
|
|
|
|
if (UTILS_UNLIKELY(!instance->mDriver)) {
|
|
// something went horribly wrong during driver initialization
|
|
instance->mDriverThread.join();
|
|
delete instance;
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// now we can initialize the largest part of the engine
|
|
instance->init();
|
|
|
|
if constexpr (!UTILS_HAS_THREADING) {
|
|
instance->execute();
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
|
|
#if UTILS_HAS_THREADING
|
|
|
|
void FEngine::create(Builder const& builder, Invocable<void(void*)>&& callback) {
|
|
SYSTRACE_ENABLE();
|
|
SYSTRACE_CALL();
|
|
|
|
FEngine* instance = new FEngine(builder);
|
|
|
|
// start the driver thread
|
|
instance->mDriverThread = std::thread(&FEngine::loop, instance);
|
|
|
|
// launch a thread to call the callback -- so it can't do any damage.
|
|
std::thread callbackThread = std::thread([instance, callback = std::move(callback)] {
|
|
instance->mDriverBarrier.await();
|
|
callback(instance);
|
|
});
|
|
|
|
// let the callback thread die on its own
|
|
callbackThread.detach();
|
|
}
|
|
|
|
FEngine* FEngine::getEngine(void* token) {
|
|
|
|
FEngine* instance = static_cast<FEngine*>(token);
|
|
|
|
FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(instance->mMainThreadId))
|
|
<< "Engine::createAsync() and Engine::getEngine() must be called on the same thread.";
|
|
|
|
if (!instance->mInitialized) {
|
|
if (UTILS_UNLIKELY(!instance->mDriver)) {
|
|
// something went horribly wrong during driver initialization
|
|
instance->mDriverThread.join();
|
|
delete instance;
|
|
return nullptr;
|
|
}
|
|
|
|
// now we can initialize the largest part of the engine
|
|
instance->init();
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
|
|
#endif
|
|
|
|
// These must be static because only a pointer is copied to the render stream
|
|
// Note that these coordinates are specified in OpenGL clip space. Other backends can transform
|
|
// these in the vertex shader as needed.
|
|
static constexpr float4 sFullScreenTriangleVertices[3] = {
|
|
{ -1.0f, -1.0f, 1.0f, 1.0f },
|
|
{ 3.0f, -1.0f, 1.0f, 1.0f },
|
|
{ -1.0f, 3.0f, 1.0f, 1.0f }
|
|
};
|
|
|
|
// these must be static because only a pointer is copied to the render stream
|
|
static constexpr uint16_t sFullScreenTriangleIndices[3] = { 0, 1, 2 };
|
|
|
|
FEngine::FEngine(Builder const& builder) :
|
|
mBackend(builder->mBackend),
|
|
mActiveFeatureLevel(builder->mFeatureLevel),
|
|
mPlatform(builder->mPlatform),
|
|
mSharedGLContext(builder->mSharedContext),
|
|
mPostProcessManager(*this),
|
|
mEntityManager(EntityManager::get()),
|
|
mRenderableManager(*this),
|
|
mLightManager(*this),
|
|
mCameraManager(*this),
|
|
mCommandBufferQueue(
|
|
builder->mConfig.minCommandBufferSizeMB * MiB,
|
|
builder->mConfig.commandBufferSizeMB * MiB,
|
|
builder->mPaused),
|
|
mPerRenderPassArena(
|
|
"FEngine::mPerRenderPassAllocator",
|
|
builder->mConfig.perRenderPassArenaSizeMB * MiB),
|
|
mHeapAllocator("FEngine::mHeapAllocator", AreaPolicy::NullArea{}),
|
|
mJobSystem(getJobSystemThreadPoolSize(builder->mConfig)),
|
|
mEngineEpoch(std::chrono::steady_clock::now()),
|
|
mDriverBarrier(1),
|
|
mMainThreadId(ThreadUtils::getThreadId()),
|
|
mConfig(builder->mConfig)
|
|
{
|
|
// update a feature flag from Engine::Config if the flag is not specified in the Builder
|
|
auto const featureFlagsBackwardCompatibility =
|
|
[this, &builder](std::string_view const name, bool const value) {
|
|
if (builder->mFeatureFlags.find(name) == builder->mFeatureFlags.end()) {
|
|
auto* const p = getFeatureFlagPtr(name, true);
|
|
if (p) {
|
|
*p = value;
|
|
}
|
|
}
|
|
};
|
|
|
|
// update all the features flags specified in the builder
|
|
for (auto const& feature : builder->mFeatureFlags) {
|
|
auto* const p = getFeatureFlagPtr(feature.first, true);
|
|
if (p) {
|
|
*p = feature.second;
|
|
}
|
|
}
|
|
|
|
// update "old" feature flags that were specified in Engine::Config
|
|
featureFlagsBackwardCompatibility("backend.disable_parallel_shader_compile",
|
|
mConfig.disableParallelShaderCompile);
|
|
featureFlagsBackwardCompatibility("backend.disable_handle_use_after_free_check",
|
|
mConfig.disableHandleUseAfterFreeCheck);
|
|
featureFlagsBackwardCompatibility("backend.opengl.assert_native_window_is_valid",
|
|
mConfig.assertNativeWindowIsValid);
|
|
|
|
// We're assuming we're on the main thread here.
|
|
// (it may not be the case)
|
|
mJobSystem.adopt();
|
|
|
|
slog.i << "FEngine (" << sizeof(void*) * 8 << " bits) created at " << this << " "
|
|
<< "(threading is " << (UTILS_HAS_THREADING ? "enabled)" : "disabled)") << io::endl;
|
|
}
|
|
|
|
uint32_t FEngine::getJobSystemThreadPoolSize(Config const& config) noexcept {
|
|
if (config.jobSystemThreadCount > 0) {
|
|
return config.jobSystemThreadCount;
|
|
}
|
|
|
|
// 1 thread for the user, 1 thread for the backend
|
|
int threadCount = int(std::thread::hardware_concurrency()) - 2;
|
|
// make sure we have at least 1 thread though
|
|
threadCount = std::max(1, threadCount);
|
|
return threadCount;
|
|
}
|
|
|
|
/*
|
|
* init() is called just after the driver thread is initialized. Driver commands are therefore
|
|
* possible.
|
|
*/
|
|
|
|
void FEngine::init() {
|
|
SYSTRACE_CALL();
|
|
|
|
// this must be first.
|
|
assert_invariant( intptr_t(&mDriverApiStorage) % alignof(DriverApi) == 0 );
|
|
::new(&mDriverApiStorage) DriverApi(*mDriver, mCommandBufferQueue.getCircularBuffer());
|
|
|
|
DriverApi& driverApi = getDriverApi();
|
|
|
|
mActiveFeatureLevel = std::min(mActiveFeatureLevel, driverApi.getFeatureLevel());
|
|
|
|
#ifndef FILAMENT_ENABLE_FEATURE_LEVEL_0
|
|
assert_invariant(mActiveFeatureLevel > FeatureLevel::FEATURE_LEVEL_0);
|
|
#endif
|
|
|
|
slog.i << "Backend feature level: " << int(driverApi.getFeatureLevel()) << io::endl;
|
|
slog.i << "FEngine feature level: " << int(mActiveFeatureLevel) << io::endl;
|
|
|
|
|
|
mResourceAllocatorDisposer = std::make_shared<ResourceAllocatorDisposer>(driverApi);
|
|
|
|
mFullScreenTriangleVb = downcast(VertexBuffer::Builder()
|
|
.vertexCount(3)
|
|
.bufferCount(1)
|
|
.attribute(POSITION, 0, VertexBuffer::AttributeType::FLOAT4, 0)
|
|
.build(*this));
|
|
|
|
mFullScreenTriangleVb->setBufferAt(*this, 0,
|
|
{ sFullScreenTriangleVertices, sizeof(sFullScreenTriangleVertices) });
|
|
|
|
mFullScreenTriangleIb = downcast(IndexBuffer::Builder()
|
|
.indexCount(3)
|
|
.bufferType(IndexBuffer::IndexType::USHORT)
|
|
.build(*this));
|
|
|
|
mFullScreenTriangleIb->setBuffer(*this,
|
|
{ sFullScreenTriangleIndices, sizeof(sFullScreenTriangleIndices) });
|
|
|
|
mFullScreenTriangleRph = driverApi.createRenderPrimitive(
|
|
mFullScreenTriangleVb->getHwHandle(), mFullScreenTriangleIb->getHwHandle(),
|
|
PrimitiveType::TRIANGLES);
|
|
|
|
// Compute a clip-space [-1 to 1] to texture space [0 to 1] matrix, taking into account
|
|
// backend differences.
|
|
const bool textureSpaceYFlipped = mBackend == Backend::METAL || mBackend == Backend::VULKAN;
|
|
if (textureSpaceYFlipped) {
|
|
mUvFromClipMatrix = mat4f(mat4f::row_major_init{
|
|
0.5f, 0.0f, 0.0f, 0.5f,
|
|
0.0f, -0.5f, 0.0f, 0.5f,
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
0.0f, 0.0f, 0.0f, 1.0f
|
|
});
|
|
} else {
|
|
mUvFromClipMatrix = mat4f(mat4f::row_major_init{
|
|
0.5f, 0.0f, 0.0f, 0.5f,
|
|
0.0f, 0.5f, 0.0f, 0.5f,
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
0.0f, 0.0f, 0.0f, 1.0f
|
|
});
|
|
}
|
|
|
|
// initialize the dummy textures so that their contents are not undefined
|
|
static constexpr uint32_t zeroes[6] = {};
|
|
static constexpr uint32_t ones = 0xffffffff;
|
|
|
|
mDefaultIblTexture = downcast(Texture::Builder()
|
|
.width(1).height(1).levels(1)
|
|
.format(Texture::InternalFormat::RGBA8)
|
|
.sampler(Texture::Sampler::SAMPLER_CUBEMAP)
|
|
.build(*this));
|
|
|
|
driverApi.update3DImage(mDefaultIblTexture->getHwHandle(), 0, 0, 0, 0, 1, 1, 6,
|
|
{ zeroes, sizeof(zeroes), Texture::Format::RGBA, Texture::Type::UBYTE });
|
|
|
|
// 3 bands = 9 float3
|
|
constexpr float sh[9 * 3] = { 0.0f };
|
|
mDefaultIbl = downcast(IndirectLight::Builder()
|
|
.irradiance(3, reinterpret_cast<const float3*>(sh))
|
|
.build(*this));
|
|
|
|
mDefaultRenderTarget = driverApi.createDefaultRenderTarget();
|
|
|
|
// Create a dummy morph target buffer, without using the builder
|
|
mDummyMorphTargetBuffer = createMorphTargetBuffer(
|
|
FMorphTargetBuffer::EmptyMorphTargetBuilder());
|
|
|
|
// create dummy textures we need throughout the engine
|
|
mDummyOneTexture = driverApi.createTexture(SamplerType::SAMPLER_2D, 1,
|
|
TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT);
|
|
|
|
mDummyZeroTexture = driverApi.createTexture(SamplerType::SAMPLER_2D, 1,
|
|
TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT);
|
|
|
|
driverApi.update3DImage(mDummyOneTexture, 0, 0, 0, 0, 1, 1, 1,
|
|
{ &ones, 4, Texture::Format::RGBA, Texture::Type::UBYTE });
|
|
|
|
driverApi.update3DImage(mDummyZeroTexture, 0, 0, 0, 0, 1, 1, 1,
|
|
{ zeroes, 4, Texture::Format::RGBA, Texture::Type::UBYTE });
|
|
|
|
|
|
mPerViewDescriptorSetLayoutSsrVariant = {
|
|
mHwDescriptorSetLayoutFactory,
|
|
driverApi,
|
|
descriptor_sets::getSsrVariantLayout() };
|
|
|
|
mPerViewDescriptorSetLayoutDepthVariant = {
|
|
mHwDescriptorSetLayoutFactory,
|
|
driverApi,
|
|
descriptor_sets::getDepthVariantLayout() };
|
|
|
|
mPerRenderableDescriptorSetLayout = {
|
|
mHwDescriptorSetLayoutFactory,
|
|
driverApi,
|
|
descriptor_sets::getPerRenderableLayout() };
|
|
|
|
#ifdef FILAMENT_ENABLE_FEATURE_LEVEL_0
|
|
if (UTILS_UNLIKELY(mActiveFeatureLevel == FeatureLevel::FEATURE_LEVEL_0)) {
|
|
FMaterial::DefaultMaterialBuilder defaultMaterialBuilder;
|
|
defaultMaterialBuilder.package(
|
|
MATERIALS_DEFAULTMATERIAL_FL0_DATA, MATERIALS_DEFAULTMATERIAL_FL0_SIZE);
|
|
mDefaultMaterial = downcast(defaultMaterialBuilder.build(*const_cast<FEngine*>(this)));
|
|
} else
|
|
#endif
|
|
{
|
|
mDefaultColorGrading = downcast(ColorGrading::Builder().build(*this));
|
|
|
|
FMaterial::DefaultMaterialBuilder defaultMaterialBuilder;
|
|
switch (mConfig.stereoscopicType) {
|
|
case StereoscopicType::NONE:
|
|
case StereoscopicType::INSTANCED:
|
|
defaultMaterialBuilder.package(
|
|
MATERIALS_DEFAULTMATERIAL_DATA, MATERIALS_DEFAULTMATERIAL_SIZE);
|
|
break;
|
|
case StereoscopicType::MULTIVIEW:
|
|
#ifdef FILAMENT_ENABLE_MULTIVIEW
|
|
defaultMaterialBuilder.package(
|
|
MATERIALS_DEFAULTMATERIAL_MULTIVIEW_DATA, MATERIALS_DEFAULTMATERIAL_MULTIVIEW_SIZE);
|
|
#else
|
|
assert_invariant(false);
|
|
#endif
|
|
break;
|
|
}
|
|
mDefaultMaterial = downcast(defaultMaterialBuilder.build(*this));
|
|
|
|
constexpr float3 dummyPositions[1] = {};
|
|
constexpr short4 dummyTangents[1] = {};
|
|
mDummyMorphTargetBuffer->setPositionsAt(*this, 0, dummyPositions, 1, 0);
|
|
mDummyMorphTargetBuffer->setTangentsAt(*this, 0, dummyTangents, 1, 0);
|
|
|
|
mDummyOneTextureArray = driverApi.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1,
|
|
TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT);
|
|
|
|
mDummyZeroTextureArray = driverApi.createTexture(SamplerType::SAMPLER_2D_ARRAY, 1,
|
|
TextureFormat::RGBA8, 1, 1, 1, 1, TextureUsage::DEFAULT);
|
|
|
|
driverApi.update3DImage(mDummyOneTextureArray, 0, 0, 0, 0, 1, 1, 1,
|
|
{ &ones, 4, Texture::Format::RGBA, Texture::Type::UBYTE });
|
|
|
|
driverApi.update3DImage(mDummyZeroTextureArray, 0, 0, 0, 0, 1, 1, 1,
|
|
{ zeroes, 4, Texture::Format::RGBA, Texture::Type::UBYTE });
|
|
|
|
mLightManager.init(*this);
|
|
mDFG.init(*this);
|
|
}
|
|
|
|
mPostProcessManager.init();
|
|
|
|
mDebugRegistry.registerProperty("d.shadowmap.debug_directional_shadowmap",
|
|
&debug.shadowmap.debug_directional_shadowmap, [this] {
|
|
mMaterials.forEach([this](FMaterial* material) {
|
|
if (material->getMaterialDomain() == MaterialDomain::SURFACE) {
|
|
|
|
material->setConstant(
|
|
+ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP,
|
|
debug.shadowmap.debug_directional_shadowmap);
|
|
|
|
material->invalidate(
|
|
Variant::DIR | Variant::SRE | Variant::DEP,
|
|
Variant::DIR | Variant::SRE);
|
|
}
|
|
});
|
|
});
|
|
|
|
mDebugRegistry.registerProperty("d.lighting.debug_froxel_visualization",
|
|
&debug.lighting.debug_froxel_visualization, [this] {
|
|
mMaterials.forEach([this](FMaterial* material) {
|
|
if (material->getMaterialDomain() == MaterialDomain::SURFACE) {
|
|
|
|
material->setConstant(
|
|
+ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION,
|
|
debug.lighting.debug_froxel_visualization);
|
|
|
|
material->invalidate(
|
|
Variant::DYN | Variant::DEP,
|
|
Variant::DYN);
|
|
}
|
|
});
|
|
});
|
|
|
|
mInitialized = true;
|
|
}
|
|
|
|
FEngine::~FEngine() noexcept {
|
|
SYSTRACE_CALL();
|
|
assert_invariant(!mResourceAllocatorDisposer);
|
|
delete mDriver;
|
|
if (mOwnPlatform) {
|
|
PlatformFactory::destroy(&mPlatform);
|
|
}
|
|
}
|
|
|
|
void FEngine::shutdown() {
|
|
SYSTRACE_CALL();
|
|
|
|
// by construction this should never be nullptr
|
|
assert_invariant(mResourceAllocatorDisposer);
|
|
|
|
FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(mMainThreadId))
|
|
<< "Engine::shutdown() called from the wrong thread!";
|
|
|
|
#ifndef NDEBUG
|
|
// print out some statistics about this run
|
|
size_t const wm = mCommandBufferQueue.getHighWatermark();
|
|
size_t const wmpct = wm / (getCommandBufferSize() / 100);
|
|
slog.d << "CircularBuffer: High watermark "
|
|
<< wm / 1024 << " KiB (" << wmpct << "%)" << io::endl;
|
|
#endif
|
|
|
|
DriverApi& driver = getDriverApi();
|
|
|
|
/*
|
|
* Destroy our own state first
|
|
*/
|
|
|
|
mPostProcessManager.terminate(driver); // free-up post-process manager resources
|
|
mResourceAllocatorDisposer->terminate();
|
|
mResourceAllocatorDisposer.reset();
|
|
mDFG.terminate(*this); // free-up the DFG
|
|
mRenderableManager.terminate(); // free-up all renderables
|
|
mLightManager.terminate(); // free-up all lights
|
|
mCameraManager.terminate(*this); // free-up all cameras
|
|
|
|
mPerViewDescriptorSetLayoutDepthVariant.terminate(mHwDescriptorSetLayoutFactory, driver);
|
|
mPerViewDescriptorSetLayoutSsrVariant.terminate(mHwDescriptorSetLayoutFactory, driver);
|
|
mPerRenderableDescriptorSetLayout.terminate(mHwDescriptorSetLayoutFactory, driver);
|
|
|
|
driver.destroyRenderPrimitive(std::move(mFullScreenTriangleRph));
|
|
|
|
destroy(mFullScreenTriangleIb);
|
|
mFullScreenTriangleIb = nullptr;
|
|
|
|
destroy(mFullScreenTriangleVb);
|
|
mFullScreenTriangleVb = nullptr;
|
|
|
|
destroy(mDummyMorphTargetBuffer);
|
|
mDummyMorphTargetBuffer = nullptr;
|
|
|
|
destroy(mDefaultIblTexture);
|
|
mDefaultIblTexture = nullptr;
|
|
|
|
destroy(mDefaultIbl);
|
|
mDefaultIbl = nullptr;
|
|
|
|
destroy(mDefaultColorGrading);
|
|
mDefaultColorGrading = nullptr;
|
|
|
|
destroy(mDefaultMaterial);
|
|
mDefaultMaterial = nullptr;
|
|
|
|
destroy(mUnprotectedDummySwapchain);
|
|
mUnprotectedDummySwapchain = nullptr;
|
|
|
|
/*
|
|
* clean-up after the user -- we call terminate on each "leaked" object and clear each list.
|
|
*
|
|
* This should free up everything.
|
|
*/
|
|
|
|
// try to destroy objects in the inverse dependency
|
|
cleanupResourceList(std::move(mRenderers));
|
|
cleanupResourceList(std::move(mViews));
|
|
cleanupResourceList(std::move(mScenes));
|
|
cleanupResourceList(std::move(mSkyboxes));
|
|
cleanupResourceList(std::move(mColorGradings));
|
|
|
|
// this must be done after Skyboxes and before materials
|
|
destroy(mSkyboxMaterial);
|
|
mSkyboxMaterial = nullptr;
|
|
|
|
cleanupResourceList(std::move(mBufferObjects));
|
|
cleanupResourceList(std::move(mIndexBuffers));
|
|
cleanupResourceList(std::move(mMorphTargetBuffers));
|
|
cleanupResourceList(std::move(mSkinningBuffers));
|
|
cleanupResourceList(std::move(mVertexBuffers));
|
|
cleanupResourceList(std::move(mTextures));
|
|
cleanupResourceList(std::move(mRenderTargets));
|
|
cleanupResourceList(std::move(mMaterials));
|
|
cleanupResourceList(std::move(mInstanceBuffers));
|
|
for (auto& item : mMaterialInstances) {
|
|
cleanupResourceList(std::move(item.second));
|
|
}
|
|
|
|
cleanupResourceListLocked(mFenceListLock, std::move(mFences));
|
|
|
|
driver.destroyTexture(std::move(mDummyOneTexture));
|
|
driver.destroyTexture(std::move(mDummyOneTextureArray));
|
|
driver.destroyTexture(std::move(mDummyZeroTexture));
|
|
driver.destroyTexture(std::move(mDummyZeroTextureArray));
|
|
|
|
driver.destroyRenderTarget(std::move(mDefaultRenderTarget));
|
|
|
|
/*
|
|
* Shutdown the backend...
|
|
*/
|
|
|
|
// There might be commands added by the `terminate()` calls, so we need to flush all commands
|
|
// up to this point. After flushCommandBuffer() is called, all pending commands are guaranteed
|
|
// to be executed before the driver thread exits.
|
|
flushCommandBuffer(mCommandBufferQueue);
|
|
|
|
// now wait for all pending commands to be executed and the thread to exit
|
|
mCommandBufferQueue.requestExit();
|
|
if constexpr (!UTILS_HAS_THREADING) {
|
|
execute();
|
|
getDriverApi().terminate();
|
|
} else {
|
|
mDriverThread.join();
|
|
// Driver::terminate() has been called here.
|
|
}
|
|
|
|
// Finally, call user callbacks that might have been scheduled.
|
|
// These callbacks CANNOT call driver APIs.
|
|
getDriver().purge();
|
|
|
|
// and destroy the CommandStream
|
|
std::destroy_at(std::launder(reinterpret_cast<DriverApi*>(&mDriverApiStorage)));
|
|
|
|
/*
|
|
* Terminate the JobSystem...
|
|
*/
|
|
|
|
// detach this thread from the JobSystem
|
|
mJobSystem.emancipate();
|
|
}
|
|
|
|
void FEngine::prepare() {
|
|
SYSTRACE_CALL();
|
|
// prepare() is called once per Renderer frame. Ideally we would upload the content of
|
|
// UBOs that are visible only. It's not such a big issue because the actual upload() is
|
|
// skipped if the UBO hasn't changed. Still we could have a lot of these.
|
|
DriverApi& driver = getDriverApi();
|
|
|
|
for (auto& materialInstanceList: mMaterialInstances) {
|
|
materialInstanceList.second.forEach([&driver](FMaterialInstance* item) {
|
|
item->commitStreamUniformAssociations(driver);
|
|
item->commit(driver);
|
|
});
|
|
}
|
|
|
|
mMaterials.forEach([](FMaterial* material) {
|
|
#if FILAMENT_ENABLE_MATDBG // NOLINT(*-include-cleaner)
|
|
material->checkProgramEdits();
|
|
#endif
|
|
});
|
|
}
|
|
|
|
void FEngine::gc() {
|
|
// Note: this runs in a Job
|
|
auto& em = mEntityManager;
|
|
mRenderableManager.gc(em);
|
|
mLightManager.gc(em);
|
|
mTransformManager.gc(em);
|
|
mCameraManager.gc(*this, em);
|
|
}
|
|
|
|
void FEngine::flush() {
|
|
// flush the command buffer
|
|
flushCommandBuffer(mCommandBufferQueue);
|
|
}
|
|
|
|
void FEngine::flushAndWait() {
|
|
flushAndWait(FENCE_WAIT_FOR_EVER);
|
|
}
|
|
|
|
bool FEngine::flushAndWait(uint64_t const timeout) {
|
|
FILAMENT_CHECK_PRECONDITION(!mCommandBufferQueue.isPaused())
|
|
<< "Cannot call Engine::flushAndWait() when rendering thread is paused!";
|
|
|
|
// first make sure we've not terminated filament
|
|
FILAMENT_CHECK_PRECONDITION(!mCommandBufferQueue.isExitRequested())
|
|
<< "Calling Engine::flushAndWait() after Engine::shutdown()!";
|
|
|
|
// enqueue finish command -- this will stall in the driver until the GPU is done
|
|
getDriverApi().finish();
|
|
|
|
FFence* fence = createFence();
|
|
FenceStatus const status = fence->wait(FFence::Mode::FLUSH, timeout);
|
|
destroy(fence);
|
|
|
|
// finally, execute callbacks that might have been scheduled
|
|
getDriver().purge();
|
|
|
|
return status == FenceStatus::CONDITION_SATISFIED;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------
|
|
// Render thread / command queue
|
|
// -----------------------------------------------------------------------------------------------
|
|
|
|
int FEngine::loop() {
|
|
if (mPlatform == nullptr) {
|
|
mPlatform = PlatformFactory::create(&mBackend);
|
|
mOwnPlatform = true;
|
|
const char* const backend = backendToString(mBackend);
|
|
slog.i << "FEngine resolved backend: " << backend << io::endl;
|
|
if (mPlatform == nullptr) {
|
|
slog.e << "Selected backend not supported in this build." << io::endl;
|
|
mDriverBarrier.latch();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
JobSystem::setThreadName("FEngine::loop");
|
|
JobSystem::setThreadPriority(JobSystem::Priority::DISPLAY);
|
|
|
|
DriverConfig const driverConfig {
|
|
.handleArenaSize = getRequestedDriverHandleArenaSize(),
|
|
.metalUploadBufferSizeBytes = mConfig.metalUploadBufferSizeBytes,
|
|
.disableParallelShaderCompile = features.backend.disable_parallel_shader_compile,
|
|
.disableHandleUseAfterFreeCheck = features.backend.disable_handle_use_after_free_check,
|
|
.disableHeapHandleTags = features.backend.disable_heap_handle_tags,
|
|
.forceGLES2Context = mConfig.forceGLES2Context,
|
|
.stereoscopicType = mConfig.stereoscopicType,
|
|
.assertNativeWindowIsValid = features.backend.opengl.assert_native_window_is_valid,
|
|
};
|
|
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
|
|
|
|
mDriverBarrier.latch();
|
|
if (UTILS_UNLIKELY(!mDriver)) {
|
|
// if we get here, it's because the driver couldn't be initialized and the problem has
|
|
// been logged.
|
|
return 0;
|
|
}
|
|
|
|
#if FILAMENT_ENABLE_MATDBG
|
|
#ifdef __ANDROID__
|
|
const char* portString = "8081";
|
|
#else
|
|
const char* portString = getenv("FILAMENT_MATDBG_PORT");
|
|
#endif
|
|
if (portString != nullptr) {
|
|
const int port = atoi(portString);
|
|
debug.server = new matdbg::DebugServer(mBackend, mDriver->getShaderLanguage(),
|
|
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).
|
|
// When this occurs, carry onward, developers can look at civetweb.txt for details.
|
|
if (!debug.server->isReady()) {
|
|
delete debug.server;
|
|
debug.server = nullptr;
|
|
} else {
|
|
debug.server->setEditCallback(FMaterial::onEditCallback);
|
|
debug.server->setQueryCallback(FMaterial::onQueryCallback);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if FILAMENT_ENABLE_FGVIEWER // NOLINT(*-include-cleaner)
|
|
#ifdef __ANDROID__
|
|
const char* fgviewerPortString = "8085";
|
|
#else
|
|
const char* fgviewerPortString = getenv("FILAMENT_FGVIEWER_PORT");
|
|
#endif
|
|
if (fgviewerPortString != nullptr) {
|
|
const int fgviewerPort = atoi(fgviewerPortString);
|
|
debug.fgviewerServer = new fgviewer::DebugServer(fgviewerPort);
|
|
|
|
// Sometimes the server can fail to spin up (e.g. if the above port is already in use).
|
|
// When this occurs, carry onward, developers can look at civetweb.txt for details.
|
|
if (!debug.fgviewerServer->isReady()) {
|
|
delete debug.fgviewerServer;
|
|
debug.fgviewerServer = nullptr;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
while (true) {
|
|
if (!execute()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if FILAMENT_ENABLE_MATDBG
|
|
if(debug.server) {
|
|
delete debug.server;
|
|
}
|
|
#endif
|
|
#if FILAMENT_ENABLE_FGVIEWER
|
|
if(debug.fgviewerServer) {
|
|
delete debug.fgviewerServer;
|
|
}
|
|
#endif
|
|
|
|
// terminate() is a synchronous API
|
|
getDriverApi().terminate();
|
|
return 0;
|
|
}
|
|
|
|
void FEngine::flushCommandBuffer(CommandBufferQueue& commandBufferQueue) const {
|
|
getDriver().purge();
|
|
commandBufferQueue.flush();
|
|
}
|
|
|
|
const FMaterial* FEngine::getSkyboxMaterial() const noexcept {
|
|
FMaterial const* material = mSkyboxMaterial;
|
|
if (UTILS_UNLIKELY(material == nullptr)) {
|
|
material = FSkybox::createMaterial(*const_cast<FEngine*>(this));
|
|
mSkyboxMaterial = material;
|
|
}
|
|
return material;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------
|
|
// Resource management
|
|
// -----------------------------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Object created from a Builder
|
|
*/
|
|
|
|
template<typename T, typename ... ARGS>
|
|
T* FEngine::create(ResourceList<T>& list,
|
|
typename T::Builder const& builder, ARGS&& ... args) noexcept {
|
|
T* p = mHeapAllocator.make<T>(*this, builder, std::forward<ARGS>(args)...);
|
|
if (UTILS_LIKELY(p)) {
|
|
list.insert(p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
FBufferObject* FEngine::createBufferObject(const BufferObject::Builder& builder) noexcept {
|
|
return create(mBufferObjects, builder);
|
|
}
|
|
|
|
FVertexBuffer* FEngine::createVertexBuffer(const VertexBuffer::Builder& builder) noexcept {
|
|
return create(mVertexBuffers, builder);
|
|
}
|
|
|
|
FIndexBuffer* FEngine::createIndexBuffer(const IndexBuffer::Builder& builder) noexcept {
|
|
return create(mIndexBuffers, builder);
|
|
}
|
|
|
|
FSkinningBuffer* FEngine::createSkinningBuffer(const SkinningBuffer::Builder& builder) noexcept {
|
|
return create(mSkinningBuffers, builder);
|
|
}
|
|
|
|
FMorphTargetBuffer* FEngine::createMorphTargetBuffer(const MorphTargetBuffer::Builder& builder) noexcept {
|
|
return create(mMorphTargetBuffers, builder);
|
|
}
|
|
|
|
FInstanceBuffer* FEngine::createInstanceBuffer(const InstanceBuffer::Builder& builder) noexcept {
|
|
return create(mInstanceBuffers, builder);
|
|
}
|
|
|
|
FTexture* FEngine::createTexture(const Texture::Builder& builder) noexcept {
|
|
return create(mTextures, builder);
|
|
}
|
|
|
|
FIndirectLight* FEngine::createIndirectLight(const IndirectLight::Builder& builder) noexcept {
|
|
return create(mIndirectLights, builder);
|
|
}
|
|
|
|
FMaterial* FEngine::createMaterial(const Material::Builder& builder,
|
|
std::unique_ptr<MaterialParser> materialParser) noexcept {
|
|
return create(mMaterials, builder, std::move(materialParser));
|
|
}
|
|
|
|
FSkybox* FEngine::createSkybox(const Skybox::Builder& builder) noexcept {
|
|
return create(mSkyboxes, builder);
|
|
}
|
|
|
|
FColorGrading* FEngine::createColorGrading(const ColorGrading::Builder& builder) noexcept {
|
|
return create(mColorGradings, builder);
|
|
}
|
|
|
|
FStream* FEngine::createStream(const Stream::Builder& builder) noexcept {
|
|
return create(mStreams, builder);
|
|
}
|
|
|
|
FRenderTarget* FEngine::createRenderTarget(const RenderTarget::Builder& builder) noexcept {
|
|
return create(mRenderTargets, builder);
|
|
}
|
|
|
|
/*
|
|
* Special cases
|
|
*/
|
|
|
|
FRenderer* FEngine::createRenderer() noexcept {
|
|
FRenderer* p = mHeapAllocator.make<FRenderer>(*this);
|
|
if (UTILS_LIKELY(p)) {
|
|
mRenderers.insert(p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
|
|
const FMaterialInstance* other, const char* name) noexcept {
|
|
FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, other, name);
|
|
if (UTILS_LIKELY(p)) {
|
|
auto const pos = mMaterialInstances.emplace(material, "MaterialInstance");
|
|
pos.first->second.insert(p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
|
|
const char* name) noexcept {
|
|
FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, material, name);
|
|
if (UTILS_LIKELY(p)) {
|
|
auto pos = mMaterialInstances.emplace(material, "MaterialInstance");
|
|
pos.first->second.insert(p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Objects created without a Builder
|
|
*/
|
|
|
|
FScene* FEngine::createScene() noexcept {
|
|
FScene* p = mHeapAllocator.make<FScene>(*this);
|
|
if (UTILS_LIKELY(p)) {
|
|
mScenes.insert(p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
FView* FEngine::createView() noexcept {
|
|
FView* p = mHeapAllocator.make<FView>(*this);
|
|
if (UTILS_LIKELY(p)) {
|
|
mViews.insert(p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
FFence* FEngine::createFence() noexcept {
|
|
FFence* p = mHeapAllocator.make<FFence>(*this);
|
|
if (UTILS_LIKELY(p)) {
|
|
std::lock_guard const guard(mFenceListLock);
|
|
mFences.insert(p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
FSwapChain* FEngine::createSwapChain(void* nativeWindow, uint64_t flags) noexcept {
|
|
if (UTILS_UNLIKELY(flags & backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER)) {
|
|
// If this flag is set, then the nativeWindow is a CVPixelBufferRef.
|
|
// The call to setupExternalImage is synchronous, and allows the driver to take ownership of
|
|
// the buffer on this thread.
|
|
// For non-Metal backends, this is a no-op.
|
|
getDriverApi().setupExternalImage(nativeWindow);
|
|
}
|
|
FSwapChain* p = mHeapAllocator.make<FSwapChain>(*this, nativeWindow, flags);
|
|
if (UTILS_LIKELY(p)) {
|
|
mSwapChains.insert(p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
FSwapChain* FEngine::createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept {
|
|
FSwapChain* p = mHeapAllocator.make<FSwapChain>(*this, width, height, flags);
|
|
if (UTILS_LIKELY(p)) {
|
|
mSwapChains.insert(p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Objects created with a component manager
|
|
*/
|
|
|
|
|
|
FCamera* FEngine::createCamera(Entity const entity) noexcept {
|
|
return mCameraManager.create(*this, entity);
|
|
}
|
|
|
|
FCamera* FEngine::getCameraComponent(Entity const entity) noexcept {
|
|
auto const ci = mCameraManager.getInstance(entity);
|
|
return ci ? mCameraManager.getCamera(ci) : nullptr;
|
|
}
|
|
|
|
void FEngine::destroyCameraComponent(Entity const entity) noexcept {
|
|
mCameraManager.destroy(*this, entity);
|
|
}
|
|
|
|
|
|
void FEngine::createRenderable(const RenderableManager::Builder& builder, Entity const entity) {
|
|
mRenderableManager.create(builder, entity);
|
|
auto& tcm = mTransformManager;
|
|
// if this entity doesn't have a transform component, add one.
|
|
if (!tcm.hasComponent(entity)) {
|
|
tcm.create(entity, 0, mat4f());
|
|
}
|
|
}
|
|
|
|
void FEngine::createLight(const LightManager::Builder& builder, Entity const entity) {
|
|
mLightManager.create(builder, entity);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------
|
|
|
|
template<typename T>
|
|
UTILS_NOINLINE
|
|
void FEngine::cleanupResourceList(ResourceList<T>&& list) {
|
|
if (UTILS_UNLIKELY(!list.empty())) {
|
|
#ifndef NDEBUG
|
|
slog.d << "cleaning up " << list.size()
|
|
<< " leaked " << CallStack::typeName<T>().c_str() << io::endl;
|
|
#endif
|
|
list.forEach([this, &allocator = mHeapAllocator](T* item) {
|
|
item->terminate(*this);
|
|
allocator.destroy(item);
|
|
});
|
|
list.clear();
|
|
}
|
|
}
|
|
template<typename T, typename Lock>
|
|
UTILS_NOINLINE
|
|
void FEngine::cleanupResourceListLocked(Lock& lock, ResourceList<T>&& list) {
|
|
// copy the list with the lock held, then proceed as usual
|
|
lock.lock();
|
|
auto copy(std::move(list));
|
|
lock.unlock();
|
|
cleanupResourceList(std::move(copy));
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------
|
|
|
|
template<typename T>
|
|
UTILS_ALWAYS_INLINE
|
|
bool FEngine::isValid(const T* ptr, ResourceList<T> const& list) const {
|
|
auto& l = const_cast<ResourceList<T>&>(list);
|
|
return l.find(ptr) != l.end();
|
|
}
|
|
|
|
template<typename T>
|
|
UTILS_ALWAYS_INLINE
|
|
bool FEngine::terminateAndDestroy(const T* p, ResourceList<T>& list) {
|
|
if (p == nullptr) return true;
|
|
bool const success = list.remove(p);
|
|
|
|
#if UTILS_HAS_RTTI
|
|
auto typeName = CallStack::typeName<T>();
|
|
const char * const typeNameCStr = typeName.c_str();
|
|
#else
|
|
const char * const typeNameCStr = "<no-rtti>";
|
|
#endif
|
|
|
|
if (ASSERT_PRECONDITION_NON_FATAL(success,
|
|
"Object %s at %p doesn't exist (double free?)", typeNameCStr, p)) {
|
|
const_cast<T*>(p)->terminate(*this);
|
|
mHeapAllocator.destroy(const_cast<T*>(p));
|
|
}
|
|
return success;
|
|
}
|
|
|
|
template<typename T, typename Lock>
|
|
UTILS_ALWAYS_INLINE
|
|
bool FEngine::terminateAndDestroyLocked(Lock& lock, const T* p, ResourceList<T>& list) {
|
|
if (p == nullptr) return true;
|
|
lock.lock();
|
|
bool const success = list.remove(p);
|
|
lock.unlock();
|
|
|
|
#if UTILS_HAS_RTTI
|
|
auto typeName = CallStack::typeName<T>();
|
|
const char * const typeNameCStr = typeName.c_str();
|
|
#else
|
|
const char * const typeNameCStr = "<no-rtti>";
|
|
#endif
|
|
|
|
if (ASSERT_PRECONDITION_NON_FATAL(success,
|
|
"Object %s at %p doesn't exist (double free?)", typeNameCStr, p)) {
|
|
const_cast<T*>(p)->terminate(*this);
|
|
mHeapAllocator.destroy(const_cast<T*>(p));
|
|
}
|
|
return success;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------------------
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FBufferObject* p) {
|
|
return terminateAndDestroy(p, mBufferObjects);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FVertexBuffer* p) {
|
|
return terminateAndDestroy(p, mVertexBuffers);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FIndexBuffer* p) {
|
|
return terminateAndDestroy(p, mIndexBuffers);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FSkinningBuffer* p) {
|
|
return terminateAndDestroy(p, mSkinningBuffers);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FMorphTargetBuffer* p) {
|
|
return terminateAndDestroy(p, mMorphTargetBuffers);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FRenderer* p) {
|
|
return terminateAndDestroy(p, mRenderers);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FScene* p) {
|
|
return terminateAndDestroy(p, mScenes);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FSkybox* p) {
|
|
return terminateAndDestroy(p, mSkyboxes);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FColorGrading* p) {
|
|
return terminateAndDestroy(p, mColorGradings);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FTexture* p) {
|
|
return terminateAndDestroy(p, mTextures);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FRenderTarget* p) {
|
|
return terminateAndDestroy(p, mRenderTargets);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FView* p) {
|
|
return terminateAndDestroy(p, mViews);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FIndirectLight* p) {
|
|
return terminateAndDestroy(p, mIndirectLights);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FFence* p) {
|
|
return terminateAndDestroyLocked(mFenceListLock, p, mFences);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FSwapChain* p) {
|
|
return terminateAndDestroy(p, mSwapChains);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FStream* p) {
|
|
return terminateAndDestroy(p, mStreams);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FInstanceBuffer* p){
|
|
return terminateAndDestroy(p, mInstanceBuffers);
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FMaterial* p) {
|
|
if (p == nullptr) return true;
|
|
bool const success = terminateAndDestroy(p, mMaterials);
|
|
if (UTILS_LIKELY(success)) {
|
|
auto const pos = mMaterialInstances.find(p);
|
|
if (UTILS_LIKELY(pos != mMaterialInstances.cend())) {
|
|
mMaterialInstances.erase(pos);
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
bool FEngine::destroy(const FMaterialInstance* p) {
|
|
if (p == nullptr) return true;
|
|
|
|
// Check that the material instance we're destroying is not in use in the RenderableManager
|
|
// To do this, we currently need to inspect all render primitives in the RenderableManager
|
|
EntityManager const& em = mEntityManager;
|
|
FRenderableManager const& rcm = mRenderableManager;
|
|
Entity const* entities = rcm.getEntities();
|
|
size_t const count = rcm.getComponentCount();
|
|
for (size_t i = 0; i < count; i++) {
|
|
Entity const entity = entities[i];
|
|
if (em.isAlive(entity)) {
|
|
RenderableManager::Instance const ri = rcm.getInstance(entity);
|
|
size_t const primitiveCount = rcm.getPrimitiveCount(ri, 0);
|
|
for (size_t j = 0; j < primitiveCount; j++) {
|
|
auto const* const mi = rcm.getMaterialInstanceAt(ri, 0, j);
|
|
if (features.engine.debug.assert_material_instance_in_use) {
|
|
FILAMENT_CHECK_PRECONDITION(mi != p)
|
|
<< "destroying MaterialInstance \""
|
|
<< mi->getName() << "\" which is still in use by Renderable (entity="
|
|
<< entity.getId() << ", instance="
|
|
<< ri.asValue() << ", index=" << j << ")";
|
|
} else {
|
|
if (UTILS_UNLIKELY(mi == p)) {
|
|
slog.e << "destroying MaterialInstance \""
|
|
<< mi->getName() << "\" which is still in use by Renderable (entity="
|
|
<< entity.getId() << ", instance="
|
|
<< ri.asValue() << ", index=" << j << ")"
|
|
<< io::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p->isDefaultInstance()) return false;
|
|
auto const pos = mMaterialInstances.find(p->getMaterial());
|
|
assert_invariant(pos != mMaterialInstances.cend());
|
|
if (pos != mMaterialInstances.cend()) {
|
|
return terminateAndDestroy(p, pos->second);
|
|
}
|
|
// this shouldn't happen, this would be double-free
|
|
return false;
|
|
}
|
|
|
|
UTILS_NOINLINE
|
|
void FEngine::destroy(Entity const e) {
|
|
mRenderableManager.destroy(e);
|
|
mLightManager.destroy(e);
|
|
mTransformManager.destroy(e);
|
|
mCameraManager.destroy(*this, e);
|
|
}
|
|
|
|
bool FEngine::isValid(const FBufferObject* p) const {
|
|
return isValid(p, mBufferObjects);
|
|
}
|
|
|
|
bool FEngine::isValid(const FVertexBuffer* p) const {
|
|
return isValid(p, mVertexBuffers);
|
|
}
|
|
|
|
bool FEngine::isValid(const FFence* p) const {
|
|
return isValid(p, mFences);
|
|
}
|
|
|
|
bool FEngine::isValid(const FIndexBuffer* p) const {
|
|
return isValid(p, mIndexBuffers);
|
|
}
|
|
|
|
bool FEngine::isValid(const FSkinningBuffer* p) const {
|
|
return isValid(p, mSkinningBuffers);
|
|
}
|
|
|
|
bool FEngine::isValid(const FMorphTargetBuffer* p) const {
|
|
return isValid(p, mMorphTargetBuffers);
|
|
}
|
|
|
|
bool FEngine::isValid(const FIndirectLight* p) const {
|
|
return isValid(p, mIndirectLights);
|
|
}
|
|
|
|
bool FEngine::isValid(const FMaterial* p) const {
|
|
return isValid(p, mMaterials);
|
|
}
|
|
|
|
bool FEngine::isValid(const FMaterial* m, const FMaterialInstance* p) const {
|
|
// first make sure the material we're given is valid.
|
|
if (!isValid(m)) {
|
|
return false;
|
|
}
|
|
|
|
// then find the material instance list for that material
|
|
auto const it = mMaterialInstances.find(m);
|
|
if (it == mMaterialInstances.end()) {
|
|
// this could happen if this material has no material instances at all
|
|
return false;
|
|
}
|
|
|
|
// finally validate the material instance
|
|
return isValid(p, it->second);
|
|
}
|
|
|
|
bool FEngine::isValidExpensive(const FMaterialInstance* p) const {
|
|
return std::any_of(mMaterialInstances.cbegin(), mMaterialInstances.cend(),
|
|
[this, p](auto&& entry) {
|
|
return isValid(p, entry.second);
|
|
});
|
|
}
|
|
|
|
bool FEngine::isValid(const FRenderer* p) const {
|
|
return isValid(p, mRenderers);
|
|
}
|
|
|
|
bool FEngine::isValid(const FScene* p) const {
|
|
return isValid(p, mScenes);
|
|
}
|
|
|
|
bool FEngine::isValid(const FSkybox* p) const {
|
|
return isValid(p, mSkyboxes);
|
|
}
|
|
|
|
bool FEngine::isValid(const FColorGrading* p) const {
|
|
return isValid(p, mColorGradings);
|
|
}
|
|
|
|
bool FEngine::isValid(const FSwapChain* p) const {
|
|
return isValid(p, mSwapChains);
|
|
}
|
|
|
|
bool FEngine::isValid(const FStream* p) const {
|
|
return isValid(p, mStreams);
|
|
}
|
|
|
|
bool FEngine::isValid(const FTexture* p) const {
|
|
return isValid(p, mTextures);
|
|
}
|
|
|
|
bool FEngine::isValid(const FRenderTarget* p) const {
|
|
return isValid(p, mRenderTargets);
|
|
}
|
|
|
|
bool FEngine::isValid(const FView* p) const {
|
|
return isValid(p, mViews);
|
|
}
|
|
|
|
bool FEngine::isValid(const FInstanceBuffer* p) const {
|
|
return isValid(p, mInstanceBuffers);
|
|
}
|
|
|
|
size_t FEngine::getBufferObjectCount() const noexcept { return mBufferObjects.size(); }
|
|
size_t FEngine::getViewCount() const noexcept { return mViews.size(); }
|
|
size_t FEngine::getSceneCount() const noexcept { return mScenes.size(); }
|
|
size_t FEngine::getSwapChainCount() const noexcept { return mSwapChains.size(); }
|
|
size_t FEngine::getStreamCount() const noexcept { return mStreams.size(); }
|
|
size_t FEngine::getIndexBufferCount() const noexcept { return mIndexBuffers.size(); }
|
|
size_t FEngine::getSkinningBufferCount() const noexcept { return mSkinningBuffers.size(); }
|
|
size_t FEngine::getMorphTargetBufferCount() const noexcept { return mMorphTargetBuffers.size(); }
|
|
size_t FEngine::getInstanceBufferCount() const noexcept { return mInstanceBuffers.size(); }
|
|
size_t FEngine::getVertexBufferCount() const noexcept { return mVertexBuffers.size(); }
|
|
size_t FEngine::getIndirectLightCount() const noexcept { return mIndirectLights.size(); }
|
|
size_t FEngine::getMaterialCount() const noexcept { return mMaterials.size(); }
|
|
size_t FEngine::getTextureCount() const noexcept { return mTextures.size(); }
|
|
size_t FEngine::getSkyboxeCount() const noexcept { return mSkyboxes.size(); }
|
|
size_t FEngine::getColorGradingCount() const noexcept { return mColorGradings.size(); }
|
|
size_t FEngine::getRenderTargetCount() const noexcept { return mRenderTargets.size(); }
|
|
|
|
size_t FEngine::getMaxShadowMapCount() const noexcept {
|
|
return features.engine.shadows.use_shadow_atlas ?
|
|
CONFIG_MAX_SHADOWMAPS : CONFIG_MAX_SHADOW_LAYERS;
|
|
}
|
|
|
|
void* FEngine::streamAlloc(size_t const size, size_t const alignment) noexcept {
|
|
// we allow this only for small allocations
|
|
if (size > 65536) {
|
|
return nullptr;
|
|
}
|
|
return getDriverApi().allocate(size, alignment);
|
|
}
|
|
|
|
bool FEngine::execute() {
|
|
// wait until we get command buffers to be executed (or thread exit requested)
|
|
auto const buffers = mCommandBufferQueue.waitForCommands();
|
|
if (UTILS_UNLIKELY(buffers.empty())) {
|
|
return false;
|
|
}
|
|
|
|
// execute all command buffers
|
|
auto& driver = getDriverApi();
|
|
for (auto& item : buffers) {
|
|
if (UTILS_LIKELY(item.begin)) {
|
|
driver.execute(item.begin);
|
|
mCommandBufferQueue.releaseBuffer(item);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FEngine::destroy(FEngine* engine) {
|
|
if (engine) {
|
|
engine->shutdown();
|
|
delete engine;
|
|
}
|
|
}
|
|
|
|
bool FEngine::isPaused() const noexcept {
|
|
return mCommandBufferQueue.isPaused();
|
|
}
|
|
|
|
void FEngine::setPaused(bool const paused) {
|
|
mCommandBufferQueue.setPaused(paused);
|
|
}
|
|
|
|
Engine::FeatureLevel FEngine::getSupportedFeatureLevel() const noexcept {
|
|
DriverApi& driver = const_cast<FEngine*>(this)->getDriverApi();
|
|
return driver.getFeatureLevel();
|
|
}
|
|
|
|
Engine::FeatureLevel FEngine::setActiveFeatureLevel(FeatureLevel featureLevel) {
|
|
FILAMENT_CHECK_PRECONDITION(featureLevel <= getSupportedFeatureLevel())
|
|
<< "Feature level " << unsigned(featureLevel) << " not supported";
|
|
FILAMENT_CHECK_PRECONDITION(mActiveFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1)
|
|
<< "Cannot adjust feature level beyond 0 at runtime";
|
|
return (mActiveFeatureLevel = std::max(mActiveFeatureLevel, featureLevel));
|
|
}
|
|
|
|
#if defined(__EMSCRIPTEN__)
|
|
void FEngine::resetBackendState() noexcept {
|
|
getDriverApi().resetState();
|
|
}
|
|
#endif
|
|
|
|
void FEngine::unprotected() noexcept {
|
|
if (UTILS_UNLIKELY(!mUnprotectedDummySwapchain)) {
|
|
mUnprotectedDummySwapchain = createSwapChain(1, 1, 0);
|
|
}
|
|
mUnprotectedDummySwapchain->makeCurrent(getDriverApi());
|
|
}
|
|
|
|
bool FEngine::setFeatureFlag(char const* name, bool const value) const noexcept {
|
|
auto* const p = getFeatureFlagPtr(name);
|
|
if (p) {
|
|
*p = value;
|
|
}
|
|
return p != nullptr;
|
|
}
|
|
|
|
std::optional<bool> FEngine::getFeatureFlag(char const* name) const noexcept {
|
|
auto* const p = getFeatureFlagPtr(name, true);
|
|
if (p) {
|
|
return *p;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
bool* FEngine::getFeatureFlagPtr(std::string_view name, bool const allowConstant) const noexcept {
|
|
auto pos = std::find_if(mFeatures.begin(), mFeatures.end(),
|
|
[name](FeatureFlag const& entry) {
|
|
return name == entry.name;
|
|
});
|
|
|
|
return (pos != mFeatures.end() && (!pos->constant || allowConstant)) ?
|
|
const_cast<bool*>(pos->value) : nullptr;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
Engine::Builder::Builder() noexcept = default;
|
|
Engine::Builder::~Builder() noexcept = default;
|
|
Engine::Builder::Builder(Builder const& rhs) noexcept = default;
|
|
Engine::Builder::Builder(Builder&& rhs) noexcept = default;
|
|
Engine::Builder& Engine::Builder::operator=(Builder const& rhs) noexcept = default;
|
|
Engine::Builder& Engine::Builder::operator=(Builder&& rhs) noexcept = default;
|
|
|
|
Engine::Builder& Engine::Builder::backend(Backend const backend) noexcept {
|
|
mImpl->mBackend = backend;
|
|
return *this;
|
|
}
|
|
|
|
Engine::Builder& Engine::Builder::platform(Platform* platform) noexcept {
|
|
mImpl->mPlatform = platform;
|
|
return *this;
|
|
}
|
|
|
|
Engine::Builder& Engine::Builder::config(Config const* config) noexcept {
|
|
mImpl->mConfig = config ? *config : Config{};
|
|
return *this;
|
|
}
|
|
|
|
Engine::Builder& Engine::Builder::featureLevel(FeatureLevel const featureLevel) noexcept {
|
|
mImpl->mFeatureLevel = featureLevel;
|
|
return *this;
|
|
}
|
|
|
|
Engine::Builder& Engine::Builder::sharedContext(void* sharedContext) noexcept {
|
|
mImpl->mSharedContext = sharedContext;
|
|
return *this;
|
|
}
|
|
|
|
Engine::Builder& Engine::Builder::paused(bool const paused) noexcept {
|
|
mImpl->mPaused = paused;
|
|
return *this;
|
|
}
|
|
|
|
Engine::Builder& Engine::Builder::feature(char const* name, bool const value) noexcept {
|
|
mImpl->mFeatureFlags[name] = value;
|
|
return *this;
|
|
}
|
|
|
|
Engine::Builder& Engine::Builder::features(std::initializer_list<char const *> const list) noexcept {
|
|
for (auto const name : list) {
|
|
if (name) {
|
|
feature(name, true);
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
#if UTILS_HAS_THREADING
|
|
|
|
void Engine::Builder::build(Invocable<void(void*)>&& callback) const {
|
|
FEngine::create(*this, std::move(callback));
|
|
}
|
|
|
|
#endif
|
|
|
|
Engine* Engine::Builder::build() const {
|
|
mImpl->mConfig = BuilderDetails::validateConfig(mImpl->mConfig);
|
|
return FEngine::create(*this);
|
|
}
|
|
|
|
Engine::Config Engine::BuilderDetails::validateConfig(Config config) noexcept {
|
|
// Rule of thumb: perRenderPassArenaMB must be roughly 1 MB larger than perFrameCommandsMB
|
|
constexpr uint32_t COMMAND_ARENA_OVERHEAD = 1;
|
|
constexpr uint32_t CONCURRENT_FRAME_COUNT = 3;
|
|
|
|
// Use at least the defaults set by the build system
|
|
config.minCommandBufferSizeMB = std::max(
|
|
config.minCommandBufferSizeMB,
|
|
uint32_t(FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB)); // NOLINT(*-include-cleaner)
|
|
|
|
config.perFrameCommandsSizeMB = std::max(
|
|
config.perFrameCommandsSizeMB,
|
|
uint32_t(FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB)); // NOLINT(*-include-cleaner)
|
|
|
|
config.perRenderPassArenaSizeMB = std::max(
|
|
config.perRenderPassArenaSizeMB,
|
|
uint32_t(FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB)); // NOLINT(*-include-cleaner)
|
|
|
|
config.commandBufferSizeMB = std::max(
|
|
config.commandBufferSizeMB,
|
|
config.minCommandBufferSizeMB * CONCURRENT_FRAME_COUNT);
|
|
|
|
// Enforce pre-render-pass arena rule-of-thumb
|
|
config.perRenderPassArenaSizeMB = std::max(
|
|
config.perRenderPassArenaSizeMB,
|
|
config.perFrameCommandsSizeMB + COMMAND_ARENA_OVERHEAD);
|
|
|
|
// This value gets validated during driver creation, so pass it through
|
|
config.driverHandleArenaSizeMB = config.driverHandleArenaSizeMB;
|
|
|
|
config.stereoscopicEyeCount =
|
|
std::clamp(config.stereoscopicEyeCount, uint8_t(1), CONFIG_MAX_STEREOSCOPIC_EYES);
|
|
|
|
return config;
|
|
}
|
|
|
|
} // namespace filament
|