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
76 changed files with 2174 additions and 2931 deletions
+2
View File
@@ -7,3 +7,5 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- 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.3'
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.3'
pod 'Filament', '~> 1.51.0'
```
### Snapshots
-10
View File
@@ -7,16 +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.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.3
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
@@ -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 -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();
@@ -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
+1 -42
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,15 +252,6 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept
}
OpenGLContext::~OpenGLContext() noexcept {
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (!isES2()) {
for (auto& item: mSamplerMap) {
unbindSampler(item.second);
glDeleteSamplers(1, &item.second);
}
mSamplerMap.clear();
}
#endif
delete mTimerQueryFactory;
}
@@ -944,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
@@ -34,8 +34,6 @@
#include <math/vec2.h>
#include <math/vec4.h>
#include <tsl/robin_map.h>
#include <array>
#include <functional>
#include <optional>
@@ -469,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;
@@ -501,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;
+53 -15
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);
@@ -262,6 +263,14 @@ void OpenGLDriver::terminate() {
// because we called glFinish(), all callbacks should have been executed
assert_invariant(mGpuCommandCompleteOps.empty());
if (!getContext().isES2()) {
for (auto& item: mSamplerMap) {
mContext.unbindSampler(item.second);
glDeleteSamplers(1, &item.second);
}
mSamplerMap.clear();
}
#endif
mPlatform.terminate();
@@ -298,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);
}
@@ -715,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;
@@ -803,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);
@@ -2336,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
{
@@ -3061,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);
@@ -3082,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;
}
@@ -3099,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
-10
View File
@@ -277,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);
}
-1
View File
@@ -102,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;
+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 {
+5 -7
View File
@@ -726,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);
}
@@ -768,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 {
+3 -3
View File
@@ -263,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;
@@ -273,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;
+34 -60
View File
@@ -15,58 +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/bitset.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Invocable.h>
#include <utils/Log.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
#include <utils/Hash.h>
#include <algorithm>
#include <array>
#include <iterator>
#include <memory>
#include <mutex>
#include <new>
#include <optional>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <variant>
#include <stddef.h>
#include <stdint.h>
namespace filament {
@@ -74,15 +45,15 @@ using namespace backend;
using namespace filaflat;
using namespace utils;
static std::unique_ptr<MaterialParser> createParser(
Backend backend, ShaderLanguage language, 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>(language, data, size);
MaterialParser::ParseResult const materialResult = materialParser->parse();
if (backend == Backend::NOOP) {
return materialParser;
return materialParser.release();
}
ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND,
@@ -100,12 +71,13 @@ static std::unique_ptr<MaterialParser> createParser(
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,
@@ -143,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;
}
@@ -174,16 +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)
FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
: mEngine(engine),
mMaterialId(engine.getMaterialId()),
mMaterialParser(std::move(materialParser))
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);
@@ -263,7 +236,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
int const maxInstanceCount = (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)
? 1 : CONFIG_MAX_INSTANCES;
int const maxFroxelBufferHeight = (int)std::min(
int const maxFroxelBufferHeight = std::min(
FROXEL_BUFFER_MAX_ENTRY_COUNT / 4,
engine.getDriverApi().getMaxUniformBufferSize() / 16u);
@@ -350,8 +323,12 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
parser->getMaskThreshold(&mMaskThreshold);
}
if (mBlendingMode == BlendingMode::CUSTOM) {
parser->getCustomBlendFunction(&mCustomBlendFunctions);
// 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) {
@@ -404,12 +381,6 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
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;
}
bool depthWriteSet = false;
@@ -492,7 +463,9 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
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) {
@@ -815,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;
}
/**
@@ -832,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) {
+6 -41
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();
}
}
@@ -244,13 +223,13 @@ private:
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;
@@ -302,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;
@@ -324,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;
};
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.51.3"
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.3/filament-v1.51.3-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 = {
@@ -59,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"),
@@ -80,8 +80,6 @@ enum class BlendingMode : uint8_t {
MULTIPLY,
//! material brightens what's behind it
SCREEN,
//! custom blending function
CUSTOM,
};
/**
@@ -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;
-20
View File
@@ -388,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;
@@ -1520,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,
@@ -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";
}
}
-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 "--";
}
-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
View File
@@ -87,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()
@@ -122,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 {
@@ -0,0 +1,29 @@
/*
* 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_UTILS_DARWIN_EXCEPTION_H
#define TNT_UTILS_DARWIN_EXCEPTION_H
#include <utils/Panic.h>
#include <utils/compiler.h>
namespace utils::details {
void throwDarwinException(const Panic& exception) UTILS_NORETURN;
}
#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 };
+2 -2
View File
@@ -369,8 +369,8 @@ using EntityVector = std::vector<utils::Entity>;
register_vector<std::string>("RegistryKeys");
register_vector<utils::Entity>("EntityVector");
register_vector<allow_raw_pointer<FilamentInstance*>>("AssetInstanceVector");
register_vector<allow_raw_pointer<MaterialInstance*>>("MaterialInstanceVector");
register_vector<FilamentInstance*>("AssetInstanceVector");
register_vector<MaterialInstance*>("MaterialInstanceVector");
// CORE FILAMENT CLASSES
// ---------------------
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.51.3",
"version": "1.51.0",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",