Compare commits

..
Author SHA1 Message Date
Benjamin Doherty f3da96d424 Throw an NSException on Apple platforms when panicking 2024-03-27 16:04:49 -07:00
106 changed files with 2560 additions and 4241 deletions
-3
View File
@@ -819,9 +819,6 @@ if (IS_HOST_PLATFORM)
add_subdirectory(${TOOLS}/glslminifier)
add_subdirectory(${TOOLS}/matc)
add_subdirectory(${TOOLS}/matinfo)
if (NOT WIN32) # matedit not yet supported on Windows
add_subdirectory(${TOOLS}/matedit)
endif()
add_subdirectory(${TOOLS}/mipgen)
add_subdirectory(${TOOLS}/normal-blending)
add_subdirectory(${TOOLS}/resgen)
+1 -1
View File
@@ -8,4 +8,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- Add new matedit tool
- engine: Add experimental APIs `Engine::builder::paused()` and `Engine::setPaused()`
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.51.5'
implementation 'com.google.android.filament:filament-android:1.51.0'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.51.5'
pod 'Filament', '~> 1.51.0'
```
### Snapshots
-13
View File
@@ -7,19 +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.51.5
## v1.51.4
## v1.51.3
## v1.51.2
- engine: Add experimental APIs `Engine::builder::paused()` and `Engine::setPaused()`
## v1.51.1
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.51.5
VERSION_NAME=1.51.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+1 -32
View File
@@ -1397,7 +1397,7 @@ Type
: `string`
Value
: Any of `opaque`, `transparent`, `fade`, `add`, `masked`, `multiply`, `screen`, `custom`. Defaults to `opaque`.
: Any of `opaque`, `transparent`, `fade`, `add`, `masked`, `multiply`, `screen`. Defaults to `opaque`.
Description
: Defines how/if the rendered object is blended with the content of the render target.
@@ -1420,7 +1420,6 @@ Description
of the material's output defines whether a fragment is discarded or not. Additionally,
ALPHA_TO_COVERAGE is enabled for non-translucent views. See the maskThreshold section for more
information.
- **Custom**: blending is enabled. But the blending function is user specified. See `blendFunction`.
!!! Note
When `blending` is set to `masked`, alpha to coverage is automatically enabled for the material.
@@ -1433,36 +1432,6 @@ material {
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Blending and transparency: blendFunction
Type
: `object`
Fields
: `srcRGB`, `srcA`, `dstRGB`, `dstA`
Description
: - *srcRGB*: source function applied to the RGB channels
- *srcA*: source function applied to the alpha channel
- *srcRGB*: destination function applied to the RGB channels
- *srcRGB*: destination function applied to the alpha channel
The values possible for each functions are one of `zero`, `one`, `srcColor`, `oneMinusSrcColor`,
`dstColor`, `oneMinusDstColor`, `srcAlpha`, `oneMinusSrcAlpha`, `dstAlpha`,
`oneMinusDstAlpha`, `srcAlphaSaturate`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
blending : custom,
blendFunction :
{
srcRGB: one,
srcA: one,
dstRGB: oneMinusSrcColor,
dstA: oneMinusSrcAlpha
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Blending and transparency: postLightingBlending
Type
+2 -6
View File
@@ -71,8 +71,6 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMEN
include/backend/platforms/OpenGLPlatform.h
src/opengl/gl_headers.cpp
src/opengl/gl_headers.h
src/opengl/GLBufferObject.h
src/opengl/GLTexture.h
src/opengl/GLUtils.cpp
src/opengl/GLUtils.h
src/opengl/OpenGLBlobCache.cpp
@@ -168,10 +166,8 @@ endif()
if (FILAMENT_SUPPORTS_VULKAN)
list(APPEND SRCS
include/backend/platforms/VulkanPlatform.h
src/vulkan/caching/VulkanDescriptorSetManager.cpp
src/vulkan/caching/VulkanDescriptorSetManager.h
src/vulkan/caching/VulkanPipelineLayoutCache.cpp
src/vulkan/caching/VulkanPipelineLayoutCache.h
src/vulkan/caching/VulkanDescriptorSet.cpp
src/vulkan/caching/VulkanDescriptorSet.h
src/vulkan/platform/VulkanPlatform.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
@@ -158,16 +158,14 @@ static constexpr const char* backendToString(Backend backend) {
}
/**
* Defines the shader language. Similar to the above backend enum, with some differences:
* - The OpenGL backend can select between two shader languages: ESSL 1.0 and ESSL 3.0.
* - The Metal backend can prefer precompiled Metal libraries, while falling back to MSL.
* Defines the shader language. Similar to the above backend enum, but the OpenGL backend can select
* between two shader languages: ESSL 1.0 and ESSL 3.0.
*/
enum class ShaderLanguage {
ESSL1 = 0,
ESSL3 = 1,
SPIRV = 2,
MSL = 3,
METAL_LIBRARY = 4,
};
static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguage) {
@@ -180,8 +178,6 @@ static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguag
return "SPIR-V";
case ShaderLanguage::MSL:
return "MSL";
case ShaderLanguage::METAL_LIBRARY:
return "Metal precompiled library";
}
}
@@ -1241,6 +1237,9 @@ enum class Workaround : uint16_t {
DISABLE_BLIT_INTO_TEXTURE_ARRAY,
// Multiple workarounds needed for PowerVR GPUs
POWER_VR_SHADER_WORKAROUNDS,
// The driver has some threads pinned, and we can't easily know on which core, it can hurt
// performance more if we end-up pinned on the same one.
DISABLE_THREAD_AFFINITY
};
//! The type of technique for stereoscopic rendering
@@ -84,9 +84,6 @@ public:
// null terminating character.
Program& shader(ShaderStage shader, void const* data, size_t size);
// sets the language of the shader sources provided with shader() (defaults to ESSL3)
Program& shaderLanguage(ShaderLanguage shaderLanguage);
// Note: This is only needed for GLES3.0 backends, because the layout(binding=) syntax is
// not permitted in glsl. The backend needs a way to associate a uniform block
// to a binding point.
@@ -139,8 +136,6 @@ public:
utils::CString const& getName() const noexcept { return mName; }
utils::CString& getName() noexcept { return mName; }
auto const& getShaderLanguage() const { return mShaderLanguage; }
utils::FixedCapacityVector<SpecializationConstant> const& getSpecializationConstants() const noexcept {
return mSpecializationConstants;
}
@@ -160,7 +155,6 @@ private:
UniformBlockInfo mUniformBlocks = {};
SamplerGroupInfo mSamplerGroups = {};
ShaderSource mShadersSource;
ShaderLanguage mShaderLanguage = ShaderLanguage::ESSL3;
utils::CString mName;
uint64_t mCacheId{};
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)> mLogger;
@@ -134,7 +134,7 @@ struct CommandType<void (Driver::*)(ARGS...)> {
public:
template<typename M, typename D>
static inline void execute(M&& method, D&& driver, CommandBase* base, intptr_t* next) {
static inline void execute(M&& method, D&& driver, CommandBase* base, intptr_t* next) noexcept {
Command* self = static_cast<Command*>(base);
*next = align(sizeof(Command));
#if DEBUG_COMMAND_STREAM
@@ -168,7 +168,7 @@ struct CommandType<void (Driver::*)(ARGS...)> {
class CustomCommand : public CommandBase {
std::function<void()> mCommand;
static void execute(Driver&, CommandBase* base, intptr_t* next);
static void execute(Driver&, CommandBase* base, intptr_t* next) noexcept;
public:
inline CustomCommand(CustomCommand&& rhs) = default;
inline explicit CustomCommand(std::function<void()> cmd)
@@ -76,7 +76,7 @@ public:
// the fn function will execute a batch of driver commands
// this gives the driver a chance to wrap their execution in a meaningful manner
// the default implementation simply calls fn
virtual void execute(std::function<void(void)> const& fn);
virtual void execute(std::function<void(void)> const& fn) noexcept;
// This is called on debug build, or when enabled manually on the backend thread side.
virtual void debugCommandBegin(CommandStream* cmds,
@@ -65,7 +65,7 @@ public:
*
*/
template<typename D, typename ... ARGS>
Handle<D> allocateAndConstruct(ARGS&& ... args) {
Handle<D> allocateAndConstruct(ARGS&& ... args) noexcept {
Handle<D> h{ allocateHandle<D>() };
D* addr = handle_cast<D*>(h);
new(addr) D(std::forward<ARGS>(args)...);
@@ -97,7 +97,7 @@ public:
*/
template<typename D, typename B, typename ... ARGS>
typename std::enable_if_t<std::is_base_of_v<B, D>, D>*
destroyAndConstruct(Handle<B> const& handle, ARGS&& ... args) {
destroyAndConstruct(Handle<B> const& handle, ARGS&& ... args) noexcept {
assert_invariant(handle);
D* addr = handle_cast<D*>(const_cast<Handle<B>&>(handle));
assert_invariant(addr);
@@ -163,7 +163,7 @@ public:
inline typename std::enable_if_t<
std::is_pointer_v<Dp> &&
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
handle_cast(Handle<B>& handle) {
handle_cast(Handle<B>& handle) noexcept {
assert_invariant(handle);
auto [p, tag] = handleToPointer(handle.getId());
@@ -185,7 +185,7 @@ public:
inline typename std::enable_if_t<
std::is_pointer_v<Dp> &&
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
handle_cast(Handle<B> const& handle) {
handle_cast(Handle<B> const& handle) noexcept {
return handle_cast<Dp>(const_cast<Handle<B>&>(handle));
}
@@ -317,7 +317,7 @@ private:
return (id & HANDLE_HEAP_FLAG) == 0u;
}
HandleBase::HandleId allocateHandleSlow(size_t size);
HandleBase::HandleId allocateHandleSlow(size_t size) noexcept;
void deallocateHandleSlow(HandleBase::HandleId id, size_t size) noexcept;
// We inline this because it's just 4 instructions in the fast case
+1 -1
View File
@@ -149,7 +149,7 @@ void CommandType<void (Driver::*)(ARGS...)>::Command<METHOD>::log() noexcept {
// ------------------------------------------------------------------------------------------------
void CustomCommand::execute(Driver&, CommandBase* base, intptr_t* next) {
void CustomCommand::execute(Driver&, CommandBase* base, intptr_t* next) noexcept {
*next = CustomCommand::align(sizeof(CustomCommand));
static_cast<CustomCommand*>(base)->mCommand();
static_cast<CustomCommand*>(base)->~CustomCommand();
+1 -1
View File
@@ -214,7 +214,7 @@ size_t Driver::getElementTypeSize(ElementType type) noexcept {
Driver::~Driver() noexcept = default;
void Driver::execute(std::function<void(void)> const& fn) {
void Driver::execute(std::function<void(void)> const& fn) noexcept {
fn();
}
+1 -1
View File
@@ -107,7 +107,7 @@ void* HandleAllocator<P0, P1, P2>::handleToPointerSlow(HandleBase::HandleId id)
}
template <size_t P0, size_t P1, size_t P2>
HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size) {
HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size) noexcept {
void* p = ::malloc(size);
std::unique_lock lock(mLock);
+1 -6
View File
@@ -21,7 +21,7 @@ namespace filament::backend {
using namespace utils;
// We want these in the .cpp file, so they're not inlined (not worth it)
Program::Program() noexcept { // NOLINT(modernize-use-equals-default)
Program::Program() noexcept { // NOLINT(modernize-use-equals-default)
}
Program::Program(Program&& rhs) noexcept = default;
@@ -47,11 +47,6 @@ Program& Program::shader(ShaderStage shader, void const* data, size_t size) {
return *this;
}
Program& Program::shaderLanguage(ShaderLanguage shaderLanguage) {
mShaderLanguage = shaderLanguage;
return *this;
}
Program& Program::uniformBlockBindings(
FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& uniformBlockBindings) noexcept {
for (auto const& item : uniformBlockBindings) {
+1 -1
View File
@@ -1634,7 +1634,7 @@ void MetalDriver::bindPipeline(PipelineState ps) {
return;
}
functions.validate();
ASSERT_PRECONDITION(bool(functions), "Attempting to bind an invalid Metal program.");
auto [fragment, vertex] = functions.getRasterFunctions();
@@ -106,41 +106,26 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
continue;
}
assert_invariant(source[source.size() - 1] == '\0');
// the shader string is null terminated and the length includes the null character
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
length:source.size() - 1
encoding:NSUTF8StringEncoding];
// By default, Metal uses the most recent language version.
MTLCompileOptions* options = [MTLCompileOptions new];
// Disable Fast Math optimizations.
// This ensures that operations adhere to IEEE standards for floating-point arithmetic,
// which is crucial for half precision floats in scenarios where fast math optimizations
// lead to inaccuracies, such as in handling special values like NaN or Infinity.
options.fastMathEnabled = NO;
NSError* error = nil;
id<MTLLibrary> library = nil;
switch (program.getShaderLanguage()) {
case ShaderLanguage::MSL: {
// By default, Metal uses the most recent language version.
MTLCompileOptions* options = [MTLCompileOptions new];
// Disable Fast Math optimizations.
// This ensures that operations adhere to IEEE standards for floating-point
// arithmetic, which is crucial for half precision floats in scenarios where fast
// math optimizations lead to inaccuracies, such as in handling special values like
// NaN or Infinity.
options.fastMathEnabled = NO;
assert_invariant(source[source.size() - 1] == '\0');
// the shader string is null terminated and the length includes the null character
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
length:source.size() - 1
encoding:NSUTF8StringEncoding];
library = [device newLibraryWithSource:objcSource options:options error:&error];
break;
}
case ShaderLanguage::METAL_LIBRARY: {
dispatch_data_t data = dispatch_data_create(source.data(), source.size(),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
DISPATCH_DATA_DESTRUCTOR_DEFAULT);
library = [device newLibraryWithData:data error:&error];
break;
}
case ShaderLanguage::ESSL1:
case ShaderLanguage::ESSL3:
case ShaderLanguage::SPIRV:
break;
}
id<MTLLibrary> library = [device newLibraryWithSource:objcSource
options:options
error:&error];
if (library == nil) {
NSString* errorMessage = @"unknown error";
if (error) {
@@ -1,51 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_OPENGL_GLBUFFEROBJECT_H
#define TNT_FILAMENT_BACKEND_OPENGL_GLBUFFEROBJECT_H
#include "DriverBase.h"
#include "gl_headers.h"
#include <backend/DriverEnums.h>
#include <stdint.h>
namespace filament::backend {
struct GLBufferObject : public HwBufferObject {
using HwBufferObject::HwBufferObject;
GLBufferObject(uint32_t size,
BufferObjectBinding bindingType, BufferUsage usage) noexcept
: HwBufferObject(size), usage(usage), bindingType(bindingType) {
}
struct {
GLuint id;
union {
GLenum binding;
void* buffer;
};
} gl;
BufferUsage usage;
BufferObjectBinding bindingType;
uint16_t age = 0;
};
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_OPENGL_GLBUFFEROBJECT_H
-55
View File
@@ -1,55 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_OPENGL_GLTEXTURE_H
#define TNT_FILAMENT_BACKEND_OPENGL_GLTEXTURE_H
#include "DriverBase.h"
#include "gl_headers.h"
#include <backend/platforms/OpenGLPlatform.h>
#include <stdint.h>
namespace filament::backend {
struct GLTexture : public HwTexture {
using HwTexture::HwTexture;
struct GL {
GL() noexcept : imported(false), sidecarSamples(1), reserved(0) {}
GLuint id = 0; // texture or renderbuffer id
GLenum target = 0;
GLenum internalFormat = 0;
GLuint sidecarRenderBufferMS = 0; // multi-sample sidecar renderbuffer
// texture parameters go here too
GLfloat anisotropy = 1.0;
int8_t baseLevel = 127;
int8_t maxLevel = -1;
uint8_t targetIndex = 0; // optimization: index corresponding to target
bool imported : 1;
uint8_t sidecarSamples : 4;
uint8_t reserved : 3;
} gl;
OpenGLPlatform::ExternalTexture* externalTexture = nullptr;
};
} // namespace filament::backend
#endif //TNT_FILAMENT_BACKEND_OPENGL_GLTEXTURE_H
+3 -47
View File
@@ -64,8 +64,7 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
}
OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept
: mPlatform(platform),
mSamplerMap(32) {
: mPlatform(platform) {
state.vao.p = &mDefaultVAO;
@@ -253,23 +252,9 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept
}
OpenGLContext::~OpenGLContext() noexcept {
// note: this is called from the main thread. Can't do any GL calls.
delete mTimerQueryFactory;
}
void OpenGLContext::terminate() noexcept {
// note: this is called from the backend thread
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (!isES2()) {
for (auto& item: mSamplerMap) {
unbindSampler(item.second);
glDeleteSamplers(1, &item.second);
}
mSamplerMap.clear();
}
#endif
}
void OpenGLContext::destroyWithContext(
size_t index, std::function<void(OpenGLContext&)> const& closure) noexcept {
if (index == 0) {
@@ -531,6 +516,8 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
bugs->delay_fbo_destruction = true;
// PowerVR seems to have no problem with this (which is good for us)
bugs->allow_read_only_ancillary_feedback_loop = true;
// PowerVR has a shader compiler thread pinned on the last core
bugs->disable_thread_affinity = true;
} else if (strstr(renderer, "Apple")) {
// Apple GPU
} else if (strstr(renderer, "Tegra") ||
@@ -947,37 +934,6 @@ void OpenGLContext::deleteVertexArray(GLuint vao) noexcept {
}
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLuint OpenGLContext::getSamplerSlow(SamplerParams params) const noexcept {
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
using namespace GLUtils;
GLuint s;
glGenSamplers(1, &s);
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, (GLint)getTextureFilter(params.filterMin));
glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, (GLint)getTextureFilter(params.filterMag));
glSamplerParameteri(s, GL_TEXTURE_WRAP_S, (GLint)getWrapMode(params.wrapS));
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, (GLint)getWrapMode(params.wrapT));
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, (GLint)getWrapMode(params.wrapR));
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, (GLint)getTextureCompareMode(params.compareMode));
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, (GLint)getTextureCompareFunc(params.compareFunc));
#if defined(GL_EXT_texture_filter_anisotropic)
if (ext.EXT_texture_filter_anisotropic &&
!bugs.texture_filter_anisotropic_broken_on_sampler) {
GLfloat const anisotropy = float(1u << params.anisotropyLog2);
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT,
std::min(gets.max_anisotropy, anisotropy));
}
#endif
CHECK_GL_ERROR(utils::slog.e)
mSamplerMap[params] = s;
return s;
}
#endif
void OpenGLContext::resetState() noexcept {
// Force GL state to match the Filament state
+7 -33
View File
@@ -34,8 +34,6 @@
#include <math/vec2.h>
#include <math/vec4.h>
#include <tsl/robin_map.h>
#include <array>
#include <functional>
#include <optional>
@@ -92,11 +90,8 @@ public:
static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept;
explicit OpenGLContext(OpenGLPlatform& platform) noexcept;
~OpenGLContext() noexcept final;
void terminate() noexcept;
// TimerQueryInterface ------------------------------------------------------------------------
// note: OpenGLContext being final ensures (clang) these are not called through the vtable
@@ -311,6 +306,10 @@ public:
// a glFinish. So we must delay the destruction until we know the GPU is finished.
bool delay_fbo_destruction;
// The driver has some threads pinned, and we can't easily know on which core, it can hurt
// performance more if we end-up pinned on the same one.
bool disable_thread_affinity;
// Force feature level 0. Typically used for low end ES3 devices with significant driver
// bugs or performance issues.
bool force_feature_level0;
@@ -468,29 +467,6 @@ public:
void unbindEverything() noexcept;
void synchronizeStateAndCache(size_t index) noexcept;
void setEs2UniformBinding(size_t index, GLuint id, void const* data, uint16_t age) noexcept {
mUniformBindings[index] = { id, data, age };
}
auto getEs2UniformBinding(size_t index) const noexcept {
return mUniformBindings[index];
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLuint getSamplerSlow(SamplerParams sp) const noexcept;
inline GLuint getSampler(SamplerParams sp) const noexcept {
assert_invariant(!sp.padding0);
assert_invariant(!sp.padding1);
assert_invariant(!sp.padding2);
auto& samplerMap = mSamplerMap;
auto pos = samplerMap.find(sp);
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
return getSamplerSlow(sp);
}
return pos->second;
}
#endif
private:
OpenGLPlatform& mPlatform;
@@ -500,11 +476,6 @@ private:
std::vector<std::function<void(OpenGLContext&)>> mDestroyWithNormalContext;
RenderPrimitive mDefaultVAO;
std::optional<GLuint> mDefaultFbo[2];
std::array<
std::tuple<GLuint, void const*, uint16_t>,
CONFIG_UNIFORM_BINDING_COUNT> mUniformBindings = {};
mutable tsl::robin_map<SamplerParams, GLuint,
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept;
@@ -551,6 +522,9 @@ private:
{ bugs.delay_fbo_destruction,
"delay_fbo_destruction",
""},
{ bugs.disable_thread_affinity,
"disable_thread_affinity",
""},
{ bugs.force_feature_level0,
"force_feature_level0",
""},
+55 -18
View File
@@ -208,6 +208,7 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
mHandleAllocator("Handles",
driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck),
mSamplerMap(32),
mDriverConfig(driverConfig) {
std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr);
@@ -232,7 +233,6 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
}
OpenGLDriver::~OpenGLDriver() noexcept { // NOLINT(modernize-use-equals-default)
// this is called from the main thread. Can't call GL.
}
Dispatcher OpenGLDriver::getDispatcher() const noexcept {
@@ -263,9 +263,15 @@ void OpenGLDriver::terminate() {
// because we called glFinish(), all callbacks should have been executed
assert_invariant(mGpuCommandCompleteOps.empty());
#endif
mContext.terminate();
if (!getContext().isES2()) {
for (auto& item: mSamplerMap) {
mContext.unbindSampler(item.second);
glDeleteSamplers(1, &item.second);
}
mSamplerMap.clear();
}
#endif
mPlatform.terminate();
}
@@ -301,7 +307,7 @@ bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
if (UTILS_UNLIKELY(mContext.isES2())) {
for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) {
auto [id, buffer, age] = mContext.getEs2UniformBinding(i);
auto [id, buffer, age] = mUniformBindings[i];
if (buffer) {
p->updateUniforms(i, id, buffer, age);
}
@@ -718,19 +724,10 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
GLenum internalFormat = getInternalFormat(format);
assert_invariant(internalFormat);
if (any(usage & TextureUsage::PROTECTED)) {
if (UTILS_UNLIKELY(usage & TextureUsage::PROTECTED)) {
// renderbuffers don't have a protected mode, so we need to use a texture
// because protected textures are only supported on GLES 3.2, MSAA will be available.
usage |= TextureUsage::SAMPLEABLE;
} else if (any(usage & TextureUsage::UPLOADABLE)) {
// if we have the uploadable flag, we also need to use a texture
usage |= TextureUsage::SAMPLEABLE;
} else if (target != SamplerType::SAMPLER_2D) {
// renderbuffers can only be 2D
usage |= TextureUsage::SAMPLEABLE;
} else if (levels > 1) {
// renderbuffers can't have mip levels
usage |= TextureUsage::SAMPLEABLE;
}
auto& gl = mContext;
@@ -806,6 +803,13 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
textureStorage(t, w, h, depth, bool(usage & TextureUsage::PROTECTED));
}
} else {
assert_invariant(any(usage & (
TextureUsage::COLOR_ATTACHMENT |
TextureUsage::DEPTH_ATTACHMENT |
TextureUsage::STENCIL_ATTACHMENT)));
assert_invariant(levels == 1);
assert_invariant(target == SamplerType::SAMPLER_2D);
assert_invariant(none(usage & TextureUsage::PROTECTED));
t->gl.internalFormat = internalFormat;
t->gl.target = GL_RENDERBUFFER;
glGenRenderbuffers(1, &t->gl.id);
@@ -2059,6 +2063,8 @@ bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
return mContext.bugs.disable_blit_into_texture_array;
case Workaround::POWER_VR_SHADER_WORKAROUNDS:
return mContext.bugs.powervr_shader_workarounds;
case Workaround::DISABLE_THREAD_AFFINITY:
return mContext.bugs.disable_thread_affinity;
default:
return false;
}
@@ -2337,7 +2343,7 @@ void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
#endif
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (UTILS_LIKELY(!es2)) {
samplerId = mContext.getSampler(params);
samplerId = getSampler(params);
} else
#endif
{
@@ -3062,10 +3068,11 @@ void OpenGLDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t ind
assert_invariant(offset + size <= ub->byteCount);
if (UTILS_UNLIKELY(ub->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
gl.setEs2UniformBinding(index,
mUniformBindings[index] = {
ub->gl.id,
static_cast<uint8_t const*>(ub->gl.buffer) + offset,
ub->age);
ub->age,
};
} else {
GLenum const target = GLUtils::getBufferBindingType(bindingType);
@@ -3083,7 +3090,7 @@ void OpenGLDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index)
auto& gl = mContext;
if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
gl.setEs2UniformBinding(index, 0, nullptr, 0);
mUniformBindings[index] = {};
return;
}
@@ -3100,6 +3107,36 @@ void OpenGLDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
CHECK_GL_ERROR(utils::slog.e)
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
GLuint s;
glGenSamplers(1, &s);
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, (GLint)getTextureFilter(params.filterMin));
glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, (GLint)getTextureFilter(params.filterMag));
glSamplerParameteri(s, GL_TEXTURE_WRAP_S, (GLint)getWrapMode(params.wrapS));
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, (GLint)getWrapMode(params.wrapT));
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, (GLint)getWrapMode(params.wrapR));
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, (GLint)getTextureCompareMode(params.compareMode));
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, (GLint)getTextureCompareFunc(params.compareFunc));
#if defined(GL_EXT_texture_filter_anisotropic)
auto& gl = mContext;
if (gl.ext.EXT_texture_filter_anisotropic &&
!gl.bugs.texture_filter_anisotropic_broken_on_sampler) {
GLfloat const anisotropy = float(1u << params.anisotropyLog2);
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT,
std::min(gl.gets.max_anisotropy, anisotropy));
}
#endif
CHECK_GL_ERROR(utils::slog.e)
mSamplerMap[params] = s;
return s;
}
#endif
void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) {
#ifndef __EMSCRIPTEN__
#ifdef GL_EXT_debug_marker
+64 -8
View File
@@ -20,8 +20,6 @@
#include "DriverBase.h"
#include "OpenGLContext.h"
#include "OpenGLTimerQuery.h"
#include "GLBufferObject.h"
#include "GLTexture.h"
#include "ShaderCompilerService.h"
#include <backend/platforms/OpenGLPlatform.h>
@@ -94,6 +92,25 @@ public:
bool rec709 = false;
};
struct GLBufferObject : public HwBufferObject {
using HwBufferObject::HwBufferObject;
GLBufferObject(uint32_t size,
BufferObjectBinding bindingType, BufferUsage usage) noexcept
: HwBufferObject(size), usage(usage), bindingType(bindingType) {
}
struct {
GLuint id;
union {
GLenum binding;
void* buffer;
};
} gl;
BufferUsage usage;
BufferObjectBinding bindingType;
uint16_t age = 0;
};
struct GLVertexBufferInfo : public HwVertexBufferInfo {
GLVertexBufferInfo() noexcept = default;
GLVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
@@ -123,6 +140,7 @@ public:
} gl;
};
struct GLTexture;
struct GLSamplerGroup : public HwSamplerGroup {
using HwSamplerGroup::HwSamplerGroup;
struct Entry {
@@ -139,9 +157,27 @@ public:
Handle<HwVertexBufferInfo> vbih;
};
using GLBufferObject = filament::backend::GLBufferObject;
struct GLTexture : public HwTexture {
using HwTexture::HwTexture;
struct GL {
GL() noexcept : imported(false), sidecarSamples(1), reserved(0) {}
GLuint id = 0; // texture or renderbuffer id
GLenum target = 0;
GLenum internalFormat = 0;
GLuint sidecarRenderBufferMS = 0; // multi-sample sidecar renderbuffer
using GLTexture = filament::backend::GLTexture;
// texture parameters go here too
GLfloat anisotropy = 1.0;
int8_t baseLevel = 127;
int8_t maxLevel = -1;
uint8_t targetIndex = 0; // optimization: index corresponding to target
bool imported : 1;
uint8_t sidecarSamples : 4;
uint8_t reserved : 3;
} gl;
OpenGLPlatform::ExternalTexture* externalTexture = nullptr;
};
using GLTimerQuery = filament::backend::GLTimerQuery;
@@ -232,13 +268,13 @@ private:
HandleAllocatorGL mHandleAllocator;
template<typename D, typename ... ARGS>
Handle<D> initHandle(ARGS&& ... args) {
Handle<D> initHandle(ARGS&& ... args) noexcept {
return mHandleAllocator.allocateAndConstruct<D>(std::forward<ARGS>(args) ...);
}
template<typename D, typename B, typename ... ARGS>
typename std::enable_if<std::is_base_of<B, D>::value, D>::type*
construct(Handle<B> const& handle, ARGS&& ... args) {
construct(Handle<B> const& handle, ARGS&& ... args) noexcept {
return mHandleAllocator.destroyAndConstruct<D, B>(handle, std::forward<ARGS>(args) ...);
}
@@ -252,7 +288,7 @@ private:
typename std::enable_if_t<
std::is_pointer_v<Dp> &&
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
handle_cast(Handle<B>& handle) {
handle_cast(Handle<B>& handle) noexcept {
return mHandleAllocator.handle_cast<Dp, B>(handle);
}
@@ -260,7 +296,7 @@ private:
inline typename std::enable_if_t<
std::is_pointer_v<Dp> &&
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
handle_cast(Handle<B> const& handle) {
handle_cast(Handle<B> const& handle) noexcept {
return mHandleAllocator.handle_cast<Dp, B>(handle);
}
@@ -312,6 +348,22 @@ private:
void resolvePass(ResolveAction action, GLRenderTarget const* rt,
TargetBufferFlags discardFlags) noexcept;
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLuint getSamplerSlow(SamplerParams sp) const noexcept;
inline GLuint getSampler(SamplerParams sp) const noexcept {
assert_invariant(!sp.padding0);
assert_invariant(!sp.padding1);
assert_invariant(!sp.padding2);
auto& samplerMap = mSamplerMap;
auto pos = samplerMap.find(sp);
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
return getSamplerSlow(sp);
}
return pos->second;
}
#endif
const std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT>& getSamplerBindings() const {
return mSamplerBindings;
}
@@ -338,10 +390,14 @@ private:
// ES2 only. Uniform buffer emulation binding points
GLuint mLastAssignedEmulatedUboId = 0;
std::array<std::tuple<GLuint, void const*, uint16_t>, Program::UNIFORM_BINDING_COUNT> mUniformBindings = {};
// sampler buffer binding points (nullptr if not used)
std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT> mSamplerBindings = {}; // 4 pointers
mutable tsl::robin_map<SamplerParams, GLuint,
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
// this must be accessed from the driver thread only
std::vector<GLTexture*> mTexturesWithStreamsAttached;
+10 -12
View File
@@ -70,14 +70,14 @@ public:
}
}
struct {
GLuint program = 0;
} gl; // 12 bytes
// For ES2 only
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept;
void setRec709ColorSpace(bool rec709) const noexcept;
struct {
GLuint program = 0;
} gl; // 4 bytes
private:
// keep these away from of other class attributes
struct LazyInitializationData;
@@ -89,17 +89,14 @@ private:
void updateSamplers(OpenGLDriver* gld) const noexcept;
ShaderCompilerService::program_token_t mToken{};
// number of bindings actually used by this program
uint8_t mUsedBindingsCount = 0u;
UTILS_UNUSED uint8_t padding[3] = {};
std::array<uint8_t, Program::SAMPLER_BINDING_COUNT> mUsedSamplerBindingPoints; // 4 bytes
ShaderCompilerService::program_token_t mToken{}; // 16 bytes
uint8_t mUsedBindingsCount = 0u; // 1 byte
UTILS_UNUSED uint8_t padding[3] = {}; // 3 bytes
// only needed for ES2
GLint mRec709Location = -1; // 4 bytes
using LocationInfo = utils::FixedCapacityVector<GLint>;
struct UniformsRecord {
Program::UniformInfo uniforms;
@@ -108,10 +105,11 @@ private:
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
};
UniformsRecord const* mUniformsRecords = nullptr;
GLint mRec709Location = -1;
};
// if OpenGLProgram is larger tha 64 bytes, it'll fall in a larger Handle bucket.
static_assert(sizeof(OpenGLProgram) <= 64); // currently 48 bytes
static_assert(sizeof(OpenGLProgram) <= 64);
} // namespace filament::backend
@@ -17,7 +17,6 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANBLITTER_H
#define TNT_FILAMENT_BACKEND_VULKANBLITTER_H
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include <utils/compiler.h>
@@ -19,7 +19,6 @@
#include "VulkanContext.h"
#include "VulkanStagePool.h"
#include "VulkanMemory.h"
namespace filament::backend {
@@ -47,8 +47,7 @@ VulkanCmdFence::VulkanCmdFence(VkFence ifence)
VulkanCommandBuffer::VulkanCommandBuffer(VulkanResourceAllocator* allocator, VkDevice device,
VkCommandPool pool)
: mResourceManager(allocator),
mPipeline(VK_NULL_HANDLE) {
: mResourceManager(allocator) {
// Create the low-level command buffer.
const VkCommandBufferAllocateInfo allocateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
@@ -89,15 +89,6 @@ struct VulkanCommandBuffer {
inline void reset() {
fence.reset();
mResourceManager.clear();
mPipeline = VK_NULL_HANDLE;
}
inline void setPipeline(VkPipeline pipeline) {
mPipeline = pipeline;
}
inline VkPipeline pipeline() const {
return mPipeline;
}
inline VkCommandBuffer buffer() const {
@@ -112,7 +103,6 @@ struct VulkanCommandBuffer {
private:
VulkanAcquireOnlyResourceManager mResourceManager;
VkCommandBuffer mBuffer;
VkPipeline mPipeline;
};
// Allows classes to be notified after a new command buffer has been activated.
+2 -1
View File
@@ -19,6 +19,7 @@
#include "VulkanConstants.h"
#include "VulkanImageUtility.h"
#include "VulkanPipelineCache.h"
#include "VulkanUtility.h"
#include <utils/bitset.h>
@@ -41,7 +42,7 @@ struct VulkanTimerQuery;
struct VulkanCommandBuffer;
struct VulkanAttachment {
VulkanTexture* texture = nullptr;
VulkanTexture* texture;
uint8_t level = 0;
uint16_t layer = 0;
VkImage getImage() const;
+80 -51
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
#include "VulkanDriver.h"
#include "vulkan/VulkanDriver.h"
#include "CommandStreamDispatcher.h"
#include "DataReshaper.h"
@@ -102,15 +102,6 @@ VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevi
return emptyTexture;
}
VulkanBufferObject* createEmptyBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanCommands* commands) {
VulkanBufferObject* obj =
new VulkanBufferObject(allocator, stagePool, 1, BufferObjectBinding::UNIFORM);
uint8_t byte = 0;
obj->buffer.loadFromCpu(commands->get().buffer(), &byte, 0, 1);
return obj;
}
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
@@ -223,14 +214,12 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
mThreadSafeResourceManager(&mResourceAllocator),
mCommands(mPlatform->getDevice(), mPlatform->getGraphicsQueue(),
mPlatform->getGraphicsQueueFamilyIndex(), &mContext, &mResourceAllocator),
mPipelineLayoutCache(mPlatform->getDevice(), &mResourceAllocator),
mPipelineCache(mPlatform->getDevice(), mAllocator),
mPipelineCache(&mResourceAllocator),
mStagePool(mAllocator, &mCommands),
mFramebufferCache(mPlatform->getDevice()),
mSamplerCache(mPlatform->getDevice()),
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
mReadPixels(mPlatform->getDevice()),
mDescriptorSetManager(mPlatform->getDevice(), &mResourceAllocator),
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported) {
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
@@ -254,17 +243,13 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
#endif
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
mCommands.setObserver(&mPipelineCache);
mPipelineCache.setDevice(mPlatform->getDevice(), mAllocator);
mEmptyTexture = createEmptyTexture(mPlatform->getDevice(), mPlatform->getPhysicalDevice(),
mContext, mAllocator, &mCommands, mStagePool);
mEmptyBufferObject = createEmptyBufferObject(mAllocator, mStagePool, &mCommands);
mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture,
mEmptyBufferObject);
mGetPipelineFunction = [this](VulkanDescriptorSetLayoutList const& layouts) {
return mPipelineLayoutCache.getLayout(layouts);
};
mPipelineCache.setDummyTexture(mEmptyTexture->getPrimaryImageView());
}
VulkanDriver::~VulkanDriver() noexcept = default;
@@ -325,14 +310,13 @@ ShaderModel VulkanDriver::getShaderModel() const noexcept {
}
void VulkanDriver::terminate() {
delete mEmptyBufferObject;
delete mEmptyTexture;
// Command buffers should come first since it might have commands depending on resources that
// are about to be destroyed.
mCommands.terminate();
delete mEmptyTexture;
mResourceManager.clear();
mTimestamps.reset();
mBlitter.terminate();
@@ -345,12 +329,6 @@ void VulkanDriver::terminate() {
mPipelineCache.terminate();
mFramebufferCache.reset();
mSamplerCache.terminate();
mDescriptorSetManager.terminate();
mPipelineLayoutCache.terminate();
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
mResourceAllocator.print();
#endif
vmaDestroyAllocator(mAllocator);
@@ -382,8 +360,6 @@ void VulkanDriver::collectGarbage() {
mCommands.gc();
mStagePool.gc();
mFramebufferCache.gc();
mPipelineCache.gc();
mDescriptorSetManager.gc();
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
mResourceAllocator.print();
@@ -513,6 +489,9 @@ void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
return;
}
auto bufferObject = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
if (bufferObject->bindingType == BufferObjectBinding::UNIFORM) {
mPipelineCache.unbindUniformBuffer(bufferObject->buffer.getGpuBuffer());
}
mResourceManager.release(bufferObject);
}
@@ -563,7 +542,6 @@ void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
return;
}
auto vkprogram = mResourceAllocator.handle_cast<VulkanProgram*>(ph);
mDescriptorSetManager.clearProgram(vkprogram);
mResourceManager.release(vkprogram);
}
@@ -1451,7 +1429,12 @@ void VulkanDriver::endRenderPass(int) {
0, 1, &barrier, 0, nullptr, 0, nullptr);
}
mDescriptorSetManager.clearState();
if (mCurrentRenderPass.currentSubpass > 0) {
for (uint32_t i = 0; i < VulkanPipelineCache::INPUT_ATTACHMENT_COUNT; i++) {
mPipelineCache.bindInputAttachment(i, {});
}
mCurrentRenderPass.currentSubpass = 0;
}
mCurrentRenderPass.renderTarget = nullptr;
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
FVK_SYSTRACE_END();
@@ -1470,9 +1453,15 @@ void VulkanDriver::nextSubpass(int) {
mPipelineCache.bindRenderPass(mCurrentRenderPass.renderPass,
++mCurrentRenderPass.currentSubpass);
if (mCurrentRenderPass.params.subpassMask & 0x1) {
VulkanAttachment subpassInput = renderTarget->getColor(0);
mDescriptorSetManager.updateInputAttachment({}, subpassInput);
for (uint32_t i = 0; i < VulkanPipelineCache::INPUT_ATTACHMENT_COUNT; i++) {
if ((1 << i) & mCurrentRenderPass.params.subpassMask) {
VulkanAttachment subpassInput = renderTarget->getColor(i);
VkDescriptorImageInfo info = {
.imageView = subpassInput.getImageView(VK_IMAGE_ASPECT_COLOR_BIT),
.imageLayout = ImgUtil::getVkLayout(subpassInput.getLayout()),
};
mPipelineCache.bindInputAttachment(i, info);
}
}
}
@@ -1512,24 +1501,25 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
void VulkanDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh) {
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
VkDeviceSize const offset = 0;
VkDeviceSize const size = VK_WHOLE_SIZE;
mDescriptorSetManager.updateBuffer({}, (uint32_t) index, bo, offset, size);
const VkDeviceSize offset = 0;
const VkDeviceSize size = VK_WHOLE_SIZE;
mPipelineCache.bindUniformBufferObject((uint32_t) index, bo, offset, size);
}
void VulkanDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
Handle<HwBufferObject> boh, uint32_t offset, uint32_t size) {
assert_invariant(bindingType == BufferObjectBinding::UNIFORM);
assert_invariant(bindingType == BufferObjectBinding::SHADER_STORAGE ||
bindingType == BufferObjectBinding::UNIFORM);
// TODO: implement BufferObjectBinding::SHADER_STORAGE case
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
mDescriptorSetManager.updateBuffer({}, (uint32_t) index, bo, offset, size);
mPipelineCache.bindUniformBufferObject((uint32_t)index, bo, offset, size);
}
void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
mDescriptorSetManager.clearBuffer((uint32_t) index);
// TODO: implement unbindBuffer()
}
void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
@@ -1777,14 +1767,22 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
// where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract
// Filament concept used to form groups of samplers.
VkDescriptorImageInfo samplerInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex();
VulkanPipelineCache::UsageFlags usage = program->getUsage();
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
auto const& bindingToName = program->getBindingToName();
#endif
for (auto binding: program->getBindings()) {
UTILS_NOUNROLL
for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) {
uint16_t const indexPair = bindingToSamplerIndex[binding];
if (indexPair == 0xffff) {
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
continue;
}
@@ -1793,14 +1791,18 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupInd];
if (!vksb) {
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
continue;
}
SamplerDescriptor const* boundSampler = ((SamplerDescriptor*) vksb->sb->data()) + samplerInd;
if (UTILS_UNLIKELY(!boundSampler->t)) {
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
continue;
}
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
VkImageViewType const expectedType = texture->getViewType();
// TODO: can this uninitialized check be checked in a higher layer?
// This fallback path is very flaky because the dummy texture might not have
@@ -1814,20 +1816,43 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
texture = mEmptyTexture;
}
SamplerParams const& samplerParams = boundSampler->s;
VkSampler const vksampler = mSamplerCache.getSampler(samplerParams);
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
VulkanDriver::DebugUtils::setName(VK_OBJECT_TYPE_SAMPLER,
reinterpret_cast<uint64_t>(vksampler), bindingToName[binding].c_str());
VulkanDriver::DebugUtils::setName(VK_OBJECT_TYPE_SAMPLER,
reinterpret_cast<uint64_t>(samplerInfo.sampler), bindingToName[binding].c_str());
#endif
VkSampler const vksampler = mSamplerCache.getSampler(boundSampler->s);
VkImageView imageView = VK_NULL_HANDLE;
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
// an attachment, then the sampler for this texture has the same view properties as a
// view for an attachment. Therefore, we can use getAttachmentView to get a
// corresponding VkImageView.
imageView = texture->getAttachmentView(range);
} else {
imageView = texture->getViewForType(range, expectedType);
}
mDescriptorSetManager.updateSampler({}, binding, texture, vksampler);
samplerInfo[binding] = {
.sampler = vksampler,
.imageView = imageView,
.imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout())
};
samplerTextures[binding] = texture;
}
mPipelineCache.bindLayout(mDescriptorSetManager.bind(commands, program, mGetPipelineFunction));
mPipelineCache.bindPipeline(commands);
mPipelineCache.bindSamplers(samplerInfo, samplerTextures, usage);
// Bind a new pipeline if the pipeline state changed.
// If allocation failed, skip the draw call and bail. We do not emit an error since the
// validation layer will already do so.
if (!mPipelineCache.bindPipeline(commands)) {
return;
}
FVK_SYSTRACE_END();
}
@@ -1867,8 +1892,12 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
VulkanCommandBuffer& commands = mCommands.get();
VkCommandBuffer cmdbuffer = commands.buffer();
// Bind "dynamic" UBOs if they need to change.
mDescriptorSetManager.dynamicBind(&commands, {});
// Bind new descriptor sets if they need to change.
// If descriptor set allocation failed, skip the draw call and bail. No need to emit an error
// message since the validation layers already do so.
if (!mPipelineCache.bindDescriptors(cmdbuffer)) {
return;
}
// Finally, make the actual draw call. TODO: support subranges
const uint32_t firstIndex = indexOffset;
+1 -11
View File
@@ -28,8 +28,6 @@
#include "VulkanSamplerCache.h"
#include "VulkanStagePool.h"
#include "VulkanUtility.h"
#include "caching/VulkanDescriptorSetManager.h"
#include "caching/VulkanPipelineLayoutCache.h"
#include "DriverBase.h"
#include "private/backend/Driver.h"
@@ -72,8 +70,6 @@ public:
#endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
private:
static constexpr uint8_t MAX_SAMPLER_BINDING_COUNT = Program::SAMPLER_BINDING_COUNT;
void debugCommandBegin(CommandStream* cmds, bool synchronous,
const char* methodName) noexcept override;
@@ -110,9 +106,7 @@ private:
VulkanPlatform* mPlatform = nullptr;
std::unique_ptr<VulkanTimestamps> mTimestamps;
// Placeholder resources
VulkanTexture* mEmptyTexture;
VulkanBufferObject* mEmptyBufferObject;
VulkanSwapChain* mCurrentSwapChain = nullptr;
VulkanRenderTarget* mDefaultRenderTarget = nullptr;
@@ -129,17 +123,13 @@ private:
VulkanThreadSafeResourceManager mThreadSafeResourceManager;
VulkanCommands mCommands;
VulkanPipelineLayoutCache mPipelineLayoutCache;
VulkanPipelineCache mPipelineCache;
VulkanStagePool mStagePool;
VulkanFboCache mFramebufferCache;
VulkanSamplerCache mSamplerCache;
VulkanBlitter mBlitter;
VulkanSamplerGroup* mSamplerBindings[MAX_SAMPLER_BINDING_COUNT] = {};
VulkanSamplerGroup* mSamplerBindings[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
VulkanReadPixels mReadPixels;
VulkanDescriptorSetManager mDescriptorSetManager;
VulkanDescriptorSetManager::GetPipelineLayoutFunction mGetPipelineFunction;
bool const mIsSRGBSwapChainSupported;
};
+22 -168
View File
@@ -20,9 +20,7 @@
#include "VulkanConstants.h"
#include "VulkanDriver.h"
#include "VulkanMemory.h"
#include "VulkanUtility.h"
#include "spirv/VulkanSpirvUtils.h"
#include "utils/Log.h"
#include <backend/platforms/VulkanPlatform.h>
@@ -32,17 +30,15 @@ using namespace bluevk;
namespace filament::backend {
namespace {
void flipVertically(VkRect2D* rect, uint32_t framebufferHeight) {
static void flipVertically(VkRect2D* rect, uint32_t framebufferHeight) {
rect->offset.y = framebufferHeight - rect->offset.y - rect->extent.height;
}
void flipVertically(VkViewport* rect, uint32_t framebufferHeight) {
static void flipVertically(VkViewport* rect, uint32_t framebufferHeight) {
rect->y = framebufferHeight - rect->y - rect->height;
}
void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) {
static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) {
int32_t x = std::max(rect->offset.x, 0);
int32_t y = std::max(rect->offset.y, 0);
int32_t right = std::min(rect->offset.x + (int32_t) rect->extent.width, (int32_t) fbWidth);
@@ -53,125 +49,17 @@ void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeight) {
rect->extent.height = std::max(top - y, 0);
}
template<typename Bitmask>
static constexpr Bitmask fromStageFlags(ShaderStageFlags2 flags, uint8_t binding) {
Bitmask ret = 0;
if (flags & ShaderStageFlags2::VERTEX) {
ret |= (getVertexStage<Bitmask>() << binding);
}
if (flags & ShaderStageFlags2::FRAGMENT) {
ret |= (getFragmentStage<Bitmask>() << binding);
}
return ret;
}
UsageFlags getUsageFlags(uint16_t binding, ShaderStageFlags flags, UsageFlags src) {
// NOTE: if you modify this function, you also need to modify getShaderStageFlags.
assert_invariant(binding < MAX_SAMPLER_COUNT);
if (any(flags & ShaderStageFlags::VERTEX)) {
src.set(binding);
}
if (any(flags & ShaderStageFlags::FRAGMENT)) {
src.set(MAX_SAMPLER_COUNT + binding);
}
// TODO: add support for compute by extending SHADER_MODULE_COUNT and ensuring UsageFlags
// has 186 bits (MAX_SAMPLER_COUNT * 3)
// assert_invariant(!any(flags & ~(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)));
return src;
}
constexpr decltype(VulkanProgram::MAX_SHADER_MODULES) MAX_SHADER_MODULES =
VulkanProgram::MAX_SHADER_MODULES;
using LayoutDescriptionList = VulkanProgram::LayoutDescriptionList;
template<typename Bitmask>
void addDescriptors(Bitmask mask,
utils::FixedCapacityVector<DescriptorSetLayoutBinding>& outputList) {
constexpr uint8_t MODULE_OFFSET = (sizeof(Bitmask) * 8) / MAX_SHADER_MODULES;
for (uint8_t i = 0; i < MODULE_OFFSET; ++i) {
bool const hasVertex = (mask & (1ULL << i)) != 0;
bool const hasFragment = (mask & (1ULL << (MODULE_OFFSET + i))) != 0;
if (!hasVertex && !hasFragment) {
continue;
}
DescriptorSetLayoutBinding binding{
.binding = i,
.flags = DescriptorFlags::NONE,
.count = 0,// This is always 0 for now as we pass the size of the UBOs in the Driver API
// instead.
};
if (hasVertex) {
binding.stageFlags = ShaderStageFlags2::VERTEX;
}
if (hasFragment) {
binding.stageFlags = static_cast<ShaderStageFlags2>(
binding.stageFlags | ShaderStageFlags2::FRAGMENT);
}
if constexpr (std::is_same_v<Bitmask, UniformBufferBitmask>) {
binding.type = DescriptorType::UNIFORM_BUFFER;
} else if constexpr (std::is_same_v<Bitmask, SamplerBitmask>) {
binding.type = DescriptorType::SAMPLER;
} else if constexpr (std::is_same_v<Bitmask, InputAttachmentBitmask>) {
binding.type = DescriptorType::INPUT_ATTACHMENT;
}
outputList.push_back(binding);
}
}
inline VkDescriptorSetLayout createDescriptorSetLayout(VkDevice device,
VkDescriptorSetLayoutCreateInfo const& info) {
VkDescriptorSetLayout layout;
vkCreateDescriptorSetLayout(device, &info, VKALLOC, &layout);
return layout;
}
} // anonymous namespace
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info,
Bitmask const& bitmask)
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT),
mDevice(device),
vklayout(createDescriptorSetLayout(device, info)),
bitmask(bitmask),
bindings(getBindings(bitmask)),
count(Count::fromLayoutBitmask(bitmask)) {
}
VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() {
vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC);
}
VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
: HwProgram(builder.getName()),
VulkanResource(VulkanResourceType::PROGRAM),
mInfo(new PipelineInfo()),
mDevice(device) {
constexpr uint8_t UBO_MODULE_OFFSET = (sizeof(UniformBufferBitmask) * 8) / MAX_SHADER_MODULES;
constexpr uint8_t SAMPLER_MODULE_OFFSET = (sizeof(SamplerBitmask) * 8) / MAX_SHADER_MODULES;
constexpr uint8_t INPUT_ATTACHMENT_MODULE_OFFSET =
(sizeof(InputAttachmentBitmask) * 8) / MAX_SHADER_MODULES;
Program::ShaderSource const& blobs = builder.getShadersSource();
auto& modules = mInfo->shaders;
auto const& specializationConstants = builder.getSpecializationConstants();
std::vector<uint32_t> shader;
// TODO: this will be moved out of the shader as the descriptor set layout will be provided by
// Filament instead of parsed from the shaders. See [GDSR] in VulkanDescriptorSetManager.h
UniformBufferBitmask uboMask = 0;
SamplerBitmask samplerMask = 0;
InputAttachmentBitmask inputAttachmentMask = 0;
static_assert(static_cast<ShaderStage>(0) == ShaderStage::VERTEX &&
static_cast<ShaderStage>(1) == ShaderStage::FRAGMENT &&
MAX_SHADER_MODULES == 2);
for (size_t i = 0; i < MAX_SHADER_MODULES; i++) {
Program::ShaderBlob const& blob = blobs[i];
@@ -184,12 +72,6 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
dataSize = shader.size() * 4;
}
auto const [ubo, sampler, inputAttachment] = getProgramBindings(blob);
uboMask |= (static_cast<UniformBufferBitmask>(ubo) << (UBO_MODULE_OFFSET * i));
samplerMask |= (static_cast<SamplerBitmask>(sampler) << (SAMPLER_MODULE_OFFSET * i));
inputAttachmentMask |= (static_cast<InputAttachmentBitmask>(inputAttachment)
<< (INPUT_ATTACHMENT_MODULE_OFFSET * i));
VkShaderModule& module = modules[i];
VkShaderModuleCreateInfo moduleInfo = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
@@ -217,25 +99,12 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
#endif
}
LayoutDescriptionList& layouts = mInfo->layouts;
layouts[0].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
countBits(collapseStages(uboMask)));
layouts[1].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
countBits(collapseStages(samplerMask)));
layouts[2].bindings = utils::FixedCapacityVector<DescriptorSetLayoutBinding>::with_capacity(
countBits(collapseStages(inputAttachmentMask)));
addDescriptors(uboMask, layouts[0].bindings);
addDescriptors(samplerMask, layouts[1].bindings);
addDescriptors(inputAttachmentMask, layouts[2].bindings);
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
auto& bindingToName = mInfo->bindingToName;
#endif
auto& groupInfo = builder.getSamplerGroupInfo();
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
auto& bindings = mInfo->bindings;
auto& usage = mInfo->usage;
for (uint8_t groupInd = 0; groupInd < Program::SAMPLER_BINDING_COUNT; groupInd++) {
auto const& group = groupInfo[groupInd];
@@ -243,9 +112,7 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
for (size_t i = 0; i < samplers.size(); ++i) {
uint32_t const binding = samplers[i].binding;
bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i);
assert_invariant(bindings.find(binding) == bindings.end());
bindings.insert(binding);
usage = getUsageFlags(binding, group.stageFlags, usage);
usage = VulkanPipelineCache::getUsageFlags(binding, group.stageFlags, usage);
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
bindingToName[binding] = samplers[i].name.c_str();
@@ -259,6 +126,24 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
#endif
}
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
CustomSamplerInfoList const& samplerInfo) noexcept
: VulkanResource(VulkanResourceType::PROGRAM),
mInfo(new PipelineInfo()),
mDevice(device) {
mInfo->shaders[0] = vs;
mInfo->shaders[1] = fs;
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
auto& usage = mInfo->usage;
bindingToSamplerIndex.resize(samplerInfo.size());
for (uint16_t binding = 0; binding < samplerInfo.size(); ++binding) {
auto const& sampler = samplerInfo[binding];
bindingToSamplerIndex[binding]
= (sampler.groupIndex << 8) | (0xff & sampler.samplerIndex);
usage = VulkanPipelineCache::getUsageFlags(binding, sampler.flags, usage);
}
}
VulkanProgram::~VulkanProgram() {
for (auto shader: mInfo->shaders) {
vkDestroyShaderModule(mDevice, shader, VKALLOC);
@@ -520,35 +405,4 @@ VulkanRenderPrimitive::VulkanRenderPrimitive(VulkanResourceAllocator* resourceAl
mResources.acquire(indexBuffer);
}
using Bitmask = VulkanDescriptorSetLayout::Bitmask;
Bitmask Bitmask::fromBackendLayout(descset::DescriptorSetLayout const& layout) {
Bitmask mask;
for (auto const& binding: layout.bindings) {
switch (binding.type) {
case descset::DescriptorType::UNIFORM_BUFFER: {
if (binding.flags == descset::DescriptorFlags::DYNAMIC_OFFSET) {
mask.dynamicUbo |= fromStageFlags<UniformBufferBitmask>(binding.stageFlags,
binding.binding);
} else {
mask.ubo |= fromStageFlags<UniformBufferBitmask>(binding.stageFlags,
binding.binding);
}
break;
}
case descset::DescriptorType::SAMPLER: {
mask.sampler |= fromStageFlags<SamplerBitmask>(binding.stageFlags, binding.binding);
break;
}
case descset::DescriptorType::INPUT_ATTACHMENT: {
mask.inputAttachment |=
fromStageFlags<InputAttachmentBitmask>(binding.stageFlags, binding.binding);
break;
}
}
}
return mask;
}
} // namespace filament::backend
+17 -175
View File
@@ -21,171 +21,35 @@
#include "DriverBase.h"
#include "VulkanBuffer.h"
#include "VulkanPipelineCache.h"
#include "VulkanResources.h"
#include "VulkanSwapChain.h"
#include "VulkanTexture.h"
#include "VulkanUtility.h"
#include "private/backend/SamplerGroup.h"
#include "utils/FixedCapacityVector.h"
#include "vulkan/vulkan_core.h"
#include <utils/Mutex.h>
#include <utils/StructureOfArrays.h>
namespace filament::backend {
using namespace descset;
class VulkanTimestamps;
struct VulkanDescriptorSetLayout : public VulkanResource {
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT = 3;
// The bitmask representation of a set layout.
struct Bitmask {
UniformBufferBitmask ubo = 0; // 4 bytes
UniformBufferBitmask dynamicUbo = 0; // 4 bytes
SamplerBitmask sampler = 0; // 8 bytes
InputAttachmentBitmask inputAttachment = 0; // 1 bytes
// Because we're using this struct as hash key, must make it's 8-bytes aligned, with no
// unaccounted bytes.
uint8_t padding0 = 0; // 1 bytes
uint16_t padding1 = 0;// 2 bytes
uint32_t padding2 = 0;// 4 bytes
bool operator==(Bitmask const& right) const {
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
inputAttachment == right.inputAttachment;
}
static Bitmask fromBackendLayout(descset::DescriptorSetLayout const& layout);
};
// This is a convenience struct to quickly check layout compatibility in terms of descriptor set
// pools.
struct Count {
uint32_t ubo = 0;
uint32_t dynamicUbo = 0;
uint32_t sampler = 0;
uint32_t inputAttachment = 0;
bool operator==(Count const& right) const noexcept {
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
inputAttachment == right.inputAttachment;
}
static inline Count fromLayoutBitmask(Bitmask const& mask) {
return {
.ubo = countBits(collapseStages(mask.ubo)),
.dynamicUbo = countBits(collapseStages(mask.dynamicUbo)),
.sampler = countBits(collapseStages(mask.sampler)),
.inputAttachment = countBits(collapseStages(mask.inputAttachment)),
};
}
Count operator*(uint16_t mult) const noexcept {
// TODO: check for overflow.
Count ret;
ret.ubo = ubo * mult;
ret.dynamicUbo = dynamicUbo * mult;
ret.sampler = sampler * mult;
ret.inputAttachment = inputAttachment * mult;
return ret;
}
};
static_assert(sizeof(Bitmask) % 8 == 0);
explicit VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info,
Bitmask const& bitmask);
~VulkanDescriptorSetLayout();
VkDevice const mDevice;
VkDescriptorSetLayout const vklayout;
Bitmask const bitmask;
// This is a convenience struct so that we don't have to iterate through all the bits of the
// bitmask (which correspondings to binding indices).
struct _Bindings {
utils::FixedCapacityVector<uint8_t> const ubo;
utils::FixedCapacityVector<uint8_t> const dynamicUbo;
utils::FixedCapacityVector<uint8_t> const sampler;
utils::FixedCapacityVector<uint8_t> const inputAttachment;
} bindings;
Count const count;
private:
template <typename MaskType>
utils::FixedCapacityVector<uint8_t> bits(MaskType mask) {
utils::FixedCapacityVector<uint8_t> ret =
utils::FixedCapacityVector<uint8_t>::with_capacity(countBits(mask));
for (uint8_t i = 0; i < sizeof(mask) * 4; ++i) {
if (mask & (1 << i)) {
ret.push_back(i);
}
}
return ret;
}
_Bindings getBindings(Bitmask const& bitmask) {
auto const uboCollapsed = collapseStages(bitmask.ubo);
auto const dynamicUboCollapsed = collapseStages(bitmask.dynamicUbo);
auto const samplerCollapsed = collapseStages(bitmask.sampler);
auto const inputAttachmentCollapsed = collapseStages(bitmask.inputAttachment);
return {
bits(uboCollapsed),
bits(dynamicUboCollapsed),
bits(samplerCollapsed),
bits(inputAttachmentCollapsed),
};
}
};
using VulkanDescriptorSetLayoutList = std::array<Handle<VulkanDescriptorSetLayout>,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
struct VulkanDescriptorSet : public VulkanResource {
public:
// Because we need to recycle descriptor sets not used, we allow for a callback that the "Pool"
// can use to repackage the vk handle.
using OnRecycle = std::function<void()>;
VulkanDescriptorSet(VulkanResourceAllocator* allocator,
VkDescriptorSet rawSet, OnRecycle&& onRecycleFn)
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET),
resources(allocator),
vkSet(rawSet),
mOnRecycleFn(std::move(onRecycleFn)) {}
~VulkanDescriptorSet() {
if (mOnRecycleFn) {
mOnRecycleFn();
}
}
// TODO: maybe change to fixed size for performance.
VulkanAcquireOnlyResourceManager resources;
VkDescriptorSet const vkSet;
private:
OnRecycle mOnRecycleFn;
};
using VulkanDescriptorSetList = std::array<Handle<VulkanDescriptorSet>,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
struct VulkanProgram : public HwProgram, VulkanResource {
using BindingList = CappedArray<uint16_t, MAX_SAMPLER_COUNT>;
VulkanProgram(VkDevice device, Program const& builder) noexcept;
struct CustomSamplerInfo {
uint8_t groupIndex;
uint8_t samplerIndex;
ShaderStageFlags flags;
};
using CustomSamplerInfoList = utils::FixedCapacityVector<CustomSamplerInfo>;
// We allow custom descriptor of the samplers within shaders. This is needed if we want to use
// a program that exists only in the backend - for example, for shader-based bliting.
VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
CustomSamplerInfoList const& samplerInfo) noexcept;
~VulkanProgram();
inline VkShaderModule getVertexShader() const {
@@ -194,61 +58,39 @@ struct VulkanProgram : public HwProgram, VulkanResource {
inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; }
inline VulkanPipelineCache::UsageFlags getUsage() const { return mInfo->usage; }
inline utils::FixedCapacityVector<uint16_t> const& getBindingToSamplerIndex() const {
return mInfo->bindingToSamplerIndex;
}
inline UsageFlags getUsage() const {
return mInfo->usage;
}
// Get a list of the sampler binding indices so that we don't have to loop through all possible
// samplers.
inline BindingList const& getBindings() const { return mInfo->bindings; }
// TODO: this is currently not used. This will replace getLayoutDescriptionList below.
// inline descset::DescriptorSetLayout const& getLayoutDescription() const {
// return mInfo->layout;
// }
// In the usual case, we would have just one layout per program. But in the current setup, we
// have a set/layout for each descriptor type. This will be changed in the future.
using LayoutDescriptionList = std::array<descset::DescriptorSetLayout,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
inline LayoutDescriptionList const& getLayoutDescriptionList() const { return mInfo->layouts; }
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
inline utils::FixedCapacityVector<std::string> const& getBindingToName() const {
return mInfo->bindingToName;
}
#endif
private:
// TODO: handle compute shaders.
// The expected order of shaders - from frontend to backend - is vertex, fragment, compute.
static constexpr uint8_t const MAX_SHADER_MODULES = 2;
private:
struct PipelineInfo {
PipelineInfo()
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff)
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
, bindingToName(MAX_SAMPLER_COUNT, "")
#endif
#endif
{}
// This bitset maps to each of the sampler in the sampler groups associated with this
// program, and whether each sampler is used in which shader (i.e. vert, frag, compute).
UsageFlags usage;
BindingList bindings;
VulkanPipelineCache::UsageFlags usage;
// We store the samplerGroupIndex as the top 8-bit and the index within each group as the lower 8-bit.
utils::FixedCapacityVector<uint16_t> bindingToSamplerIndex;
VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE };
// TODO: Use this instead of `layouts` after Filament-side Descriptor Set API is in place.
// descset::DescriptorSetLayout layout;
LayoutDescriptionList layouts;
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
// We store the sampler name mapped from binding index (only for debug purposes).
utils::FixedCapacityVector<std::string> bindingToName;
@@ -31,8 +31,4 @@
#include "vk_mem_alloc.h"
VK_DEFINE_HANDLE(VmaAllocator)
VK_DEFINE_HANDLE(VmaAllocation)
VK_DEFINE_HANDLE(VmaPool)
#endif // TNT_FILAMENT_BACKEND_VULKANMEMORY_H
@@ -14,9 +14,8 @@
* limitations under the License.
*/
#include "VulkanPipelineCache.h"
#include "VulkanMemory.h"
#include "caching/VulkanDescriptorSetManager.h"
#include "vulkan/VulkanMemory.h"
#include "vulkan/VulkanPipelineCache.h"
#include <utils/Log.h>
#include <utils/Panic.h>
@@ -35,9 +34,63 @@ using namespace bluevk;
namespace filament::backend {
VulkanPipelineCache::VulkanPipelineCache(VkDevice device, VmaAllocator allocator)
: mDevice(device),
mAllocator(allocator) {
static VkShaderStageFlags getShaderStageFlags(VulkanPipelineCache::UsageFlags key, uint16_t binding) {
// NOTE: if you modify this function, you also need to modify getUsageFlags.
assert_invariant(binding < MAX_SAMPLER_COUNT);
VkShaderStageFlags flags = 0;
if (key.test(binding)) {
flags |= VK_SHADER_STAGE_VERTEX_BIT;
}
if (key.test(MAX_SAMPLER_COUNT + binding)) {
flags |= VK_SHADER_STAGE_FRAGMENT_BIT;
}
return flags;
}
VulkanPipelineCache::UsageFlags
VulkanPipelineCache::getUsageFlags(uint16_t binding, ShaderStageFlags flags, UsageFlags src) {
// NOTE: if you modify this function, you also need to modify getShaderStageFlags.
assert_invariant(binding < MAX_SAMPLER_COUNT);
if (any(flags & ShaderStageFlags::VERTEX)) {
src.set(binding);
}
if (any(flags & ShaderStageFlags::FRAGMENT)) {
src.set(MAX_SAMPLER_COUNT + binding);
}
// TODO: add support for compute by extending SHADER_MODULE_COUNT and ensuring UsageFlags
// has 186 bits (MAX_SAMPLER_COUNT * 3)
// assert_invariant(!any(flags & ~(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)));
return src;
}
VulkanPipelineCache::UsageFlags VulkanPipelineCache::disableUsageFlags(uint16_t binding,
UsageFlags src) {
src.unset(binding);
src.unset(MAX_SAMPLER_COUNT + binding);
return src;
}
VulkanPipelineCache::VulkanPipelineCache(VulkanResourceAllocator* allocator)
: mResourceAllocator(allocator),
mPipelineBoundResources(allocator) {
mDummyBufferWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
mDummyBufferWriteInfo.pNext = nullptr;
mDummyBufferWriteInfo.dstArrayElement = 0;
mDummyBufferWriteInfo.descriptorCount = 1;
mDummyBufferWriteInfo.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
mDummyBufferWriteInfo.pImageInfo = nullptr;
mDummyBufferWriteInfo.pBufferInfo = &mDummyBufferInfo;
mDummyBufferWriteInfo.pTexelBufferView = nullptr;
mDummyTargetInfo.imageLayout = VulkanImageUtility::getVkLayout(VulkanLayout::READ_ONLY);
mDummyTargetWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
mDummyTargetWriteInfo.pNext = nullptr;
mDummyTargetWriteInfo.dstArrayElement = 0;
mDummyTargetWriteInfo.descriptorCount = 1;
mDummyTargetWriteInfo.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
mDummyTargetWriteInfo.pImageInfo = &mDummyTargetInfo;
mDummyTargetWriteInfo.pBufferInfo = nullptr;
mDummyTargetWriteInfo.pTexelBufferView = nullptr;
}
VulkanPipelineCache::~VulkanPipelineCache() {
@@ -45,47 +98,244 @@ VulkanPipelineCache::~VulkanPipelineCache() {
// be explicit about teardown order of various components.
}
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept {
mPipelineRequirements.layout = layout;
void VulkanPipelineCache::setDevice(VkDevice device, VmaAllocator allocator) {
assert_invariant(mDevice == VK_NULL_HANDLE);
mDevice = device;
mAllocator = allocator;
mDescriptorPool = createDescriptorPool(mDescriptorPoolSize);
// Formulate some dummy objects and dummy descriptor info used only for clearing out unused
// bindings. This is especially crucial after a texture has been destroyed. Since core Vulkan
// does not allow specifying VK_NULL_HANDLE without the robustness2 extension, we would need to
// change the pipeline layout more frequently if we wanted to get rid of these dummy objects.
VkBufferCreateInfo bufferInfo {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = 16,
.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
};
VmaAllocationCreateInfo allocInfo { .usage = VMA_MEMORY_USAGE_GPU_ONLY };
vmaCreateBuffer(mAllocator, &bufferInfo, &allocInfo, &mDummyBuffer, &mDummyMemory, nullptr);
mDummyBufferInfo.buffer = mDummyBuffer;
mDummyBufferInfo.range = bufferInfo.size;
}
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::getOrCreatePipeline() noexcept {
// If a cached object exists, re-use it, otherwise create a new one.
if (PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements);
pipelineIter != mPipelines.end()) {
auto& pipeline = pipelineIter.value();
pipeline.lastUsed = mCurrentTime;
return &pipeline;
bool VulkanPipelineCache::bindDescriptors(VkCommandBuffer cmdbuffer) noexcept {
DescriptorMap::iterator descriptorIter = mDescriptorSets.find(mDescriptorRequirements);
// Check if the required descriptors are already bound. If so, there's no need to do anything.
if (DescEqual equals; UTILS_LIKELY(equals(mBoundDescriptor, mDescriptorRequirements))) {
// If the pipeline state during an app's first draw call happens to match the default state
// vector of the cache, then the cache is uninitialized and we should not return early.
if (UTILS_LIKELY(!mDescriptorSets.empty())) {
// Since the descriptors are already bound, they should be found in the cache.
assert_invariant(descriptorIter != mDescriptorSets.end());
// Update the LRU "time stamp" (really a count of cmd buf submissions) before returning.
descriptorIter.value().lastUsed = mCurrentTime;
return true;
}
}
auto ret = createPipeline();
ret->lastUsed = mCurrentTime;
return ret;
// If a cached object exists, re-use it, otherwise create a new one.
DescriptorCacheEntry* cacheEntry = UTILS_LIKELY(descriptorIter != mDescriptorSets.end()) ?
&descriptorIter.value() : createDescriptorSets();
// If a descriptor set overflow occurred, allow higher levels to handle it gracefully.
assert_invariant(cacheEntry != nullptr);
if (UTILS_UNLIKELY(cacheEntry == nullptr)) {
return false;
}
cacheEntry->lastUsed = mCurrentTime;
mBoundDescriptor = mDescriptorRequirements;
// This passes the currently "bound" uniform buffer objects to pipeline that will be used in the
// draw call.
auto resourceEntry = mDescriptorResources.find(cacheEntry->id);
if (resourceEntry == mDescriptorResources.end()) {
mDescriptorResources[cacheEntry->id]
= std::make_unique<VulkanAcquireOnlyResourceManager>(mResourceAllocator);
resourceEntry = mDescriptorResources.find(cacheEntry->id);
}
resourceEntry->second->acquireAll(&mPipelineBoundResources);
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
getOrCreatePipelineLayout()->handle, 0, VulkanPipelineCache::DESCRIPTOR_TYPE_COUNT,
cacheEntry->handles.data(), 0, nullptr);
return true;
}
void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
bool VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) noexcept {
VkCommandBuffer const cmdbuffer = commands->buffer();
PipelineCacheEntry* cacheEntry = getOrCreatePipeline();
PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements);
// Check if the required pipeline is already bound.
if (cacheEntry->handle == commands->pipeline()) {
return;
if (PipelineEqual equals; UTILS_LIKELY(equals(mBoundPipeline, mPipelineRequirements))) {
assert_invariant(pipelineIter != mPipelines.end());
pipelineIter.value().lastUsed = mCurrentTime;
return true;
}
// If a cached object exists, re-use it, otherwise create a new one.
PipelineCacheEntry* cacheEntry = UTILS_LIKELY(pipelineIter != mPipelines.end()) ?
&pipelineIter.value() : createPipeline();
// If an error occurred, allow higher levels to handle it gracefully.
assert_invariant(cacheEntry != nullptr && "Failed to create/find pipeline");
assert_invariant(cacheEntry != nullptr);
if (UTILS_UNLIKELY(cacheEntry == nullptr)) {
return false;
}
cacheEntry->lastUsed = mCurrentTime;
getOrCreatePipelineLayout()->lastUsed = mCurrentTime;
mBoundPipeline = mPipelineRequirements;
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle);
commands->setPipeline(cacheEntry->handle);
return true;
}
void VulkanPipelineCache::bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept {
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
if (UTILS_UNLIKELY(!equivalent(mCurrentScissor, scissor))) {
mCurrentScissor = scissor;
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
}
}
VulkanPipelineCache::DescriptorCacheEntry* VulkanPipelineCache::createDescriptorSets() noexcept {
PipelineLayoutCacheEntry* layoutCacheEntry = getOrCreatePipelineLayout();
DescriptorCacheEntry descriptorCacheEntry = {
.pipelineLayout = mPipelineRequirements.layout,
.id = mDescriptorCacheEntryCount++,
};
// Each of the arenas for this particular layout are guaranteed to have the same size. Check
// the first arena to see if any descriptor sets are available that can be re-claimed. If not,
// create brand new ones (one for each type). They will be added to the arena later, after they
// are no longer used. This occurs during the cleanup phase during command buffer submission.
auto& descriptorSetArenas = layoutCacheEntry->descriptorSetArenas;
if (descriptorSetArenas[0].empty()) {
// If allocating a new descriptor set from the pool would cause it to overflow, then
// recreate the pool. The number of descriptor sets that have already been allocated from
// the pool is the sum of the "active" descriptor sets (mDescriptorSets) and the "dormant"
// descriptor sets (mDescriptorArenasCount).
//
// NOTE: technically both sides of the inequality below should be multiplied by
// DESCRIPTOR_TYPE_COUNT to get the true number of descriptor sets.
if (mDescriptorSets.size() + mDescriptorArenasCount + 1 > mDescriptorPoolSize) {
growDescriptorPool();
}
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = mDescriptorPool;
allocInfo.descriptorSetCount = DESCRIPTOR_TYPE_COUNT;
allocInfo.pSetLayouts = layoutCacheEntry->descriptorSetLayouts.data();
VkResult error = vkAllocateDescriptorSets(mDevice, &allocInfo,
descriptorCacheEntry.handles.data());
assert_invariant(error == VK_SUCCESS);
if (error != VK_SUCCESS) {
return nullptr;
}
} else {
for (uint32_t i = 0; i < DESCRIPTOR_TYPE_COUNT; ++i) {
descriptorCacheEntry.handles[i] = descriptorSetArenas[i].back();
descriptorSetArenas[i].pop_back();
}
assert_invariant(mDescriptorArenasCount > 0);
mDescriptorArenasCount--;
}
// Rewrite every binding in the new descriptor sets.
VkDescriptorBufferInfo descriptorBuffers[UBUFFER_BINDING_COUNT];
VkDescriptorImageInfo descriptorSamplers[SAMPLER_BINDING_COUNT];
VkDescriptorImageInfo descriptorInputAttachments[INPUT_ATTACHMENT_COUNT];
VkWriteDescriptorSet descriptorWrites[UBUFFER_BINDING_COUNT + SAMPLER_BINDING_COUNT +
INPUT_ATTACHMENT_COUNT];
uint32_t nwrites = 0;
VkWriteDescriptorSet* writes = descriptorWrites;
nwrites = 0;
for (uint32_t binding = 0; binding < UBUFFER_BINDING_COUNT; binding++) {
VkWriteDescriptorSet& writeInfo = writes[nwrites++];
if (mDescriptorRequirements.uniformBuffers[binding]) {
VkDescriptorBufferInfo& bufferInfo = descriptorBuffers[binding];
bufferInfo.buffer = mDescriptorRequirements.uniformBuffers[binding];
bufferInfo.offset = mDescriptorRequirements.uniformBufferOffsets[binding];
bufferInfo.range = mDescriptorRequirements.uniformBufferSizes[binding];
// We store size with 32 bits, so our "WHOLE" sentinel is different from Vk.
if (bufferInfo.range == WHOLE_SIZE) {
bufferInfo.range = VK_WHOLE_SIZE;
}
writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeInfo.pNext = nullptr;
writeInfo.dstArrayElement = 0;
writeInfo.descriptorCount = 1;
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
writeInfo.pImageInfo = nullptr;
writeInfo.pBufferInfo = &bufferInfo;
writeInfo.pTexelBufferView = nullptr;
} else {
writeInfo = mDummyBufferWriteInfo;
assert_invariant(mDummyBufferWriteInfo.pBufferInfo->buffer);
}
assert_invariant(writeInfo.pBufferInfo->buffer);
writeInfo.dstSet = descriptorCacheEntry.handles[0];
writeInfo.dstBinding = binding;
}
for (uint32_t binding = 0; binding < SAMPLER_BINDING_COUNT; binding++) {
if (mDescriptorRequirements.samplers[binding].sampler) {
VkWriteDescriptorSet& writeInfo = writes[nwrites++];
VkDescriptorImageInfo& imageInfo = descriptorSamplers[binding];
imageInfo = mDescriptorRequirements.samplers[binding];
writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeInfo.pNext = nullptr;
writeInfo.dstArrayElement = 0;
writeInfo.descriptorCount = 1;
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
writeInfo.pImageInfo = &imageInfo;
writeInfo.pBufferInfo = nullptr;
writeInfo.pTexelBufferView = nullptr;
writeInfo.dstSet = descriptorCacheEntry.handles[1];
writeInfo.dstBinding = binding;
}
}
for (uint32_t binding = 0; binding < INPUT_ATTACHMENT_COUNT; binding++) {
if (mDescriptorRequirements.inputAttachments[binding].imageView) {
VkWriteDescriptorSet& writeInfo = writes[nwrites++];
VkDescriptorImageInfo& imageInfo = descriptorInputAttachments[binding];
imageInfo = mDescriptorRequirements.inputAttachments[binding];
writeInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeInfo.pNext = nullptr;
writeInfo.dstArrayElement = 0;
writeInfo.descriptorCount = 1;
writeInfo.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
writeInfo.pImageInfo = &imageInfo;
writeInfo.pBufferInfo = nullptr;
writeInfo.pTexelBufferView = nullptr;
writeInfo.dstSet = descriptorCacheEntry.handles[2];
writeInfo.dstBinding = binding;
}
}
vkUpdateDescriptorSets(mDevice, nwrites, writes, 0, nullptr);
return &mDescriptorSets.emplace(mDescriptorRequirements, descriptorCacheEntry).first.value();
}
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {
assert_invariant(mPipelineRequirements.shaders[0] && "Vertex shader is not bound.");
assert_invariant(mPipelineRequirements.layout && "No pipeline layout specified");
PipelineLayoutCacheEntry* layout = getOrCreatePipelineLayout();
assert_invariant(layout);
VkPipelineShaderStageCreateInfo shaderStages[SHADER_MODULE_COUNT];
shaderStages[0] = VkPipelineShaderStageCreateInfo{};
@@ -154,7 +404,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {};
pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineCreateInfo.layout = mPipelineRequirements.layout;
pipelineCreateInfo.layout = layout->handle;
pipelineCreateInfo.renderPass = mPipelineRequirements.renderPass;
pipelineCreateInfo.subpass = mPipelineRequirements.subpassIndex;
pipelineCreateInfo.stageCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1;
@@ -248,6 +498,68 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value();
}
VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreatePipelineLayout() noexcept {
auto iter = mPipelineLayouts.find(mPipelineRequirements.layout);
if (UTILS_LIKELY(iter != mPipelineLayouts.end())) {
return &iter.value();
}
PipelineLayoutCacheEntry cacheEntry = {};
VkDescriptorSetLayoutBinding binding = {};
binding.descriptorCount = 1; // NOTE: We never use arrays-of-blocks.
binding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; // NOTE: This is potentially non-optimal.
// First create the descriptor set layout for UBO's.
VkDescriptorSetLayoutBinding ubindings[UBUFFER_BINDING_COUNT];
binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
for (uint32_t i = 0; i < UBUFFER_BINDING_COUNT; i++) {
binding.binding = i;
ubindings[i] = binding;
}
VkDescriptorSetLayoutCreateInfo dlinfo = {};
dlinfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
dlinfo.bindingCount = UBUFFER_BINDING_COUNT;
dlinfo.pBindings = ubindings;
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[0]);
// Next create the descriptor set layout for samplers.
VkDescriptorSetLayoutBinding sbindings[SAMPLER_BINDING_COUNT];
binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
for (uint32_t i = 0; i < SAMPLER_BINDING_COUNT; i++) {
binding.stageFlags = getShaderStageFlags(mPipelineRequirements.layout, i);
binding.binding = i;
sbindings[i] = binding;
}
dlinfo.bindingCount = SAMPLER_BINDING_COUNT;
dlinfo.pBindings = sbindings;
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[1]);
// Next create the descriptor set layout for input attachments.
VkDescriptorSetLayoutBinding tbindings[INPUT_ATTACHMENT_COUNT];
binding.descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
for (uint32_t i = 0; i < INPUT_ATTACHMENT_COUNT; i++) {
binding.binding = i;
tbindings[i] = binding;
}
dlinfo.bindingCount = INPUT_ATTACHMENT_COUNT;
dlinfo.pBindings = tbindings;
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &cacheEntry.descriptorSetLayouts[2]);
// Create VkPipelineLayout based on how to resources are bounded.
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {};
pPipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pPipelineLayoutCreateInfo.setLayoutCount = cacheEntry.descriptorSetLayouts.size();
pPipelineLayoutCreateInfo.pSetLayouts = cacheEntry.descriptorSetLayouts.data();
VkResult result = vkCreatePipelineLayout(mDevice, &pPipelineLayoutCreateInfo, VKALLOC,
&cacheEntry.handle);
if (UTILS_UNLIKELY(result != VK_SUCCESS)) {
return nullptr;
}
return &mPipelineLayouts.emplace(mPipelineRequirements.layout, cacheEntry).first.value();
}
void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept {
mPipelineRequirements.shaders[0] = program->getVertexShader();
mPipelineRequirements.shaders[1] = program->getFragmentShader();
@@ -288,15 +600,97 @@ void VulkanPipelineCache::bindVertexArray(VkVertexInputAttributeDescription cons
}
}
VulkanPipelineCache::UniformBufferBinding VulkanPipelineCache::getUniformBufferBinding(
uint32_t bindingIndex) const noexcept {
auto& key = mDescriptorRequirements;
return {
key.uniformBuffers[bindingIndex],
key.uniformBufferOffsets[bindingIndex],
key.uniformBufferSizes[bindingIndex],
};
}
void VulkanPipelineCache::unbindUniformBuffer(VkBuffer uniformBuffer) noexcept {
auto& key = mDescriptorRequirements;
for (uint32_t bindingIndex = 0u; bindingIndex < UBUFFER_BINDING_COUNT; ++bindingIndex) {
if (key.uniformBuffers[bindingIndex] == uniformBuffer) {
key.uniformBuffers[bindingIndex] = {};
key.uniformBufferSizes[bindingIndex] = {};
key.uniformBufferOffsets[bindingIndex] = {};
}
}
}
void VulkanPipelineCache::unbindImageView(VkImageView imageView) noexcept {
for (auto& sampler : mDescriptorRequirements.samplers) {
if (sampler.imageView == imageView) {
sampler = {};
}
}
for (auto& target : mDescriptorRequirements.inputAttachments) {
if (target.imageView == imageView) {
target = {};
}
}
}
void VulkanPipelineCache::bindUniformBufferObject(uint32_t bindingIndex,
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
VkBuffer buffer = bufferObject->buffer.getGpuBuffer();
ASSERT_POSTCONDITION(bindingIndex < UBUFFER_BINDING_COUNT,
"Uniform bindings overflow: index = %d, capacity = %d.", bindingIndex,
UBUFFER_BINDING_COUNT);
auto& key = mDescriptorRequirements;
key.uniformBuffers[bindingIndex] = buffer;
if (size == VK_WHOLE_SIZE) {
size = WHOLE_SIZE;
}
assert_invariant(offset <= 0xffffffffu);
assert_invariant(size <= 0xffffffffu);
key.uniformBufferOffsets[bindingIndex] = offset;
key.uniformBufferSizes[bindingIndex] = size;
mPipelineBoundResources.acquire(bufferObject);
}
void VulkanPipelineCache::bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept {
for (uint32_t bindingIndex = 0; bindingIndex < SAMPLER_BINDING_COUNT; bindingIndex++) {
mDescriptorRequirements.samplers[bindingIndex] = samplers[bindingIndex];
if (textures[bindingIndex]) {
mPipelineBoundResources.acquire(textures[bindingIndex]);
}
}
mPipelineRequirements.layout = flags;
}
void VulkanPipelineCache::bindInputAttachment(uint32_t bindingIndex,
VkDescriptorImageInfo targetInfo) noexcept {
ASSERT_POSTCONDITION(bindingIndex < INPUT_ATTACHMENT_COUNT,
"Input attachment bindings overflow: index = %d, capacity = %d.",
bindingIndex, INPUT_ATTACHMENT_COUNT);
mDescriptorRequirements.inputAttachments[bindingIndex] = targetInfo;
}
void VulkanPipelineCache::terminate() noexcept {
// Symmetric to createLayoutsAndDescriptors.
destroyLayoutsAndDescriptors();
for (auto& iter : mPipelines) {
vkDestroyPipeline(mDevice, iter.second.handle, VKALLOC);
}
mPipelineBoundResources.clear();
mPipelines.clear();
mBoundPipeline = {};
vmaDestroyBuffer(mAllocator, mDummyBuffer, mDummyMemory);
mDummyBuffer = VK_NULL_HANDLE;
mDummyMemory = VK_NULL_HANDLE;
}
void VulkanPipelineCache::gc() noexcept {
void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& commands) {
// The timestamp associated with a given cache entry represents "time" as a count of flush
// events since the cache was constructed. If any cache entry was most recently used over
// FVK_MAX_PIPELINE_AGE flush events in the past, then we can be sure that it is no longer
@@ -306,22 +700,194 @@ void VulkanPipelineCache::gc() noexcept {
// The Vulkan spec says: "When a command buffer begins recording, all state in that command
// buffer is undefined." Therefore, we need to clear all bindings at this time.
mBoundPipeline = {};
mBoundDescriptor = {};
mCurrentScissor = {};
// NOTE: Due to robin_map restrictions, we cannot use auto or range-based loops.
// Check if any bundles in the cache are no longer in use by any command buffer. Descriptors
// from unused bundles are moved back to their respective arenas.
using ConstDescIterator = decltype(mDescriptorSets)::const_iterator;
for (ConstDescIterator iter = mDescriptorSets.begin(); iter != mDescriptorSets.end();) {
const DescriptorCacheEntry& cacheEntry = iter.value();
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
auto& arenas = mPipelineLayouts[cacheEntry.pipelineLayout].descriptorSetArenas;
for (uint32_t i = 0; i < DESCRIPTOR_TYPE_COUNT; ++i) {
arenas[i].push_back(cacheEntry.handles[i]);
}
++mDescriptorArenasCount;
mDescriptorResources.erase(cacheEntry.id);
iter = mDescriptorSets.erase(iter);
} else {
++iter;
}
}
// Evict any pipelines that have not been used in a while.
// Any pipeline older than FVK_MAX_COMMAND_BUFFERS can be safely destroyed.
using ConstPipeIterator = decltype(mPipelines)::const_iterator;
for (ConstPipeIterator iter = mPipelines.begin(); iter != mPipelines.end();) {
const PipelineCacheEntry& cacheEntry = iter.value();
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
vkDestroyPipeline(mDevice, iter->second.handle, VKALLOC);
iter = mPipelines.erase(iter);
} else {
++iter;
}
}
using ConstPipeIterator = decltype(mPipelines)::const_iterator;
for (ConstPipeIterator iter = mPipelines.begin(); iter != mPipelines.end();) {
const PipelineCacheEntry& cacheEntry = iter.value();
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
vkDestroyPipeline(mDevice, iter->second.handle, VKALLOC);
iter = mPipelines.erase(iter);
} else {
++iter;
}
}
// Evict any layouts that have not been used in a while.
using ConstLayoutIterator = decltype(mPipelineLayouts)::const_iterator;
for (ConstLayoutIterator iter = mPipelineLayouts.begin(); iter != mPipelineLayouts.end();) {
const PipelineLayoutCacheEntry& cacheEntry = iter.value();
if (cacheEntry.lastUsed + FVK_MAX_PIPELINE_AGE < mCurrentTime) {
vkDestroyPipelineLayout(mDevice, iter->second.handle, VKALLOC);
for (auto setLayout : iter->second.descriptorSetLayouts) {
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
PipelineLayoutKey key = iter.key();
for (auto& pair : mDescriptorSets) {
assert_invariant(pair.second.pipelineLayout != key);
}
#endif
vkDestroyDescriptorSetLayout(mDevice, setLayout, VKALLOC);
}
auto& arenas = iter->second.descriptorSetArenas;
assert_invariant(mDescriptorArenasCount >= arenas[0].size());
mDescriptorArenasCount -= arenas[0].size();
for (auto& arena : arenas) {
vkFreeDescriptorSets(mDevice, mDescriptorPool, arena.size(), arena.data());
}
iter = mPipelineLayouts.erase(iter);
} else {
++iter;
}
}
// If there are no descriptors from any extinct pool that are still in use, we can safely
// destroy the extinct pools, which implicitly frees their associated descriptor sets.
bool canPurgeExtinctPools = true;
for (auto& bundle : mExtinctDescriptorBundles) {
if (bundle.lastUsed + FVK_MAX_PIPELINE_AGE >= mCurrentTime) {
canPurgeExtinctPools = false;
break;
}
}
if (canPurgeExtinctPools) {
for (VkDescriptorPool pool : mExtinctDescriptorPools) {
vkDestroyDescriptorPool(mDevice, pool, VKALLOC);
}
mExtinctDescriptorPools.clear();
for (auto const& entry : mExtinctDescriptorBundles) {
mDescriptorResources.erase(entry.id);
}
mExtinctDescriptorBundles.clear();
}
}
VkDescriptorPool VulkanPipelineCache::createDescriptorPool(uint32_t size) const {
VkDescriptorPoolSize poolSizes[DESCRIPTOR_TYPE_COUNT] = {};
VkDescriptorPoolCreateInfo poolInfo {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.pNext = nullptr,
.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
.maxSets = size * DESCRIPTOR_TYPE_COUNT,
.poolSizeCount = DESCRIPTOR_TYPE_COUNT,
.pPoolSizes = poolSizes
};
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = poolInfo.maxSets * UBUFFER_BINDING_COUNT;
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[1].descriptorCount = poolInfo.maxSets * SAMPLER_BINDING_COUNT;
poolSizes[2].type = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
poolSizes[2].descriptorCount = poolInfo.maxSets * INPUT_ATTACHMENT_COUNT;
VkDescriptorPool pool;
const UTILS_UNUSED VkResult result = vkCreateDescriptorPool(mDevice, &poolInfo, VKALLOC, &pool);
assert_invariant(result == VK_SUCCESS);
return pool;
}
void VulkanPipelineCache::destroyLayoutsAndDescriptors() noexcept {
// Our current descriptor set strategy can cause the # of descriptor sets to explode in certain
// situations, so it's interesting to report the number that get stuffed into the cache.
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
utils::slog.d << "Destroying " << mDescriptorSets.size() << " bundles of descriptor sets."
<< utils::io::endl;
#endif
mDescriptorSets.clear();
// Our current layout bundle strategy can cause the # of layout bundles to explode in certain
// situations, so it's interesting to report the number that get stuffed into the cache.
#if FVK_ENABLED(FVK_DEBUG_PIPELINE_CACHE)
utils::slog.d << "Destroying " << mPipelineLayouts.size() << " pipeline layouts."
<< utils::io::endl;
#endif
for (auto& iter : mPipelineLayouts) {
vkDestroyPipelineLayout(mDevice, iter.second.handle, VKALLOC);
for (auto setLayout : iter.second.descriptorSetLayouts) {
vkDestroyDescriptorSetLayout(mDevice, setLayout, VKALLOC);
}
// There is no need to free descriptor sets individually since destroying the VkDescriptorPool
// implicitly frees them.
}
mPipelineLayouts.clear();
vkDestroyDescriptorPool(mDevice, mDescriptorPool, VKALLOC);
mDescriptorPool = VK_NULL_HANDLE;
for (VkDescriptorPool pool : mExtinctDescriptorPools) {
vkDestroyDescriptorPool(mDevice, pool, VKALLOC);
}
mExtinctDescriptorPools.clear();
mExtinctDescriptorBundles.clear();
// Both mDescriptorSets and mExtinctDescriptorBundles have been cleared, so it's safe to call
// clear() on mDescriptorResources.
mDescriptorResources.clear();
mBoundDescriptor = {};
}
void VulkanPipelineCache::growDescriptorPool() noexcept {
// We need to destroy the old VkDescriptorPool, but we can't do so immediately because many
// of its descriptors are still in use. So, stash it in an "extinct" list.
mExtinctDescriptorPools.push_back(mDescriptorPool);
// Create the new VkDescriptorPool, twice as big as the old one.
mDescriptorPoolSize *= 2;
mDescriptorPool = createDescriptorPool(mDescriptorPoolSize);
// Clear out all unused descriptor sets in the arena so they don't get reclaimed. There is no
// need to free them individually since the old VkDescriptorPool will be destroyed.
for (auto iter = mPipelineLayouts.begin(); iter != mPipelineLayouts.end(); ++iter) {
for (auto& arena : iter.value().descriptorSetArenas) {
arena.clear();
}
}
mDescriptorArenasCount = 0;
// Move all in-use descriptors from the primary cache into an "extinct" list, so that they will
// later be destroyed rather than reclaimed.
using DescIterator = decltype(mDescriptorSets)::iterator;
for (DescIterator iter = mDescriptorSets.begin(); iter != mDescriptorSets.end(); ++iter) {
mExtinctDescriptorBundles.push_back(iter.value());
}
mDescriptorSets.clear();
}
size_t VulkanPipelineCache::PipelineLayoutKeyHashFn::operator()(
const PipelineLayoutKey& key) const {
std::hash<uint64_t> hasher;
auto h0 = hasher(key.getBitsAt(0));
auto h1 = hasher(key.getBitsAt(1));
return h0 ^ (h1 << 1);
}
bool VulkanPipelineCache::PipelineLayoutKeyEqual::operator()(const PipelineLayoutKey& k1,
const PipelineLayoutKey& k2) const {
return k1 == k2;
}
bool VulkanPipelineCache::PipelineEqual::operator()(const PipelineKey& k1,
@@ -329,6 +895,31 @@ bool VulkanPipelineCache::PipelineEqual::operator()(const PipelineKey& k1,
return 0 == memcmp((const void*) &k1, (const void*) &k2, sizeof(k1));
}
bool VulkanPipelineCache::DescEqual::operator()(const DescriptorKey& k1,
const DescriptorKey& k2) const {
for (uint32_t i = 0; i < UBUFFER_BINDING_COUNT; i++) {
if (k1.uniformBuffers[i] != k2.uniformBuffers[i] ||
k1.uniformBufferOffsets[i] != k2.uniformBufferOffsets[i] ||
k1.uniformBufferSizes[i] != k2.uniformBufferSizes[i]) {
return false;
}
}
for (uint32_t i = 0; i < SAMPLER_BINDING_COUNT; i++) {
if (k1.samplers[i].sampler != k2.samplers[i].sampler ||
k1.samplers[i].imageView != k2.samplers[i].imageView ||
k1.samplers[i].imageLayout != k2.samplers[i].imageLayout) {
return false;
}
}
for (uint32_t i = 0; i < INPUT_ATTACHMENT_COUNT; i++) {
if (k1.inputAttachments[i].imageView != k2.inputAttachments[i].imageView ||
k1.inputAttachments[i].imageLayout != k2.inputAttachments[i].imageLayout) {
return false;
}
}
return true;
}
} // namespace filament::backend
#pragma clang diagnostic pop
+205 -18
View File
@@ -17,11 +17,6 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
#define TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
#include "VulkanCommands.h"
#include "VulkanMemory.h"
#include "VulkanResources.h"
#include "VulkanUtility.h"
#include <backend/DriverEnums.h>
#include <backend/TargetBufferInfo.h>
@@ -39,6 +34,12 @@
#include <vector>
#include <unordered_map>
#include "VulkanCommands.h"
VK_DEFINE_HANDLE(VmaAllocator)
VK_DEFINE_HANDLE(VmaAllocation)
VK_DEFINE_HANDLE(VmaPool)
namespace filament::backend {
struct VulkanProgram;
@@ -55,14 +56,32 @@ class VulkanResourceAllocator;
// - Assumes that viewport and scissor should be dynamic. (not baked into VkPipeline)
// - Assumes that uniform buffers should be visible across all shader stages.
//
class VulkanPipelineCache {
class VulkanPipelineCache : public CommandBufferObserver {
public:
VulkanPipelineCache(VulkanPipelineCache const&) = delete;
VulkanPipelineCache& operator=(VulkanPipelineCache const&) = delete;
static constexpr uint32_t UBUFFER_BINDING_COUNT = Program::UNIFORM_BINDING_COUNT;
static constexpr uint32_t SAMPLER_BINDING_COUNT = MAX_SAMPLER_COUNT;
// We assume only one possible input attachment between two subpasses. See also the subpasses
// definition in VulkanFboCache.
static constexpr uint32_t INPUT_ATTACHMENT_COUNT = 1;
static constexpr uint32_t SHADER_MODULE_COUNT = 2;
static constexpr uint32_t VERTEX_ATTRIBUTE_COUNT = MAX_VERTEX_ATTRIBUTE_COUNT;
// Three descriptor set layouts: uniforms, combined image samplers, and input attachments.
static constexpr uint32_t DESCRIPTOR_TYPE_COUNT = 3;
static constexpr uint32_t INITIAL_DESCRIPTOR_SET_POOL_SIZE = 512;
// The VertexArray POD is an array of buffer targets and an array of attributes that refer to
// those targets. It does not include any references to actual buffers, so you can think of it
// as a vertex assembler configuration. For simplicity it contains fixed-size arrays and does
// not store sizes; all unused entries are simply zeroed out.
struct VertexArray {
};
// The ProgramBundle contains weak references to the compiled vertex and fragment shaders.
struct ProgramBundle {
VkShaderModule vertex;
@@ -70,6 +89,10 @@ public:
VkSpecializationInfo* specializationInfos = nullptr;
};
using UsageFlags = utils::bitset128;
static UsageFlags getUsageFlags(uint16_t binding, ShaderStageFlags stages, UsageFlags src = {});
static UsageFlags disableUsageFlags(uint16_t binding, UsageFlags src);
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wpadded"
@@ -112,13 +135,17 @@ public:
// Upon construction, the pipeCache initializes some internal state but does not make any Vulkan
// calls. On destruction it will free any cached Vulkan objects that haven't already been freed.
VulkanPipelineCache(VkDevice device, VmaAllocator allocator);
VulkanPipelineCache(VulkanResourceAllocator* allocator);
~VulkanPipelineCache();
void setDevice(VkDevice device, VmaAllocator allocator);
void bindLayout(VkPipelineLayout layout) noexcept;
// Creates new descriptor sets if necessary and binds them using vkCmdBindDescriptorSets.
// Returns false if descriptor set allocation fails.
bool bindDescriptors(VkCommandBuffer cmdbuffer) noexcept;
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
void bindPipeline(VulkanCommandBuffer* commands);
// Returns false if an error occurred.
bool bindPipeline(VulkanCommandBuffer* commands) noexcept;
// Sets up a new scissor rectangle if it has been dirtied.
void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept;
@@ -128,13 +155,42 @@ public:
void bindRasterState(const RasterState& rasterState) noexcept;
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
void bindUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject,
VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept;
void bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept;
void bindInputAttachment(uint32_t bindingIndex, VkDescriptorImageInfo imageInfo) noexcept;
void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
VkVertexInputBindingDescription const* bufferDesc, uint8_t count);
// Gets the current UBO at the given slot, useful for push / pop.
UniformBufferBinding getUniformBufferBinding(uint32_t bindingIndex) const noexcept;
// Checks if the given uniform is bound to any slot, and if so binds "null" to that slot.
// Also invalidates all cached descriptors that refer to the given buffer.
// This is only necessary when the client knows that the UBO is about to be destroyed.
void unbindUniformBuffer(VkBuffer uniformBuffer) noexcept;
// Checks if an image view is bound to any sampler, and if so resets that particular slot.
// Also invalidates all cached descriptors that refer to the given image view.
// This is only necessary when the client knows that a texture is about to be destroyed.
void unbindImageView(VkImageView imageView) noexcept;
// NOTE: In theory we should proffer "unbindSampler" but in practice we never destroy samplers.
// Destroys all managed Vulkan objects. This should be called before changing the VkDevice.
void terminate() noexcept;
// vkCmdBindPipeline and vkCmdBindDescriptorSets establish bindings to a specific command
// buffer; they are not global to the device. Therefore we need to be notified when a
// new command buffer becomes active.
void onCommandBuffer(const VulkanCommandBuffer& cmdbuffer) override;
// Injects a dummy texture that can be used to clear out old descriptor sets.
void setDummyTexture(VkImageView imageView) {
mDummyTargetInfo.imageView = imageView;
}
static VkPrimitiveTopology getPrimitiveTopology(PrimitiveType pt) noexcept {
switch (pt) {
case PrimitiveType::POINTS:
@@ -150,9 +206,22 @@ public:
}
}
void gc() noexcept;
private:
// PIPELINE LAYOUT CACHE KEY
// -------------------------
using PipelineLayoutKey = utils::bitset128;
static_assert(PipelineLayoutKey::BIT_COUNT >= 2 * MAX_SAMPLER_COUNT);
struct PipelineLayoutKeyHashFn {
size_t operator()(const PipelineLayoutKey& key) const;
};
struct PipelineLayoutKeyEqual {
bool operator()(const PipelineLayoutKey& k1, const PipelineLayoutKey& k2) const;
};
// PIPELINE CACHE KEY
// ------------------
@@ -205,10 +274,10 @@ private:
VertexInputBindingDescription vertexBuffers[VERTEX_ATTRIBUTE_COUNT]; // 128 : 156
RasterState rasterState; // 16 : 284
uint32_t padding; // 4 : 300
VkPipelineLayout layout; // 8 : 304
PipelineLayoutKey layout; // 16 : 304
};
static_assert(sizeof(PipelineKey) == 312, "PipelineKey must not have implicit padding.");
static_assert(sizeof(PipelineKey) == 320, "PipelineKey must not have implicit padding.");
using PipelineHashFn = utils::hash::MurmurHashFn<PipelineKey>;
@@ -216,6 +285,52 @@ private:
bool operator()(const PipelineKey& k1, const PipelineKey& k2) const;
};
// DESCRIPTOR SET CACHE KEY
// ------------------------
// Equivalent to VkDescriptorImageInfo but with explicit padding.
struct DescriptorImageInfo {
DescriptorImageInfo& operator=(const VkDescriptorImageInfo& that) {
sampler = that.sampler;
imageView = that.imageView;
imageLayout = that.imageLayout;
padding = 0;
return *this;
}
operator VkDescriptorImageInfo() const { return { sampler, imageView, imageLayout }; }
// TODO: replace the 64-bit sampler handle with `uint32_t samplerParams` and remove the
// padding field. This is possible if we have access to the VulkanSamplerCache.
VkSampler sampler;
VkImageView imageView;
VkImageLayout imageLayout;
uint32_t padding;
};
// We store size with 32 bits, so our "WHOLE" sentinel is different from Vk.
static const uint32_t WHOLE_SIZE = 0xffffffffu;
// Represents all the Vulkan state that comprises a bound descriptor set.
struct DescriptorKey {
VkBuffer uniformBuffers[UBUFFER_BINDING_COUNT]; // 80 0
DescriptorImageInfo samplers[SAMPLER_BINDING_COUNT]; // 1488 80
DescriptorImageInfo inputAttachments[INPUT_ATTACHMENT_COUNT]; // 24 1568
uint32_t uniformBufferOffsets[UBUFFER_BINDING_COUNT]; // 40 1592
uint32_t uniformBufferSizes[UBUFFER_BINDING_COUNT]; // 40 1632
};
static_assert(offsetof(DescriptorKey, samplers) == 80);
static_assert(offsetof(DescriptorKey, inputAttachments) == 1568);
static_assert(offsetof(DescriptorKey, uniformBufferOffsets) == 1592);
static_assert(offsetof(DescriptorKey, uniformBufferSizes) == 1632);
static_assert(sizeof(DescriptorKey) == 1672, "DescriptorKey must not have implicit padding.");
using DescHashFn = utils::hash::MurmurHashFn<DescriptorKey>;
struct DescEqual {
bool operator()(const DescriptorKey& k1, const DescriptorKey& k2) const;
};
#pragma clang diagnostic pop
// CACHE ENTRY STRUCTS
@@ -228,6 +343,16 @@ private:
using Timestamp = uint64_t;
Timestamp mCurrentTime = 0;
// The descriptor set cache entry is a group of descriptor sets that are bound simultaneously.
struct DescriptorCacheEntry {
std::array<VkDescriptorSet, DESCRIPTOR_TYPE_COUNT> handles;
Timestamp lastUsed;
PipelineLayoutKey pipelineLayout;
uint32_t id;
};
uint32_t mDescriptorCacheEntryCount = 0;
struct PipelineCacheEntry {
VkPipeline handle;
Timestamp lastUsed;
@@ -236,36 +361,98 @@ private:
struct PipelineLayoutCacheEntry {
VkPipelineLayout handle;
Timestamp lastUsed;
std::array<VkDescriptorSetLayout, DESCRIPTOR_TYPE_COUNT> descriptorSetLayouts;
// Each pipeline layout has 3 arenas of unused descriptors (one for each binding type).
//
// The difference between the "arenas" and the "pool" are as follows.
//
// - The "pool" is a single, centralized factory for all descriptors (VkDescriptorPool).
//
// - Each "arena" is a set of unused (but alive) descriptors that can only be used with a
// specific pipeline layout and a specific binding type. We manually manage each arena.
// The arenas are created in an empty state, and they are gradually populated as new
// descriptors are reclaimed over time. This is quite different from the pool, which is
// given a fixed size when it is constructed.
//
std::array<std::vector<VkDescriptorSet>, DESCRIPTOR_TYPE_COUNT> descriptorSetArenas;
};
// CACHE CONTAINERS
// ----------------
using PipelineLayoutMap = tsl::robin_map<PipelineLayoutKey , PipelineLayoutCacheEntry,
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
using PipelineMap = tsl::robin_map<PipelineKey, PipelineCacheEntry,
PipelineHashFn, PipelineEqual>;
using DescriptorMap
= tsl::robin_map<DescriptorKey, DescriptorCacheEntry, DescHashFn, DescEqual>;
using DescriptorResourceMap
= std::unordered_map<uint32_t, std::unique_ptr<VulkanAcquireOnlyResourceManager>>;
private:
PipelineCacheEntry* getOrCreatePipeline() noexcept;
PipelineLayoutMap mPipelineLayouts;
PipelineMap mPipelines;
DescriptorMap mDescriptorSets;
DescriptorResourceMap mDescriptorResources;
// These helpers all return unstable pointers that should not be stored.
DescriptorCacheEntry* createDescriptorSets() noexcept;
PipelineCacheEntry* createPipeline() noexcept;
PipelineLayoutCacheEntry* getOrCreatePipelineLayout() noexcept;
// Misc helper methods.
void destroyLayoutsAndDescriptors() noexcept;
VkDescriptorPool createDescriptorPool(uint32_t size) const;
void growDescriptorPool() noexcept;
// Immutable state.
VkDevice mDevice = VK_NULL_HANDLE;
VmaAllocator mAllocator = VK_NULL_HANDLE;
// Current requirements for the pipeline layout, pipeline, and descriptor sets.
PipelineKey mPipelineRequirements = {};
DescriptorKey mDescriptorRequirements = {};
// Current bindings for the pipeline and descriptor sets.
PipelineKey mBoundPipeline = {};
DescriptorKey mBoundDescriptor = {};
// Current state for scissoring.
VkRect2D mCurrentScissor = {};
// The descriptor set pool starts out with a decent number of descriptor sets. The cache can
// grow the pool by re-creating it with a larger size. See growDescriptorPool().
VkDescriptorPool mDescriptorPool;
// This describes the number of descriptor sets in mDescriptorPool. Note that this needs to be
// multiplied by DESCRIPTOR_TYPE_COUNT to get the actual number of descriptor sets. Also note
// that the number of low-level "descriptors" (not descriptor *sets*) is actually much more than
// this size. It can be computed only by factoring in UBUFFER_BINDING_COUNT etc.
uint32_t mDescriptorPoolSize = INITIAL_DESCRIPTOR_SET_POOL_SIZE;
// To get the actual number of descriptor sets that have been allocated from the pool,
// take the sum of mDescriptorArenasCount (these are inactive descriptor sets) and the
// number of entries in the mDescriptorPool map (active descriptor sets). Multiply the result by
// DESCRIPTOR_TYPE_COUNT.
uint32_t mDescriptorArenasCount = 0;
// After a growth event (i.e. when the VkDescriptorPool is replaced with a bigger version), all
// currently used descriptors are moved into the "extinct" sets so that they can be safely
// destroyed a few frames later.
std::list<VkDescriptorPool> mExtinctDescriptorPools;
std::list<DescriptorCacheEntry> mExtinctDescriptorBundles;
VkDescriptorBufferInfo mDummyBufferInfo = {};
VkWriteDescriptorSet mDummyBufferWriteInfo = {};
VkDescriptorImageInfo mDummyTargetInfo = {};
VkWriteDescriptorSet mDummyTargetWriteInfo = {};
VkBuffer mDummyBuffer;
VmaAllocation mDummyMemory;
VulkanResourceAllocator* mResourceAllocator;
VulkanAcquireOnlyResourceManager mPipelineBoundResources;
};
} // namespace filament::backend
@@ -49,8 +49,8 @@ namespace filament::backend {
#endif
class VulkanResourceAllocator {
public:
using AllocatorImpl = HandleAllocatorVK;
VulkanResourceAllocator(size_t arenaSize, bool disableUseAfterFreeCheck)
: mHandleAllocatorImpl("Handles", arenaSize, disableUseAfterFreeCheck)
#if DEBUG_RESOURCE_LEAKS
@@ -106,7 +106,7 @@ public:
}
private:
AllocatorImpl mHandleAllocatorImpl;
HandleAllocatorVK mHandleAllocatorImpl;
#if DEBUG_RESOURCE_LEAKS
public:
@@ -17,7 +17,7 @@
#include "VulkanResources.h"
#include "VulkanHandles.h"
#include "VulkanResourceAllocator.h"
#include "VulkanPipelineCache.h"
#include "caching/VulkanDescriptorSet.h"
namespace filament::backend {
@@ -62,9 +62,6 @@ void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType t
case VulkanResourceType::RENDER_PRIMITIVE:
allocator->destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
break;
case VulkanResourceType::DESCRIPTOR_SET_LAYOUT:
allocator->destruct<VulkanDescriptorSetLayout>(Handle<VulkanDescriptorSetLayout>(id));
break;
case VulkanResourceType::DESCRIPTOR_SET:
allocator->destruct<VulkanDescriptorSet>(Handle<VulkanDescriptorSet>(id));
break;
+15 -16
View File
@@ -35,25 +35,24 @@ struct VulkanThreadSafeResource;
// Subclasses of VulkanResource must provide this enum in their construction.
enum class VulkanResourceType : uint8_t {
BUFFER_OBJECT = 0,
INDEX_BUFFER = 1,
PROGRAM = 2,
RENDER_TARGET = 3,
SAMPLER_GROUP = 4,
SWAP_CHAIN = 5,
RENDER_PRIMITIVE = 6,
TEXTURE = 7,
TIMER_QUERY = 8,
VERTEX_BUFFER = 9,
VERTEX_BUFFER_INFO = 10,
DESCRIPTOR_SET_LAYOUT = 11,
DESCRIPTOR_SET = 12,
BUFFER_OBJECT,
INDEX_BUFFER,
PROGRAM,
RENDER_TARGET,
SAMPLER_GROUP,
SWAP_CHAIN,
RENDER_PRIMITIVE,
TEXTURE,
TIMER_QUERY,
VERTEX_BUFFER,
VERTEX_BUFFER_INFO,
DESCRIPTOR_SET,
// Below are resources that are managed manually (i.e. not ref counted).
FENCE = 13,
HEAP_ALLOCATED = 14,
FENCE,
HEAP_ALLOCATED,
END_TYPE = 15, // A placeholder
END_TYPE, // A placeholder
};
#define IS_HEAP_ALLOC_TYPE(f) \
@@ -17,9 +17,7 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
#define TNT_FILAMENT_BACKEND_VULKANSTAGEPOOL_H
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanMemory.h"
#include <map>
#include <unordered_set>
@@ -19,7 +19,6 @@
#include "DriverBase.h"
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanResources.h"
@@ -23,7 +23,6 @@
#include "VulkanResources.h"
#include "VulkanImageUtility.h"
#include <utils/Hash.h>
#include <utils/RangeMap.h>
#include <unordered_map>
@@ -655,6 +655,4 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) {
return mostSignificantBit((sampleCount - 1) & mask);
}
_BitCountHelper _BitCountHelper::BitCounter = {};
} // namespace filament::backend
+4 -141
View File
@@ -90,9 +90,6 @@ utils::FixedCapacityVector<OutType> enumerate(
#undef EXPAND_ENUM_NO_ARGS
#undef EXPAND_ENUM_ARGS
// Used across pipeline related classes.
using UsageFlags = utils::bitset128;
// Useful shorthands
using VkFormatList = utils::FixedCapacityVector<VkFormat>;
@@ -125,14 +122,14 @@ public:
clear();
}
inline const_iterator begin() const {
inline const_iterator begin() {
if (mInd == 0) {
return mArray.cend();
}
return mArray.cbegin();
}
inline const_iterator end() const {
inline const_iterator end() {
if (mInd > 0 && mInd < CAPACITY) {
return mArray.begin() + mInd;
}
@@ -173,26 +170,10 @@ public:
return mArray[ind];
}
inline T const& operator[](uint16_t ind) const {
return mArray[ind];
}
inline uint32_t size() const {
inline size_t size() {
return mInd;
}
T* data() {
return mArray.data();
}
T const* data() const {
return mArray.data();
}
bool operator==(CappedArray const& b) const {
return this->mArray == b.mArray;
}
private:
void swap(CappedArray& rhs) {
std::swap(mArray, rhs.mArray);
@@ -200,127 +181,9 @@ private:
}
FixedSizeArray mArray;
uint32_t mInd = 0;
size_t mInd = 0;
};
// TODO: ok to remove once Filament-side API is complete
namespace descset {
// Used to describe the descriptor binding in shader stages. We assume that the binding index does
// not exceed 31. We also assume that we have two shader stages - vertex and fragment. The below
// types and struct are used across VulkanDescriptorSet and VulkanProgram.
using UniformBufferBitmask = uint32_t;
using SamplerBitmask = uint64_t;
// We only have at most one input attachment, so this bitmask exists only to make the code more
// general.
using InputAttachmentBitmask = uint8_t;
constexpr UniformBufferBitmask UBO_VERTEX_STAGE = 0x1;
constexpr UniformBufferBitmask UBO_FRAGMENT_STAGE = (0x1ULL << (sizeof(UniformBufferBitmask) * 4));
constexpr SamplerBitmask SAMPLER_VERTEX_STAGE = 0x1;
constexpr SamplerBitmask SAMPLER_FRAGMENT_STAGE = (0x1ULL << (sizeof(SamplerBitmask) * 4));
constexpr InputAttachmentBitmask INPUT_ATTACHMENT_VERTEX_STAGE = 0x1;
constexpr InputAttachmentBitmask INPUT_ATTACHMENT_FRAGMENT_STAGE =
(0x1ULL << (sizeof(InputAttachmentBitmask) * 4));
template<typename Bitmask>
static constexpr Bitmask getVertexStage() noexcept {
if constexpr (std::is_same_v<Bitmask, UniformBufferBitmask>) {
return UBO_VERTEX_STAGE;
}
if constexpr (std::is_same_v<Bitmask, SamplerBitmask>) {
return SAMPLER_VERTEX_STAGE;
}
if constexpr (std::is_same_v<Bitmask, InputAttachmentBitmask>) {
return INPUT_ATTACHMENT_VERTEX_STAGE;
}
}
template<typename Bitmask>
static constexpr Bitmask getFragmentStage() noexcept {
if constexpr (std::is_same_v<Bitmask, UniformBufferBitmask>) {
return UBO_FRAGMENT_STAGE;
}
if constexpr (std::is_same_v<Bitmask, SamplerBitmask>) {
return SAMPLER_FRAGMENT_STAGE;
}
if constexpr (std::is_same_v<Bitmask, InputAttachmentBitmask>) {
return INPUT_ATTACHMENT_FRAGMENT_STAGE;
}
}
typedef enum ShaderStageFlags2 : uint8_t {
NONE = 0,
VERTEX = 0x1,
FRAGMENT = 0x2,
} ShaderStageFlags2;
enum class DescriptorType : uint8_t {
UNIFORM_BUFFER,
SAMPLER,
INPUT_ATTACHMENT,
};
enum class DescriptorFlags : uint8_t {
NONE = 0x00,
DYNAMIC_OFFSET = 0x01
};
struct DescriptorSetLayoutBinding {
DescriptorType type;
ShaderStageFlags2 stageFlags;
uint8_t binding;
DescriptorFlags flags;
uint16_t count;
};
struct DescriptorSetLayout {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
};
} // namespace descset
// Use constexpr to statically generate a bit count table for 8-bit numbers.
struct _BitCountHelper {
constexpr _BitCountHelper() : data{} {
for (uint16_t i = 0; i < 256; ++i) {
data[i] = 0;
for (auto j = i; j > 0; j /= 2) {
if (j & 1) {
data[i]++;
}
}
}
}
template<typename MaskType>
constexpr uint8_t count(MaskType num) {
uint8_t count = 0;
for (uint8_t i = 0; i < sizeof(MaskType) * 8; i+=8) {
count += data[(num >> i) & 0xFF];
}
return count;
}
static _BitCountHelper BitCounter;
private:
uint8_t data[256];
};
template<typename MaskType>
inline uint8_t countBits(MaskType num) {
return _BitCountHelper::BitCounter.count(num);
}
// This is useful for counting the total number of descriptors for both vertex and fragment stages.
template<typename MaskType>
inline MaskType collapseStages(MaskType mask) {
constexpr uint8_t NBITS_DIV_2 = sizeof(MaskType) * 4;
// First zero out the top-half and then or the bottom-half against the original top-half.
return ((mask << NBITS_DIV_2) >> NBITS_DIV_2) | (mask >> NBITS_DIV_2);
}
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKANUTILITY_H
@@ -0,0 +1,647 @@
/*
* Copyright (C) 2024 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 "VulkanDescriptorSet.h"
#include <vulkan/VulkanConstants.h>
#include <vulkan/VulkanResources.h>
#include <type_traits>
namespace filament::backend {
namespace {
template<typename MaskType>
using LayoutMap = tsl::robin_map<MaskType, VkDescriptorSetLayout>;
using LayoutArray = VulkanDescriptorSetManager::LayoutArray;
using UniformBufferBitmask = VulkanDescriptorSetManager::UniformBufferBitmask;
using SamplerBitmask = VulkanDescriptorSetManager::SamplerBitmask;
constexpr uint8_t EXPECTED_IN_FLIGHT_FRAMES = 3; // Asssume triple buffering
#define PORT_CONSTANT(NameSpace, K) constexpr decltype(NameSpace::K) K = NameSpace::K;
PORT_CONSTANT(VulkanDescriptorSetManager, MAX_SUPPORTED_SHADER_STAGE)
PORT_CONSTANT(VulkanDescriptorSetManager, VERTEX_STAGE)
PORT_CONSTANT(VulkanDescriptorSetManager, FRAGMENT_STAGE)
PORT_CONSTANT(Program, UNIFORM_BINDING_COUNT)
PORT_CONSTANT(Program, SAMPLER_BINDING_COUNT)
#undef PORT_CONSTANT
// Use constexpr to statically generate a bit count table for 8-bit numbers.
struct BitCountHelper {
constexpr BitCountHelper() : data{} {
for (uint16_t i = 0; i < 256; ++i) {
data[i] = 0;
for (auto j = i; j > 0; j /= 2) {
if (j & 1) {
data[i]++;
}
}
}
}
uint8_t data[256];
} BitCountImpl;
inline uint8_t countBits(uint32_t num) {
static constexpr uint8_t const* BIT_COUNT = BitCountImpl.data;
return BIT_COUNT[num & 0xFF] + BIT_COUNT[(num >> 8) & 0xFF] + BIT_COUNT[(num >> 16) & 0xFF] +
BIT_COUNT[(num >> 24) & 0xFF];
}
struct SamplerSet {
using Mask = UniformBufferBitmask;
struct Key {
Mask mask;
static_assert(sizeof(Mask) == 4);
uint32_t padding;// We need a padding to ensure 8-byte alignement.
VkDescriptorSetLayout layout;
VkSampler sampler[SAMPLER_BINDING_COUNT];
VkImageView imageView[SAMPLER_BINDING_COUNT];
VkImageLayout imageLayout[SAMPLER_BINDING_COUNT];
};
struct Equal {
bool operator()(Key const& k1, Key const& k2) const {
if (k1.mask != k2.mask) return false;
if (k1.layout != k2.layout) return false;
for (uint8_t i = 0, bitCount = countBits(k1.mask); i < bitCount; ++i) {
if (k1.sampler[i] != k2.sampler[i] || k1.imageView[i] != k2.imageView[i] ||
k1.imageLayout[i] != k2.imageLayout[i]) {
return false;
}
}
return true;
}
};
using HashFn = utils::hash::MurmurHashFn<Key>;
using Cache = std::unordered_map<Key, VulkanDescriptorSet*, HashFn, Equal>;
};
struct UBOSet {
using Mask = SamplerBitmask;
struct Key {
Mask mask;
static_assert(sizeof(Mask) == 4);
uint32_t padding;// We need a padding to ensure 8-byte alignement.
VkDescriptorSetLayout layout;
VkBuffer buffers[UNIFORM_BINDING_COUNT]; // 80 0
VkDeviceSize offsets[UNIFORM_BINDING_COUNT];// 40 1592
VkDeviceSize sizes[UNIFORM_BINDING_COUNT]; // 40 1632
};
struct Equal {
bool operator()(Key const& k1, Key const& k2) const {
if (k1.mask != k2.mask) return false;
if (k1.layout != k2.layout) return false;
for (uint8_t i = 0, bitCount = countBits(k1.mask); i < bitCount; ++i) {
if (k1.buffers[i] != k2.buffers[i] || k1.offsets[i] != k2.offsets[i] ||
k1.sizes[i] != k2.sizes[i]) {
return false;
}
}
return true;
}
};
using HashFn = utils::hash::MurmurHashFn<Key>;
using Cache = std::unordered_map<Key, VulkanDescriptorSet*, HashFn, Equal>;
};
constexpr uint8_t MAX_BINDINGS = UNIFORM_BINDING_COUNT * MAX_SUPPORTED_SHADER_STAGE;
static_assert(MAX_BINDINGS >= SAMPLER_BINDING_COUNT * MAX_SUPPORTED_SHADER_STAGE);
template<typename TYPE>
class DescriptorPool {
private:
static constexpr uint32_t COUNT_MULTIPLIER =
MAX_SUPPORTED_SHADER_STAGE * EXPECTED_IN_FLIGHT_FRAMES;
static constexpr uint32_t CAPACITY =
std::is_same_v<TYPE, UBOSet> ? Program::UNIFORM_BINDING_COUNT * COUNT_MULTIPLIER
: Program::SAMPLER_BINDING_COUNT * COUNT_MULTIPLIER;
public:
DescriptorPool()
: mDevice(VK_NULL_HANDLE),
mResourceAllocator(nullptr) {}
DescriptorPool(VkDevice device, VulkanResourceAllocator* allocator)
: mDevice(device),
mResourceAllocator(allocator) {
VkDescriptorPoolSize sizes[1] = {
{
.type = std::is_same_v<TYPE, UBOSet> ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = CAPACITY,
},
};
VkDescriptorPoolCreateInfo info{
.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
.maxSets = EXPECTED_IN_FLIGHT_FRAMES,
.poolSizeCount = 1,
.pPoolSizes = sizes,
};
vkCreateDescriptorPool(mDevice, &info, VKALLOC, &mPool);
}
DescriptorPool(DescriptorPool const&) = delete;
DescriptorPool& operator=(DescriptorPool const&) = delete;
~DescriptorPool() {
vkDestroyDescriptorPool(mDevice, mPool, VKALLOC);
}
VulkanDescriptorSet* obtainSet(VkDescriptorSetLayout layout) {
if (mCount == CAPACITY) {
return nullptr;
}
if (auto unused = mUnused.find(layout);
unused != mUnused.end() && !unused->second.empty()) {
auto& unusedList = unused->second;
auto set = unusedList.back();
unusedList.pop_back();
mCount++;
return set;
}
// Creating a new set
VkDescriptorSetLayout layouts[1] = {layout};
VkDescriptorSetAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorPool = mPool,
.descriptorSetCount = 1,
.pSetLayouts = layouts,
};
VkDescriptorSet vkSet;
UTILS_UNUSED VkResult result = vkAllocateDescriptorSets(mDevice, &allocInfo, &vkSet);
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Cannot allocate descriptor set. error=%d",
(int) result);
mCount++;
return createSet(layout, vkSet);
}
private:
inline VulkanDescriptorSet* createSet(VkDescriptorSetLayout layout, VkDescriptorSet vkSet) {
return VulkanDescriptorSet::create(mResourceAllocator, vkSet, layout,
[this](VulkanDescriptorSet* set) {
auto const layout = set->layout;
auto const vkSet = set->vkSet;
auto listIter = mUnused.find(layout);
assert_invariant(listIter != mUnused.end());
auto& unusedList = listIter->second;
// At this point, we know that the pointer will be stale, and we need to remove
// any non-ref-counted references to it.
// TODO: this will be inefficient
auto iter = std::find(unusedList.begin(), unusedList.end(), set);
unusedList.erase(iter);
// We are recycling - release the vk resource back into the pool. Note that the
// vk handle has not changed, but we need to change the backend handle to allow
// for proper refcounting of resources referenced in this set.
unusedList.push_back(createSet(layout, vkSet));
mCount--;
});
}
VkDevice mDevice;
VulkanResourceAllocator* mResourceAllocator;
VkDescriptorPool mPool;
uint32_t mCount = 0;
std::unordered_map<VkDescriptorSetLayout, std::vector<VulkanDescriptorSet*>> mUnused;
};
template<typename TYPE>
class DescriptorInfinitePool {
public:
DescriptorInfinitePool(VkDevice device, VulkanResourceAllocator* allocator)
: mDevice(device),
mResourceAllocator(allocator) {}
~DescriptorInfinitePool() {
for (auto pool: mPools) {
delete pool;
}
}
VulkanDescriptorSet* obtainSet(VkDescriptorSetLayout layout) {
for (auto pool: mPools) {
auto set = pool->obtainSet(layout);
if (set) {
return set;
}
}
// We need to increase the number of pools
mPools.push_back(new DescriptorPool<TYPE>{mDevice, mResourceAllocator});
auto pool = mPools.back();
return pool->obtainSet(layout);
}
private:
VkDevice mDevice;
VulkanResourceAllocator* mResourceAllocator;
std::vector<DescriptorPool<TYPE>*> mPools;
};
// This holds a cache of descriptor sets of a TYPE (UBO or Sampler). The Key is defined as the
// layout and the content (for example specific samplers).
template<typename TYPE, typename Cache, typename Key>
class DescriptorSetCache {
public:
DescriptorSetCache(VkDevice device, VulkanResourceAllocator* allocator)
: mPool(device, allocator),
mResourceManager(allocator) {}
std::pair<VulkanDescriptorSet*, bool> obtainSet(Key const& key) {
mAge++;
if (auto result = mCache.find(key); result != mCache.end()) {
auto set = result->second;
auto const oldAge = mReverseHistory[set];
mHistory.erase(oldAge);
mHistory[mAge] = set;
mReverseHistory[set] = mAge;
return {set, true};
}
VulkanDescriptorSet* set = mPool.obtainSet(key.layout);
mCache[key] = set;
mReverseCache[set] = key;
mHistory[mAge] = set;
mReverseHistory[set] = mAge;
mResourceManager.acquire(set);
return {set, false};
}
// We need to periodically purge the descriptor sets so that we're not holding on to resources
// unnecessarily. The strategy for purging needs to be examined more.
void gc() noexcept {
constexpr uint32_t ALLOWED_ENTRIES = EXPECTED_IN_FLIGHT_FRAMES * 3;
int32_t const toCutCount = mHistory.size() - ALLOWED_ENTRIES;
if (toCutCount <= 0) {
return;
}
std::vector<uint64_t> remove;
for (auto entry = mHistory.begin(); entry != mHistory.end(); entry++) {
auto const& set = entry->second;
Key const& key = mReverseCache[set];
mCache.erase(key);
mReverseCache.erase(set);
mReverseHistory.erase(set);
remove.push_back(entry->first);
mResourceManager.release(set);
}
for (auto removeAge: remove) {
mHistory.erase(removeAge);
}
}
private:
DescriptorInfinitePool<TYPE> mPool;
// TODO: combine some of these data structures
Cache mCache;
std::unordered_map<VulkanDescriptorSet*, Key> mReverseCache;
// Use the ordering for purging if needed;
std::map<uint64_t, VulkanDescriptorSet*> mHistory;
std::unordered_map<VulkanDescriptorSet*, uint64_t> mReverseHistory;
uint64_t mAge;
// Note that instead of owning the resources (i.e. descriptor set) in the pools, we keep them
// here since all the allocated sets have to pass through this class, and this class has better
// knowledge to make decisions about gc.
VulkanResourceManager mResourceManager;
};
using UBOSetCache = DescriptorSetCache<UBOSet, UBOSet::Cache, UBOSet::Key>;
using SamplerSetCache = DescriptorSetCache<SamplerSet, SamplerSet::Cache, SamplerSet::Key>;
template<typename MaskType>
class LayoutCache {
public:
explicit LayoutCache(VkDevice device)
: mDevice(device) {}
~LayoutCache() {
for (auto [mask, layout]: mLayouts) {
vkDestroyDescriptorSetLayout(mDevice, layout, VKALLOC);
}
mLayouts.clear();
}
VkDescriptorSetLayout getLayout(MaskType stageMask) noexcept {
if (stageMask == 0) {
return VK_NULL_HANDLE;
}
if (auto iter = mLayouts.find(stageMask); iter != mLayouts.end()) {
return iter->second;
}
VkDescriptorType descriptorType;
if constexpr (std::is_same_v<MaskType, UBOSet::Mask>) {
descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
} else if constexpr (std::is_same_v<MaskType, SamplerSet::Mask>) {
descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
}
VkDescriptorSetLayoutBinding toBind[MAX_BINDINGS];
uint32_t count = 0;
for (uint8_t i = 0, maxBindings = sizeof(stageMask) * 4; i < maxBindings; i++) {
VkShaderStageFlags stages = 0;
if ((stageMask & (VERTEX_STAGE << i)) != 0) {
stages |= VK_SHADER_STAGE_VERTEX_BIT;
}
if ((stageMask & (FRAGMENT_STAGE << i)) == 0) {
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
}
if (stages == 0) {
continue;
}
auto& bindInfo = toBind[count++];
bindInfo = {
.binding = i,
.descriptorType = descriptorType,
.descriptorCount = 1,
.stageFlags = stages,
};
}
VkDescriptorSetLayoutCreateInfo dlinfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = count,
.pBindings = toBind,
};
VkDescriptorSetLayout handle;
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &handle);
mLayouts[stageMask] = handle;
return handle;
}
private:
VkDevice mDevice;
LayoutMap<MaskType> mLayouts;
};
using UBOSetLayoutCache = LayoutCache<UBOSet::Mask>;
using SamplerSetLayoutCache = LayoutCache<SamplerSet::Mask>;
} // anonymous namespace
VulkanDescriptorSet::VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet,
VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn)
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET),
resources(allocator),
vkSet(rawSet),
layout(layout),
mOnRecycleFn(std::move(onRecycleFn)) {}
VulkanDescriptorSet::~VulkanDescriptorSet() {
if (mOnRecycleFn) {
mOnRecycleFn(this);
}
}
VulkanDescriptorSet* VulkanDescriptorSet::create(VulkanResourceAllocator* allocator,
VkDescriptorSet rawSet, VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn) {
auto handle = allocator->allocHandle<VulkanDescriptorSet>();
auto set = allocator->construct<VulkanDescriptorSet>(handle, allocator, rawSet, layout,
std::move(onRecycleFn));
return set;
}
class VulkanDescriptorSetManager::Impl {
public:
Impl(VkDevice device, VulkanResourceAllocator* allocator)
: mDevice(device),
mUBOLayoutCache(device),
mUBOCache(device, allocator),
mSamplerLayoutCache(device),
mSamplerCache(device, allocator),
mResources(allocator) {}
void setUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject,
VkDeviceSize offset, VkDeviceSize size) noexcept {
mUbos.push_back({bindingIndex, bufferObject, offset, size});
// Between "set" and "commit", we need to ref the buffer object to avoid it being collected.
mResources.acquire(bufferObject);
}
void setSamplers(SamplerArray&& samplers) {
mSamplers = std::move(samplers);
for (auto const& sampler: mSamplers) {
mResources.acquire(sampler.texture);
}
}
void gc() noexcept {
mUBOCache.gc();
mSamplerCache.gc();
}
// This will write/update all of the descriptor set (and create a set if a one of the same
// layout is not available).
void bind(VulkanCommandBuffer* commands, GetPipelineLayoutFunction& getPipelineLayoutFn) {
LayoutArray layouts = {VK_NULL_HANDLE, VK_NULL_HANDLE};
std::array<VkDescriptorSet, 2> descSets = {VK_NULL_HANDLE, VK_NULL_HANDLE};
uint8_t descSetCount = 0;
VkWriteDescriptorSet descriptorWrites[UNIFORM_BINDING_COUNT + SAMPLER_BINDING_COUNT];
VkDescriptorBufferInfo uboWrite[UNIFORM_BINDING_COUNT];
VkDescriptorImageInfo samplerWrite[SAMPLER_BINDING_COUNT];
uint32_t nwrites = 0;
UBOSet::Key uboKey = {};
auto& uboMask = uboKey.mask;
uint8_t uboCount = 0;
for (auto const& ubo: mUbos) {
auto const& binding = std::get<0>(ubo);
uboKey.buffers[uboCount] = std::get<1>(ubo)->buffer.getGpuBuffer();
uboKey.offsets[uboCount] = std::get<2>(ubo);
uboKey.sizes[uboCount] = std::get<3>(ubo);
uboCount++;
// Currently we let ubo be visible in all stages.
uboMask |= (VERTEX_STAGE << binding);
uboMask |= (FRAGMENT_STAGE << binding);
}
if (uboMask) {
layouts[UBO_SET_INDEX] = uboKey.layout = mUBOLayoutCache.getLayout(uboMask);
auto [descriptorSet, cached] = mUBOCache.obtainSet(uboKey);
descSets[descSetCount++] = descriptorSet->vkSet;
// We need to write to the descriptor set since it wasn't cached.
if (!cached) {
// If it wasn't cached before, we need to ref the resources that it touches.
for (auto const& ubo: mUbos) {
auto const buffer = std::get<1>(ubo);
descriptorSet->resources.acquire(buffer);
}
for (uint8_t i = 0; i < uboCount; ++i) {
auto const& binding = std::get<0>(mUbos[i]);
VkWriteDescriptorSet& descriptorWrite = descriptorWrites[nwrites++];
auto& writeInfo = uboWrite[i];
writeInfo = {
.buffer = uboKey.buffers[i],
.offset = uboKey.offsets[i],
.range = uboKey.sizes[i],
};
descriptorWrite = {
.dstSet = descriptorSet->vkSet,
.dstBinding = binding,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.pBufferInfo = &writeInfo,
};
}
}
commands->acquire(descriptorSet);
}
SamplerSet::Key samplerKey = {};
auto& samplerMask = samplerKey.mask;
uint8_t samplerCount = 0;
for (auto const sampler: mSamplers) {
samplerMask |= (sampler.stage << sampler.binding);
samplerKey.sampler[samplerCount] = sampler.info.sampler;
samplerKey.imageView[samplerCount] = sampler.info.imageView;
samplerKey.imageLayout[samplerCount] = sampler.info.imageLayout;
samplerCount++;
}
if (samplerMask) {
layouts[SAMPLER_SET_INDEX] = samplerKey.layout =
mSamplerLayoutCache.getLayout(samplerMask);
auto [descriptorSet, cached] = mSamplerCache.obtainSet(samplerKey);
descSets[descSetCount++] = descriptorSet->vkSet;
if (!cached) {
for (auto const& sampler: mSamplers) {
descriptorSet->resources.acquire(sampler.texture);
}
for (uint8_t i = 0; i < samplerCount; ++i) {
auto const& binding = mSamplers[i].binding;
VkWriteDescriptorSet& descriptorWrite = descriptorWrites[nwrites++];
auto& writeInfo = samplerWrite[i];
writeInfo = mSamplers[i].info;
descriptorWrite = {
.dstSet = descriptorSet->vkSet,
.dstBinding = binding,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.pImageInfo = &writeInfo,
};
}
}
commands->acquire(descriptorSet);
}
if (nwrites) {
vkUpdateDescriptorSets(mDevice, nwrites, descriptorWrites, 0, nullptr);
}
VkPipelineLayout const pipelineLayout = getPipelineLayoutFn(layouts);
VkCommandBuffer const cmdbuffer = commands->buffer();
BoundState state {
.cmdbuf = cmdbuffer,
.pipelineLayout = pipelineLayout,
};
if (state == mPreviousBoundState) {
return;
}
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0,
descSetCount, descSets.data(), 0, nullptr);
mPreviousBoundState = state;
// Once bound, the resources are now ref'd in the descriptor set and the references in this
// class can be released and the descriptor set is ref'd by the command buffer.
mResources.clear();
}
private:
struct BoundState {
VkCommandBuffer cmdbuf;
VkPipelineLayout pipelineLayout;
bool operator==(BoundState const& b) const {
return cmdbuf == b.cmdbuf && pipelineLayout == b.pipelineLayout;
}
};
VkDevice mDevice;
UBOSetLayoutCache mUBOLayoutCache;
UBOSetCache mUBOCache;
SamplerSetLayoutCache mSamplerLayoutCache;
SamplerSetCache mSamplerCache;
std::vector<std::tuple<uint32_t, VulkanBufferObject*, VkDeviceSize, VkDeviceSize>> mUbos;
SamplerArray mSamplers;
VulkanAcquireOnlyResourceManager mResources;
BoundState mPreviousBoundState;
};
VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device,
VulkanResourceAllocator* resourceAllocator)
: mImpl(new Impl(device, resourceAllocator)) {}
void VulkanDescriptorSetManager::terminate() noexcept {
assert_invariant(mImpl);
delete mImpl;
mImpl = nullptr;
}
void VulkanDescriptorSetManager::gc() noexcept { mImpl->gc(); }
void VulkanDescriptorSetManager::bind(VulkanCommandBuffer* commands,
GetPipelineLayoutFunction& getPipelineLayoutFn) {
mImpl->bind(commands, getPipelineLayoutFn);
}
void VulkanDescriptorSetManager::setUniformBufferObject(uint32_t bindingIndex,
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
mImpl->setUniformBufferObject(bindingIndex, bufferObject, offset, size);
}
void VulkanDescriptorSetManager::setSamplers(SamplerArray&& samplers) {
mImpl->setSamplers(std::move(samplers));
}
} // namespace filament::backend
@@ -0,0 +1,111 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSET_H
#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSET_H
#include <vulkan/VulkanResourceAllocator.h>
#include <vulkan/VulkanTexture.h>
#include <vulkan/VulkanUtility.h>
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <backend/TargetBufferInfo.h>
#include <utils/bitset.h>
#include <bluevk/BlueVK.h>
#include <tsl/robin_map.h>
namespace filament::backend {
// We need to make this class public to enable allocation on the HandleAllocator.
struct VulkanDescriptorSet : public VulkanResourceBase {
public:
// Because we need to recycle descriptor set not used, we allow for a callback that the "Pool"
// can use to repackage the vk handle.
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet,
VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn);
~VulkanDescriptorSet();
static VulkanDescriptorSet* create(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet,
VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn);
// TODO: maybe change to fixed size for performance.
VulkanAcquireOnlyResourceManager resources;
VkDescriptorSet const vkSet;
VkDescriptorSetLayout const layout;
private:
OnRecycle mOnRecycleFn;
};
// Abstraction over the pool and the layout cache.
class VulkanDescriptorSetManager {
public:
using StageBitMask = uint32_t;
using UniformBufferBitmask = StageBitMask;
using SamplerBitmask = StageBitMask;
static constexpr uint8_t CONCURRENT_DESCRIPTOR_SET_COUNT = 2;// UBO and samplers
static constexpr uint8_t MAX_SUPPORTED_SHADER_STAGE = 2; // Vertex and fragment.
static_assert(sizeof(UniformBufferBitmask) * 8 >=
(Program::UNIFORM_BINDING_COUNT) *MAX_SUPPORTED_SHADER_STAGE);
static_assert(sizeof(SamplerBitmask) * 8 >=
Program::SAMPLER_BINDING_COUNT * MAX_SUPPORTED_SHADER_STAGE);
static constexpr StageBitMask VERTEX_STAGE = 0x1;
static constexpr StageBitMask FRAGMENT_STAGE = (0x1 << (sizeof(StageBitMask) / 4));
static constexpr uint8_t UBO_SET_INDEX = 0;
static constexpr uint8_t SAMPLER_SET_INDEX = 1;
using LayoutArray = std::array<VkDescriptorSetLayout, CONCURRENT_DESCRIPTOR_SET_COUNT>;
struct SamplerBundle {
VkDescriptorImageInfo info = {};
VulkanTexture* texture = nullptr;
uint8_t binding = 0;
SamplerBitmask stage = 0;
};
using SamplerArray = CappedArray<SamplerBundle, Program::SAMPLER_BINDING_COUNT>;
using GetPipelineLayoutFunction = std::function<VkPipelineLayout(LayoutArray)>;
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
void terminate() noexcept;
void gc() noexcept;
// This will write/update all of the descriptor set.
void bind(VulkanCommandBuffer* commands, GetPipelineLayoutFunction& getPipelineFn);
void setUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject,
VkDeviceSize offset, VkDeviceSize size) noexcept;
void setSamplers(SamplerArray&& samplers);
private:
class Impl;
Impl* mImpl;
};
} // namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSET_H
File diff suppressed because it is too large Load Diff
@@ -1,110 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
#include <vulkan/VulkanResourceAllocator.h>
#include <vulkan/VulkanTexture.h>
#include <vulkan/VulkanUtility.h>
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <backend/TargetBufferInfo.h>
#include <utils/bitset.h>
#include <bluevk/BlueVK.h>
#include <tsl/robin_map.h>
#include <functional>
namespace filament::backend {
using namespace descset;
// [GDSR]: Great-Descriptor-Set-Refactor: As of 03/20/24, the Filament frontend is planning to
// introduce descriptor set. This PR will arrive before that change is complete. As such, some of
// the methods introduced here will be obsolete, and certain logic will be generalized.
// Abstraction over the pool and the layout cache.
class VulkanDescriptorSetManager {
public:
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT =
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
using GetPipelineLayoutFunction =
std::function<VkPipelineLayout(VulkanDescriptorSetLayoutList const&)>;
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
void terminate() noexcept;
void gc();
// TODO: Obsolete after [GDSR].
// This will write/update/bind all of the descriptor set. After [GDSR], the binding for
// descriptor sets will not depend on the layout described in the program.
VkPipelineLayout bind(VulkanCommandBuffer* commands, VulkanProgram* program,
GetPipelineLayoutFunction& getPipelineLayoutFn);
// TODO: Obsolete after [GDSR].
// This is to "dynamically" bind UBOs that might have offsets changed between pipeline binding
// and the draw call. We do this because UBOs for primitives that are part of the same
// renderable can be stored within one buffer. This can be a no-op if there were no range
// changes between the pipeline bind and the draw call. We will re-use applicable states
// provided within the bind() call, including the UBO descriptor set layout. TODO: make it a
// proper dynamic binding when Filament-side descriptor changes are completed.
void dynamicBind(VulkanCommandBuffer* commands, Handle<VulkanDescriptorSetLayout> uboLayout);
// TODO: Obsolete after [GDSR].
// Since we use program pointer as cache key, we need to clear the cache when it's freed.
void clearProgram(VulkanProgram* program) noexcept;
Handle<VulkanDescriptorSetLayout> createLayout(descset::DescriptorSetLayout const& layout);
void destroyLayout(Handle<VulkanDescriptorSetLayout> layout);
void updateBuffer(Handle<VulkanDescriptorSet> set, uint8_t binding,
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept;
void updateSampler(Handle<VulkanDescriptorSet> set, uint8_t binding,
VulkanTexture* texture, VkSampler sampler) noexcept;
void updateInputAttachment(Handle<VulkanDescriptorSet> set, VulkanAttachment attachment) noexcept;
void clearBuffer(uint32_t bindingIndex);
void setPlaceHolders(VkSampler sampler, VulkanTexture* texture,
VulkanBufferObject* bufferObject) noexcept;
void clearState() noexcept;
// TODO: To be completed after [GDSR]
Handle<VulkanDescriptorSet> createSet(Handle<VulkanDescriptorSetLayout> layout) {
return Handle<VulkanDescriptorSet>();
}
// TODO: To be completed after [GDSR]
void destroySet(Handle<VulkanDescriptorSet> set) {}
private:
class Impl;
Impl* mImpl;
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
@@ -1,64 +0,0 @@
/*
* Copyright (C) 2024 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 "VulkanPipelineLayoutCache.h"
#include <vulkan/VulkanResourceAllocator.h>
namespace filament::backend {
VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
VulkanDescriptorSetLayoutList const& descriptorSetLayouts) {
PipelineLayoutKey key = {VK_NULL_HANDLE};
uint8_t descSetLayoutCount = 0;
for (auto layoutHandle: descriptorSetLayouts) {
if (layoutHandle) {
auto layout = mAllocator->handle_cast<VulkanDescriptorSetLayout*>(layoutHandle);
key[descSetLayoutCount++] = layout->vklayout;
}
}
auto iter = mPipelineLayouts.find(key);
if (iter != mPipelineLayouts.end()) {
PipelineLayoutCacheEntry& entry = mPipelineLayouts[key];
entry.lastUsed = mTimestamp++;
return entry.handle;
}
VkPipelineLayoutCreateInfo info{
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.pNext = nullptr,
.setLayoutCount = (uint32_t) descSetLayoutCount,
.pSetLayouts = key.data(),
.pushConstantRangeCount = 0,
};
VkPipelineLayout layout;
vkCreatePipelineLayout(mDevice, &info, VKALLOC, &layout);
mPipelineLayouts[key] = {
.handle = layout,
.lastUsed = mTimestamp++,
};
return layout;
}
void VulkanPipelineLayoutCache::terminate() noexcept {
for (auto const& [key, entry]: mPipelineLayouts) {
vkDestroyPipelineLayout(mDevice, entry.handle, VKALLOC);
}
}
}// namespace filament::backend
@@ -1,71 +0,0 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKANPIPELINELAYOUTCACHE_H
#define TNT_FILAMENT_BACKEND_VULKANPIPELINELAYOUTCACHE_H
#include <vulkan/VulkanHandles.h>
#include <bluevk/BlueVK.h>
#include <utils/Hash.h>
#include <tsl/robin_map.h>
namespace filament::backend {
class VulkanPipelineLayoutCache {
public:
VulkanPipelineLayoutCache(VkDevice device, VulkanResourceAllocator* allocator)
: mDevice(device),
mAllocator(allocator),
mTimestamp(0) {}
void terminate() noexcept;
using PipelineLayoutKey = std::array<VkDescriptorSetLayout,
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
VulkanPipelineLayoutCache(VulkanPipelineLayoutCache const&) = delete;
VulkanPipelineLayoutCache& operator=(VulkanPipelineLayoutCache const&) = delete;
VkPipelineLayout getLayout(VulkanDescriptorSetLayoutList const& descriptorSetLayouts);
private:
using Timestamp = uint64_t;
struct PipelineLayoutCacheEntry {
VkPipelineLayout handle;
Timestamp lastUsed;
};
using PipelineLayoutKeyHashFn = utils::hash::MurmurHashFn<PipelineLayoutKey>;
struct PipelineLayoutKeyEqual {
bool operator()(PipelineLayoutKey const& k1, PipelineLayoutKey const& k2) const {
return 0 == memcmp((const void*) &k1, (const void*) &k2, sizeof(PipelineLayoutKey));
}
};
using PipelineLayoutMap = tsl::robin_map<PipelineLayoutKey, PipelineLayoutCacheEntry,
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
VkDevice mDevice;
VulkanResourceAllocator* mAllocator;
Timestamp mTimestamp;
PipelineLayoutMap mPipelineLayouts;
};
}
#endif // TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
@@ -24,7 +24,6 @@
#include <unordered_map>
#include <variant>
#include <vector>
namespace filament::backend {
@@ -73,7 +72,7 @@ void workaroundSpecConstant(Program::ShaderBlob const& blob,
std::vector<uint32_t>& output) {
using WordMap = std::unordered_map<uint32_t, uint32_t>;
using SpecValueMap = std::unordered_map<uint32_t, SpecConstantValue>;
constexpr size_t HEADER_SIZE = 5;
constexpr size_t const HEADER_SIZE = 5;
WordMap varToIdMap;
SpecValueMap idToValue;
@@ -146,61 +145,4 @@ void workaroundSpecConstant(Program::ShaderBlob const& blob,
output.resize(outputCursor);
}
std::tuple<uint32_t,uint32_t, uint32_t> getProgramBindings(Program::ShaderBlob const& blob) {
std::unordered_map<uint32_t, uint32_t> targetToSet, targetToBinding;
constexpr size_t HEADER_SIZE = 5;
size_t const dataSize = blob.size() / 4;
uint32_t const* data = (uint32_t*) blob.data();
for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) {
uint32_t const firstWord = data[cursor];
uint32_t const wordCount = firstWord >> 16;
uint32_t const op = firstWord & 0x0000FFFF;
switch(op) {
case spv::Op::OpDecorate: {
if (data[cursor + 2] == spv::Decoration::DecorationDescriptorSet) {
uint32_t const targetVar = data[cursor + 1];
uint32_t const setId = data[cursor + 3];
targetToSet[targetVar] = setId;
} else if (data[cursor + 2] == spv::Decoration::DecorationBinding) {
uint32_t const targetVar = data[cursor + 1];
uint32_t const binding = data[cursor + 3];
targetToBinding[targetVar] = binding;
}
break;
}
default:
break;
}
cursor += wordCount;
}
constexpr uint32_t UBO = 0;
constexpr uint32_t SAMPLER = 1;
constexpr uint32_t IATTACHMENT = 2;
uint32_t ubo = 0, sampler = 0, inputAttachment = 0;
for (auto const [target, setId]: targetToSet) {
uint32_t const binding = targetToBinding[target];
assert_invariant(binding < 32);
switch(setId) {
case UBO:
ubo |= (1 << binding);
break;
case SAMPLER:
sampler |= (1 << binding);
break;
case IATTACHMENT:
inputAttachment |= (1 << binding);
break;
default:
PANIC_POSTCONDITION("unexpected %d", (int) setId);
}
}
return {ubo, sampler, inputAttachment};
}
} // namespace filament::backend
@@ -17,11 +17,10 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H
#define TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H
#include <backend/Program.h>
#include "backend/Program.h"
#include <utils/FixedCapacityVector.h>
#include <tuple>
#include <vector>
namespace filament::backend {
@@ -43,9 +42,6 @@ void workaroundSpecConstant(Program::ShaderBlob const& blob,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants,
std::vector<uint32_t>& output);
// bindings for UBO, samplers, input attachment
std::tuple<uint32_t, uint32_t, uint32_t> getProgramBindings(Program::ShaderBlob const& blob);
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H
-15
View File
@@ -79,20 +79,6 @@ ShaderGenerator::ShaderGenerator(std::string vertex, std::string fragment,
mVertexBlob(transpileShader(ShaderStage::VERTEX, std::move(vertex), backend, isMobile, sib)),
mFragmentBlob(transpileShader(ShaderStage::FRAGMENT, std::move(fragment), backend,
isMobile, sib)) {
switch (backend) {
case Backend::OPENGL:
mShaderLanguage = filament::backend::ShaderLanguage::ESSL3;
break;
case Backend::VULKAN:
mShaderLanguage = filament::backend::ShaderLanguage::SPIRV;
break;
case Backend::METAL:
mShaderLanguage = filament::backend::ShaderLanguage::MSL;
break;
case Backend::NOOP:
mShaderLanguage = filament::backend::ShaderLanguage::ESSL3;
break;
}
}
ShaderGenerator::Blob ShaderGenerator::transpileShader(
@@ -174,7 +160,6 @@ ShaderGenerator::Blob ShaderGenerator::transpileShader(
Program ShaderGenerator::getProgram(filament::backend::DriverApi&) noexcept {
Program program;
program.shaderLanguage(mShaderLanguage);
program.shader(ShaderStage::VERTEX, mVertexBlob.data(), mVertexBlob.size());
program.shader(ShaderStage::FRAGMENT, mFragmentBlob.data(), mFragmentBlob.size());
return program;
-1
View File
@@ -60,7 +60,6 @@ private:
Blob mFragmentBlob;
std::string mCompiledVertexShader;
std::string mCompiledFragmentShader;
filament::backend::ShaderLanguage mShaderLanguage;
};
@@ -58,7 +58,6 @@ kernel void main0() {}
}
Program program;
program.shaderLanguage(ShaderLanguage::ESSL3);
program.shader(ShaderStage::COMPUTE, shader.data(), shader.size() + 1);
Handle<HwProgram> ph = driver.createProgram(std::move(program));
@@ -145,7 +144,6 @@ kernel void main0(device Output_data& output_data [[buffer(0)]],
driver.updateBufferObject(input_data, { data.data(), size }, 0);
Program program;
program.shaderLanguage(ShaderLanguage::ESSL3);
program.shader(ShaderStage::COMPUTE, shader.data(), shader.size() + 1);
Handle<HwProgram> ph = driver.createProgram(std::move(program));
+29 -59
View File
@@ -34,7 +34,6 @@
#include <utils/CString.h>
#include <stdlib.h>
#include <optional>
using namespace utils;
using namespace filament::backend;
@@ -43,30 +42,31 @@ using namespace filamat;
namespace filament {
constexpr std::pair<ChunkType, ChunkType> shaderLanguageToTags(ShaderLanguage language) {
switch (language) {
case ShaderLanguage::ESSL3:
return { ChunkType::MaterialGlsl, ChunkType::DictionaryText };
case ShaderLanguage::ESSL1:
return { ChunkType::MaterialEssl1, ChunkType::DictionaryText };
case ShaderLanguage::MSL:
return { ChunkType::MaterialMetal, ChunkType::DictionaryText };
case ShaderLanguage::SPIRV:
return { ChunkType::MaterialSpirv, ChunkType::DictionarySpirv };
case ShaderLanguage::METAL_LIBRARY:
return { ChunkType::MaterialMetalLibrary, ChunkType::DictionaryMetalLibrary };
}
}
// ------------------------------------------------------------------------------------------------
MaterialParser::MaterialParserDetails::MaterialParserDetails(
const utils::FixedCapacityVector<ShaderLanguage>& preferredLanguages, const void* data,
size_t size)
: mManagedBuffer(data, size),
mChunkContainer(mManagedBuffer.data(), mManagedBuffer.size()),
mPreferredLanguages(preferredLanguages),
mMaterialChunk(mChunkContainer) {}
MaterialParser::MaterialParserDetails::MaterialParserDetails(ShaderLanguage language, const void* data, size_t size)
: mManagedBuffer(data, size),
mChunkContainer(mManagedBuffer.data(), mManagedBuffer.size()),
mMaterialChunk(mChunkContainer) {
switch (language) {
case ShaderLanguage::ESSL3:
mMaterialTag = ChunkType::MaterialGlsl;
mDictionaryTag = ChunkType::DictionaryText;
break;
case ShaderLanguage::ESSL1:
mMaterialTag = ChunkType::MaterialEssl1;
mDictionaryTag = ChunkType::DictionaryText;
break;
case ShaderLanguage::MSL:
mMaterialTag = ChunkType::MaterialMetal;
mDictionaryTag = ChunkType::DictionaryText;
break;
case ShaderLanguage::SPIRV:
mMaterialTag = ChunkType::MaterialSpirv;
mDictionaryTag = ChunkType::DictionarySpirv;
break;
}
}
template<typename T>
UTILS_NOINLINE
@@ -83,9 +83,9 @@ bool MaterialParser::MaterialParserDetails::getFromSimpleChunk(
// ------------------------------------------------------------------------------------------------
MaterialParser::MaterialParser(utils::FixedCapacityVector<ShaderLanguage> preferredLanguages,
const void* data, size_t size)
: mImpl(preferredLanguages, data, size) {}
MaterialParser::MaterialParser(ShaderLanguage language, const void* data, size_t size)
: mImpl(language, data, size) {
}
ChunkContainer& MaterialParser::getChunkContainer() noexcept {
return mImpl.mChunkContainer;
@@ -100,40 +100,20 @@ MaterialParser::ParseResult MaterialParser::parse() noexcept {
if (UTILS_UNLIKELY(!cc.parse())) {
return ParseResult::ERROR_OTHER;
}
using MaybeShaderLanguageAndChunks =
std::optional<std::tuple<ShaderLanguage, ChunkType, ChunkType>>;
auto chooseLanguage = [this, &cc]() -> MaybeShaderLanguageAndChunks {
for (auto language : mImpl.mPreferredLanguages) {
const auto [matTag, dictTag] = shaderLanguageToTags(language);
if (cc.hasChunk(matTag) && cc.hasChunk(dictTag)) {
return std::make_tuple(language, matTag, dictTag);
}
}
return {};
};
const auto result = chooseLanguage();
if (!result.has_value()) {
const ChunkType matTag = mImpl.mMaterialTag;
const ChunkType dictTag = mImpl.mDictionaryTag;
if (UTILS_UNLIKELY(!cc.hasChunk(matTag) || !cc.hasChunk(dictTag))) {
return ParseResult::ERROR_MISSING_BACKEND;
}
const auto [chosenLanguage, matTag, dictTag] = result.value();
if (UTILS_UNLIKELY(!DictionaryReader::unflatten(cc, dictTag, mImpl.mBlobDictionary))) {
return ParseResult::ERROR_OTHER;
}
if (UTILS_UNLIKELY(!mImpl.mMaterialChunk.initialize(matTag))) {
return ParseResult::ERROR_OTHER;
}
mImpl.mChosenLanguage = chosenLanguage;
return ParseResult::SUCCESS;
}
backend::ShaderLanguage MaterialParser::getShaderLanguage() const noexcept {
return mImpl.mChosenLanguage;
}
// Accessors
bool MaterialParser::getMaterialVersion(uint32_t* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialVersion, value);
@@ -297,16 +277,6 @@ bool MaterialParser::getBlendingMode(BlendingMode* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialBlendingMode, reinterpret_cast<uint8_t*>(value));
}
bool MaterialParser::getCustomBlendFunction(std::array<BlendFunction, 4>* value) const noexcept {
uint32_t blendFunctions = 0;
bool const result = mImpl.getFromSimpleChunk(ChunkType::MaterialBlendFunction, &blendFunctions);
(*value)[0] = BlendFunction((blendFunctions >> 24) & 0xFF);
(*value)[1] = BlendFunction((blendFunctions >> 16) & 0xFF);
(*value)[2] = BlendFunction((blendFunctions >> 8) & 0xFF);
(*value)[3] = BlendFunction((blendFunctions >> 0) & 0xFF);
return result;
}
bool MaterialParser::getMaskThreshold(float* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialMaskThreshold, value);
}
+5 -12
View File
@@ -29,12 +29,10 @@
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/compiler.h>
#include <utils/CString.h>
#include <inttypes.h>
#include <utility>
namespace filaflat {
class ChunkContainer;
@@ -50,8 +48,7 @@ struct MaterialConstant;
class MaterialParser {
public:
MaterialParser(utils::FixedCapacityVector<backend::ShaderLanguage> preferredLanguages,
const void* data, size_t size);
MaterialParser(backend::ShaderLanguage language, const void* data, size_t size);
MaterialParser(MaterialParser const& rhs) noexcept = delete;
MaterialParser& operator=(MaterialParser const& rhs) noexcept = delete;
@@ -63,7 +60,6 @@ public:
};
ParseResult parse() noexcept;
backend::ShaderLanguage getShaderLanguage() const noexcept;
// Accessors
bool getMaterialVersion(uint32_t* value) const noexcept;
@@ -106,7 +102,6 @@ public:
bool getShading(Shading*) const noexcept;
bool getBlendingMode(BlendingMode*) const noexcept;
bool getCustomBlendFunction(std::array<backend::BlendFunction, 4>*) const noexcept;
bool getMaskThreshold(float*) const noexcept;
bool getAlphaToCoverageSet(bool*) const noexcept;
bool getAlphaToCoverage(bool*) const noexcept;
@@ -134,9 +129,7 @@ public:
private:
struct MaterialParserDetails {
MaterialParserDetails(
const utils::FixedCapacityVector<backend::ShaderLanguage>& preferredLanguages,
const void* data, size_t size);
MaterialParserDetails(backend::ShaderLanguage language, const void* data, size_t size);
template<typename T>
bool getFromSimpleChunk(filamat::ChunkType type, T* value) const noexcept;
@@ -163,12 +156,12 @@ private:
ManagedBuffer mManagedBuffer;
filaflat::ChunkContainer mChunkContainer;
utils::FixedCapacityVector<backend::ShaderLanguage> mPreferredLanguages;
backend::ShaderLanguage mChosenLanguage;
// Keep MaterialChunk alive between calls to getShader to avoid reload the shader index.
filaflat::MaterialChunk mMaterialChunk;
filaflat::BlobDictionary mBlobDictionary;
filamat::ChunkType mMaterialTag = filamat::ChunkType::Unknown;
filamat::ChunkType mDictionaryTag = filamat::ChunkType::Unknown;
};
filaflat::ChunkContainer& getChunkContainer() noexcept;
+4 -1
View File
@@ -450,8 +450,11 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
.levels = uint8_t(levelCount),
.format = isES2 ? TextureFormat::DEPTH24 : TextureFormat::DEPTH32F });
// workaround: since we have levels, this implies SAMPLEABLE (because of the gl
// backend, which implements non-sampleables with renderbuffers, which don't have levels).
// (should the gl driver revert to textures, in that case?)
data.depth = builder.write(data.depth,
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
FrameGraphTexture::Usage::DEPTH_ATTACHMENT | FrameGraphTexture::Usage::SAMPLEABLE);
if (config.picking) {
data.picking = builder.createTexture("Picking Buffer", {
+8 -19
View File
@@ -16,35 +16,17 @@
#include "RendererUtils.h"
#include "PostProcessManager.h"
#include "details/Engine.h"
#include "details/View.h"
#include "fg/FrameGraph.h"
#include "fg/FrameGraphId.h"
#include "fg/FrameGraphResources.h"
#include "fg/FrameGraphTexture.h"
#include <filament/Options.h>
#include <filament/RenderableManager.h>
#include <filament/Viewport.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/PixelBufferDescriptor.h>
#include <utils/BitmaskEnum.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Panic.h>
#include <algorithm>
#include <utility>
#include <stddef.h>
#include <stdint.h>
namespace filament {
using namespace backend;
@@ -171,9 +153,16 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
data.depth = builder.read(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
data.color = builder.write(data.color, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
data.depth = builder.write(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
if (engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) {
// Add sampleable usage flag for depth in multiview rendering, othewise it's
// treated as renderbuffer in the backend and crashed.
data.depth = builder.write(data.depth,
FrameGraphTexture::Usage::DEPTH_ATTACHMENT |
FrameGraphTexture::Usage::SAMPLEABLE);
layerCount = engine.getConfig().stereoscopicEyeCount;
} else {
data.depth = builder.write(data.depth,
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
}
/*
+101 -127
View File
@@ -63,7 +63,8 @@ namespace filament {
using namespace backend;
using namespace math;
ShadowMapManager::ShadowMapManager(FEngine& engine) {
ShadowMapManager::ShadowMapManager(FEngine& engine)
: mEngine(engine) {
FDebugRegistry& debugRegistry = engine.getDebugRegistry();
debugRegistry.registerProperty("d.shadowmap.visualize_cascades",
&engine.debug.shadowmap.visualize_cascades);
@@ -272,26 +273,9 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
if (!spotShadowCastersRange.empty()) {
for (auto& shadowMap : getSpotShadowMaps()) {
assert_invariant(!shadowMap.isDirectionalShadow());
switch (shadowMap.getShadowType()) {
case ShadowType::DIRECTIONAL:
// we should never be here
break;
case ShadowType::SPOT:
prepareSpotShadowMap(shadowMap, engine, view, mainCameraInfo,
scene->getLightData(), mSceneInfo);
break;
case ShadowType::POINT:
preparePointShadowMap(shadowMap, engine, view, mainCameraInfo,
scene->getLightData());
break;
}
if (shadowMap.hasVisibleShadows()) {
passList.push_back({
{}, &shadowMap, spotShadowCastersRange,
VISIBLE_DYN_SHADOW_RENDERABLE });
}
passList.push_back({
{}, &shadowMap, spotShadowCastersRange,
VISIBLE_DYN_SHADOW_RENDERABLE });
}
}
@@ -305,7 +289,6 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
scene, mainCameraInfo, userTime, passBuilder = passBuilder](
FrameGraphResources const&, auto const& data, DriverApi& driver) mutable {
// Note: we could almost parallel_for the loop below, the problem currently is
// that updatePrimitivesLod() updates temporary global state.
// prepareSpotShadowMap() also update the visibility of renderable. These two
@@ -314,54 +297,53 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
// Generate a RenderPass for each shadow map
for (auto const& entry : data.passList) {
ShadowMap const& shadowMap = *entry.shadowMap;
assert_invariant(shadowMap.hasVisibleShadows());
// Note: this loop can generate a lot of commands that come out of the
// "per frame command arena". The allocation persists until the
// end of the frame.
// One way to possibly mitigate this, would be to always use the
// same command buffer for all shadow map, but then we'd generate
// a lot of unneeded draw calls.
// To do this efficiently, we'd need a way to cull draw calls already
// recorded in the command buffer, per shadow map.
// Note: the output of culling below is stored in scene->getRenderableData()
ShadowMap& shadowMap = *entry.shadowMap;
// for spot shadow map, we need to do the culling
switch (shadowMap.getShadowType()) {
case ShadowType::DIRECTIONAL:
// we should never be here
break;
case ShadowType::SPOT:
ShadowMapManager::cullSpotShadowMap(shadowMap, engine, view,
prepareSpotShadowMap(shadowMap, engine, view, mainCameraInfo,
scene->getRenderableData(), entry.range,
scene->getLightData());
scene->getLightData(), mSceneInfo);
break;
case ShadowType::POINT:
ShadowMapManager::cullPointShadowMap(shadowMap, view,
preparePointShadowMap(shadowMap, engine, view, mainCameraInfo,
scene->getRenderableData(), entry.range,
scene->getLightData());
scene->getLightData(), mSceneInfo);
break;
}
// cameraInfo only valid after calling update
const CameraInfo cameraInfo{ shadowMap.getCamera(), mainCameraInfo };
if (shadowMap.hasVisibleShadows()) {
// Note: this loop can generate a lot of commands that come out of the
// "per frame command arena". The allocation persists until the
// end of the frame.
// One way to possibly mitigate this, would be to always use the
// same command buffer for all shadow map, but then we'd generate
// a lot of unneeded draw calls.
// To do this efficiently, we'd need a way to cull draw calls already
// recorded in the command buffer, per shadow map.
auto transaction = ShadowMap::open(driver);
ShadowMap::prepareCamera(transaction, engine, cameraInfo);
ShadowMap::prepareViewport(transaction, shadowMap.getViewport());
ShadowMap::prepareTime(transaction, engine, userTime);
ShadowMap::prepareShadowMapping(transaction,
vsmShadowOptions.highPrecision);
shadowMap.commit(transaction, driver);
// updatePrimitivesLod must be run before RenderPass::appendCommands.
view.updatePrimitivesLod(engine,
cameraInfo, scene->getRenderableData(), entry.range);
// cameraInfo only valid after calling update
const CameraInfo cameraInfo{ shadowMap.getCamera(), mainCameraInfo };
// generate and sort the commands for rendering the shadow map
auto transaction = ShadowMap::open(driver);
ShadowMap::prepareCamera(transaction, engine, cameraInfo);
ShadowMap::prepareViewport(transaction, shadowMap.getViewport());
ShadowMap::prepareTime(transaction, engine, userTime);
ShadowMap::prepareShadowMapping(transaction,
vsmShadowOptions.highPrecision);
shadowMap.commit(transaction, driver);
RenderPass const pass = passBuilder
// updatePrimitivesLod must be run before RenderPass::appendCommands.
view.updatePrimitivesLod(engine,
cameraInfo, scene->getRenderableData(), entry.range);
// generate and sort the commands for rendering the shadow map
RenderPass const pass = passBuilder
.camera(cameraInfo)
.visibilityMask(entry.visibilityMask)
.geometry(scene->getRenderableData(),
@@ -369,15 +351,16 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
.commandTypeFlags(RenderPass::CommandTypeFlags::SHADOW)
.build(engine);
entry.executor = pass.getExecutor();
entry.executor = pass.getExecutor();
if (!view.hasVSM()) {
auto const* options = shadowMap.getShadowOptions();
PolygonOffset const polygonOffset = { // handle reversed Z
.slope = -options->polygonOffsetSlope,
.constant = -options->polygonOffsetConstant
};
entry.executor.overridePolygonOffset(&polygonOffset);
if (!view.hasVSM()) {
auto const* options = shadowMap.getShadowOptions();
PolygonOffset const polygonOffset = { // handle reversed Z
.slope = -options->polygonOffsetSlope,
.constant = -options->polygonOffsetConstant
};
entry.executor.overridePolygonOffset(&polygonOffset);
}
}
}
@@ -402,8 +385,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
auto const& passList = prepareShadowPass.getData().passList;
for (auto const& entry: passList) {
assert_invariant(entry.shadowMap->hasVisibleShadows());
if (!entry.shadowMap->hasVisibleShadows()) {
continue;
}
const uint8_t layer = entry.shadowMap->getLayer();
const auto* options = entry.shadowMap->getShadowOptions();
const auto msaaSamples = textureRequirements.msaaSamples;
@@ -630,7 +615,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
// Compute scene-dependent values shared across all cascades
ShadowMap::updateSceneInfoDirectional(MvAtOrigin, *scene, sceneInfo);
shadowMap.updateDirectional(engine,
shadowMap.updateDirectional(mEngine,
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
hasVisibleShadows = shadowMap.hasVisibleShadows();
@@ -696,7 +681,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
sceneInfo.csNearFar = { csSplitPosition[i], csSplitPosition[i + 1] };
auto shaderParameters = shadowMap.updateDirectional(engine,
auto shaderParameters = shadowMap.updateDirectional(mEngine,
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
if (shadowMap.hasVisibleShadows()) {
@@ -772,58 +757,15 @@ void ShadowMapManager::updateSpotVisibilityMasks(
}
}
void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engine, FView& view,
CameraInfo const& mainCameraInfo,
FScene::LightSoa& lightData, ShadowMap::SceneInfo const& sceneInfo) noexcept {
const size_t lightIndex = shadowMap.getLightIndex();
FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions();
// update the shadow map frustum/camera
const ShadowMap::ShadowMapInfo shadowMapInfo{
.atlasDimension = mTextureAtlasRequirements.size,
.textureDimension = uint16_t(options->mapSize),
.shadowDimension = uint16_t(options->mapSize - 2u),
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
engine.getBackend() == Backend::VULKAN,
.vsm = view.hasVSM()
};
auto shaderParameters = shadowMap.updateSpot(engine,
lightData, lightIndex, mainCameraInfo, shadowMapInfo, *view.getScene(), sceneInfo);
// and if we need to generate it, update all the UBO data
if (shadowMap.hasVisibleShadows()) {
const size_t shadowIndex = shadowMap.getShadowIndex();
const float wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
// note: normalBias is set to zero for VSM
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
auto& s = mShadowUb.edit();
const double n = shadowMap.getCamera().getNear();
const double f = shadowMap.getCamera().getCullingFar();
s.shadows[shadowIndex].layer = shadowMap.getLayer();
s.shadows[shadowIndex].lightFromWorldMatrix = shaderParameters.lightSpace;
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
s.shadows[shadowIndex].texelSizeAtOneMeter = wsTexelSizeAtOneMeter;
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
s.shadows[shadowIndex].bulbRadiusLs =
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
/ wsTexelSizeAtOneMeter;
}
}
void ShadowMapManager::cullSpotShadowMap(ShadowMap const& shadowMap, FEngine& engine, FView& view,
void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap,
FEngine& engine, FView& view, CameraInfo const& mainCameraInfo,
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> range,
FScene::LightSoa& lightData) noexcept {
FScene::LightSoa& lightData, ShadowMap::SceneInfo const& sceneInfo) noexcept {
auto& lcm = engine.getLightManager();
const size_t lightIndex = shadowMap.getLightIndex();
const FLightManager::Instance li = lightData.elementAt<FScene::LIGHT_INSTANCE>(lightIndex);
FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions();
// compute the frustum for this light
// for spotlights, we cull shadow casters first because we already know the frustum,
@@ -860,28 +802,19 @@ void ShadowMapManager::cullSpotShadowMap(ShadowMap const& shadowMap, FEngine& en
visibility + range.first,
visibleArray + range.first,
range.size());
}
void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
FEngine& engine, FView& view, CameraInfo const& mainCameraInfo,
FScene::LightSoa& lightData) noexcept {
const uint8_t face = shadowMap.getFace();
const size_t lightIndex = shadowMap.getLightIndex();
FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions();
// update the shadow map frustum/camera
const ShadowMap::ShadowMapInfo shadowMapInfo{
.atlasDimension = mTextureAtlasRequirements.size,
.textureDimension = uint16_t(options->mapSize),
.shadowDimension = uint16_t(options->mapSize), // point-lights don't have a border
.shadowDimension = uint16_t(options->mapSize - 2u),
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
engine.getBackend() == Backend::VULKAN,
.vsm = view.hasVSM()
};
auto shaderParameters = shadowMap.updatePoint(engine, lightData, lightIndex,
mainCameraInfo, shadowMapInfo, *view.getScene(), face);
auto shaderParameters = shadowMap.updateSpot(mEngine,
lightData, lightIndex, mainCameraInfo, shadowMapInfo, *view.getScene(), sceneInfo);
// and if we need to generate it, update all the UBO data
if (shadowMap.hasVisibleShadows()) {
@@ -904,15 +837,19 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
s.shadows[shadowIndex].bulbRadiusLs =
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
/ wsTexelSizeAtOneMeter;
}
}
void ShadowMapManager::cullPointShadowMap(ShadowMap const& shadowMap, FView& view,
void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
FEngine& engine, FView& view, CameraInfo const& mainCameraInfo,
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> range,
FScene::LightSoa& lightData) noexcept {
FScene::LightSoa& lightData,
ShadowMap::SceneInfo const&) noexcept {
const uint8_t face = shadowMap.getFace();
const size_t lightIndex = shadowMap.getLightIndex();
FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions();
// compute the frustum for this light
// for spotlights, we cull shadow casters first because we already know the frustum,
@@ -946,6 +883,43 @@ void ShadowMapManager::cullPointShadowMap(ShadowMap const& shadowMap, FView& vie
visibility + range.first,
visibleArray + range.first,
range.size());
// update the shadow map frustum/camera
const ShadowMap::ShadowMapInfo shadowMapInfo{
.atlasDimension = mTextureAtlasRequirements.size,
.textureDimension = uint16_t(options->mapSize),
.shadowDimension = uint16_t(options->mapSize), // point-lights don't have a border
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
engine.getBackend() == Backend::VULKAN,
.vsm = view.hasVSM()
};
auto shaderParameters = shadowMap.updatePoint(mEngine, lightData, lightIndex,
mainCameraInfo, shadowMapInfo, *view.getScene(), face);
// and if we need to generate it, update all the UBO data
if (shadowMap.hasVisibleShadows()) {
const size_t shadowIndex = shadowMap.getShadowIndex();
const float wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
// note: normalBias is set to zero for VSM
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
auto& s = mShadowUb.edit();
const double n = shadowMap.getCamera().getNear();
const double f = shadowMap.getCamera().getCullingFar();
s.shadows[shadowIndex].layer = shadowMap.getLayer();
s.shadows[shadowIndex].lightFromWorldMatrix = shaderParameters.lightSpace;
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
s.shadows[shadowIndex].texelSizeAtOneMeter = wsTexelSizeAtOneMeter;
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
s.shadows[shadowIndex].bulbRadiusLs =
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
/ wsTexelSizeAtOneMeter;
}
}
ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine& engine,
+5 -9
View File
@@ -160,20 +160,14 @@ private:
void prepareSpotShadowMap(ShadowMap& shadowMap,
FEngine& engine, FView& view, CameraInfo const& mainCameraInfo,
FScene::LightSoa& lightData, ShadowMap::SceneInfo const& sceneInfo) noexcept;
static void cullSpotShadowMap(ShadowMap const& map,
FEngine& engine, FView& view,
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> range,
FScene::LightSoa& lightData) noexcept;
FScene::LightSoa& lightData, ShadowMap::SceneInfo const& sceneInfo) noexcept;
void preparePointShadowMap(ShadowMap& map,
FEngine& engine, FView& view, CameraInfo const& mainCameraInfo,
FScene::LightSoa& lightData) noexcept;
static void cullPointShadowMap(ShadowMap const& shadowMap, FView& view,
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> range,
FScene::LightSoa& lightData) noexcept;
FScene::LightSoa& lightData,
ShadowMap::SceneInfo const& sceneInfo) noexcept;
static void updateSpotVisibilityMasks(
uint8_t visibleLayers,
@@ -209,6 +203,8 @@ private:
size_t mSplitCount;
};
FEngine& mEngine;
// Atlas requirements, updated in ShadowMapManager::update(),
// consumed in ShadowMapManager::render()
struct TextureAtlasRequirements {
+16 -7
View File
@@ -682,7 +682,18 @@ int FEngine::loop() {
return 0;
}
// Set thread affinity for the backend thread.
// see https://developer.android.com/agi/sys-trace/threads-scheduling#cpu_core_affinity
// Certain backends already have some threads pinned, and we can't easily know on which core.
const bool disableThreadAffinity
= mDriver->isWorkaroundNeeded(Workaround::DISABLE_THREAD_AFFINITY);
uint32_t const id = std::thread::hardware_concurrency() - 1;
while (true) {
// looks like thread affinity needs to be reset regularly (on Android)
if (!disableThreadAffinity) {
JobSystem::setThreadAffinityById(id);
}
if (!execute()) {
break;
}
@@ -715,10 +726,9 @@ const FMaterial* FEngine::getSkyboxMaterial() const noexcept {
* Object created from a Builder
*/
template<typename T, typename ... ARGS>
inline 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)...);
template<typename T>
inline T* FEngine::create(ResourceList<T>& list, typename T::Builder const& builder) noexcept {
T* p = mHeapAllocator.make<T>(*this, builder);
if (UTILS_UNLIKELY(p)) { // this should never happen
list.insert(p);
}
@@ -757,9 +767,8 @@ FIndirectLight* FEngine::createIndirectLight(const IndirectLight::Builder& build
return create(mIndirectLights, builder);
}
FMaterial* FEngine::createMaterial(const Material::Builder& builder,
std::unique_ptr<MaterialParser> materialParser) noexcept {
return create(mMaterials, builder, std::move(materialParser));
FMaterial* FEngine::createMaterial(const Material::Builder& builder) noexcept {
return create(mMaterials, builder);
}
FSkybox* FEngine::createSkybox(const Skybox::Builder& builder) noexcept {
+12 -15
View File
@@ -59,11 +59,10 @@
#include <filament/Texture.h>
#include <filament/VertexBuffer.h>
#include <utils/Allocator.h>
#include <utils/CountDownLatch.h>
#include <utils/FixedCapacityVector.h>
#include <utils/JobSystem.h>
#include <utils/compiler.h>
#include <utils/Allocator.h>
#include <utils/JobSystem.h>
#include <utils/CountDownLatch.h>
#include <chrono>
#include <memory>
@@ -232,21 +231,19 @@ public:
return mPlatform;
}
// Return a vector of shader languages, in order of preference.
utils::FixedCapacityVector<backend::ShaderLanguage> getShaderLanguage() const noexcept {
backend::ShaderLanguage getShaderLanguage() const noexcept {
switch (mBackend) {
case Backend::DEFAULT:
case Backend::NOOP:
default:
return { backend::ShaderLanguage::ESSL3 };
return backend::ShaderLanguage::ESSL3;
case Backend::OPENGL:
return { getDriver().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0
? backend::ShaderLanguage::ESSL1
: backend::ShaderLanguage::ESSL3 };
return getDriver().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0
? backend::ShaderLanguage::ESSL1 : backend::ShaderLanguage::ESSL3;
case Backend::VULKAN:
return { backend::ShaderLanguage::SPIRV };
return backend::ShaderLanguage::SPIRV;
case Backend::METAL:
return { backend::ShaderLanguage::METAL_LIBRARY, backend::ShaderLanguage::MSL };
return backend::ShaderLanguage::MSL;
}
}
@@ -266,8 +263,8 @@ public:
return mDefaultRenderTarget;
}
template <typename T, typename ... ARGS>
T* create(ResourceList<T>& list, typename T::Builder const& builder, ARGS&& ... args) noexcept;
template <typename T>
T* create(ResourceList<T>& list, typename T::Builder const& builder) noexcept;
FBufferObject* createBufferObject(const BufferObject::Builder& builder) noexcept;
FVertexBuffer* createVertexBuffer(const VertexBuffer::Builder& builder) noexcept;
@@ -276,7 +273,7 @@ public:
FMorphTargetBuffer* createMorphTargetBuffer(const MorphTargetBuffer::Builder& builder) noexcept;
FInstanceBuffer* createInstanceBuffer(const InstanceBuffer::Builder& builder) noexcept;
FIndirectLight* createIndirectLight(const IndirectLight::Builder& builder) noexcept;
FMaterial* createMaterial(const Material::Builder& builder, std::unique_ptr<MaterialParser> materialParser) noexcept;
FMaterial* createMaterial(const Material::Builder& builder) noexcept;
FTexture* createTexture(const Texture::Builder& builder) noexcept;
FSkybox* createSkybox(const Skybox::Builder& builder) noexcept;
FColorGrading* createColorGrading(const ColorGrading::Builder& builder) noexcept;
+286 -339
View File
@@ -15,59 +15,29 @@
*/
#include "details/Material.h"
#include "details/Engine.h"
#include "Froxelizer.h"
#include "MaterialParser.h"
#include "details/Engine.h"
#include "FilamentAPI-impl.h"
#include <private/filament/Variant.h>
#include <private/filament/EngineEnums.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/BufferInterfaceBlock.h>
#include <private/filament/Variant.h>
#include <filament/Material.h>
#include <filament/MaterialEnums.h>
#if FILAMENT_ENABLE_MATDBG
#include <matdbg/DebugServer.h>
#endif
#include <filaflat/ChunkContainer.h>
#include <filaflat/MaterialChunk.h>
#include <backend/DriverEnums.h>
#include <backend/CallbackHandler.h>
#include <backend/Program.h>
#include <utils/BitmaskEnum.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Hash.h>
#include <utils/Invocable.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/bitset.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <utils/Hash.h>
#include <algorithm>
#include <array>
#include <iterator>
#include <memory>
#include <mutex>
#include <new>
#include <optional>
#include <sstream>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <variant>
#include <stddef.h>
#include <stdint.h>
namespace filament {
@@ -75,32 +45,22 @@ using namespace backend;
using namespace filaflat;
using namespace utils;
static std::unique_ptr<MaterialParser> createParser(Backend backend,
utils::FixedCapacityVector<ShaderLanguage> languages, const void* data, size_t size) {
static MaterialParser* createParser(Backend backend, ShaderLanguage language,
const void* data, size_t size) {
// unique_ptr so we don't leak MaterialParser on failures below
auto materialParser = std::make_unique<MaterialParser>(languages, data, size);
auto materialParser = std::make_unique<MaterialParser>(language, data, size);
MaterialParser::ParseResult const materialResult = materialParser->parse();
if (UTILS_UNLIKELY(materialResult == MaterialParser::ParseResult::ERROR_MISSING_BACKEND)) {
std::stringstream languageNames;
for (auto it = languages.begin(); it != languages.end(); ++it) {
languageNames << shaderLanguageToString(*it);
if (std::next(it) != languages.end()) {
languageNames << ", ";
}
}
ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND,
"the material was not built for any of the %s backend's supported shader "
"languages (%s)\n",
backendToString(backend), languageNames.str().c_str());
}
if (backend == Backend::NOOP) {
return materialParser;
return materialParser.release();
}
ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND,
"the material was not built for the %s backend and %s shader language\n",
backendToString(backend),
shaderLanguageToString(language));
ASSERT_PRECONDITION(materialResult == MaterialParser::ParseResult::SUCCESS,
"could not parse the material package");
@@ -111,12 +71,13 @@ static std::unique_ptr<MaterialParser> createParser(Backend backend,
assert_invariant(backend != Backend::DEFAULT && "Default backend has not been resolved.");
return materialParser;
return materialParser.release();
}
struct Material::BuilderDetails {
const void* mPayload = nullptr;
size_t mSize = 0;
MaterialParser* mMaterialParser = nullptr;
bool mDefaultMaterial = false;
std::unordered_map<
utils::CString,
@@ -154,11 +115,11 @@ template Material::Builder& Material::Builder::constant<float>(const char*, size
template Material::Builder& Material::Builder::constant<bool>(const char*, size_t, bool);
Material* Material::Builder::build(Engine& engine) {
std::unique_ptr<MaterialParser> materialParser = createParser(
std::unique_ptr<MaterialParser> materialParser{ createParser(
downcast(engine).getBackend(), downcast(engine).getShaderLanguage(),
mImpl->mPayload, mImpl->mSize);
mImpl->mPayload, mImpl->mSize) };
if (!materialParser) {
if (materialParser == nullptr) {
return nullptr;
}
@@ -185,17 +146,17 @@ Material* Material::Builder::build(Engine& engine) {
return nullptr;
}
return downcast(engine).createMaterial(*this, std::move(materialParser));
mImpl->mMaterialParser = materialParser.release();
return downcast(engine).createMaterial(*this);
}
FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
std::unique_ptr<MaterialParser> materialParser)
: mIsDefaultMaterial(builder->mDefaultMaterial),
mEngine(engine),
mMaterialId(engine.getMaterialId()),
mMaterialParser(std::move(materialParser))
FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
: mEngine(engine),
mMaterialId(engine.getMaterialId())
{
MaterialParser* const parser = mMaterialParser.get();
MaterialParser* parser = builder->mMaterialParser;
mMaterialParser = parser;
UTILS_UNUSED_IN_RELEASE bool const nameOk = parser->getName(&mName);
assert_invariant(nameOk);
@@ -228,7 +189,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
success = parser->getUIB(&mUniformInterfaceBlock);
assert_invariant(success);
if (UTILS_UNLIKELY(parser->getShaderLanguage() == ShaderLanguage::ESSL1)) {
if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) {
success = parser->getBindingUniformInfo(&mBindingUniformInfo);
assert_invariant(success);
@@ -244,66 +205,6 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
&mSamplerGroupBindingInfoList, &mSamplerBindingToNameMap);
assert_invariant(success);
// Older materials will not have a subpass chunk; this should not be an error.
if (!parser->getSubpasses(&mSubpassInfo)) {
mSubpassInfo.isValid = false;
}
parser->getShading(&mShading);
parser->getMaterialProperties(&mMaterialProperties);
parser->getInterpolation(&mInterpolation);
parser->getVertexDomain(&mVertexDomain);
parser->getMaterialDomain(&mMaterialDomain);
parser->getMaterialVariantFilterMask(&mVariantFilterMask);
parser->getRequiredAttributes(&mRequiredAttributes);
parser->getRefractionMode(&mRefractionMode);
parser->getRefractionType(&mRefractionType);
parser->getReflectionMode(&mReflectionMode);
parser->getTransparencyMode(&mTransparencyMode);
parser->getDoubleSided(&mDoubleSided);
parser->getCullingMode(&mCullingMode);
if (mShading == Shading::UNLIT) {
parser->hasShadowMultiplier(&mHasShadowMultiplier);
}
mIsVariantLit = mShading != Shading::UNLIT || mHasShadowMultiplier;
// color write
bool colorWrite = false;
parser->getColorWrite(&colorWrite);
mRasterState.colorWrite = colorWrite;
// depth test
bool depthTest = false;
parser->getDepthTest(&depthTest);
mRasterState.depthFunc = depthTest ? RasterState::DepthFunc::GE : RasterState::DepthFunc::A;
// if doubleSided() was called we override culling()
bool doubleSideSet = false;
parser->getDoubleSidedSet(&doubleSideSet);
if (doubleSideSet) {
mDoubleSidedCapability = true;
mRasterState.culling = mDoubleSided ? CullingMode::NONE : mCullingMode;
} else {
mRasterState.culling = mCullingMode;
}
// specular anti-aliasing
parser->hasSpecularAntiAliasing(&mSpecularAntiAliasing);
if (mSpecularAntiAliasing) {
parser->getSpecularAntiAliasingVariance(&mSpecularAntiAliasingVariance);
parser->getSpecularAntiAliasingThreshold(&mSpecularAntiAliasingThreshold);
}
processBlendingMode(parser);
processSpecializationConstants(engine, builder, parser);
processDepthVariants(engine, parser);
// we can only initialize the default instance once we're initialized ourselves
mDefaultInstance.initDefaultInstance(engine, this);
#if FILAMENT_ENABLE_MATDBG
// Register the material with matdbg.
matdbg::DebugServer* server = downcast(engine).debug.server;
@@ -312,9 +213,259 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
mDebuggerId = server->addMaterial(mName, details->mPayload, details->mSize, this);
}
#endif
// Older materials will not have a subpass chunk; this should not be an error.
if (!parser->getSubpasses(&mSubpassInfo)) {
mSubpassInfo.isValid = false;
}
// Older materials won't have a constants chunk, but that's okay.
parser->getConstants(&mMaterialConstants);
for (size_t i = 0, c = mMaterialConstants.size(); i < c; i++) {
auto& item = mMaterialConstants[i];
// the key can be a string_view because mMaterialConstant owns the CString
std::string_view const key{ item.name.data(), item.name.size() };
mSpecializationConstantsNameToIndex[key] = i;
}
// Verify that all the constant specializations exist in the material and that their types match.
// The first specialization constants are defined internally by Filament.
// The subsequent constants are user-defined in the material.
// Feature level 0 doesn't support instancing
int const maxInstanceCount = (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)
? 1 : CONFIG_MAX_INSTANCES;
int const maxFroxelBufferHeight = std::min(
FROXEL_BUFFER_MAX_ENTRY_COUNT / 4,
engine.getDriverApi().getMaxUniformBufferSize() / 16u);
bool const staticTextureWorkaround =
engine.getDriverApi().isWorkaroundNeeded(Workaround::A8X_STATIC_TEXTURE_TARGET_ERROR);
bool const powerVrShaderWorkarounds =
engine.getDriverApi().isWorkaroundNeeded(Workaround::POWER_VR_SHADER_WORKAROUNDS);
mSpecializationConstants.reserve(mMaterialConstants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::BACKEND_FEATURE_LEVEL,
(int)engine.getSupportedFeatureLevel() });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_MAX_INSTANCES,
(int)maxInstanceCount });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_FROXEL_BUFFER_HEIGHT,
(int)maxFroxelBufferHeight });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP,
engine.debug.shadowmap.debug_directional_shadowmap });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION,
engine.debug.lighting.debug_froxel_visualization });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_STATIC_TEXTURE_TARGET_WORKAROUND,
staticTextureWorkaround });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS,
powerVrShaderWorkarounds });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT,
(int)engine.getConfig().stereoscopicEyeCount });
if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) {
// The actual value of this spec-constant is set in the OpenGLDriver backend.
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_SRGB_SWAPCHAIN_EMULATION,
false});
}
for (auto const& [name, value] : builder->mConstantSpecializations) {
std::string_view const key{ name.data(), name.size() };
auto pos = mSpecializationConstantsNameToIndex.find(key);
ASSERT_PRECONDITION(pos != mSpecializationConstantsNameToIndex.end(),
"The material %s does not have a constant parameter named %s.",
mName.c_str_safe(), name.c_str());
const char* const types[3] = {"an int", "a float", "a bool"};
const char* const errorMessage =
"The constant parameter %s on material %s is of type %s, but %s was "
"provided.";
auto& constant = mMaterialConstants[pos->second];
switch (constant.type) {
case ConstantType::INT:
ASSERT_PRECONDITION(std::holds_alternative<int32_t>(value), errorMessage,
name.c_str(), mName.c_str_safe(), "int", types[value.index()]);
break;
case ConstantType::FLOAT:
ASSERT_PRECONDITION(std::holds_alternative<float>(value), errorMessage,
name.c_str(), mName.c_str_safe(), "float", types[value.index()]);
break;
case ConstantType::BOOL:
ASSERT_PRECONDITION(std::holds_alternative<bool>(value), errorMessage,
name.c_str(), mName.c_str_safe(), "bool", types[value.index()]);
break;
}
uint32_t const index = pos->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
mSpecializationConstants.push_back({ index, value });
}
parser->getShading(&mShading);
parser->getMaterialProperties(&mMaterialProperties);
parser->getBlendingMode(&mBlendingMode);
parser->getInterpolation(&mInterpolation);
parser->getVertexDomain(&mVertexDomain);
parser->getMaterialDomain(&mMaterialDomain);
parser->getMaterialVariantFilterMask(&mVariantFilterMask);
parser->getRequiredAttributes(&mRequiredAttributes);
parser->getRefractionMode(&mRefractionMode);
parser->getRefractionType(&mRefractionType);
parser->getReflectionMode(&mReflectionMode);
if (mBlendingMode == BlendingMode::MASKED) {
parser->getMaskThreshold(&mMaskThreshold);
}
// The fade blending mode only affects shading. For proper sorting we need to
// treat this blending mode as a regular transparent blending operation.
if (UTILS_UNLIKELY(mBlendingMode == BlendingMode::FADE)) {
mRenderBlendingMode = BlendingMode::TRANSPARENT;
} else {
mRenderBlendingMode = mBlendingMode;
}
if (mShading == Shading::UNLIT) {
parser->hasShadowMultiplier(&mHasShadowMultiplier);
}
mIsVariantLit = mShading != Shading::UNLIT || mHasShadowMultiplier;
// create raster state
using BlendFunction = RasterState::BlendFunction;
using DepthFunc = RasterState::DepthFunc;
switch (mBlendingMode) {
// Do not change the MASKED behavior without checking for regressions with
// AlphaBlendModeTest and TextureLinearInterpolationTest, with and without
// View::BlendMode::TRANSLUCENT.
case BlendingMode::MASKED:
case BlendingMode::OPAQUE:
mRasterState.blendFunctionSrcRGB = BlendFunction::ONE;
mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
mRasterState.blendFunctionDstRGB = BlendFunction::ZERO;
mRasterState.blendFunctionDstAlpha = BlendFunction::ZERO;
mRasterState.depthWrite = true;
break;
case BlendingMode::TRANSPARENT:
case BlendingMode::FADE:
mRasterState.blendFunctionSrcRGB = BlendFunction::ONE;
mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
mRasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA;
mRasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA;
mRasterState.depthWrite = false;
break;
case BlendingMode::ADD:
mRasterState.blendFunctionSrcRGB = BlendFunction::ONE;
mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
mRasterState.blendFunctionDstRGB = BlendFunction::ONE;
mRasterState.blendFunctionDstAlpha = BlendFunction::ONE;
mRasterState.depthWrite = false;
break;
case BlendingMode::MULTIPLY:
mRasterState.blendFunctionSrcRGB = BlendFunction::ZERO;
mRasterState.blendFunctionSrcAlpha = BlendFunction::ZERO;
mRasterState.blendFunctionDstRGB = BlendFunction::SRC_COLOR;
mRasterState.blendFunctionDstAlpha = BlendFunction::SRC_COLOR;
mRasterState.depthWrite = false;
break;
case BlendingMode::SCREEN:
mRasterState.blendFunctionSrcRGB = BlendFunction::ONE;
mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
mRasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_COLOR;
mRasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_COLOR;
mRasterState.depthWrite = false;
break;
}
bool depthWriteSet = false;
parser->getDepthWriteSet(&depthWriteSet);
if (depthWriteSet) {
bool depthWrite = false;
parser->getDepthWrite(&depthWrite);
mRasterState.depthWrite = depthWrite;
}
// if doubleSided() was called we override culling()
bool doubleSideSet = false;
parser->getDoubleSidedSet(&doubleSideSet);
parser->getDoubleSided(&mDoubleSided);
parser->getCullingMode(&mCullingMode);
bool depthTest = false;
parser->getDepthTest(&depthTest);
if (doubleSideSet) {
mDoubleSidedCapability = true;
mRasterState.culling = mDoubleSided ? CullingMode::NONE : mCullingMode;
} else {
mRasterState.culling = mCullingMode;
}
parser->getTransparencyMode(&mTransparencyMode);
parser->hasCustomDepthShader(&mHasCustomDepthShader);
mIsDefaultMaterial = builder->mDefaultMaterial;
if (UTILS_UNLIKELY(mIsDefaultMaterial)) {
assert_invariant(mMaterialDomain == MaterialDomain::SURFACE);
filaflat::MaterialChunk const& materialChunk{ mMaterialParser->getMaterialChunk() };
auto variants = FixedCapacityVector<Variant>::with_capacity(materialChunk.getShaderCount());
materialChunk.visitShaders([&variants](
ShaderModel, Variant variant, ShaderStage) {
if (Variant::isValidDepthVariant(variant)) {
variants.push_back(variant);
}
});
std::sort(variants.begin(), variants.end(),
[](Variant lhs, Variant rhs) { return lhs.key < rhs.key; });
auto pos = std::unique(variants.begin(), variants.end());
variants.resize(std::distance(variants.begin(), pos));
std::swap(mDepthVariants, variants);
}
if (mMaterialDomain == MaterialDomain::SURFACE) {
if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
auto& cachedPrograms = mCachedPrograms;
for (Variant const variant: pDefaultMaterial->mDepthVariants) {
pDefaultMaterial->prepareProgram(variant);
cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant);
}
}
}
bool colorWrite = false;
parser->getColorWrite(&colorWrite);
mRasterState.colorWrite = colorWrite;
mRasterState.depthFunc = depthTest ? DepthFunc::GE : DepthFunc::A;
bool alphaToCoverageSet = false;
parser->getAlphaToCoverageSet(&alphaToCoverageSet);
if (alphaToCoverageSet) {
bool alphaToCoverage = false;
parser->getAlphaToCoverage(&alphaToCoverage);
mRasterState.alphaToCoverage = alphaToCoverage;
} else {
mRasterState.alphaToCoverage = mBlendingMode == BlendingMode::MASKED;
}
parser->hasSpecularAntiAliasing(&mSpecularAntiAliasing);
if (mSpecularAntiAliasing) {
parser->getSpecularAntiAliasingVariance(&mSpecularAntiAliasingVariance);
parser->getSpecularAntiAliasingThreshold(&mSpecularAntiAliasingThreshold);
}
// we can only initialize the default instance once we're initialized ourselves
mDefaultInstance.initDefaultInstance(engine, this);
}
FMaterial::~FMaterial() noexcept = default;
FMaterial::~FMaterial() noexcept {
delete mMaterialParser;
}
void FMaterial::invalidate(Variant::type_t variantMask, Variant::type_t variantValue) noexcept {
if (mMaterialDomain == MaterialDomain::SURFACE) {
@@ -541,7 +692,6 @@ Program FMaterial::getProgramWithVariants(
Program program;
program.shader(ShaderStage::VERTEX, vsBuilder.data(), vsBuilder.size())
.shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size())
.shaderLanguage(mMaterialParser->getShaderLanguage())
.uniformBlockBindings(mUniformBlockBindings)
.diagnostics(mName,
[this, variant](io::ostream& out) -> io::ostream& {
@@ -563,7 +713,8 @@ Program FMaterial::getProgramWithVariants(
samplers.data(), info.count);
}
}
if (UTILS_UNLIKELY(mMaterialParser->getShaderLanguage() == ShaderLanguage::ESSL1)) {
if (UTILS_UNLIKELY(mEngine.getShaderLanguage() == ShaderLanguage::ESSL1)) {
assert_invariant(!mBindingUniformInfo.empty());
for (auto const& [index, uniforms] : mBindingUniformInfo) {
program.uniforms(uint32_t(index), uniforms);
@@ -637,7 +788,9 @@ void FMaterial::applyPendingEdits() noexcept {
const char* name = mName.c_str();
slog.d << "Applying edits to " << (name ? name : "(untitled)") << io::endl;
destroyPrograms(mEngine); // FIXME: this will not destroy the shared variants
latchPendingEdits();
delete mMaterialParser;
mMaterialParser = mPendingEdits;
mPendingEdits = nullptr;
}
/**
@@ -654,9 +807,8 @@ void FMaterial::onEditCallback(void* userdata, const utils::CString&, const void
// This is called on a web server thread, so we defer clearing the program cache
// and swapping out the MaterialParser until the next getProgram call.
std::unique_ptr<MaterialParser> pending = createParser(
engine.getBackend(), engine.getShaderLanguage(), packageData, packageSize);
material->setPendingEdits(std::move(pending));
material->mPendingEdits = createParser(engine.getBackend(), engine.getShaderLanguage(),
packageData, packageSize);
}
void FMaterial::onQueryCallback(void* userdata, VariantList* pVariants) {
@@ -736,211 +888,6 @@ bool FMaterial::setConstant(uint32_t id, T value) noexcept {
return false;
}
void FMaterial::processBlendingMode(MaterialParser const* const parser) {
parser->getBlendingMode(&mBlendingMode);
if (mBlendingMode == BlendingMode::MASKED) {
parser->getMaskThreshold(&mMaskThreshold);
}
if (mBlendingMode == BlendingMode::CUSTOM) {
parser->getCustomBlendFunction(&mCustomBlendFunctions);
}
// blending mode
switch (mBlendingMode) {
// Do not change the MASKED behavior without checking for regressions with
// AlphaBlendModeTest and TextureLinearInterpolationTest, with and without
// View::BlendMode::TRANSLUCENT.
case BlendingMode::MASKED:
case BlendingMode::OPAQUE:
mRasterState.blendFunctionSrcRGB = BlendFunction::ONE;
mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
mRasterState.blendFunctionDstRGB = BlendFunction::ZERO;
mRasterState.blendFunctionDstAlpha = BlendFunction::ZERO;
mRasterState.depthWrite = true;
break;
case BlendingMode::TRANSPARENT:
case BlendingMode::FADE:
mRasterState.blendFunctionSrcRGB = BlendFunction::ONE;
mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
mRasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA;
mRasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA;
mRasterState.depthWrite = false;
break;
case BlendingMode::ADD:
mRasterState.blendFunctionSrcRGB = BlendFunction::ONE;
mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
mRasterState.blendFunctionDstRGB = BlendFunction::ONE;
mRasterState.blendFunctionDstAlpha = BlendFunction::ONE;
mRasterState.depthWrite = false;
break;
case BlendingMode::MULTIPLY:
mRasterState.blendFunctionSrcRGB = BlendFunction::ZERO;
mRasterState.blendFunctionSrcAlpha = BlendFunction::ZERO;
mRasterState.blendFunctionDstRGB = BlendFunction::SRC_COLOR;
mRasterState.blendFunctionDstAlpha = BlendFunction::SRC_COLOR;
mRasterState.depthWrite = false;
break;
case BlendingMode::SCREEN:
mRasterState.blendFunctionSrcRGB = BlendFunction::ONE;
mRasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
mRasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_COLOR;
mRasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_COLOR;
mRasterState.depthWrite = false;
break;
case BlendingMode::CUSTOM:
mRasterState.blendFunctionSrcRGB = mCustomBlendFunctions[0];
mRasterState.blendFunctionSrcAlpha = mCustomBlendFunctions[1];
mRasterState.blendFunctionDstRGB = mCustomBlendFunctions[2];
mRasterState.blendFunctionDstAlpha = mCustomBlendFunctions[3];
mRasterState.depthWrite = false;
}
// depth write
bool depthWriteSet = false;
parser->getDepthWriteSet(&depthWriteSet);
if (depthWriteSet) {
bool depthWrite = false;
parser->getDepthWrite(&depthWrite);
mRasterState.depthWrite = depthWrite;
}
// alpha to coverage
bool alphaToCoverageSet = false;
parser->getAlphaToCoverageSet(&alphaToCoverageSet);
if (alphaToCoverageSet) {
bool alphaToCoverage = false;
parser->getAlphaToCoverage(&alphaToCoverage);
mRasterState.alphaToCoverage = alphaToCoverage;
} else {
mRasterState.alphaToCoverage = mBlendingMode == BlendingMode::MASKED;
}
}
void FMaterial::processSpecializationConstants(FEngine& engine, Material::Builder const& builder,
MaterialParser const* const parser) {
// Older materials won't have a constants chunk, but that's okay.
parser->getConstants(&mMaterialConstants);
for (size_t i = 0, c = mMaterialConstants.size(); i < c; i++) {
auto& item = mMaterialConstants[i];
// the key can be a string_view because mMaterialConstant owns the CString
std::string_view const key{ item.name.data(), item.name.size() };
mSpecializationConstantsNameToIndex[key] = i;
}
// Verify that all the constant specializations exist in the material and that their types match.
// The first specialization constants are defined internally by Filament.
// The subsequent constants are user-defined in the material.
// Feature level 0 doesn't support instancing
int const maxInstanceCount = (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)
? 1 : CONFIG_MAX_INSTANCES;
int const maxFroxelBufferHeight = (int)std::min(
FROXEL_BUFFER_MAX_ENTRY_COUNT / 4,
engine.getDriverApi().getMaxUniformBufferSize() / 16u);
bool const staticTextureWorkaround =
engine.getDriverApi().isWorkaroundNeeded(Workaround::A8X_STATIC_TEXTURE_TARGET_ERROR);
bool const powerVrShaderWorkarounds =
engine.getDriverApi().isWorkaroundNeeded(Workaround::POWER_VR_SHADER_WORKAROUNDS);
mSpecializationConstants.reserve(mMaterialConstants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::BACKEND_FEATURE_LEVEL,
(int)engine.getSupportedFeatureLevel() });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_MAX_INSTANCES,
(int)maxInstanceCount });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_FROXEL_BUFFER_HEIGHT,
(int)maxFroxelBufferHeight });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP,
engine.debug.shadowmap.debug_directional_shadowmap });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION,
engine.debug.lighting.debug_froxel_visualization });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_STATIC_TEXTURE_TARGET_WORKAROUND,
staticTextureWorkaround });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS,
powerVrShaderWorkarounds });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT,
(int)engine.getConfig().stereoscopicEyeCount });
if (UTILS_UNLIKELY(parser->getShaderLanguage() == ShaderLanguage::ESSL1)) {
// The actual value of this spec-constant is set in the OpenGLDriver backend.
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_SRGB_SWAPCHAIN_EMULATION,
false});
}
for (auto const& [name, value] : builder->mConstantSpecializations) {
std::string_view const key{ name.data(), name.size() };
auto pos = mSpecializationConstantsNameToIndex.find(key);
ASSERT_PRECONDITION(pos != mSpecializationConstantsNameToIndex.end(),
"The material %s does not have a constant parameter named %s.",
mName.c_str_safe(), name.c_str());
const char* const types[3] = {"an int", "a float", "a bool"};
const char* const errorMessage =
"The constant parameter %s on material %s is of type %s, but %s was "
"provided.";
auto& constant = mMaterialConstants[pos->second];
switch (constant.type) {
case ConstantType::INT:
ASSERT_PRECONDITION(std::holds_alternative<int32_t>(value), errorMessage,
name.c_str(), mName.c_str_safe(), "int", types[value.index()]);
break;
case ConstantType::FLOAT:
ASSERT_PRECONDITION(std::holds_alternative<float>(value), errorMessage,
name.c_str(), mName.c_str_safe(), "float", types[value.index()]);
break;
case ConstantType::BOOL:
ASSERT_PRECONDITION(std::holds_alternative<bool>(value), errorMessage,
name.c_str(), mName.c_str_safe(), "bool", types[value.index()]);
break;
}
uint32_t const index = pos->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
mSpecializationConstants.push_back({ index, value });
}
}
void FMaterial::processDepthVariants(FEngine& engine, MaterialParser const* const parser) {
parser->hasCustomDepthShader(&mHasCustomDepthShader);
if (UTILS_UNLIKELY(mIsDefaultMaterial)) {
assert_invariant(mMaterialDomain == MaterialDomain::SURFACE);
filaflat::MaterialChunk const& materialChunk{ parser->getMaterialChunk() };
auto variants = FixedCapacityVector<Variant>::with_capacity(materialChunk.getShaderCount());
materialChunk.visitShaders([&variants](
ShaderModel, Variant variant, ShaderStage) {
if (Variant::isValidDepthVariant(variant)) {
variants.push_back(variant);
}
});
std::sort(variants.begin(), variants.end(),
[](Variant lhs, Variant rhs) { return lhs.key < rhs.key; });
auto pos = std::unique(variants.begin(), variants.end());
variants.resize(std::distance(variants.begin(), pos));
std::swap(mDepthVariants, variants);
}
if (mMaterialDomain == MaterialDomain::SURFACE) {
if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
auto& cachedPrograms = mCachedPrograms;
for (Variant const variant: pDefaultMaterial->mDepthVariants) {
pDefaultMaterial->prepareProgram(variant);
cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant);
}
}
}
}
template bool FMaterial::setConstant<int32_t>(uint32_t id, int32_t value) noexcept;
template bool FMaterial::setConstant<float>(uint32_t id, float value) noexcept;
template bool FMaterial::setConstant<bool>(uint32_t id, bool value) noexcept;
+6 -48
View File
@@ -22,39 +22,18 @@
#include "details/MaterialInstance.h"
#include <filament/Material.h>
#include <filament/MaterialEnums.h>
#include <private/filament/EngineEnums.h>
#include <private/filament/BufferInterfaceBlock.h>
#include <private/filament/SamplerBindingsInfo.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/SubpassInfo.h>
#include <private/filament/Variant.h>
#include <private/filament/ConstantInfo.h>
#include <backend/CallbackHandler.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/Program.h>
#include <utils/compiler.h>
#include <utils/CString.h>
#include <utils/debug.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/Mutex.h>
#include <array>
#include <atomic>
#include <memory>
#include <mutex>
#include <optional>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <stddef.h>
#include <stdint.h>
namespace filament {
@@ -64,8 +43,7 @@ class FEngine;
class FMaterial : public Material {
public:
FMaterial(FEngine& engine, const Material::Builder& builder,
std::unique_ptr<MaterialParser> materialParser);
FMaterial(FEngine& engine, const Material::Builder& builder);
~FMaterial() noexcept;
class DefaultMaterialBuilder : public Material::Builder {
@@ -149,6 +127,7 @@ public:
Shading getShading() const noexcept { return mShading; }
Interpolation getInterpolation() const noexcept { return mInterpolation; }
BlendingMode getBlendingMode() const noexcept { return mBlendingMode; }
BlendingMode getRenderBlendingMode() const noexcept { return mRenderBlendingMode; }
VertexDomain getVertexDomain() const noexcept { return mVertexDomain; }
MaterialDomain getMaterialDomain() const noexcept { return mMaterialDomain; }
CullingMode getCullingMode() const noexcept { return mCullingMode; }
@@ -219,7 +198,7 @@ public:
static void onQueryCallback(void* userdata, VariantList* pActiveVariants);
void checkProgramEdits() noexcept {
if (UTILS_UNLIKELY(hasPendingEdits())) {
if (UTILS_UNLIKELY(mPendingEdits.load())) {
applyPendingEdits();
}
}
@@ -238,26 +217,19 @@ private:
backend::Program getProgramWithVariants(Variant variant,
Variant vertexVariant, Variant fragmentVariant) const noexcept;
void processBlendingMode(MaterialParser const* const parser);
void processSpecializationConstants(FEngine& engine, Material::Builder const& builder,
MaterialParser const* const parser);
void processDepthVariants(FEngine& engine, MaterialParser const* const parser);
void createAndCacheProgram(backend::Program&& p, Variant variant) const noexcept;
// try to order by frequency of use
mutable std::array<backend::Handle<backend::HwProgram>, VARIANT_COUNT> mCachedPrograms;
backend::RasterState mRasterState;
BlendingMode mRenderBlendingMode = BlendingMode::OPAQUE;
TransparencyMode mTransparencyMode = TransparencyMode::DEFAULT;
bool mIsVariantLit = false;
backend::FeatureLevel mFeatureLevel = backend::FeatureLevel::FEATURE_LEVEL_1;
Shading mShading = Shading::UNLIT;
BlendingMode mBlendingMode = BlendingMode::OPAQUE;
std::array<backend::BlendFunction, 4> mCustomBlendFunctions = {};
Interpolation mInterpolation = Interpolation::SMOOTH;
VertexDomain mVertexDomain = VertexDomain::OBJECT;
MaterialDomain mMaterialDomain = MaterialDomain::SURFACE;
@@ -309,21 +281,7 @@ private:
matdbg::MaterialKey mDebuggerId;
mutable utils::Mutex mActiveProgramsLock;
mutable VariantList mActivePrograms;
mutable utils::Mutex mPendingEditsLock;
std::unique_ptr<MaterialParser> mPendingEdits;
void setPendingEdits(std::unique_ptr<MaterialParser> pendingEdits) noexcept {
std::lock_guard lock(mPendingEditsLock);
std::swap(pendingEdits, mPendingEdits);
}
bool hasPendingEdits() noexcept {
std::lock_guard lock(mPendingEditsLock);
return (bool)mPendingEdits;
}
void latchPendingEdits() noexcept {
std::lock_guard lock(mPendingEditsLock);
std::swap(mPendingEdits, mMaterialParser);
}
std::atomic<MaterialParser*> mPendingEdits = {};
#endif
utils::CString mName;
@@ -331,7 +289,7 @@ private:
const uint32_t mMaterialId;
uint64_t mCacheId = 0;
mutable uint32_t mMaterialInstanceId = 0;
std::unique_ptr<MaterialParser> mMaterialParser;
MaterialParser* mMaterialParser = nullptr;
};
@@ -31,7 +31,7 @@ using namespace filament;
// This will re-compile the test material with the current version of matc.
// To verify, rebuild and re-run test_material_parser (this test suite).
TEST(MaterialParser, Parse) {
MaterialParser parser({ backend::ShaderLanguage::ESSL3 },
MaterialParser parser(backend::ShaderLanguage::ESSL3,
FILAMENT_TEST_RESOURCES_TEST_MATERIAL_DATA, FILAMENT_TEST_RESOURCES_TEST_MATERIAL_SIZE);
MaterialParser::ParseResult materialOk = parser.parse();
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.51.5"
spec.version = "1.51.0"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.5/filament-v1.51.5-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.0/filament-v1.51.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
@@ -45,7 +45,6 @@ enum UTILS_PUBLIC ChunkType : uint64_t {
MaterialEssl1 = charTo64bitNum("MAT_ESS1"),
MaterialSpirv = charTo64bitNum("MAT_SPIR"),
MaterialMetal = charTo64bitNum("MAT_METL"),
MaterialMetalLibrary = charTo64bitNum("MAT_MLIB"),
MaterialShaderModels = charTo64bitNum("MAT_SMDL"),
MaterialSamplerBindings = charTo64bitNum("MAT_SAMP"),
MaterialUniformBindings = charTo64bitNum("MAT_UNIF"),
@@ -60,7 +59,6 @@ enum UTILS_PUBLIC ChunkType : uint64_t {
MaterialFeatureLevel = charTo64bitNum("MAT_FEAT"),
MaterialShading = charTo64bitNum("MAT_SHAD"),
MaterialBlendingMode = charTo64bitNum("MAT_BLEN"),
MaterialBlendFunction = charTo64bitNum("MAT_BLFN"),
MaterialTransparencyMode = charTo64bitNum("MAT_TRMD"),
MaterialMaskThreshold = charTo64bitNum("MAT_THRS"),
MaterialShadowMultiplier = charTo64bitNum("MAT_SHML"),
@@ -94,7 +92,6 @@ enum UTILS_PUBLIC ChunkType : uint64_t {
DictionaryText = charTo64bitNum("DIC_TEXT"),
DictionarySpirv = charTo64bitNum("DIC_SPIR"),
DictionaryMetalLibrary = charTo64bitNum("DIC_MLIB"),
};
} // namespace filamat
@@ -80,8 +80,6 @@ enum class BlendingMode : uint8_t {
MULTIPLY,
//! material brightens what's behind it
SCREEN,
//! custom blending function
CUSTOM,
};
/**
@@ -74,7 +74,7 @@ private:
BlobDictionary const& dictionary, ShaderContent& shaderContent,
ShaderModel shaderModel, filament::Variant variant, ShaderStage shaderStage);
bool getBinaryShader(
bool getSpirvShader(
BlobDictionary const& dictionary, ShaderContent& shaderContent,
ShaderModel shaderModel, filament::Variant variant, ShaderStage shaderStage);
};
-19
View File
@@ -78,25 +78,6 @@ bool DictionaryReader::unflatten(ChunkContainer const& container,
}
return true;
} else if (dictionaryTag == ChunkType::DictionaryMetalLibrary) {
uint32_t blobCount;
if (!unflattener.read(&blobCount)) {
return false;
}
dictionary.reserve(blobCount);
for (uint32_t i = 0; i < blobCount; i++) {
unflattener.skipAlignmentPadding();
const char* data;
size_t dataSize;
if (!unflattener.read(&data, &dataSize)) {
return false;
}
dictionary.emplace_back(dataSize);
memcpy(dictionary.back().data(), data, dictionary.back().size());
}
return true;
} else if (dictionaryTag == ChunkType::DictionaryText) {
uint32_t stringCount = 0;
if (!unflattener.read(&stringCount)) {
+2 -3
View File
@@ -153,7 +153,7 @@ bool MaterialChunk::getTextShader(Unflattener unflattener,
return true;
}
bool MaterialChunk::getBinaryShader(BlobDictionary const& dictionary,
bool MaterialChunk::getSpirvShader(BlobDictionary const& dictionary,
ShaderContent& shaderContent, ShaderModel shaderModel, filament::Variant variant, ShaderStage shaderStage) {
if (mBase == nullptr) {
@@ -186,8 +186,7 @@ bool MaterialChunk::getShader(ShaderContent& shaderContent, BlobDictionary const
case filamat::ChunkType::MaterialMetal:
return getTextShader(mUnflattener, dictionary, shaderContent, shaderModel, variant, stage);
case filamat::ChunkType::MaterialSpirv:
case filamat::ChunkType::MaterialMetalLibrary:
return getBinaryShader(dictionary, shaderContent, shaderModel, variant, stage);
return getSpirvShader(dictionary, shaderContent, shaderModel, variant, stage);
default:
return false;
}
+2 -4
View File
@@ -50,8 +50,7 @@ set(PRIVATE_HDRS
${COMMON_PRIVATE_HDRS}
src/eiff/BlobDictionary.h
src/eiff/DictionarySpirvChunk.h
src/eiff/DictionaryMetalLibraryChunk.h
src/eiff/MaterialBinaryChunk.h
src/eiff/MaterialSpirvChunk.h
src/GLSLPostProcessor.h
src/MetalArgumentBuffer.h
src/ShaderMinifier.h
@@ -64,8 +63,7 @@ set(SRCS
${COMMON_SRCS}
src/eiff/BlobDictionary.cpp
src/eiff/DictionarySpirvChunk.cpp
src/eiff/DictionaryMetalLibraryChunk.cpp
src/eiff/MaterialBinaryChunk.cpp
src/eiff/MaterialSpirvChunk.cpp
src/MetalArgumentBuffer.cpp
src/sca/ASTHelpers.cpp
src/sca/GLSLTools.cpp
@@ -227,7 +227,6 @@ public:
using ShaderQuality = filament::ShaderQuality;
using BlendingMode = filament::BlendingMode;
using BlendFunction = filament::backend::BlendFunction;
using Shading = filament::Shading;
using Interpolation = filament::Interpolation;
using VertexDomain = filament::VertexDomain;
@@ -408,15 +407,6 @@ public:
*/
MaterialBuilder& blending(BlendingMode blending) noexcept;
/**
* Set the blend function for this material. blending must be et to CUSTOM.
*/
MaterialBuilder& customBlendFunctions(
BlendFunction srcRGB,
BlendFunction srcA,
BlendFunction dstRGB,
BlendFunction dstA) noexcept;
/**
* Set the blending mode of the post-lighting color for this material.
* Only OPAQUE, TRANSPARENT and ADD are supported, the default is TRANSPARENT.
@@ -838,7 +828,6 @@ private:
FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1;
BlendingMode mBlendingMode = BlendingMode::OPAQUE;
BlendingMode mPostLightingBlendingMode = BlendingMode::TRANSPARENT;
std::array<BlendFunction, 4> mCustomBlendFunctions = {};
CullingMode mCullingMode = CullingMode::BACK;
Shading mShading = Shading::LIT;
MaterialDomain mMaterialDomain = MaterialDomain::SURFACE;
+8 -32
View File
@@ -33,12 +33,11 @@
#include "eiff/LineDictionary.h"
#include "eiff/MaterialInterfaceBlockChunk.h"
#include "eiff/MaterialTextChunk.h"
#include "eiff/MaterialBinaryChunk.h"
#include "eiff/MaterialSpirvChunk.h"
#include "eiff/ChunkContainer.h"
#include "eiff/SimpleFieldChunk.h"
#include "eiff/DictionaryTextChunk.h"
#include "eiff/DictionarySpirvChunk.h"
#include "eiff/DictionaryMetalLibraryChunk.h"
#include <private/filament/BufferInterfaceBlock.h>
#include <private/filament/SamplerInterfaceBlock.h>
@@ -389,16 +388,6 @@ MaterialBuilder& MaterialBuilder::blending(BlendingMode blending) noexcept {
return *this;
}
MaterialBuilder& MaterialBuilder::customBlendFunctions(
BlendFunction srcRGB, BlendFunction srcA,
BlendFunction dstRGB, BlendFunction dstA) noexcept {
mCustomBlendFunctions[0] = srcRGB;
mCustomBlendFunctions[1] = srcA;
mCustomBlendFunctions[2] = dstRGB;
mCustomBlendFunctions[3] = dstA;
return *this;
}
MaterialBuilder& MaterialBuilder::postLightingBlending(BlendingMode blending) noexcept {
mPostLightingBlendingMode = blending;
return *this;
@@ -822,7 +811,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
Mutex entriesLock;
std::vector<TextEntry> glslEntries;
std::vector<TextEntry> essl1Entries;
std::vector<BinaryEntry> spirvEntries;
std::vector<SpirvEntry> spirvEntries;
std::vector<TextEntry> metalEntries;
LineDictionary textDictionary;
BlobDictionary spirvDictionary;
@@ -873,7 +862,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
std::string* pMsl = targetApiNeedsMsl ? &msl : nullptr;
TextEntry glslEntry{};
BinaryEntry spirvEntry{};
SpirvEntry spirvEntry{};
TextEntry metalEntry{};
glslEntry.shaderModel = params.shaderModel;
@@ -964,15 +953,12 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
glslEntries.push_back(glslEntry);
}
break;
case TargetApi::VULKAN: {
case TargetApi::VULKAN:
assert(!spirv.empty());
const std::vector<uint8_t> d(reinterpret_cast<uint8_t*>(spirv.data()),
reinterpret_cast<uint8_t*>(spirv.data() + spirv.size()));
spirvEntry.stage = v.stage;
spirvEntry.data = std::move(d);
spirvEntry.spirv = std::move(spirv);
spirvEntries.push_back(spirvEntry);
break;
}
case TargetApi::METAL:
assert(!spirv.empty());
assert(msl.length() > 0);
@@ -1022,7 +1008,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
textDictionary.addText(s.shader);
}
for (auto& s : spirvEntries) {
std::vector<uint8_t> spirv = std::move(s.data);
std::vector<uint32_t> spirv = std::move(s.spirv);
s.dictionaryIndex = spirvDictionary.addBlob(spirv);
}
for (const auto& s : metalEntries) {
@@ -1045,11 +1031,11 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
dictionaryChunk.getDictionary(), ChunkType::MaterialEssl1);
}
// Emit SPIRV chunks (SpirvDictionaryReader and MaterialBinaryChunk).
// Emit SPIRV chunks (SpirvDictionaryReader and MaterialSpirvChunk).
if (!spirvEntries.empty()) {
const bool stripInfo = !mGenerateDebugInfo;
container.push<filamat::DictionarySpirvChunk>(std::move(spirvDictionary), stripInfo);
container.push<MaterialBinaryChunk>(std::move(spirvEntries), ChunkType::MaterialSpirv);
container.push<MaterialSpirvChunk>(std::move(spirvEntries));
}
// Emit Metal chunk (MaterialTextChunk).
@@ -1524,16 +1510,6 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo&
container.emplace<bool>(ChunkType::MaterialDoubleSided, mDoubleSided);
container.emplace<uint8_t>(ChunkType::MaterialBlendingMode,
static_cast<uint8_t>(mBlendingMode));
if (mBlendingMode == BlendingMode::CUSTOM) {
uint32_t const blendFunctions =
(uint32_t(mCustomBlendFunctions[0]) << 24) |
(uint32_t(mCustomBlendFunctions[1]) << 16) |
(uint32_t(mCustomBlendFunctions[2]) << 8) |
(uint32_t(mCustomBlendFunctions[3]) << 0);
container.emplace< uint32_t >(ChunkType::MaterialBlendFunction, blendFunctions);
}
container.emplace<uint8_t>(ChunkType::MaterialTransparencyMode,
static_cast<uint8_t>(mTransparencyMode));
container.emplace<uint8_t>(ChunkType::MaterialReflectionMode,
+2 -2
View File
@@ -20,8 +20,8 @@
namespace filamat {
size_t BlobDictionary::addBlob(const std::vector<uint8_t>& vblob) noexcept {
std::string_view blob((char*) vblob.data(), vblob.size());
size_t BlobDictionary::addBlob(const std::vector<uint32_t>& vblob) noexcept {
std::string_view blob((char*) vblob.data(), vblob.size() * 4);
auto iter = mBlobIndices.find(blob);
if (iter != mBlobIndices.end()) {
return iter->second;
+1 -1
View File
@@ -36,7 +36,7 @@ public:
BlobDictionary(BlobDictionary&&) = default;
// Adds a blob if it's not already a duplicate and returns its index.
size_t addBlob(const std::vector<uint8_t>& blob) noexcept;
size_t addBlob(const std::vector<uint32_t>& blob) noexcept;
size_t getBlobCount() const noexcept {
return mBlobs.size();
@@ -1,33 +0,0 @@
/*
* Copyright (C) 2024 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 "DictionaryMetalLibraryChunk.h"
namespace filamat {
DictionaryMetalLibraryChunk::DictionaryMetalLibraryChunk(BlobDictionary&& dictionary)
: Chunk(ChunkType::DictionaryMetalLibrary), mDictionary(std::move(dictionary)) {}
void DictionaryMetalLibraryChunk::flatten(Flattener& f) {
f.writeUint32(mDictionary.getBlobCount());
for (size_t i = 0 ; i < mDictionary.getBlobCount() ; i++) {
std::string_view blob = mDictionary.getBlob(i);
f.writeAlignmentPadding();
f.writeBlob((const char*) blob.data(), blob.size());
}
}
} // namespace filamat
@@ -1,43 +0,0 @@
/*
* Copyright (C) 2024 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_FILAMAT_DIC_METAL_LIBRARY_CHUNK_H
#define TNT_FILAMAT_DIC_METAL_LIBRARY_CHUNK_H
#include <stdint.h>
#include <vector>
#include "Chunk.h"
#include "Flattener.h"
#include "BlobDictionary.h"
namespace filamat {
class DictionaryMetalLibraryChunk final : public Chunk {
public:
explicit DictionaryMetalLibraryChunk(BlobDictionary&& dictionary);
~DictionaryMetalLibraryChunk() = default;
private:
void flatten(Flattener& f) override;
BlobDictionary mDictionary;
bool mStripDebugInfo;
};
} // namespace filamat
#endif // TNT_FILAMAT_DIC_METAL_LIBRARY_CHUNK_H
-7
View File
@@ -127,13 +127,6 @@ public:
mCursor += nbytes;
}
void writeRaw(const char* raw, size_t nbytes) {
if (mStart != nullptr) {
memcpy(reinterpret_cast<char*>(mCursor), raw, nbytes);
}
mCursor += nbytes;
}
void writeSizePlaceholder() {
mSizePlaceholders.push_back(mCursor);
if (mStart != nullptr) {
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
*DictionaryGlsl
* 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
@@ -14,17 +14,16 @@
* limitations under the License.
*/
#include "MaterialBinaryChunk.h"
#include "MaterialSpirvChunk.h"
namespace filamat {
MaterialBinaryChunk::MaterialBinaryChunk(
const std::vector<BinaryEntry>&& entries, ChunkType chunkType)
: Chunk(chunkType), mEntries(entries) {}
MaterialSpirvChunk::MaterialSpirvChunk(const std::vector<SpirvEntry>&& entries) :
Chunk(ChunkType::MaterialSpirv), mEntries(entries) {}
void MaterialBinaryChunk::flatten(Flattener &f) {
void MaterialSpirvChunk::flatten(Flattener &f) {
f.writeUint64(mEntries.size());
for (const BinaryEntry& entry : mEntries) {
for (const SpirvEntry& entry : mEntries) {
f.writeUint8(uint8_t(entry.shaderModel));
f.writeUint8(entry.variant.key);
f.writeUint8(uint8_t(entry.stage));
@@ -14,8 +14,8 @@
* limitations under the License.
*/
#ifndef TNT_FILAMAT_MATERIAL_BINARY_CHUNK_H
#define TNT_FILAMAT_MATERIAL_BINARY_CHUNK_H
#ifndef TNT_FILAMAT_MATERIAL_SPIRV_CHUNK_H
#define TNT_FILAMAT_MATERIAL_SPIRV_CHUNK_H
#include "Chunk.h"
#include "ShaderEntry.h"
@@ -24,17 +24,17 @@
namespace filamat {
class MaterialBinaryChunk final : public Chunk {
class MaterialSpirvChunk final : public Chunk {
public:
explicit MaterialBinaryChunk(const std::vector<BinaryEntry>&& entries, ChunkType type);
~MaterialBinaryChunk() = default;
explicit MaterialSpirvChunk(const std::vector<SpirvEntry>&& entries);
~MaterialSpirvChunk() = default;
private:
void flatten(Flattener& f) override;
const std::vector<BinaryEntry> mEntries;
const std::vector<SpirvEntry> mEntries;
};
} // namespace filamat
#endif // TNT_FILAMAT_MATERIAL_BINARY_CHUNK_H
#endif // TNT_FILAMAT_MATERIAL_SPIRV_CHUNK_H
+4 -4
View File
@@ -34,14 +34,14 @@ struct TextEntry {
std::string shader;
};
struct BinaryEntry {
struct SpirvEntry {
filament::backend::ShaderModel shaderModel;
filament::Variant variant;
filament::backend::ShaderStage stage;
size_t dictionaryIndex; // maps to an index in the blob dictionary
size_t dictionaryIndex;
// temporarily holds this entry's binary data until added to the dictionary
std::vector<uint8_t> data;
// temporarily holds this entry's spirv until added to the dictionary
std::vector<uint32_t> spirv;
};
} // namespace filamat
@@ -157,9 +157,6 @@ void ShaderGenerator::generateSurfaceMaterialVariantDefines(utils::io::sstream&
case BlendingMode::SCREEN:
CodeGenerator::generateDefine(out, "BLEND_MODE_SCREEN", true);
break;
case BlendingMode::CUSTOM:
CodeGenerator::generateDefine(out, "BLEND_MODE_CUSTOM", true);
break;
}
switch (material.postLightingBlendingMode) {
@@ -178,9 +175,6 @@ void ShaderGenerator::generateSurfaceMaterialVariantDefines(utils::io::sstream&
case BlendingMode::SCREEN:
CodeGenerator::generateDefine(out, "POST_LIGHTING_BLEND_MODE_SCREEN", true);
break;
case BlendingMode::CUSTOM:
CodeGenerator::generateDefine(out, "POST_LIGHTING_BLEND_MODE_CUSTOM", true);
break;
default:
break;
}
-1
View File
@@ -338,7 +338,6 @@ const char* toString(BlendingMode blendingMode) noexcept {
case BlendingMode::FADE: return "fade";
case BlendingMode::MULTIPLY: return "multiply";
case BlendingMode::SCREEN: return "screen";
case BlendingMode::CUSTOM: return "custom";
}
}
@@ -122,9 +122,10 @@ void postProcess(inout PostProcessInputs postProcess) {
float side = materialParams.side;
// compute the view (and normal, since v = n) direction for each face
vec3 rx = normalize(vec3( side, -p.y, side * -p.x));
vec3 ry = normalize(vec3( p.x, side, side * p.y));
vec3 rz = normalize(vec3(side * p.x, -p.y, side));
float l = inversesqrt(p.x * p.x + p.y * p.y + 1.0);
vec3 rx = vec3( side, p.y, side * -p.x) * l;
vec3 ry = vec3( p.x, side, side * -p.y) * l;
vec3 rz = vec3(side * p.x, p.y, side) * l;
// random rotation around r
mediump float a = 2.0 * PI * random(gl_FragCoord.xy);
-1
View File
@@ -65,7 +65,6 @@ const char* toString(BlendingMode blendingMode) noexcept {
case BlendingMode::FADE: return "fade";
case BlendingMode::MULTIPLY: return "multiply";
case BlendingMode::SCREEN: return "screen";
case BlendingMode::CUSTOM: return "custom";
}
return "--";
}
-4
View File
@@ -53,10 +53,6 @@ ShaderExtractor::ShaderExtractor(backend::ShaderLanguage target, const void* dat
mMaterialTag = ChunkType::MaterialMetal;
mDictionaryTag = ChunkType::DictionaryText;
break;
case backend::ShaderLanguage::METAL_LIBRARY:
mMaterialTag = ChunkType::MaterialMetalLibrary;
mDictionaryTag = ChunkType::DictionaryMetalLibrary;
break;
case backend::ShaderLanguage::SPIRV:
mMaterialTag = ChunkType::MaterialSpirv;
mDictionaryTag = ChunkType::DictionarySpirv;
+6 -6
View File
@@ -35,7 +35,7 @@
#include "eiff/ChunkContainer.h"
#include "eiff/DictionarySpirvChunk.h"
#include "eiff/DictionaryTextChunk.h"
#include "eiff/MaterialBinaryChunk.h"
#include "eiff/MaterialSpirvChunk.h"
#include "eiff/MaterialTextChunk.h"
#include "eiff/LineDictionary.h"
@@ -73,7 +73,7 @@ private:
vector<TextEntry> mShaderRecords;
};
// Tiny database of data blobs that can import / export MaterialBinaryChunk and DictionarySpirvChunk.
// Tiny database of data blobs that can import / export MaterialSpirvChunk and DictionarySpirvChunk.
// The blobs are stored *after* they have been compressed by SMOL-V.
class BlobIndex {
public:
@@ -90,7 +90,7 @@ public:
private:
const ChunkType mDictTag;
const ChunkType mMatTag;
vector<BinaryEntry> mShaderRecords;
vector<SpirvEntry> mShaderRecords;
filaflat::BlobDictionary mDataBlobs;
};
@@ -364,7 +364,7 @@ BlobIndex::BlobIndex(ChunkType dictTag, ChunkType matTag, const filaflat::ChunkC
const auto& offsets = matChunk.getOffsets();
mShaderRecords.reserve(offsets.size());
for (auto [key, offset] : offsets) {
BinaryEntry info;
SpirvEntry info;
filaflat::MaterialChunk::decodeKey(key, &info.shaderModel, &info.variant, &info.stage);
info.dictionaryIndex = offset;
mShaderRecords.emplace_back(info);
@@ -378,7 +378,7 @@ void BlobIndex::writeChunks(ostream& stream) {
const auto& src = mDataBlobs[record.dictionaryIndex];
assert(src.size() % 4 == 0);
const uint32_t* ptr = (const uint32_t*) src.data();
record.dictionaryIndex = blobs.addBlob(vector<uint8_t>(ptr, ptr + src.size()));
record.dictionaryIndex = blobs.addBlob(vector<uint32_t>(ptr, ptr + src.size() / 4));
}
// Adjust start cursor of flatteners to match alignment of output stream.
@@ -391,7 +391,7 @@ void BlobIndex::writeChunks(ostream& stream) {
// Apply SMOL-V compression and write out the results.
filamat::ChunkContainer cc;
cc.push<MaterialBinaryChunk>(std::move(mShaderRecords), ChunkType::MaterialSpirv);
cc.push<MaterialSpirvChunk>(std::move(mShaderRecords));
cc.push<DictionarySpirvChunk>(std::move(blobs), false);
Flattener prepass = Flattener::getDryRunner();
-6
View File
@@ -421,9 +421,6 @@ static bool printShaderInfo(ostream& text, const ChunkContainer& container, Chun
case ChunkType::MaterialMetal:
text << "Metal shaders:" << endl;
break;
case ChunkType::MaterialMetalLibrary:
text << "Metal precompiled shader libraries:" << endl;
break;
default:
assert(false && "Invalid shader ChunkType");
break;
@@ -458,9 +455,6 @@ bool TextWriter::writeMaterialInfo(const filaflat::ChunkContainer& container) {
if (!printShaderInfo(text, container, ChunkType::MaterialMetal)) {
return false;
}
if (!printShaderInfo(text, container, ChunkType::MaterialMetalLibrary)) {
return false;
}
printChunks(text, container);
-2
View File
@@ -76,8 +76,6 @@ static string_view readBlendingMode(string_view cursor, BlendingMode* blending)
*blending = BlendingMode::MULTIPLY;
} else if (sz = readPrefix("screen"sv, cursor); sz > 0) {
*blending = BlendingMode::SCREEN;
} else if (sz = readPrefix("custom"sv, cursor); sz > 0) {
*blending = BlendingMode::CUSTOM;
}
return { cursor.data(), sz };
}
+5 -6
View File
@@ -38,7 +38,6 @@ set(DIST_HDRS
${PUBLIC_HDR_DIR}/${TARGET}/SingleInstanceComponentManager.h
${PUBLIC_HDR_DIR}/${TARGET}/Slice.h
${PUBLIC_HDR_DIR}/${TARGET}/StructureOfArrays.h
${PUBLIC_HDR_DIR}/${TARGET}/Systrace.h
${PUBLIC_HDR_DIR}/${TARGET}/unwindows.h
)
@@ -88,6 +87,7 @@ if (LINUX OR ANDROID)
list(APPEND SRCS src/linux/Path.cpp)
endif()
if (APPLE)
list(APPEND SRCS src/darwin/Exception.mm)
list(APPEND SRCS src/darwin/Path.mm)
list(APPEND SRCS src/darwin/Systrace.cpp)
endif()
@@ -116,11 +116,6 @@ if (WIN32)
target_link_libraries(${TARGET} PUBLIC Shlwapi)
endif()
if (APPLE)
# Needed for NSTemporaryDirectory()
target_link_libraries(${TARGET} PRIVATE "-framework Foundation")
endif()
if (LINUX)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
@@ -128,6 +123,10 @@ if (LINUX)
target_link_libraries(${TARGET} PRIVATE dl)
endif()
if (APPLE AND NOT IOS)
target_link_libraries(${TARGET} PRIVATE "-framework Cocoa")
endif()
# ==================================================================================================
# Installation
# ==================================================================================================
+21 -36
View File
@@ -250,71 +250,54 @@ namespace utils {
*/
class UTILS_PUBLIC Panic {
public:
using PanicHandlerCallback = void(*)(void* user, Panic const& panic);
/**
* Sets a user-defined handler for the Panic. If exceptions are enabled, the concrete Panic
* object will be thrown upon return; moreover it is acceptable to throw from the provided
* callback, but it is unsafe to throw the Panic object itself, since it's just an interface.
* It is also acceptable to abort from the callback. If exceptions are not enabled, std::abort()
* will be automatically called upon return.
*
* The PanicHandlerCallback can be called from any thread.
*
* Caveat: this API can misbehave if is used as a static library in multiple translation units,
* some of these translation units might not see the callback.
*
* @param handler pointer to the user defined handler for the Panic
* @param user user pointer given back to the callback
*/
static void setPanicHandler(PanicHandlerCallback handler, void* user) noexcept;
virtual ~Panic() noexcept;
/**
* @return a formatted and detailed description of the error including all available
* information.
* @return a detailed description of the error
* @see std::exception
*/
virtual const char* what() const noexcept = 0;
/**
* Get the reason string for the panic
* @return a C string containing the reason for the panic
* Get the reason for the panic.
* @return a C string containing the reason for the panic.
*/
virtual const char* getReason() const noexcept = 0;
/**
* Get the function name where the panic was detected. On debug build the fully qualified
* function name is returned; on release builds only the function name is.
* Get the type of the panic. When compiled without RTTI, this returns a generic type name.
* @return a C string containing the type of the panic.
*/
virtual const char* getType() const noexcept = 0;
/**
* Get the function name where the panic was detected
* @return a C string containing the function name where the panic was detected
*/
virtual const char* getFunction() const noexcept = 0;
/**
* Get the file name where the panic was detected. Only available on debug builds.
* Get the file name where the panic was detected
* @return a C string containing the file name where the panic was detected
*/
virtual const char* getFile() const noexcept = 0;
/**
* Get the line number in the file where the panic was detected. Only available on debug builds.
* Get the line number in the file where the panic was detected
* @return an integer containing the line number in the file where the panic was detected
*/
virtual int getLine() const noexcept = 0;
/**
* Get the CallStack when the panic was detected if available.
* @return the CallStack when the panic was detected
*/
virtual const CallStack& getCallStack() const noexcept = 0;
/**
* Logs this exception to the system-log
*/
virtual void log() const noexcept = 0;
/**
* Get the CallStack when the panic was detected
* @return the CallStack when the panic was detected
*/
virtual const CallStack& getCallStack() const noexcept = 0;
};
// -----------------------------------------------------------------------------------------------
@@ -335,6 +318,7 @@ public:
// Panic interface
const char* getReason() const noexcept override;
const char* getType() const noexcept override;
const char* getFunction() const noexcept override;
const char* getFile() const noexcept override;
int getLine() const noexcept override;
@@ -399,7 +383,8 @@ private:
char const* const m_function = nullptr;
char const* const m_file = nullptr;
const int m_line = -1;
mutable std::string m_msg;
std::string m_msg;
std::string m_type;
};
namespace details {
+2 -2
View File
@@ -97,7 +97,7 @@
namespace utils {
namespace details {
class UTILS_PUBLIC Systrace {
class Systrace {
public:
enum tags {
@@ -220,7 +220,7 @@ class UTILS_PUBLIC Systrace {
// ------------------------------------------------------------------------------------------------
class UTILS_PUBLIC ScopedTrace {
class ScopedTrace {
public:
// we don't inline this because it's relatively heavy due to a global check
ScopedTrace(uint32_t tag, const char* name) noexcept: mTrace(tag), mTag(tag) {
@@ -14,18 +14,16 @@
* limitations under the License.
*/
#ifndef TNT_MATEDIT_EXTERNALCOMPILE_H
#define TNT_MATEDIT_EXTERNALCOMPILE_H
#ifndef TNT_UTILS_DARWIN_EXCEPTION_H
#define TNT_UTILS_DARWIN_EXCEPTION_H
#include <utils/Path.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <string>
#include <vector>
namespace utils::details {
namespace matedit {
void throwDarwinException(const Panic& exception) UTILS_NORETURN;
int externalCompile(utils::Path input, utils::Path output, std::vector<std::string> args);
}
} // namespace matedit
#endif
#endif // TNT_UTILS_DARWIN_EXCEPTION_H
-1
View File
@@ -20,7 +20,6 @@
#include <utils/compiler.h>
namespace utils {
UTILS_PUBLIC
void panic(const char *func, const char * file, int line, const char *assertion) noexcept;
} // namespace filament
+26 -79
View File
@@ -14,64 +14,21 @@
* limitations under the License.
*/
#include <utils/compiler.h>
#include <utils/Panic.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#if defined(__APPLE__)
#include <utils/darwin/Exception.h>
#endif
#include <atomic>
#include <cstdarg>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <new>
#include <string>
#include <utility>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utils/Log.h>
namespace utils {
// ------------------------------------------------------------------------------------------------
class UserPanicHandler {
struct CallBack {
Panic::PanicHandlerCallback handler = nullptr;
void* user = nullptr;
void call(Panic const& panic) const noexcept {
if (UTILS_UNLIKELY(handler)) {
handler(user, panic);
}
}
};
mutable std::mutex mLock{};
CallBack mCallBack{};
CallBack getCallback() const noexcept {
std::lock_guard const lock(mLock);
return mCallBack;
}
public:
static UserPanicHandler& get() noexcept {
static UserPanicHandler data;
return data;
}
void call(Panic const& panic) const noexcept {
getCallback().call(panic);
}
void set(Panic::PanicHandlerCallback handler, void* user) noexcept {
std::lock_guard const lock(mLock);
mCallBack = { handler, user };
}
};
// ------------------------------------------------------------------------------------------------
static std::string formatString(const char* format, va_list args) noexcept {
std::string reason;
@@ -82,12 +39,10 @@ static std::string formatString(const char* format, va_list args) noexcept {
if (n >= 0) {
++n; // for the nul-terminating char
char* const buf = new(std::nothrow) char[n];
if (buf) {
vsnprintf(buf, size_t(n), format, args);
reason.assign(buf);
delete [] buf;
}
char* buf = new char[n];
vsnprintf(buf, size_t(n), format, args);
reason.assign(buf);
delete [] buf;
}
return reason;
}
@@ -112,16 +67,8 @@ static std::string panicString(
#endif
}
// ------------------------------------------------------------------------------------------------
Panic::~Panic() noexcept = default;
void Panic::setPanicHandler(PanicHandlerCallback handler, void* user) noexcept {
UserPanicHandler::get().set(handler, user);
}
// ------------------------------------------------------------------------------------------------
template<typename T>
TPanic<T>::TPanic(std::string reason) :
m_reason(std::move(reason)) {
@@ -150,6 +97,11 @@ const char* TPanic<T>::getReason() const noexcept {
return m_reason.c_str();
}
template<typename T>
const char* TPanic<T>::getType() const noexcept {
return m_type.c_str();
}
template<typename T>
const char* TPanic<T>::getFunction() const noexcept {
return m_function;
@@ -178,13 +130,12 @@ void TPanic<T>::log() const noexcept {
template<typename T>
void TPanic<T>::buildMessage() {
std::string type;
#if UTILS_HAS_RTTI
type = CallStack::demangleTypeName(typeid(T).name()).c_str();
m_type = CallStack::demangleTypeName(typeid(T).name()).c_str();
#else
type = "Panic";
m_type = "Panic";
#endif
m_msg = panicString(type, m_function, m_line, m_file, m_reason.c_str());
m_msg = panicString(m_type, m_function, m_line, m_file, m_reason.c_str());
}
UTILS_ALWAYS_INLINE
@@ -197,22 +148,18 @@ template<typename T>
void TPanic<T>::panic(char const* function, char const* file, int line, const char* format, ...) {
va_list args;
va_start(args, format);
std::string const reason(formatString(format, args));
std::string reason(formatString(format, args));
va_end(args);
T e(function, formatFile(file), line, reason);
// always log the Panic at the point it is detected
e.log();
// Call the user provided handler
UserPanicHandler::get().call(e);
// if exceptions are enabled, throw now.
#ifdef __EXCEPTIONS
throw e;
#if defined(__APPLE__)
details::throwDarwinException(e);
#endif
// and finally abort if we somehow get here
#ifdef __EXCEPTIONS
throw e;
#endif
std::abort();
}
@@ -223,7 +170,7 @@ namespace details {
void panicLog(char const* function, char const* file, int line, const char* format, ...) noexcept {
va_list args;
va_start(args, format);
std::string const reason(formatString(format, args));
std::string reason(formatString(format, args));
va_end(args);
const std::string msg = panicString("" /* no extra message */,
+35
View File
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2024 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/Panic.h>
#include <Foundation/Foundation.h>
namespace utils::details {
void throwDarwinException(const Panic& exception) {
// Turn this exception into an NSException and raise it.
// This is allowed even if we're built without Objective-C exceptions (-fno-objc-exceptions).
[[NSException exceptionWithName:@(exception.getType())
reason:@(exception.getReason())
userInfo:@{
@"file" : @(exception.getFile()),
@"line" : @(exception.getLine()),
@"function" : @(exception.getFunction())
}] raise];
}
}
@@ -671,7 +671,6 @@ static bool processBlending(MaterialBuilder& builder, const JsonishValue& value)
{ "fade", MaterialBuilder::BlendingMode::FADE },
{ "multiply", MaterialBuilder::BlendingMode::MULTIPLY },
{ "screen", MaterialBuilder::BlendingMode::SCREEN },
{ "custom", MaterialBuilder::BlendingMode::CUSTOM },
};
auto jsonString = value.toJsonString();
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
@@ -682,52 +681,6 @@ static bool processBlending(MaterialBuilder& builder, const JsonishValue& value)
return true;
}
static bool processBlendFunction(MaterialBuilder& builder, const JsonishValue& value) {
static const std::unordered_map<std::string, MaterialBuilder::BlendFunction> strToEnum{
{ "zero", MaterialBuilder::BlendFunction::ZERO },
{ "one", MaterialBuilder::BlendFunction::ONE },
{ "srcColor", MaterialBuilder::BlendFunction::SRC_COLOR },
{ "oneMinusSrcColor", MaterialBuilder::BlendFunction::ONE_MINUS_SRC_COLOR },
{ "dstColor", MaterialBuilder::BlendFunction::DST_COLOR },
{ "oneMinusDstColor", MaterialBuilder::BlendFunction::ONE_MINUS_DST_COLOR },
{ "srcAlpha", MaterialBuilder::BlendFunction::SRC_ALPHA },
{ "oneMinusSrcAlpha", MaterialBuilder::BlendFunction::ONE_MINUS_SRC_ALPHA },
{ "dstAlpha", MaterialBuilder::BlendFunction::DST_ALPHA },
{ "oneMinusDstAlpha", MaterialBuilder::BlendFunction::ONE_MINUS_DST_ALPHA },
{ "srcAlphaSaturate", MaterialBuilder::BlendFunction::SRC_ALPHA_SATURATE },
};
if (value.getType() != JsonishValue::Type::OBJECT) {
std::cerr << "blendFunction must be an OBJECT." << std::endl;
}
JsonishObject const* const jsonObject = value.toJsonObject();
MaterialBuilder::BlendFunction srcRGB, srcA, dstRGB, dstA;
std::pair<const char*, MaterialBuilder::BlendFunction*> functions[] = {
{ "srcRGB", &srcRGB },
{ "srcA", &srcA },
{ "dstRGB", &dstRGB },
{ "dstA", &dstA },
};
for (auto&& entry : functions) {
const char* key = entry.first;
const JsonishValue* v = jsonObject->getValue(key);
if (!v) {
std::cerr << "blendFunction: entry without '" << key << "' key." << std::endl;
return false;
}
if (v->getType() != JsonishValue::STRING) {
std::cerr << "blendFunction: '" << key << "' value must be STRING." << std::endl;
return false;
}
*entry.second = stringToEnum(strToEnum, v->toJsonString()->getString());
}
builder.customBlendFunctions(srcRGB, srcA, dstRGB, dstA);
return true;
}
static bool processPostLightingBlending(MaterialBuilder& builder, const JsonishValue& value) {
static const std::unordered_map<std::string, MaterialBuilder::BlendingMode> strToEnum {
{ "add", MaterialBuilder::BlendingMode::ADD },
@@ -1241,7 +1194,6 @@ ParametersProcessor::ParametersProcessor() {
mParameters["variables"] = { &processVariables, Type::ARRAY };
mParameters["requires"] = { &processRequires, Type::ARRAY };
mParameters["blending"] = { &processBlending, Type::STRING };
mParameters["blendFunction"] = { &processBlendFunction, Type::OBJECT };
mParameters["postLightingBlending"] = { &processPostLightingBlending, Type::STRING };
mParameters["vertexDomain"] = { &processVertexDomain, Type::STRING };
mParameters["culling"] = { &processCulling, Type::STRING };

Some files were not shown because too many files have changed in this diff Show More