Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3da96d424 |
@@ -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()`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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", {
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 "--";
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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
|
||||
# ==================================================================================================
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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 */,
|
||||
|
||||
Executable
+35
@@ -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 };
|
||||
|
||||
@@ -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,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",
|
||||
|
||||
Reference in New Issue
Block a user