Compare commits

..
37 Commits
Author SHA1 Message Date
Benjamin Doherty 9e960b7d45 Merge branch 'rc/1.27.0' into release 2022-09-13 10:14:24 -07:00
Ben Doherty 0c54d4a6a1 Add return case to fix G3 compiler error (#6062) 2022-09-12 12:34:56 -07:00
Philip Rideout 526e846a81 gltfio: fix crash when material is unspecified 2022-09-06 22:13:19 -04:00
Philip Rideout 8bcfa373d4 gltfio: do not clear out the texture slots too early.
This broke asyncGetLoadProgress() and caused WebGL to crash reliably
because ResourceLoader got destroyed too soon.

Bug was introduced with de7dfc2ea6.

I intend to cherry pick this to rc/1.27.0, which is where it was
introduced, so there's no need to update the release notes.
2022-09-06 22:09:25 -04:00
Ben Doherty db9a0f2c1f Fix uberarchive not included in PodSpec (#6024) 2022-09-01 12:18:20 -04:00
Benjamin Doherty 646b1e2193 Bump version to 1.27.0 2022-08-31 12:04:55 -04:00
Benjamin Doherty a886166489 Release Filament 1.26.0 2022-08-31 12:03:43 -04:00
Mathias Agopian 2073a9f3e5 cleanup Froxelizer comments
also fix typos, formatting.
2022-08-30 20:31:47 -07:00
Mathias Agopian be12049a87 The default arena sizes in the makefile didn't match the code
Also added some ascii art to explain better how the various arenas are
used.
2022-08-30 16:48:30 -07:00
Philip Rideout de7dfc2ea6 gltfio: Better behavior for releaseSourceData() 2022-08-30 16:00:11 -07:00
Philip Rideout 7337a467b9 gltfio cleanup: NodeMap is now a FixedCapacityVector, etc. 2022-08-30 15:57:30 -07:00
Philip Rideout 8118b4a774 gltfio: reset MaterialInstance cache for each instance.
For each "cgltf_material", we now create one "MaterialInstance", even
if a given asset has multiple instances. In the future, we might make
this behavior configurable to make better use of Filament's
auto-instancing feature.

This change is a feature request from Google, but also this behavior is
more consistent with the code comments.

Also some related cleanup:

(1) Use FixedCapacityVector instead of robin_map.

(2) Move the cache to move out of the asset and into the loader,
    because it is only used at load time.
2022-08-30 12:03:52 -07:00
Mathias Agopian b295743330 Fix filamat internal state tracking
in a few places filamat loses the value of targetApi and targetLanguage
and attempt to guess it from other values, this lead to inconsistencies.

we now keep both targetApi and targetLanguage and use them appropriately.

in particular we don't use the optimization level to "guess" what 
targetLangage we're on.


In the end this resolve a prior unit test failure and allows us to not
have to use "spirv" rules when generating only GLSL.
2022-08-29 22:11:17 -07:00
Mathias Agopian 4f8d7092e0 fix typos and reformat code 2022-08-29 22:11:17 -07:00
Mathias Agopian 2701e57779 fix filamat unit tests
We use TargetLanguage::SPIRV instead of GLSL which affects how the
source GLSL is generated, in particular which version is used.

This change is not a problem for these unit tests, but is probably
exposing another problem -- we will address that separately.
2022-08-29 17:31:51 -07:00
Philip Rideout f9375d5c33 gltfio: fix spotlight regression.
Spotlights were accidentally disabled in PR #5932.
2022-08-29 15:29:35 -07:00
Mathias Agopian d04f049fa3 fix (again) frustum-box intersection
We were early-exiting too aggressively in the some case.
2022-08-29 14:55:49 -07:00
Mathias Agopian 8b8e724761 fix a use after free when setting a Program's uniform infos
We were keeping the list if uniform names in Material and passing
pointers to the backend. Unfortunately, HwProgram can outlive
Material because it is legal to destroy a Material once it's not
needed on the client side. e.g. just after rendering something.

This was happening with the IBLPrefilter code, but probably elsewhere
too.

We now just store CStrings in HwProgram.
2022-08-29 13:49:34 -07:00
Philip Rideout 45e8c57f77 gltfio: Innocuous comment fixes / renamings. 2022-08-29 13:06:13 -07:00
Philip Rideout d01b3301d0 Vulkan: fix a warning, fix use-before-init. 2022-08-29 10:53:48 -07:00
Mathias Agopian a7a55c7617 Specification constants support (#6001)
* Add support for specialization constants

* remove WebGL instancing count hack

use a specification constant instead

* update release notes
2022-08-29 09:38:23 -07:00
Philip Rideout 0695c12420 gltfio: Refactor skinning, fix duplicated data.
The immutable inverse bind matrices can be shared among instances, so
they are now stored in Asset, not in Instance.

Also, there are now two "load" phases for skinning data:

(1) storing the inverse bind matrices
(2) building the Entity mappings (for animation efficiency)

Phase 1 is done in `ResourceLoader` because inverse bind matrices can
live in an external bin file.

Phase 2 is done during Instance creation, because that's when entities
are created.
2022-08-29 09:18:17 -07:00
Mathias Agopian 4af533c010 material instanced property must be false by default.
this fix auto-instancing.
2022-08-26 14:43:52 -07:00
Balazs Vegh 6a28d2b81c Implement setThreadName() on Windows (#5999) 2022-08-26 13:29:42 -04:00
Philip Rideout 54769a2ad3 gltfio: Move the API for recomputeBoundingBoxes.
Prior to this change, `recomputeBoundingBoxes` was an opt-in config
parameter in ResourceLoader. It is now a method on FilamentInstance.

The old API did not work for dynamically created instances. Since this
is a relatively obscure feature, we considered removing it completely,
especially since the computation requires the presence of CPU-side
vertex data combined with the transform hierarchy.

Instead of removing the feature, we decided to move it to a better
place. This paves the way for some upcoming improvements, which include
reducing the memory footprint for assets. It also improves overall code
organization and separation of concerns.
2022-08-26 09:48:04 -07:00
Mathias AgopianandBenjamin Doherty 87cece8379 use separate enums for uniform and sampler binding points (#5984)
Co-authored-by: Benjamin Doherty <bendoherty@google.com>
2022-08-24 16:36:13 -07:00
Philip Rideout fe3790cb9c WASM: Set CONFIG_MAX_INSTANCES to a small value + hack the GLSL.
Temporary fix until spec constants arrive.

Second attempt, fixes #5859.
2022-08-24 16:24:25 -07:00
gaborvalasekandLevente Koncz 39cb238065 Fix MSVC warnings (#5926)
* Fix warning C4146: unary minus operator applied to unsigned type, result still unsigned

* Fix warning C4068: unknown pragma 'nounroll'

* Fix warning C4068: unknown pragma 'unroll'

* Fix warning C4068: unknown pragma 'clang'

* Fix warning C4305: 'initializing': truncation from 'double' to 'float'

* Fix warning C4267: 'argument': conversion from 'size_t' to 'utils::FixedCapacityVector<filament::uberz::WritableArchive::Material,std::allocator<T>,true>::size_type', possible loss of data

* Fix warning C4267: 'argument': conversion from 'size_t' to 'uint32_t', possible loss of data

* Fix warning C4244: 'initializing': conversion from 'A' to 'T', possible loss of data

* Fix warning C4334: '<<': result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?)

* Fix warning C4293: '>>': shift count negative or too big, undefined behavior

* Fix diagnostic warning C4189: 'channels': local variable is initialized but not referenced

* Use [[maybe_unused]] where possible and revert aa79bd6fa8.

* Revert unary minus for non-MSVC compilers

* Add macro for enabling warnings temporarily

* Get rid of UTILS_HAS_CXX17

* Revisit warning related macros

Co-authored-by: Levente Koncz <levente.koncz@shapr3d.com>
2022-08-24 11:41:10 -07:00
Mathias Agopian 2182149b7a fix shadows when there is no receivers in the frustum
this case could cause an assert in debug builds. There was a few bugs:

- when computing the intersection between a box and a frustum, a special
  case test had a typo which would cause extra work in some case. Also,
  we were not detecting correctly when there was no intersection at all.
  This could result in some intersection point being found.

- Finally, we reject the intersection volume when it has only 3 vertices
  because that's not a volume.

Fixes #5933
2022-08-24 11:07:32 -07:00
Ben Doherty 006e0548c1 Adjust Metal buffer bindings in preparation for argument buffers (#5986) 2022-08-24 13:50:45 -04:00
Mathias Agopian 8257fbdbf3 Fix a merge that went bad 2022-08-23 17:16:56 -07:00
Mathias Agopian f57224952a record sampler informations in the material file
The main goal of this change is to have "ugly" code only in filamat.
In particular, the sampler binding information is now recorded
into the material file and used on the filament side to inform the
backend.

This means that on the filament side we only have generic code with
all the "uglyness" in filamat. In particular SamplerBindingMap and
SibGenerator now only exist in filamat.

Files of greater interest:
Material.cpp, MaterialBuilder.cpp
2022-08-23 17:03:30 -07:00
Philip Rideout 3c982cae64 Revert "WASM: Set CONFIG_MAX_INSTANCES to a small value."
This reverts commit 7aa5d4a375.
2022-08-23 16:42:16 -07:00
Mathias Agopian 3ffe63c02c don't use std::is_pod which is soon to be deprecated
fixes #5980
2022-08-23 15:49:35 -07:00
Philip Rideout 7aa5d4a375 WASM: Set CONFIG_MAX_INSTANCES to a small value.
This is a workaround for the following Chrome issues:

https://crbug.com/1348017 Compiling GLSL is very slow with struct arrays
https://crbug.com/1348363 Lighting looks wrong with D3D11 but not OpenGL

This requires a rebuild of materials for WebGL users.

Fixes #5859.
2022-08-23 11:57:26 -07:00
Philip Rideout 1d06dd630a Add CONFIG for MaxInstanceCount. 2022-08-23 09:32:01 -07:00
Mathias Agopian f630fb6732 'strict' field is no longer needed in Program::Sampler 2022-08-23 09:08:13 -07:00
123 changed files with 2120 additions and 1411 deletions
+6 -6
View File
@@ -35,16 +35,16 @@ set(FILAMENT_NDK_VERSION "" CACHE STRING
"Android NDK version or version prefix to be used when building for Android."
)
set(FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB "2" CACHE STRING
"Per render pass arena size. Must be roughly 1 MB larger than FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB, default 2."
set(FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB "3" CACHE STRING
"Per render pass arena size. Must be roughly 1 MB larger than FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB, default 3."
)
set(FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB "1" CACHE STRING
"Size of the high-level draw commands buffer. Rule of thumb, 1 MB less than FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB, default 1."
set(FILAMENT_PER_FRAME_COMMANDS_SIZE_IN_MB "2" CACHE STRING
"Size of the high-level draw commands buffer. Rule of thumb, 1 MB less than FILAMENT_PER_RENDER_PASS_ARENA_SIZE_IN_MB, default 2."
)
set(FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB "1" CACHE STRING
"Size of the command-stream buffer. As a rule of thumb use the same value as FILAMENT_PER_FRRAME_COMMANDS_SIZE_IN_MB, default 1."
set(FILAMENT_MIN_COMMAND_BUFFERS_SIZE_IN_MB "2" CACHE STRING
"Size of the command-stream buffer. As a rule of thumb use the same value as FILAMENT_PER_FRRAME_COMMANDS_SIZE_IN_MB, default 2."
)
set(FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB "4" CACHE STRING
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.26.0'
implementation 'com.google.android.filament:filament-android:1.27.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:
```
pod 'Filament', '~> 1.26.0'
pod 'Filament', '~> 1.27.0'
```
### Snapshots
+9
View File
@@ -5,6 +5,15 @@ A new header is inserted each time a *tag* is created.
## main branch
## v1.27.0
- WebGL: reduce max instance count to work around Chrome issues [⚠️ **Recompile Materials**]
- engine: rework material/shader sampler binding code [⚠️ **Recompile Materials**]
- gltfio: move the API for `recomputeBoundingBoxes` [⚠️ **API Change**]
- engine: add support for specialization constants [⚠️ **Recompile Materials**]
- gltfio: fix spotlight regression
- gltfio: clear the MaterialInstance cache when creating new instances
## v1.26.0
- engine: new feature level APIs, see `Engine::getSupportedFeatureLevel()`
@@ -73,8 +73,6 @@ class ModelViewer(
get() = resourceLoader.asyncGetLoadProgress()
var normalizeSkinningWeights = true
var recomputeBoundingBoxes = false
var ignoreBindTransform = false
var cameraFocalLength = 28f
set(value) {
@@ -116,7 +114,7 @@ class ModelViewer(
materialProvider = UbershaderProvider(engine)
assetLoader = AssetLoader(engine, materialProvider, EntityManager.get())
resourceLoader = ResourceLoader(engine, normalizeSkinningWeights, recomputeBoundingBoxes, ignoreBindTransform)
resourceLoader = ResourceLoader(engine, normalizeSkinningWeights)
// Always add a direct light source since it is required for shadowing.
// We highly recommend adding an indirect light as well.
@@ -36,11 +36,9 @@ static void destroy(void*, size_t, void *userData) {
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_gltfio_ResourceLoader_nCreateResourceLoader(JNIEnv*, jclass,
jlong nativeEngine, jboolean normalizeSkinningWeights, jboolean recomputeBoundingBoxes,
jboolean ignoreBindTransform) {
jlong nativeEngine, jboolean normalizeSkinningWeights) {
Engine* engine = (Engine*) nativeEngine;
return (jlong) new ResourceLoader({ engine, {}, (bool) normalizeSkinningWeights,
(bool) recomputeBoundingBoxes, (bool) ignoreBindTransform});
return (jlong) new ResourceLoader({ engine, {}, (bool) normalizeSkinningWeights});
}
extern "C" JNIEXPORT void JNICALL
@@ -160,7 +160,6 @@ public class AssetLoader {
* create/destroy churn, as noted above.
*
* This cannot be called after FilamentAsset#releaseSourceData().
* Animation is not supported in new instances.
* See also AssetLoader#createInstancedAsset().
*/
@Nullable
@@ -47,7 +47,7 @@ public class ResourceLoader {
*/
public ResourceLoader(@NonNull Engine engine) {
long nativeEngine = engine.getNativeObject();
mNativeObject = nCreateResourceLoader(nativeEngine, false, false, false);
mNativeObject = nCreateResourceLoader(nativeEngine, false);
mNativeStbProvider = nCreateStbProvider(nativeEngine);
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
@@ -60,16 +60,12 @@ public class ResourceLoader {
*
* @param engine the engine that gets passed to all builder methods
* @param normalizeSkinningWeights scale non-conformant skinning weights so they sum to 1
* @param recomputeBoundingBoxes use computed bounding boxes rather than the ones in the asset
* @param ignoreBindTransform ignore skinned primitives bind transform when compute bounding boxes
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public ResourceLoader(@NonNull Engine engine, boolean normalizeSkinningWeights,
boolean recomputeBoundingBoxes, boolean ignoreBindTransform) {
public ResourceLoader(@NonNull Engine engine, boolean normalizeSkinningWeights) {
long nativeEngine = engine.getNativeObject();
mNativeObject = nCreateResourceLoader(nativeEngine, normalizeSkinningWeights,
recomputeBoundingBoxes, ignoreBindTransform);
mNativeObject = nCreateResourceLoader(nativeEngine, normalizeSkinningWeights);
mNativeStbProvider = nCreateStbProvider(nativeEngine);
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
@@ -181,7 +177,7 @@ public class ResourceLoader {
}
private static native long nCreateResourceLoader(long nativeEngine,
boolean normalizeSkinningWeights, boolean recomputeBoundingBoxes, boolean ignoreBindTransform);
boolean normalizeSkinningWeights);
private static native void nDestroyResourceLoader(long nativeLoader);
private static native void nAddResourceData(long nativeLoader, String url, Buffer buffer,
int remaining);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.26.0
VERSION_NAME=1.27.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -57,7 +57,8 @@ static_assert(MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_ATTRIBUTE_COUNT,
"The number of buffer objects that can be attached to a VertexBuffer must be "
"less than or equal to the maximum number of vertex attributes.");
static constexpr size_t CONFIG_BINDING_COUNT = 12; // This is guaranteed by OpenGL ES.
static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 10; // This is guaranteed by OpenGL ES.
static constexpr size_t CONFIG_SAMPLER_BINDING_COUNT = 4; // This is guaranteed by OpenGL ES.
/**
* Defines the backend's feature levels.
@@ -280,7 +280,7 @@ public:
}
size_t bpr = bpp * stride;
size_t bprAligned = (bpr + (alignment - 1)) & -alignment;
size_t bprAligned = (bpr + (alignment - 1)) & (~alignment + 1);
return bprAligned * height;
}
+25 -6
View File
@@ -27,6 +27,7 @@
#include <backend/DriverEnums.h>
#include <array>
#include <variant>
namespace filament::backend {
@@ -34,12 +35,12 @@ class Program {
public:
static constexpr size_t SHADER_TYPE_COUNT = 2;
static constexpr size_t BINDING_COUNT = CONFIG_BINDING_COUNT;
static constexpr size_t UNIFORM_BINDING_COUNT = CONFIG_UNIFORM_BINDING_COUNT;
static constexpr size_t SAMPLER_BINDING_COUNT = CONFIG_SAMPLER_BINDING_COUNT;
struct Sampler {
utils::CString name = {}; // name of the sampler in the shader
uint16_t binding = 0; // binding point of the sampler in the shader
bool strict = false; // if true, this sampler must always have a bound texture
uint32_t binding = 0; // binding point of the sampler in the shader
};
struct SamplerGroupData {
@@ -47,8 +48,8 @@ public:
ShaderStageFlags stageFlags = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS;
};
using UniformBlockInfo = std::array<const char*, BINDING_COUNT>;
using SamplerGroupInfo = std::array<SamplerGroupData, BINDING_COUNT>;
using UniformBlockInfo = std::array<utils::CString, UNIFORM_BINDING_COUNT>;
using SamplerGroupInfo = std::array<SamplerGroupData, SAMPLER_BINDING_COUNT>;
using ShaderBlob = utils::FixedCapacityVector<uint8_t>;
using ShaderSource = std::array<ShaderBlob, SHADER_TYPE_COUNT>;
@@ -75,7 +76,7 @@ public:
// not permitted in glsl. The backend needs a way to associate a uniform block
// to a binding point.
Program& uniformBlockBindings(
utils::FixedCapacityVector<std::pair<const char*, uint8_t>> const& uniformBlockBindings) noexcept;
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& uniformBlockBindings) noexcept;
// sets the 'bindingPoint' sampler group descriptor for this program.
// 'samplers' can be destroyed after this call.
@@ -84,10 +85,20 @@ public:
Program& setSamplerGroup(size_t bindingPoint, ShaderStageFlags stageFlags,
Sampler const* samplers, size_t count) noexcept;
struct SpecializationConstant {
uint32_t id; // id set in glsl
std::variant<int32_t, float, bool> value; // value and type
};
Program& specializationConstants(
utils::FixedCapacityVector<SpecializationConstant> specConstants) noexcept;
ShaderSource const& getShadersSource() const noexcept { return mShadersSource; }
ShaderSource& getShadersSource() noexcept { return mShadersSource; }
UniformBlockInfo const& getUniformBlockBindings() const noexcept { return mUniformBlocks; }
UniformBlockInfo& getUniformBlockBindings() noexcept { return mUniformBlocks; }
SamplerGroupInfo const& getSamplerGroupInfo() const { return mSamplerGroups; }
SamplerGroupInfo& getSamplerGroupInfo() { return mSamplerGroups; }
@@ -95,6 +106,13 @@ public:
utils::CString const& getName() const noexcept { return mName; }
utils::CString& getName() noexcept { return mName; }
utils::FixedCapacityVector<SpecializationConstant> const& getSpecializationConstants() const noexcept {
return mSpecializationConstants;
}
utils::FixedCapacityVector<SpecializationConstant>& getSpecializationConstants() noexcept {
return mSpecializationConstants;
}
private:
friend utils::io::ostream& operator<<(utils::io::ostream& out, const Program& builder);
@@ -103,6 +121,7 @@ private:
ShaderSource mShadersSource;
utils::CString mName;
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)> mLogger;
utils::FixedCapacityVector<SpecializationConstant> mSpecializationConstants;
};
} // namespace filament::backend
@@ -21,15 +21,16 @@
#include <backend/PixelBufferDescriptor.h>
#include <string_view>
#include <stddef.h>
namespace filament {
namespace backend {
namespace filament::backend {
/**
* Returns true if the shader string requests the Google-style line directive extension.
*/
bool requestsGoogleLineDirectivesExtension(const char* shader, size_t length) noexcept;
bool requestsGoogleLineDirectivesExtension(std::string_view source) noexcept;
/**
* Edit a GLSL shader string in-place so any Google-style line directives are turned into regular
@@ -65,7 +66,6 @@ size_t getBlockHeight(TextureFormat format) noexcept;
*/
bool reshape(const PixelBufferDescriptor& data, PixelBufferDescriptor& reshaped);
} // namespace backend
} // namespace filament
#endif // TNT_FILAMENT_BACKEND_PRIVATE_BACKENDUTILS_H
+4 -7
View File
@@ -22,12 +22,10 @@
#include <string_view>
namespace filament {
namespace backend {
namespace filament::backend {
bool requestsGoogleLineDirectivesExtension(const char* shader, size_t length) noexcept {
std::string_view s(shader, length);
return s.find("GL_GOOGLE_cpp_style_line_directive") != std::string_view::npos;
bool requestsGoogleLineDirectivesExtension(std::string_view source) noexcept {
return source.find("GL_GOOGLE_cpp_style_line_directive") != std::string_view::npos;
}
void removeGoogleLineDirectives(char* shader, size_t length) noexcept {
@@ -380,8 +378,7 @@ bool reshape(const PixelBufferDescriptor& data, PixelBufferDescriptor& reshaped)
}
}
} // namespace backend
} // namespace filament
} // namespace backend::filament
namespace utils {
+13 -6
View File
@@ -16,10 +16,10 @@
#include "backend/Program.h"
using namespace utils;
namespace filament::backend {
using namespace utils;
// We want these in the .cpp file, so they're not inlined (not worth it)
Program::Program() noexcept { // NOLINT(modernize-use-equals-default)
}
@@ -37,8 +37,8 @@ Program& Program::operator=(Program&& rhs) noexcept {
Program::~Program() noexcept = default;
Program& Program::diagnostics(utils::CString const& name,
utils::Invocable<io::ostream&(utils::io::ostream&)>&& logger) {
Program& Program::diagnostics(CString const& name,
Invocable<io::ostream&(io::ostream&)>&& logger) {
mName = name;
mLogger = std::move(logger);
return *this;
@@ -52,9 +52,9 @@ Program& Program::shader(ShaderType shader, void const* data, size_t size) {
}
Program& Program::uniformBlockBindings(
utils::FixedCapacityVector<std::pair<const char*, uint8_t>> const& uniformBlockBindings) noexcept {
FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& uniformBlockBindings) noexcept {
for (auto const& item : uniformBlockBindings) {
assert_invariant(item.second < BINDING_COUNT);
assert_invariant(item.second < UNIFORM_BINDING_COUNT);
mUniformBlocks[item.second] = item.first;
}
return *this;
@@ -71,6 +71,12 @@ Program& Program::setSamplerGroup(size_t bindingPoint, ShaderStageFlags stageFla
return *this;
}
Program& Program::specializationConstants(
FixedCapacityVector<SpecializationConstant> specConstants) noexcept {
mSpecializationConstants = std::move(specConstants);
return *this;
}
io::ostream& operator<<(io::ostream& out, const Program& builder) {
out << "Program{";
builder.mLogger(out);
@@ -78,4 +84,5 @@ io::ostream& operator<<(io::ostream& out, const Program& builder) {
return out;
}
} // namespace filament::backend
+1 -1
View File
@@ -79,7 +79,7 @@ struct MetalContext {
// State trackers.
PipelineStateTracker pipelineState;
DepthStencilStateTracker depthStencilState;
UniformBufferState uniformState[VERTEX_BUFFER_START];
UniformBufferState uniformState[UNIFORM_BUFFER_COUNT];
CullModeStateTracker cullModeState;
WindingStateTracker windingState;
+12 -10
View File
@@ -915,6 +915,7 @@ void MetalDriver::commit(Handle<HwSwapChain> sch) {
}
void MetalDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh) {
assert_invariant(index < UNIFORM_BUFFER_COUNT);
auto* bo = handle_cast<MetalBufferObject>(boh);
auto* currentBo = mContext->uniformState[index].buffer;
if (currentBo) {
@@ -930,6 +931,7 @@ void MetalDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh)
void MetalDriver::bindUniformBufferRange(uint32_t index, Handle<HwBufferObject> boh,
uint32_t offset, uint32_t size) {
assert_invariant(index < UNIFORM_BUFFER_COUNT);
auto* bo = handle_cast<MetalBufferObject>(boh);
auto* currentBo = mContext->uniformState[index].buffer;
if (currentBo) {
@@ -1317,8 +1319,8 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
[mContext->currentRenderPassEncoder setScissorRect:scissorRect];
// Bind uniform buffers.
MetalBuffer* uniformsToBind[Program::BINDING_COUNT] = { nil };
NSUInteger offsets[Program::BINDING_COUNT] = { 0 };
MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil };
NSUInteger offsets[Program::UNIFORM_BINDING_COUNT] = { 0 };
enumerateBoundUniformBuffers([&uniformsToBind, &offsets](const UniformBufferState& state,
MetalBuffer* buffer, uint32_t index) {
@@ -1326,8 +1328,8 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
offsets[index] = state.offset;
});
MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
0, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT, uniformsToBind, offsets,
Program::BINDING_COUNT);
UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT,
uniformsToBind, offsets, Program::UNIFORM_BINDING_COUNT);
// Enumerate all the sampler buffers for the program and check which textures and samplers need
// to be bound.
@@ -1428,10 +1430,10 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
[mContext->currentRenderPassEncoder setFragmentSamplerStates:samplersToBindFragment
withRange:fragmentSamplerRange];
// Bind the vertex buffers.
// Bind the user vertex buffers.
MetalBuffer* buffers[MAX_VERTEX_BUFFER_COUNT];
size_t vertexBufferOffsets[MAX_VERTEX_BUFFER_COUNT];
MetalBuffer* buffers[MAX_VERTEX_BUFFER_COUNT] = {};
size_t vertexBufferOffsets[MAX_VERTEX_BUFFER_COUNT] = {};
size_t bufferIndex = 0;
auto vb = primitive->vertexBuffer;
@@ -1449,14 +1451,14 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
const auto bufferCount = bufferIndex;
MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
VERTEX_BUFFER_START, MetalBuffer::Stage::VERTEX, buffers,
USER_VERTEX_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX, buffers,
vertexBufferOffsets, bufferCount);
// Bind the zero buffer, used for missing vertex attributes.
static const char bytes[16] = { 0 };
[mContext->currentRenderPassEncoder setVertexBytes:bytes
length:16
atIndex:(VERTEX_BUFFER_START + ZERO_VERTEX_BUFFER)];
atIndex:ZERO_VERTEX_BUFFER_BINDING];
MetalIndexBuffer* indexBuffer = primitive->indexBuffer;
@@ -1517,7 +1519,7 @@ void MetalDriver::enumerateSamplerGroups(
void MetalDriver::enumerateBoundUniformBuffers(
const std::function<void(const UniformBufferState&, MetalBuffer*, uint32_t)>& f) {
for (uint32_t i = 0; i < Program::BINDING_COUNT; i++) {
for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) {
auto& thisUniform = mContext->uniformState[i];
if (!thisUniform.bound) {
continue;
+1 -1
View File
@@ -125,7 +125,7 @@ public:
MetalBuffer* getBuffer() { return &buffer; }
// Tracks which uniform buffers this buffer object is bound into.
static_assert(Program::BINDING_COUNT <= 32);
static_assert(Program::UNIFORM_BINDING_COUNT <= 32);
utils::bitset32 boundUniformBuffers;
private:
+20 -6
View File
@@ -296,9 +296,8 @@ void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalInde
vertexDescription = {};
// Each attribute gets its own vertex buffer.
uint32_t bufferIndex = 0;
// Each attribute gets its own vertex buffer, starting at logical buffer 1.
uint32_t bufferIndex = 1;
for (uint32_t attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) {
const auto& attribute = vertexBuffer->attributes[attributeIndex];
if (attribute.buffer == Attribute::BUFFER_UNUSED) {
@@ -310,10 +309,10 @@ void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalInde
// shader to read from missing vertex attributes.
vertexDescription.attributes[attributeIndex] = {
.format = format,
.buffer = ZERO_VERTEX_BUFFER,
.buffer = ZERO_VERTEX_BUFFER_LOGICAL_INDEX,
.offset = 0
};
vertexDescription.layouts[ZERO_VERTEX_BUFFER] = {
vertexDescription.layouts[ZERO_VERTEX_BUFFER_LOGICAL_INDEX] = {
.step = MTLVertexStepFunctionConstant,
.stride = 16
};
@@ -373,7 +372,22 @@ MetalProgram::MetalProgram(id<MTLDevice> device, const Program& program) noexcep
return;
}
*shaderFunctions[i] = [library newFunctionWithName:@"main0"];
MTLFunctionConstantValues* constants = [MTLFunctionConstantValues new];
auto const& specializationConstants = program.getSpecializationConstants();
for (auto const& sc : specializationConstants) {
const std::array<MTLDataType, 3> types{
MTLDataTypeInt, MTLDataTypeFloat, MTLDataTypeBool };
std::visit([&sc, constants, type = types[sc.value.index()]](auto&& arg) {
[constants setConstantValue:&arg
type:type
atIndex:sc.id];
}, sc.value);
}
*shaderFunctions[i] = [library newFunctionWithName:@"main0"
constantValues:constants
error:&error];
}
// All stages of the program have compiled successfully, this is a valid program.
+27 -10
View File
@@ -35,17 +35,33 @@ inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) {
return lhs.u == rhs.u;
}
static constexpr uint32_t SAMPLER_GROUP_COUNT = Program::BINDING_COUNT;
static constexpr uint32_t SAMPLER_BINDING_COUNT = MAX_SAMPLER_COUNT;
static constexpr uint32_t VERTEX_BUFFER_START = Program::BINDING_COUNT;
// Bindings Buffer name Count
// ------------------------------------------------------
// 0 Zero buffer (placeholder vertex buffer) 1
// 1-16 Filament vertex buffers 16 limited by MAX_VERTEX_BUFFER_COUNT
// 17-26 Uniform buffers 10
// 27-30 Sampler Uniforms (argument buffers) 4 TODO: use arg buffers for samplers
//
// Total 31
// The "zero" buffer is a small buffer for missing attributes that resides in the vertex slot
// immediately following any user-provided vertex buffers.
static constexpr uint32_t ZERO_VERTEX_BUFFER = MAX_VERTEX_BUFFER_COUNT;
static constexpr uint32_t SAMPLER_GROUP_COUNT = Program::SAMPLER_BINDING_COUNT;
static constexpr uint32_t SAMPLER_BINDING_COUNT = MAX_SAMPLER_COUNT;
// The total number of vertex buffer "slots" that the Metal backend can bind.
// + 1 to account for the zero buffer.
static constexpr uint32_t VERTEX_BUFFER_COUNT = MAX_VERTEX_BUFFER_COUNT + 1;
// + 1 to account for the zero buffer, a placeholder buffer used internally by the Metal backend.
// MAX_VERTEX_BUFFER_COUNT represents the max number of vertex buffers Filament can bind.
static constexpr uint32_t LOGICAL_VERTEX_BUFFER_COUNT = MAX_VERTEX_BUFFER_COUNT + 1;
// The "zero" buffer is a small buffer for missing attributes.
static constexpr uint32_t ZERO_VERTEX_BUFFER_LOGICAL_INDEX = 0u;
static constexpr uint32_t ZERO_VERTEX_BUFFER_BINDING = 0u;
static constexpr uint32_t USER_VERTEX_BUFFER_BINDING_START = 1u;
// The max number of uniform buffers that the Metal backend can simultaneously bind.
static constexpr uint32_t UNIFORM_BUFFER_COUNT = 10u;
// This constant must match the equivalent in CodeGenerator.cpp.
static constexpr uint32_t UNIFORM_BUFFER_BINDING_START = 17u;
// Forward declarations necessary here, definitions at end of file.
inline bool operator==(const MTLViewport& lhs, const MTLViewport& rhs);
@@ -58,7 +74,7 @@ inline bool operator!=(const MTLViewport& lhs, const MTLViewport& rhs);
struct VertexDescription {
struct Attribute {
MTLVertexFormat format; // 8 bytes
uint32_t buffer; // 4 bytes
uint32_t buffer; // 4 bytes a logical vertex buffer index
uint32_t offset; // 4 bytes
};
struct Layout {
@@ -66,7 +82,8 @@ struct VertexDescription {
uint64_t stride; // 8 bytes
};
Attribute attributes[MAX_VERTEX_ATTRIBUTE_COUNT] = {}; // 256 bytes
Layout layouts[VERTEX_BUFFER_COUNT] = {}; // 272 bytes
// layouts[n] represents the layout of the vertex buffer at logical index n
Layout layouts[LOGICAL_VERTEX_BUFFER_COUNT] = {}; // 272 bytes
bool operator==(const VertexDescription& rhs) const noexcept {
bool result = true;
+5 -5
View File
@@ -38,18 +38,18 @@ id<MTLRenderPipelineState> PipelineStateCreator::operator()(id<MTLDevice> device
if (vertexDescription.attributes[i].format > MTLVertexFormatInvalid) {
const auto& attribute = vertexDescription.attributes[i];
vertex.attributes[i].format = attribute.format;
vertex.attributes[i].bufferIndex = VERTEX_BUFFER_START + attribute.buffer;
vertex.attributes[i].bufferIndex = attribute.buffer;
vertex.attributes[i].offset = attribute.offset;
}
}
for (uint32_t i = 0; i < VERTEX_BUFFER_COUNT; i++) {
for (uint32_t i = 0; i < LOGICAL_VERTEX_BUFFER_COUNT; i++) {
if (vertexDescription.layouts[i].stride > 0) {
const auto& layout = vertexDescription.layouts[i];
vertex.layouts[VERTEX_BUFFER_START + i].stride = layout.stride;
vertex.layouts[VERTEX_BUFFER_START + i].stepFunction = layout.step;
vertex.layouts[i].stride = layout.stride;
vertex.layouts[i].stepFunction = layout.step;
if (layout.step == MTLVertexStepFunctionConstant) {
vertex.layouts[VERTEX_BUFFER_START + i].stepRate = 0;
vertex.layouts[i].stepRate = 0;
}
}
}
+1 -1
View File
@@ -2589,7 +2589,7 @@ void OpenGLDriver::bindUniformBufferRange(uint32_t index, Handle<HwBufferObject>
void OpenGLDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
DEBUG_MARKER()
assert_invariant(index < Program::BINDING_COUNT);
assert_invariant(index < Program::SAMPLER_BINDING_COUNT);
GLSamplerGroup* sb = handle_cast<GLSamplerGroup *>(sbh);
mSamplerBindings[index] = sb;
CHECK_GL_ERROR(utils::slog.e)
+2 -2
View File
@@ -323,7 +323,7 @@ private:
return pos->second;
}
const std::array<GLSamplerGroup*, Program::BINDING_COUNT>& getSamplerBindings() const {
const std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT>& getSamplerBindings() const {
return mSamplerBindings;
}
@@ -344,7 +344,7 @@ private:
void setViewportScissor(Viewport const& viewportScissor) noexcept;
// sampler buffer binding points (nullptr if not used)
std::array<GLSamplerGroup*, Program::BINDING_COUNT> mSamplerBindings = {}; // 12 pointers
std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT> mSamplerBindings = {}; // 4 pointers
mutable tsl::robin_map<uint32_t, GLuint> mSamplerMap;
mutable std::vector<GLTexture*> mExternalStreams;
+109 -64
View File
@@ -51,13 +51,16 @@ OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& programBuilder) noexce
OpenGLContext& context = gld.getContext();
mLazyInitializationData->uniformBlockInfo = programBuilder.getUniformBlockBindings();
mLazyInitializationData->uniformBlockInfo = std::move(programBuilder.getUniformBlockBindings());
mLazyInitializationData->samplerGroupInfo = std::move(programBuilder.getSamplerGroupInfo());
// this cannot fail because we check compilation status after linking the program
// shaders[] is filled with id of shader stages present.
OpenGLProgram::compileShaders(context, programBuilder.getShadersSource(),
gl.shaders, mLazyInitializationData->shaderSourceCode);
OpenGLProgram::compileShaders(context,
std::move(programBuilder.getShadersSource()),
programBuilder.getSpecializationConstants(),
gl.shaders,
mLazyInitializationData->shaderSourceCode);
gld.runAtNextRenderPass(this, [this]() {
// by this point we must not have a GL program
@@ -98,9 +101,22 @@ OpenGLProgram::~OpenGLProgram() noexcept {
*/
void OpenGLProgram::compileShaders(OpenGLContext& context,
Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
GLuint shaderIds[Program::SHADER_TYPE_COUNT],
std::array<CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept {
std::string specificationConstantString;
for (auto const& sc : specializationConstants) {
specificationConstantString += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
specificationConstantString += std::visit([](auto&& arg) {
return std::to_string(arg);
}, sc.value);
specificationConstantString += '\n';
}
if (!specificationConstantString.empty()) {
specificationConstantString += '\n';
}
// build all shaders
UTILS_NOUNROLL
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
@@ -116,31 +132,67 @@ void OpenGLProgram::compileShaders(OpenGLContext& context,
}
if (UTILS_LIKELY(!shadersSource[i].empty())) {
Program::ShaderBlob const& shader = shadersSource[i];
std::string_view shaderView(reinterpret_cast<const char*>(shader.data()), shader.size());
std::string temp;
Program::ShaderBlob& shader = shadersSource[i];
if (!context.ext.GOOGLE_cpp_style_line_directive) {
// If usages of the Google-style line directive are present, remove them, as some
// drivers don't allow the quotation marks.
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension(
shaderView.data(), shaderView.size()))) {
temp = shaderView; // copy string
removeGoogleLineDirectives(temp.data(), temp.size()); // length is unaffected
shaderView = temp;
}
}
// remove GOOGLE_cpp_style_line_directive
std::string_view source = process_GOOGLE_cpp_style_line_directive(context,
reinterpret_cast<char*>(shader.data()), shader.size());
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 and
// MacOS doesn't support GL_ARB_shading_language_packing
if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
if (context.state.major == 4 && context.state.minor == 1 &&
!context.ext.ARB_shading_language_packing) {
if (temp.empty()) {
temp = shaderView; // copy string
}
// add support for ARB_shading_language_packing if needed
auto const packingFunctions = process_ARB_shading_language_packing(context);
std::string unpackHalf2x16{ R"(
// split shader source, so we can insert the specification constants and the packing functions
auto const [prolog, body] = splitShaderSource(source);
const std::array<const char*, 4> sources = {
prolog.data(),
specificationConstantString.c_str(),
packingFunctions.data(),
body.data()
};
const std::array<GLint, 4> lengths = {
(GLint)prolog.length(),
(GLint)specificationConstantString.length(),
(GLint)packingFunctions.length(),
(GLint)body.length() - 1 // null terminated
};
GLuint shaderId = glCreateShader(glShaderType);
glShaderSource(shaderId, sources.size(), sources.data(), lengths.data());
glCompileShader(shaderId);
#ifndef NDEBUG
// for debugging we return the original shader source (without the modifications we
// made here), otherwise the line numbers wouldn't match.
outShaderSourceCode[i] = { source.data(), source.length() };
#endif
shaderIds[i] = shaderId;
}
}
}
// If usages of the Google-style line directive are present, remove them, as some
// drivers don't allow the quotation marks. This happens in-place.
std::string_view OpenGLProgram::process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
char* source, size_t len) noexcept {
if (!context.ext.GOOGLE_cpp_style_line_directive) {
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
removeGoogleLineDirectives(source, len); // length is unaffected
}
}
return { source, len };
}
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 and
// macOS doesn't support GL_ARB_shading_language_packing
std::string_view OpenGLProgram::process_ARB_shading_language_packing(OpenGLContext& context) noexcept {
using namespace std::literals;
if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
if (context.state.major == 4 && context.state.minor == 1 &&
!context.ext.ARB_shading_language_packing) {
return R"(
// these don't handle denormals, NaNs or inf
float u16tofp32(highp uint v) {
@@ -175,33 +227,29 @@ highp uint packHalf2x16(vec2 v) {
highp uint y = fp32tou16(v.y);
return (y << 16) | x;
}
)"};
// a good point for insertion is just before the first occurrence of an uniform block
auto pos = temp.find("layout(std140)");
if (pos != std::string_view::npos) {
temp.insert(pos, unpackHalf2x16);
}
shaderView = temp;
}
}
GLuint shaderId = glCreateShader(glShaderType);
{ // scope for source/length (we don't want them to leak out)
const char* const source = shaderView.data();
// the shader string is null terminated and the length includes the null character
const GLint length = (GLint)shaderView.length() - 1;
assert_invariant( source[length] == '\0' );
glShaderSource(shaderId, 1, &source, &length);
glCompileShader(shaderId);
#ifndef NDEBUG
outShaderSourceCode[i] = { source, static_cast<size_t>(length) };
#endif
}
shaderIds[i] = shaderId;
)"sv;
}
}
return ""sv;
}
// split shader source code in two, the first section goes from the start to the line after the
// last #extension, and the 2nd part goes from there to the end.
std::array<std::string_view, 2> OpenGLProgram::splitShaderSource(std::string_view source) noexcept {
auto start = source.find("#version");
assert_invariant(start != std::string_view::npos);
auto pos = source.rfind("#extension");
if (pos == std::string_view::npos) {
pos = start;
}
auto eol = source.find('\n', pos) + 1;
assert_invariant(eol != std::string_view::npos);
std::string_view version = source.substr(start, eol - start);
std::string_view body = source.substr(version.length(), source.length() - version.length());
return { version, body };
}
/*
@@ -271,9 +319,7 @@ void OpenGLProgram::initialize(OpenGLContext& context) {
gl.program, gl.shaders, initializationData->shaderSourceCode);
if (UTILS_LIKELY(mValid)) {
initializeProgramState(context, gl.program,
initializationData->uniformBlockInfo,
initializationData->samplerGroupInfo);
initializeProgramState(context, gl.program, *initializationData);
}
// and destroy all temporary init data
@@ -287,16 +333,15 @@ void OpenGLProgram::initialize(OpenGLContext& context) {
* checkProgramStatus() has been successfully called.
*/
void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint program,
Program::UniformBlockInfo const& uniformBlockInfo,
Program::SamplerGroupInfo const& samplerGroupInfo) noexcept {
LazyInitializationData const& lazyInitializationData) noexcept {
// Note: This is only needed, because the layout(binding=) syntax is not permitted in glsl
// (ES3.0 and GL4.1). The backend needs a way to associate a uniform block to a binding point.
UTILS_NOUNROLL
for (GLuint binding = 0, n = uniformBlockInfo.size(); binding < n; binding++) {
const char* name = uniformBlockInfo[binding];
if (name) {
GLint index = glGetUniformBlockIndex(program, name);
for (GLuint binding = 0, n = lazyInitializationData.uniformBlockInfo.size(); binding < n; binding++) {
auto const& name = lazyInitializationData.uniformBlockInfo[binding];
if (!name.empty()) {
GLint index = glGetUniformBlockIndex(program, name.c_str());
if (index >= 0) {
glUniformBlockBinding(program, GLuint(index), binding);
}
@@ -308,8 +353,8 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
uint8_t tmu = 0;
UTILS_NOUNROLL
for (size_t i = 0, c = samplerGroupInfo.size(); i < c; i++) {
auto const& samplers = samplerGroupInfo[i].samplers;
for (size_t i = 0, c = lazyInitializationData.samplerGroupInfo.size(); i < c; i++) {
auto const& samplers = lazyInitializationData.samplerGroupInfo[i].samplers;
if (samplers.empty()) {
// this binding point doesn't have any samplers, skip it.
continue;
@@ -336,7 +381,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
// whole group.
if (atLeastOneSamplerUsed) {
// Cache the sampler uniform locations for each interface block
mUsedBindingPoints[usedBindingCount] = i;
mUsedSamplerBindingPoints[usedBindingCount] = i;
usedBindingCount++;
} else {
tmu -= samplers.size();
@@ -345,12 +390,12 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
mUsedBindingsCount = usedBindingCount;
}
void OpenGLProgram::updateSamplers(OpenGLDriver* gld) noexcept {
void OpenGLProgram::updateSamplers(OpenGLDriver* gld) const noexcept {
using GLTexture = OpenGLDriver::GLTexture;
// cache a few member variable locally, outside the loop
auto const& UTILS_RESTRICT samplerBindings = gld->getSamplerBindings();
auto const& UTILS_RESTRICT usedBindingPoints = mUsedBindingPoints;
auto const& UTILS_RESTRICT usedBindingPoints = mUsedSamplerBindingPoints;
for (uint8_t i = 0, tmu = 0, n = mUsedBindingsCount; i < n; i++) {
auto const binding = usedBindingPoints[i];
+18 -11
View File
@@ -72,11 +72,26 @@ public:
} gl; // 12 bytes
private:
// keep these away from of other class attributes
struct LazyInitializationData {
Program::UniformBlockInfo uniformBlockInfo;
Program::SamplerGroupInfo samplerGroupInfo;
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
};
static void compileShaders(OpenGLContext& context,
Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
GLuint shaderIds[Program::SHADER_TYPE_COUNT],
std::array<utils::CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept;
static std::string_view process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
char* source, size_t len) noexcept;
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
static std::array<std::string_view, 2> splitShaderSource(std::string_view source) noexcept;
static GLuint linkProgram(const GLuint shaderIds[Program::SHADER_TYPE_COUNT]) noexcept;
static bool checkProgramStatus(const char* name,
@@ -86,17 +101,9 @@ private:
void initialize(OpenGLContext& context);
void initializeProgramState(OpenGLContext& context, GLuint program,
Program::UniformBlockInfo const& uniformBlockInfo,
Program::SamplerGroupInfo const& samplerGroupInfo) noexcept;
LazyInitializationData const& lazyInitializationData) noexcept;
void updateSamplers(OpenGLDriver* gld) noexcept;
// keep these away from of other class attributes
struct LazyInitializationData {
Program::UniformBlockInfo uniformBlockInfo;
Program::SamplerGroupInfo samplerGroupInfo;
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
};
void updateSamplers(OpenGLDriver* gld) const noexcept;
// number of bindings actually used by this program
uint8_t mUsedBindingsCount = 0u;
@@ -109,7 +116,7 @@ private:
union {
// when mInitialized == true:
// information about each USED sampler buffer per binding (no gaps)
std::array<uint8_t, Program::BINDING_COUNT> mUsedBindingPoints; // 12 bytes
std::array<uint8_t, Program::SAMPLER_BINDING_COUNT> mUsedSamplerBindingPoints; // 4 bytes
// when mInitialized == false:
// lazy initialization data pointer
LazyInitializationData* mLazyInitializationData;
+1 -1
View File
@@ -1824,7 +1824,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
VkDescriptorImageInfo iInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::BINDING_COUNT; samplerGroupIdx++) {
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT; samplerGroupIdx++) {
const auto& samplerGroup = program->samplerGroupInfo[samplerGroupIdx];
const auto& samplers = samplerGroup.samplers;
if (samplers.empty()) {
@@ -65,6 +65,47 @@ VulkanProgram::VulkanProgram(VulkanContext& context, const Program& builder) noe
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create shader module.");
}
// populate the specialization constants requirements right now
auto const& specializationConstants = builder.getSpecializationConstants();
if (!specializationConstants.empty()) {
// Allocate a single heap block to store all the specialization constants structures
// our supported types are int32, float and bool, so we use 4 bytes per data. bool will
// just use the first byte.
char* pStorage = (char*)malloc(
sizeof(VkSpecializationInfo) +
specializationConstants.size() * sizeof(VkSpecializationMapEntry) +
specializationConstants.size() * 4);
VkSpecializationInfo* const pInfo = (VkSpecializationInfo*)pStorage;
VkSpecializationMapEntry* const pEntries =
(VkSpecializationMapEntry*)(pStorage + sizeof(VkSpecializationInfo));
void* pData = pStorage + sizeof(VkSpecializationInfo) +
specializationConstants.size() * sizeof(VkSpecializationMapEntry);
*pInfo = {
.mapEntryCount = specializationConstants.size(),
.pMapEntries = pEntries,
.dataSize = specializationConstants.size() * 4,
.pData = pData,
};
for (size_t i = 0; i < specializationConstants.size(); i++) {
const uint32_t offset = uint32_t(i) * 4;
std::visit([&](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
pEntries[i] = {
.constantID = specializationConstants[i].id,
.offset = offset,
.size = sizeof(arg)
};
T* const addr = (T*)((char*)pData + offset);
*addr = arg;
}, specializationConstants[i].value);
}
bundle.specializationInfos = pInfo;
}
// Output a warning because it's okay to encounter empty blobs, but it's not okay to use
// this program handle in a draw call.
if (missing) {
@@ -90,6 +131,7 @@ VulkanProgram::VulkanProgram(VulkanContext& context, VkShaderModule vs, VkShader
VulkanProgram::~VulkanProgram() {
vkDestroyShaderModule(context.device, bundle.vertex, VKALLOC);
vkDestroyShaderModule(context.device, bundle.fragment, VKALLOC);
free(bundle.specializationInfos);
}
// Creates a special "default" render target (i.e. associated with the swap chain)
@@ -49,7 +49,7 @@ static VkShaderStageFlags getShaderStageFlags(utils::bitset64 key, uint16_t bind
utils::bitset64
static getPipelineLayoutKey(const Program::SamplerGroupInfo& samplerGroupInfo) noexcept {
utils::bitset64 key = {};
for (uint32_t binding = 0; binding < Program::BINDING_COUNT; ++binding) {
for (uint32_t binding = 0; binding < Program::SAMPLER_BINDING_COUNT; ++binding) {
const auto& stageFlags = samplerGroupInfo[binding].stageFlags;
for (const auto& sampler : samplerGroupInfo[binding].samplers) {
if (any(stageFlags & ShaderStageFlags::VERTEX)) {
@@ -371,7 +371,9 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
// If we reach this point, we need to create and stash a brand new pipeline object.
shaderStages[0].module = mPipelineRequirements.shaders[0];
shaderStages[0].pSpecializationInfo = mSpecializationRequirements;
shaderStages[1].module = mPipelineRequirements.shaders[1];
shaderStages[1].pSpecializationInfo = mSpecializationRequirements;
// Expand our size-optimized structs into the proper Vk structs.
uint32_t numVertexAttribs = 0;
@@ -581,6 +583,7 @@ void VulkanPipelineCache::bindProgram(const VulkanProgram& program) noexcept {
mPipelineRequirements.shaders[ssi] = shaders[ssi];
}
mLayoutRequirements = getPipelineLayoutKey(program.samplerGroupInfo);
mSpecializationRequirements = program.bundle.specializationInfos;
}
void VulkanPipelineCache::bindRasterState(const RasterState& rasterState) noexcept {
@@ -56,7 +56,7 @@ public:
VulkanPipelineCache(VulkanPipelineCache const&) = delete;
VulkanPipelineCache& operator=(VulkanPipelineCache const&) = delete;
static constexpr uint32_t UBUFFER_BINDING_COUNT = Program::BINDING_COUNT;
static constexpr uint32_t UBUFFER_BINDING_COUNT = Program::UNIFORM_BINDING_COUNT;
static constexpr uint32_t SAMPLER_BINDING_COUNT = MAX_SAMPLER_COUNT;
static constexpr uint32_t TARGET_BINDING_COUNT = MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT;
static constexpr uint32_t SHADER_MODULE_COUNT = 2;
@@ -66,9 +66,6 @@ public:
static constexpr uint32_t DESCRIPTOR_TYPE_COUNT = 3;
static constexpr uint32_t INITIAL_DESCRIPTOR_SET_POOL_SIZE = 512;
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wpadded"
// 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
@@ -82,8 +79,12 @@ public:
struct ProgramBundle {
VkShaderModule vertex;
VkShaderModule fragment;
VkSpecializationInfo* specializationInfos = nullptr;
};
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wpadded"
// The RasterState POD contains standard graphics-related state like blending, culling, etc.
// The following states are omitted because Filament never changes them:
// >>> depthClampEnable, rasterizerDiscardEnable, depthBoundsTestEnable, stencilTestEnable
@@ -289,17 +290,17 @@ private:
// Represents all the Vulkan state that comprises a bound descriptor set.
struct DescriptorKey {
VkBuffer uniformBuffers[UBUFFER_BINDING_COUNT]; // 96 0
DescriptorImageInfo samplers[SAMPLER_BINDING_COUNT]; // 768 96
DescriptorImageInfo inputAttachments[TARGET_BINDING_COUNT]; // 192 864
uint32_t uniformBufferOffsets[UBUFFER_BINDING_COUNT]; // 48 1056
uint32_t uniformBufferSizes[UBUFFER_BINDING_COUNT]; // 48 1104
VkBuffer uniformBuffers[UBUFFER_BINDING_COUNT]; // 80 0
DescriptorImageInfo samplers[SAMPLER_BINDING_COUNT]; // 768 80
DescriptorImageInfo inputAttachments[TARGET_BINDING_COUNT]; // 192 848
uint32_t uniformBufferOffsets[UBUFFER_BINDING_COUNT]; // 40 1040
uint32_t uniformBufferSizes[UBUFFER_BINDING_COUNT]; // 40 1080
};
static_assert(offsetof(DescriptorKey, samplers) == 96);
static_assert(offsetof(DescriptorKey, inputAttachments) == 864);
static_assert(offsetof(DescriptorKey, uniformBufferOffsets) == 1056);
static_assert(offsetof(DescriptorKey, uniformBufferSizes) == 1104);
static_assert(sizeof(DescriptorKey) == 1152, "DescriptorKey must not have implicit padding.");
static_assert(offsetof(DescriptorKey, samplers) == 80);
static_assert(offsetof(DescriptorKey, inputAttachments) == 848);
static_assert(offsetof(DescriptorKey, uniformBufferOffsets) == 1040);
static_assert(offsetof(DescriptorKey, uniformBufferSizes) == 1080);
static_assert(sizeof(DescriptorKey) == 1120, "DescriptorKey must not have implicit padding.");
using DescHashFn = utils::hash::MurmurHashFn<DescriptorKey>;
@@ -385,6 +386,7 @@ private:
PipelineLayoutKey mLayoutRequirements = {};
PipelineKey mPipelineRequirements = {};
DescriptorKey mDescriptorRequirements = {};
VkSpecializationInfo* mSpecializationRequirements = {};
// Current bindings for the pipeline layout, pipeline, and descriptor sets.
PipelineLayoutKey mBoundLayout = {};
+3 -3
View File
@@ -417,7 +417,7 @@ TEST_F(BackendTest, DepthMinify) {
{
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
prog.uniformBlockBindings({{"params", 1}});
program = api.createProgram(std::move(prog));
@@ -559,7 +559,7 @@ TEST_F(BackendTest, ColorResolve) {
{
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
prog.uniformBlockBindings({{"params", 1}});
program = api.createProgram(std::move(prog));
@@ -667,7 +667,7 @@ TEST_F(BackendTest, DepthResolve) {
{
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
prog.uniformBlockBindings({{"params", 1}});
program = api.createProgram(std::move(prog));
+1 -1
View File
@@ -135,7 +135,7 @@ TEST_F(BackendTest, FeedbackLoops) {
{
ShaderGenerator shaderGen(fullscreenVs, fullscreenFs, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
prog.uniformBlockBindings({{"params", 1}});
program = api.createProgram(std::move(prog));
+4 -4
View File
@@ -350,7 +350,7 @@ TEST_F(BackendTest, UpdateImage2D) {
getSamplerTypeName(t.textureFormat), fragmentTemplate);
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
program = api.createProgram(std::move(prog));
@@ -429,7 +429,7 @@ TEST_F(BackendTest, UpdateImageSRGB) {
getSamplerTypeName(textureFormat), fragmentTemplate);
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
ProgramHandle program = api.createProgram(std::move(prog));
@@ -515,7 +515,7 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
getSamplerTypeName(textureFormat), fragmentUpdateImageMip);
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
ProgramHandle program = api.createProgram(std::move(prog));
@@ -587,7 +587,7 @@ TEST_F(BackendTest, UpdateImage3D) {
getSamplerTypeName(samplerType), fragmentUpdateImage3DTemplate);
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0, false };
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
ProgramHandle program = api.createProgram(std::move(prog));
+24 -1
View File
@@ -173,6 +173,29 @@ public:
* Config is used to define the memory footprint used by the engine, such as the
* command buffer size. Config can be used to customize engine requirements based
* on the applications needs.
*
* .perRenderPassArenaSizeMB (default: 3 MiB)
* +--------------------------+
* | |
* | .perFrameCommandsSizeMB |
* | (default 2 MiB) |
* | |
* +--------------------------+
* | (froxel, etc...) |
* +--------------------------+
*
*
* .commandBufferSizeMB (default 3MiB)
* +--------------------------+
* | .minCommandBufferSizeMB |
* +--------------------------+
* | .minCommandBufferSizeMB |
* +--------------------------+
* | .minCommandBufferSizeMB |
* +--------------------------+
* : :
* : :
*
*/
struct Config {
/**
@@ -190,7 +213,7 @@ public:
/**
* Size in MiB of the per-frame high level command buffer arena.
* Size in MiB of the per-frame data arena.
*
* This is the main arena used for allocations when preparing a frame.
* e.g.: Froxel data and high-level commands are allocated from this arena.
+2 -1
View File
@@ -327,7 +327,8 @@ public:
* PixelBufferDescriptor&& buffer)
*/
inline void setImage(Engine& engine, size_t level, PixelBufferDescriptor&& buffer) const {
setImage(engine, level, 0, 0, 0, getWidth(level), getHeight(level), 1, std::move(buffer));
setImage(engine, level, 0, 0, 0,
uint32_t(getWidth(level)), uint32_t(getHeight(level)), 1, std::move(buffer));
}
/**
+41 -26
View File
@@ -49,28 +49,34 @@ static constexpr size_t FROXEL_SLICE_COUNT = 16;
static constexpr float FROXEL_FIRST_SLICE_DEPTH = 5;
static constexpr float FROXEL_LAST_SLICE_DISTANCE = 100;
// Max number of froxels limited by:
// - max texture size [min 2048]
// - chosen texture width [64]
//
// Also, increasing the number of froxels adds more pressure on the "record buffer" which stores
// the light indices per froxel. The record buffer is limited to min(16K[ubo], 64K[uint16]) entries,
// so with 8192 froxels, we can store 2 lights per froxels assuming they're all used. In practice, some
// froxels are not used, so we can store more.
constexpr size_t FROXEL_BUFFER_ENTRY_COUNT = 8192;
// The record buffer is limited by both the UBO size and our use of 16-bits indices.
constexpr size_t RECORD_BUFFER_ENTRY_COUNT = CONFIG_MINSPEC_UBO_SIZE; // 16 KiB UBO minspec
// The Froxel buffer is set to FROXEL_BUFFER_WIDTH x n
// With n limited by the supported texture dimension, which is guaranteed to be at least 2048
// in all version of GLES.
// Make sure this matches the same constants in shading_lit.fs
constexpr size_t FROXEL_BUFFER_WIDTH_SHIFT = 6u;
constexpr size_t FROXEL_BUFFER_WIDTH = 1u << FROXEL_BUFFER_WIDTH_SHIFT;
constexpr size_t FROXEL_BUFFER_WIDTH_MASK = FROXEL_BUFFER_WIDTH - 1u;
constexpr size_t FROXEL_BUFFER_HEIGHT = (FROXEL_BUFFER_ENTRY_COUNT_MAX + FROXEL_BUFFER_WIDTH_MASK) / FROXEL_BUFFER_WIDTH;
constexpr size_t RECORD_BUFFER_WIDTH_SHIFT = 4u;
constexpr size_t RECORD_BUFFER_WIDTH = 1u << RECORD_BUFFER_WIDTH_SHIFT;
constexpr size_t RECORD_BUFFER_HEIGHT = 1024;
constexpr size_t RECORD_BUFFER_ENTRY_COUNT = RECORD_BUFFER_WIDTH * RECORD_BUFFER_HEIGHT; // 16K
constexpr size_t FROXEL_BUFFER_HEIGHT = (FROXEL_BUFFER_ENTRY_COUNT + FROXEL_BUFFER_WIDTH_MASK) / FROXEL_BUFFER_WIDTH;
// Buffer needed for Froxelizer internal data structures (~256 KiB)
constexpr size_t PER_FROXELDATA_ARENA_SIZE = sizeof(float4) *
(FROXEL_BUFFER_ENTRY_COUNT_MAX +
FROXEL_BUFFER_ENTRY_COUNT_MAX + 3 +
(FROXEL_BUFFER_ENTRY_COUNT +
FROXEL_BUFFER_ENTRY_COUNT + 3 +
FROXEL_SLICE_COUNT / 4 + 1);
// number of lights processed by one group (e.g. 32)
static constexpr size_t LIGHT_PER_GROUP = sizeof(Froxelizer::LightGroupType) * 8;
@@ -78,12 +84,22 @@ static constexpr size_t LIGHT_PER_GROUP = sizeof(Froxelizer::LightGroupType) * 8
static constexpr size_t GROUP_COUNT =
(CONFIG_MAX_LIGHT_COUNT + LIGHT_PER_GROUP - 1) / LIGHT_PER_GROUP;
// This depends on the maximum number of lights (currently 256)
static_assert(CONFIG_MAX_LIGHT_INDEX <= std::numeric_limits<Froxelizer::RecordBufferType>::max(),
"can't have more than 256 lights");
// record buffer cannot be larger than 65K entries because we're using uint16_t to store indices
// so its maximum size is 128 KiB
// Record buffer cannot be larger than 65K entries because froxels use uint16_t to store indices
// to it.
static_assert(RECORD_BUFFER_ENTRY_COUNT <= 65536,
"RecordBuffer cannot be larger than 65536 entries");
static_assert(RECORD_BUFFER_ENTRY_COUNT <= CONFIG_MINSPEC_UBO_SIZE,
"RecordBuffer cannot be larger than the UBO minspec (16KiB)");
struct Froxelizer::FroxelThreadData :
public std::array<LightGroupType, FROXEL_BUFFER_ENTRY_COUNT> {
};
Froxelizer::Froxelizer(FEngine& engine)
: mArena("froxel", PER_FROXELDATA_ARENA_SIZE),
mZLightNear(FROXEL_FIRST_SLICE_DEPTH),
@@ -156,14 +172,14 @@ bool Froxelizer::prepare(
}
/*
* Allocations that need to persists until the driver consumes them are done from
* Allocations that need to persist until the driver consumes them are done from
* the command stream.
*/
// froxel buffer (~32 KiB)
mFroxelBufferUser = {
driverApi.allocatePod<FroxelEntry>(FROXEL_BUFFER_ENTRY_COUNT_MAX),
FROXEL_BUFFER_ENTRY_COUNT_MAX };
driverApi.allocatePod<FroxelEntry>(FROXEL_BUFFER_ENTRY_COUNT),
FROXEL_BUFFER_ENTRY_COUNT };
// record buffer (~16 KiB)
mRecordBufferUser = {
@@ -176,8 +192,8 @@ bool Froxelizer::prepare(
// light records per froxel (~256 KiB)
mLightRecords = {
arena.allocate<LightRecord>(FROXEL_BUFFER_ENTRY_COUNT_MAX, CACHELINE_SIZE),
FROXEL_BUFFER_ENTRY_COUNT_MAX };
arena.allocate<LightRecord>(FROXEL_BUFFER_ENTRY_COUNT, CACHELINE_SIZE),
FROXEL_BUFFER_ENTRY_COUNT };
// froxel thread data (~256 KiB)
mFroxelShardedData = {
@@ -208,7 +224,7 @@ void Froxelizer::computeFroxelLayout(
// calculate froxel dimension from FROXEL_BUFFER_ENTRY_COUNT_MAX and viewport
// - Start from the maximum number of froxels we can use in the x-y plane
size_t froxelSliceCount = FROXEL_SLICE_COUNT;
size_t froxelPlaneCount = FROXEL_BUFFER_ENTRY_COUNT_MAX / froxelSliceCount;
size_t froxelPlaneCount = FROXEL_BUFFER_ENTRY_COUNT / froxelSliceCount;
// - compute the number of square froxels we need in width and height, rounded down
// solving: | froxelCountX * froxelCountY == froxelPlaneCount
// | froxelCountX / froxelCountY == width / height
@@ -248,8 +264,8 @@ bool Froxelizer::update() noexcept {
computeFroxelLayout(&froxelDimension, &froxelCountX, &froxelCountY, &froxelCountZ, viewport);
mFroxelDimension = froxelDimension;
mClipToFroxelX = (0.5f * viewport.width) / froxelDimension.x;
mClipToFroxelY = (0.5f * viewport.height) / froxelDimension.y;
mClipToFroxelX = (0.5f * float(viewport.width)) / float(froxelDimension.x);
mClipToFroxelY = (0.5f * float(viewport.height)) / float(froxelDimension.y);
uniformsNeedUpdating = true;
@@ -258,15 +274,14 @@ bool Froxelizer::update() noexcept {
<< froxelDimension.x << "x" << froxelDimension.y << io::endl
<< "Froxel: " << froxelCountX << "x" << froxelCountY << "x" << froxelCountZ
<< " = " << (froxelCountX * froxelCountY * froxelCountZ)
<< " (" << FROXEL_BUFFER_ENTRY_COUNT_MAX - froxelCountX * froxelCountY * froxelCountZ << " lost)"
<< " (" << FROXEL_BUFFER_ENTRY_COUNT - froxelCountX * froxelCountY * froxelCountZ << " lost)"
<< io::endl;
#endif
mFroxelCountX = froxelCountX;
mFroxelCountY = froxelCountY;
mFroxelCountZ = froxelCountZ;
// froxel count must fit on 16 bits
const uint16_t froxelCount = uint16_t(froxelCountX * froxelCountY * froxelCountZ);
const uint32_t froxelCount = uint32_t(froxelCountX * froxelCountY * froxelCountZ);
mFroxelCount = froxelCount;
if (mDistancesZ) {
@@ -629,7 +644,7 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept {
// this gets very well vectorized...
utils::Slice<LightRecord> records(mLightRecords);
for (size_t j = 0, jc = FROXEL_BUFFER_ENTRY_COUNT_MAX; j < jc; j++) {
for (size_t j = 0, jc = FROXEL_BUFFER_ENTRY_COUNT; j < jc; j++) {
for (size_t i = 0; i < LightRecord::bitset::WORLD_COUNT; i++) {
using container_type = LightRecord::bitset::container_type;
constexpr size_t r = sizeof(container_type) / sizeof(LightGroupType);
@@ -642,7 +657,7 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept {
}
LightRecord::bitset allLights{};
for (size_t j = 0, jc = FROXEL_BUFFER_ENTRY_COUNT_MAX; j < jc; j++) {
for (size_t j = 0, jc = FROXEL_BUFFER_ENTRY_COUNT; j < jc; j++) {
allLights |= records[j].lights;
}
@@ -670,7 +685,7 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept {
// how many froxel record entries were reused (for debugging)
UTILS_UNUSED size_t reused = 0;
for (size_t i = 0, c = getFroxelCount(); i < c;) {
for (size_t i = 0, c = mFroxelCount; i < c;) {
LightRecord b = records[i];
if (b.lights.none()) {
froxels[i++].u32 = 0;
+22 -28
View File
@@ -48,9 +48,10 @@ public:
};
//
// Light UBO Froxel Record Buffer per-froxel light list texture
// {4 x float4} R_U8 {index into RG_U16 {offset, point-count, spot-count}
// Light UBO Froxel Record UBO per-froxel light list texture
// {4 x float4} {index into RG_U16 {offset, point-count, spot-count}
// (spot/point light texture}
// {uint4 -> 16 indices}
//
// +----+ +-+ +----+
// 0|....| <------------+ 0| | +-----------|0230| (e.g. offset=02, 3-lights)
@@ -69,16 +70,6 @@ public:
// 256 lights max
//
// Max number of froxels limited by:
// - max texture size [min 2048]
// - chosen texture width [64]
// - size of CPU-side indices [16 bits]
// Also, increasing the number of froxels adds more pressure on the "record buffer" which stores
// the light indices per froxel. The record buffer is limited to 65536 entries, so with
// 8192 froxels, we can store 8 lights per froxels assuming they're all used. In practice, some
// froxels are not used, so we can store more.
static constexpr size_t FROXEL_BUFFER_ENTRY_COUNT_MAX = 8192;
class Froxelizer {
public:
explicit Froxelizer(FEngine& engine);
@@ -100,8 +91,8 @@ public:
* Allocate per-frame data structures for froxelization.
*
* driverApi used to allocate memory in the stream
* arena use to allocate per-frame memory
* viewport viewport used to calculate froxel dimensions
* arena used to allocate per-frame memory
* viewport used to calculate froxel dimensions
* projection camera projection matrix
* projectionNear near plane
* projectionFar far plane
@@ -147,9 +138,10 @@ public:
};
};
};
// This depends on the maximum number of lights (currently 255),and can't be more than 16 bits.
static_assert(CONFIG_MAX_LIGHT_INDEX <= std::numeric_limits<uint16_t>::max(), "can't have more than 65536 lights");
using RecordBufferType = std::conditional_t<CONFIG_MAX_LIGHT_INDEX <= std::numeric_limits<uint8_t>::max(), uint8_t, uint16_t>;
// we can't change this easily because the shader expects 16 indices per uint4
using RecordBufferType = uint8_t;
const utils::Slice<FroxelEntry>& getFroxelBufferUser() const { return mFroxelBufferUser; }
const utils::Slice<RecordBufferType>& getRecordBufferUser() const { return mRecordBufferUser; }
@@ -185,7 +177,7 @@ private:
uint16_t reserved;
};
using FroxelThreadData = std::array<LightGroupType, FROXEL_BUFFER_ENTRY_COUNT_MAX>;
struct FroxelThreadData;
inline void setViewport(Viewport const& viewport) noexcept;
inline void setProjection(const math::mat4f& projection, float near, float far) noexcept;
@@ -203,8 +195,8 @@ private:
utils::Slice<RecordBufferType> const& lightList,
const FScene::LightSoa& lightData, size_t lightRecordsOffset) noexcept;
uint16_t getFroxelIndex(size_t ix, size_t iy, size_t iz) const noexcept {
return uint16_t(ix + (iy * mFroxelCountX) + (iz * mFroxelCountX * mFroxelCountY));
size_t getFroxelIndex(size_t ix, size_t iy, size_t iz) const noexcept {
return ix + (iy * mFroxelCountX) + (iz * mFroxelCountX * mFroxelCountY);
}
size_t findSliceZ(float viewSpaceZ) const noexcept UTILS_PURE;
@@ -215,25 +207,27 @@ private:
math::uint2* dim, uint16_t* countX, uint16_t* countY, uint16_t* countZ,
Viewport const& viewport) noexcept;
// internal state dependant on the viewport and needed for froxelizing
LinearAllocatorArena mArena; // ~256 KiB
// internal state dependent on the viewport and needed for froxelizing
LinearAllocatorArena mArena; // ~256 KiB
float* mDistancesZ = nullptr; // max 2.1 MiB (actual: resolution dependant)
// allocations in the private froxel arena
float* mDistancesZ = nullptr;
math::float4* mPlanesX = nullptr;
math::float4* mPlanesY = nullptr;
math::float4* mBoundingSpheres = nullptr;
math::float4* mBoundingSpheres = nullptr; // 128 KiB w/ 8192 froxels
utils::Slice<FroxelThreadData> mFroxelShardedData; // 256 KiB w/ 256 lights
// allocations in the per frame arena
utils::Slice<FroxelThreadData> mFroxelShardedData; // 256 KiB w/ 256 lights and 8192 froxels
utils::Slice<FroxelEntry> mFroxelBufferUser; // 32 KiB w/ 8192 froxels
utils::Slice<LightRecord> mLightRecords; // 256 KiB w/ 256 lights
// max 32 KiB (actual: resolution dependant)
// allocations in the command stream
utils::Slice<RecordBufferType> mRecordBufferUser; // 16 KiB
utils::Slice<LightRecord> mLightRecords; // 256 KiB w/ 256 lights
uint16_t mFroxelCountX = 0;
uint16_t mFroxelCountY = 0;
uint16_t mFroxelCountZ = 0;
uint16_t mFroxelCount = 0;
uint32_t mFroxelCount = 0;
math::uint2 mFroxelDimension = {};
math::mat4f mProjection;
+56
View File
@@ -24,6 +24,7 @@
#include <filament/MaterialChunkType.h>
#include <private/filament/SamplerBindingsInfo.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/UniformInterfaceBlock.h>
#include <private/filament/SubpassInfo.h>
@@ -171,6 +172,17 @@ bool MaterialParser::getUniformBlockBindings(
return ChunkUniformBlockBindings::unflatten(unflattener, value);
}
bool MaterialParser::getSamplerBlockBindings(
SamplerGroupBindingInfoList* pSamplerGroupInfoList,
SamplerBindingToNameMap* pSamplerBindingToNameMap) const noexcept {
auto type = MaterialSamplerBindings;
const uint8_t* start = mImpl.mChunkContainer.getChunkStart(type);
const uint8_t* end = mImpl.mChunkContainer.getChunkEnd(type);
Unflattener unflattener(start, end);
return ChunkSamplerBlockBindings::unflatten(unflattener,
pSamplerGroupInfoList, pSamplerBindingToNameMap);
}
bool MaterialParser::getDepthWriteSet(bool* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialDepthWriteSet, value);
}
@@ -486,4 +498,48 @@ bool ChunkUniformBlockBindings::unflatten(filaflat::Unflattener& unflattener,
}
bool ChunkSamplerBlockBindings::unflatten(Unflattener& unflattener,
SamplerGroupBindingInfoList* pSamplerGroupBindingInfoList,
SamplerBindingToNameMap* pSamplerBindingToNameMap) {
assert_invariant(pSamplerGroupBindingInfoList && pSamplerBindingToNameMap);
SamplerGroupBindingInfoList& samplerGroupBindingInfoList = *pSamplerGroupBindingInfoList;
SamplerBindingToNameMap& samplerBindingToNameMap = *pSamplerBindingToNameMap;
uint8_t count;
if (!unflattener.read(&count)) {
return false;
}
assert_invariant(count == utils::Enum::count<SamplerBindingPoints>());
UTILS_NOUNROLL
for (size_t i = 0; i < count; i++) {
if (!unflattener.read(&samplerGroupBindingInfoList[i].bindingOffset)) {
return false;
}
if (!unflattener.read((uint8_t *)&samplerGroupBindingInfoList[i].shaderStageFlags)) {
return false;
}
if (!unflattener.read(&samplerGroupBindingInfoList[i].count)) {
return false;
}
}
if (!unflattener.read(&count)) {
return false;
}
for (size_t i = 0; i < count; i++) {
uint8_t binding;
if (!unflattener.read(&binding)) {
return false;
}
assert_invariant(binding < backend::MAX_SAMPLER_COUNT);
if (!unflattener.read(&samplerBindingToNameMap[binding])) {
return false;
}
}
return true;
}
} // namespace filament
+13 -2
View File
@@ -21,14 +21,17 @@
#include <filaflat/MaterialChunk.h>
#include <filament/MaterialEnums.h>
#include <backend/DriverEnums.h>
#include <filament/MaterialChunkType.h>
#include "../../libs/filamat/src/SamplerBindingMap.h"
#include <private/filament/Variant.h>
#include <backend/DriverEnums.h>
#include <utils/compiler.h>
#include <utils/CString.h>
#include <inttypes.h>
#include "private/filament/Variant.h"
namespace filaflat {
class ChunkContainer;
@@ -66,6 +69,8 @@ public:
bool getShaderModels(uint32_t* value) const noexcept;
bool getMaterialProperties(uint64_t* value) const noexcept;
bool getUniformBlockBindings(utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>>* value) const noexcept;
bool getSamplerBlockBindings(SamplerGroupBindingInfoList* pSamplerGroupInfoList,
SamplerBindingToNameMap* pSamplerBindingToNameMap) const noexcept;
bool getDepthWriteSet(bool* value) const noexcept;
bool getDepthWrite(bool* value) const noexcept;
@@ -155,6 +160,12 @@ struct ChunkUniformBlockBindings {
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>>* uniformBlockBindings);
};
struct ChunkSamplerBlockBindings {
static bool unflatten(filaflat::Unflattener& unflattener,
SamplerGroupBindingInfoList* pSamplerGroupBindingInfoList,
SamplerBindingToNameMap* pSamplerBindingToNameMap);
};
} // namespace filament
#endif // TNT_FILAMENT_MATERIALPARSER_H
+3 -3
View File
@@ -30,7 +30,7 @@
#include <filament/TextureSampler.h>
#include <private/filament/EngineEnums.h>
#include <private/filament/SibGenerator.h>
#include <private/filament/SibStructs.h>
#include <math/mat4.h>
@@ -369,8 +369,8 @@ void PerViewUniforms::commit(backend::DriverApi& driver) noexcept {
}
void PerViewUniforms::bind(backend::DriverApi& driver) noexcept {
driver.bindUniformBuffer(+BindingPoints::PER_VIEW, mUniformBufferHandle);
driver.bindSamplers(+BindingPoints::PER_VIEW, mSamplerGroupHandle);
driver.bindUniformBuffer(+UniformBindingPoints::PER_VIEW, mUniformBufferHandle);
driver.bindSamplers(+SamplerBindingPoints::PER_VIEW, mSamplerGroupHandle);
}
void PerViewUniforms::unbindSamplers() noexcept {
+6 -7
View File
@@ -213,8 +213,7 @@ void RenderPass::instanceify() noexcept {
uint32_t instancedPrimitiveOffset = 0;
// TODO: for the case of instancing we could actually use 128 instead of 64 instances
constexpr size_t maxInstanceCount = sizeof(PerRenderableUib) / sizeof(PerRenderableData);
static_assert(maxInstanceCount == 64); // just to make sure we don't change by accident
constexpr size_t maxInstanceCount = CONFIG_MAX_INSTANCES;
while (curr != last) {
@@ -761,28 +760,28 @@ void RenderPass::Executor::recordDriverCommands(FEngine& engine, backend::Driver
// bind per-renderable uniform block. there is no need to attempt to skip this command
// because the backends already do this.
driver.bindUniformBufferRange(+BindingPoints::PER_RENDERABLE,
driver.bindUniformBufferRange(+UniformBindingPoints::PER_RENDERABLE,
(info.instanceCount > 1) ? mInstancedUboHandle : uboHandle,
info.index * sizeof(PerRenderableData),
sizeof(PerRenderableUib));
if (UTILS_UNLIKELY(info.skinningHandle)) {
// note: we can't bind less than sizeof(PerRenderableBoneUib) due to glsl limitations
driver.bindUniformBufferRange(+BindingPoints::PER_RENDERABLE_BONES,
driver.bindUniformBufferRange(+UniformBindingPoints::PER_RENDERABLE_BONES,
info.skinningHandle,
info.skinningOffset * sizeof(PerRenderableBoneUib::BoneData),
sizeof(PerRenderableBoneUib));
// note: even if only skinning is enabled, binding morphTargetBuffer is needed.
driver.bindSamplers(+BindingPoints::PER_RENDERABLE_MORPHING,
driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING,
info.morphTargetBuffer);
}
if (UTILS_UNLIKELY(info.morphWeightBuffer)) {
// Instead of using a UBO per primitive, we could also have a single UBO for all
// primitives and use bindUniformBufferRange which might be more efficient.
driver.bindUniformBuffer(+BindingPoints::PER_RENDERABLE_MORPHING,
driver.bindUniformBuffer(+UniformBindingPoints::PER_RENDERABLE_MORPHING,
info.morphWeightBuffer);
driver.bindSamplers(+BindingPoints::PER_RENDERABLE_MORPHING,
driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING,
info.morphTargetBuffer);
}
+22 -2
View File
@@ -228,7 +228,7 @@ void ShadowMap::updateDirectional(const FScene::LightSoa& lightData, size_t inde
}
}
mHasVisibleShadows = vertexCount >= 2;
mHasVisibleShadows = vertexCount >= 4;
if (mHasVisibleShadows) {
// We can't use LISPSM in stable mode
const bool USE_LISPSM = ENABLE_LISPSM && mEngine.debug.shadowmap.lispsm && !params.options.stable;
@@ -613,6 +613,7 @@ mat4f ShadowMap::computeVsmLightSpaceMatrix(const mat4f& lightSpacePcf,
// This construct a frustum (similar to glFrustum or frustum), except
// it looks towards the +y axis, and assumes -1,1 for the left/right and bottom/top planes.
mat4f ShadowMap::warpFrustum(float n, float f) noexcept {
assert_invariant(f > n);
const float d = 1 / (f - n);
const float A = (f + n) * d;
const float B = -2 * n * f * d;
@@ -835,6 +836,23 @@ size_t ShadowMap::intersectFrustumWithBox(
}
}
/*
* At this point it's possible to have zero vertices (i.e.: no vertices of the box
* are in the frustum and no vertices of the frustum are in the box) but both objects
* have a non-null intersection, e.g.:
*
* +--------+
* | |
* +----|--------|---+
* | | | |
* | | | |
* | | | |
* +----|--------|---+
* | |
* +--------+
*
*/
/*
* It's not enough here to have all 8 vertices, consider this:
*
@@ -859,7 +877,9 @@ size_t ShadowMap::intersectFrustumWithBox(
* However, a special case is if all the vertices of the box are inside the frustum.
*/
if (someFrustumVerticesAreInTheBox || vertexCount < 8) {
if (someFrustumVerticesAreInTheBox && vertexCount >= 8) {
// special case, we don't need to calculate any edge intersections
} else {
// c) intersect scene's volume edges with frustum planes
vertexCount = intersectFrustum(outVertices.data(), vertexCount,
wsSceneReceiversCorners.vertices, wsFrustumCorners);
@@ -26,7 +26,8 @@
#include "details/Texture.h"
#include "details/Material.h"
#include "private/filament/SibGenerator.h"
#include <private/filament/SibStructs.h>
#include "filament/RenderableManager.h"
+1 -1
View File
@@ -36,7 +36,7 @@
#include "details/VertexBuffer.h"
#include "details/View.h"
#include <private/filament/SibGenerator.h>
#include <private/filament/SibStructs.h>
#include <filament/MaterialEnums.h>
+25 -49
View File
@@ -16,24 +16,20 @@
#include "details/Material.h"
#include "DFG.h"
#include "MaterialParser.h"
#include "details/Engine.h"
#include "FilamentAPI-impl.h"
#include <private/filament/SibGenerator.h>
#include <private/filament/UibStructs.h>
#include <private/filament/Variant.h>
#include <private/filament/EngineEnums.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/UniformInterfaceBlock.h>
#include "backend/Program.h"
#include <backend/DriverEnums.h>
#include <backend/Program.h>
#include <utils/CString.h>
#include <utils/Panic.h>
@@ -127,24 +123,6 @@ Material* Material::Builder::build(Engine& engine) {
return upcast(engine).createMaterial(*this);
}
static void addSamplerGroup(Program& pb, BindingPoints bindingPoint,
SamplerInterfaceBlock const& sib, SamplerBindingMap const& map) {
const size_t samplerCount = sib.getSize();
if (samplerCount) {
std::vector<Program::Sampler> samplers(samplerCount);
auto const& list = sib.getSamplerInfoList();
for (size_t i = 0, c = samplerCount; i < c; ++i) {
CString uniformName(
SamplerInterfaceBlock::getUniformName(sib.getName().c_str(),
list[i].name.c_str()));
uint8_t binding = map.getSamplerBinding(bindingPoint, (uint8_t)i);
const bool strict = (bindingPoint == filament::BindingPoints::PER_MATERIAL_INSTANCE);
samplers[i] = { std::move(uniformName), binding, strict };
}
pb.setSamplerGroup(+bindingPoint, sib.getStageFlags(), samplers.data(), samplers.size());
}
}
FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
: mEngine(engine),
mMaterialId(engine.getMaterialId())
@@ -176,16 +154,11 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
// read the uniform binding list
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> uniformBlockBindings;
success = parser->getUniformBlockBindings(&uniformBlockBindings);
success = parser->getUniformBlockBindings(&mUniformBlockBindings);
assert_invariant(success);
success = parser->getSamplerBlockBindings(&mSamplerGroupBindingInfoList, &mSamplerBindingToNameMap);
assert_invariant(success);
// store the name in an array (so we can use pointers), and create the uniform block binding
// list for the backend (to be used with Program)
mUniformBlockNames.reserve(uniformBlockBindings.size());
mUniformBlockBindings.reserve(uniformBlockBindings.size());
for (auto& item : uniformBlockBindings) {
mUniformBlockNames.emplace_back(std::move(item.first));
mUniformBlockBindings.emplace_back(mUniformBlockNames.back().c_str(), item.second);
}
#if FILAMENT_ENABLE_MATDBG
// Register the material with matdbg.
@@ -201,9 +174,6 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
mSubpassInfo.isValid = false;
}
// Populate sampler bindings for the backend that will consume this Material.
mSamplerBindings.populate(&mSamplerInterfaceBlock);
parser->getShading(&mShading);
parser->getMaterialProperties(&mMaterialProperties);
parser->getBlendingMode(&mBlendingMode);
@@ -396,25 +366,11 @@ void FMaterial::getSurfaceProgramSlow(Variant variant) const noexcept {
Variant fragmentVariant = Variant::filterVariantFragment(variant);
Program pb{ getProgramBuilderWithVariants(variant, vertexVariant, fragmentVariant) };
// Always add the morphing sampler group because there is no way
// that SamplerBindingMap knows the current variant.
addSamplerGroup(pb, BindingPoints::PER_RENDERABLE_MORPHING,
SibGenerator::getPerRenderPrimitiveMorphingSib(variant), mSamplerBindings);
addSamplerGroup(pb, BindingPoints::PER_VIEW, SibGenerator::getPerViewSib(variant), mSamplerBindings);
addSamplerGroup(pb, BindingPoints::PER_MATERIAL_INSTANCE, mSamplerInterfaceBlock, mSamplerBindings);
// getSurfaceBindingIndexMap in GLSLPostProcessor.cpp also needs to update if sampler groups are added.
createAndCacheProgram(std::move(pb), variant);
}
void FMaterial::getPostProcessProgramSlow(Variant variant) const noexcept {
Program pb{ getProgramBuilderWithVariants(variant, variant, variant) };
addSamplerGroup(pb, BindingPoints::PER_MATERIAL_INSTANCE, mSamplerInterfaceBlock, mSamplerBindings);
// getPostProcessBindingIndexMap in GLSLPostProcessor.cpp also needs to update if sampler groups are added.
createAndCacheProgram(std::move(pb), variant);
}
@@ -463,6 +419,26 @@ Program FMaterial::getProgramBuilderWithVariants(
<< ", variant=(" << io::hex << variant.key << io::dec << ")";
});
UTILS_NOUNROLL
for (size_t i = 0; i < Enum::count<SamplerBindingPoints>(); i++) {
SamplerBindingPoints bindingPoint = (SamplerBindingPoints)i;
auto const& info = mSamplerGroupBindingInfoList[i];
if (info.count) {
std::array<Program::Sampler, backend::MAX_SAMPLER_COUNT> samplers{};
for (size_t j = 0, c = info.count; j < c; ++j) {
uint8_t binding = info.bindingOffset + j;
samplers[j] = { mSamplerBindingToNameMap[binding], binding };
}
program.setSamplerGroup(+bindingPoint, info.shaderStageFlags,
samplers.data(), info.count);
}
}
program.specializationConstants({
{ 0, (int)mEngine.getSupportedFeatureLevel() },
{ 1, (int)CONFIG_MAX_INSTANCES }
});
return program;
}
+4 -4
View File
@@ -23,7 +23,7 @@
#include <filament/Material.h>
#include <private/filament/SamplerBindingMap.h>
#include <private/filament/SamplerBindingsInfo.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/SubpassInfo.h>
#include <private/filament/Variant.h>
@@ -226,9 +226,9 @@ private:
SamplerInterfaceBlock mSamplerInterfaceBlock;
UniformInterfaceBlock mUniformInterfaceBlock;
SubpassInfo mSubpassInfo;
SamplerBindingMap mSamplerBindings;
utils::FixedCapacityVector<std::pair<const char*, uint8_t>> mUniformBlockBindings;
utils::FixedCapacityVector<utils::CString> mUniformBlockNames;
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> mUniformBlockBindings;
SamplerGroupBindingInfoList mSamplerGroupBindingInfoList;
SamplerBindingToNameMap mSamplerBindingToNameMap;
#if FILAMENT_ENABLE_MATDBG
matdbg::MaterialKey mDebuggerId;
+2 -2
View File
@@ -56,10 +56,10 @@ public:
void use(FEngine::DriverApi& driver) const {
if (mUbHandle) {
driver.bindUniformBuffer(+BindingPoints::PER_MATERIAL_INSTANCE, mUbHandle);
driver.bindUniformBuffer(+UniformBindingPoints::PER_MATERIAL_INSTANCE, mUbHandle);
}
if (mSbHandle) {
driver.bindSamplers(+BindingPoints::PER_MATERIAL_INSTANCE, mSbHandle);
driver.bindSamplers(+SamplerBindingPoints::PER_MATERIAL_INSTANCE, mSbHandle);
}
}
+2 -3
View File
@@ -16,14 +16,13 @@
#include "details/MorphTargetBuffer.h"
#include "private/filament/SibGenerator.h"
#include <private/filament/SibStructs.h>
#include "details/Engine.h"
#include <details/Engine.h>
#include "FilamentAPI-impl.h"
#include <math/mat4.h>
#include <math/norm.h>
namespace filament {
+1 -1
View File
@@ -32,7 +32,7 @@
#include <filament/TextureSampler.h>
#include <filament/View.h>
#include <private/filament/SibGenerator.h>
#include <private/filament/SibStructs.h>
#include <private/filament/UibStructs.h>
#include <utils/Profiler.h>
+3 -3
View File
@@ -453,9 +453,9 @@ private:
void bindPerViewUniformsAndSamplers(FEngine::DriverApi& driver) const noexcept {
mPerViewUniforms.bind(driver);
driver.bindUniformBuffer(+BindingPoints::LIGHTS, mLightUbh);
driver.bindUniformBuffer(+BindingPoints::SHADOW, mShadowMapManager.getShadowUniformsHandle());
driver.bindUniformBuffer(+BindingPoints::FROXEL_RECORDS, mFroxelizer.getRecordBuffer());
driver.bindUniformBuffer(+UniformBindingPoints::LIGHTS, mLightUbh);
driver.bindUniformBuffer(+UniformBindingPoints::SHADOW, mShadowMapManager.getShadowUniformsHandle());
driver.bindUniformBuffer(+UniformBindingPoints::FROXEL_RECORDS, mFroxelizer.getRecordBuffer());
}
// Clean-up the whole history, free all resources. This is typically called when the View is
+2 -2
View File
@@ -738,13 +738,13 @@ TEST(FilamentTest, FroxelData) {
TEST(FilamentTest, GoogleLineDirective) {
{
char s[512] = "#line 10 \"foobar\"";
EXPECT_FALSE(filament::backend::requestsGoogleLineDirectivesExtension(&s[0], strlen(s)));
EXPECT_FALSE(filament::backend::requestsGoogleLineDirectivesExtension({ &s[0], strlen(s) }));
}
{
char s[512] =
"#extension GL_GOOGLE_cpp_style_line_directive : enable\n"
"#line 10 \"foobar\"";
EXPECT_TRUE(filament::backend::requestsGoogleLineDirectivesExtension(&s[0], strlen(s)));
EXPECT_TRUE(filament::backend::requestsGoogleLineDirectivesExtension({ &s[0], strlen(s) }));
}
{
char s[512] =
+4 -4
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.26.0"
spec.version = "1.27.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.26.0/filament-v1.26.0-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.27.0/filament-v1.27.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
@@ -50,13 +50,13 @@ Pod::Spec.new do |spec|
end
spec.subspec "gltfio_core" do |ss|
ss.source_files = "include/gltfio/*.h"
ss.source_files = "include/gltfio/**/*.h"
ss.header_mappings_dir = "include"
ss.vendored_libraries =
"lib/universal/libgltfio_core.a",
"lib/universal/libdracodec.a",
"lib/universal/libuberarchive.a",
"lib/universal/libstb.a"
ss.header_dir = "gltfio"
ss.dependency "Filament/filament"
ss.dependency "Filament/ktxreader"
ss.dependency "Filament/uberz"
@@ -133,7 +133,7 @@ const float kSensitivity = 100.0f;
NameComponentManager* ncm = new NameComponentManager(em);
_assetLoader = AssetLoader::create({_engine, _materialProvider, ncm, &em});
_resourceLoader = new ResourceLoader(
{.engine = _engine, .normalizeSkinningWeights = true, .recomputeBoundingBoxes = false});
{.engine = _engine, .normalizeSkinningWeights = true});
_stbDecoder = createStbProvider(_engine);
_ktxDecoder = createKtx2Provider(_engine);
_resourceLoader->addTextureProvider("image/png", _stbDecoder);
@@ -141,8 +141,7 @@ void App::setupMesh() {
filament::gltfio::ResourceLoader({
.engine = engine,
.normalizeSkinningWeights = true,
.recomputeBoundingBoxes = false
.normalizeSkinningWeights = true
}).loadResources(app.asset);
scene->addEntities(app.asset->getEntities(), app.asset->getEntityCount());
-2
View File
@@ -10,9 +10,7 @@ set(PUBLIC_HDR_DIR include)
file(GLOB_RECURSE PUBLIC_HDRS ${PUBLIC_HDR_DIR}/**/*.h)
set(SRCS
src/SamplerBindingMap.cpp
src/SamplerInterfaceBlock.cpp
src/SibGenerator.cpp
src/UniformInterfaceBlock.cpp
src/Variant.cpp
)
@@ -45,7 +45,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t {
MaterialSpirv = charTo64bitNum("MAT_SPIR"),
MaterialMetal = charTo64bitNum("MAT_METL"),
MaterialShaderModels = charTo64bitNum("MAT_SMDL"),
MaterialSamplerBindings = charTo64bitNum("MAT_SAMP"), // no longer used
MaterialSamplerBindings = charTo64bitNum("MAT_SAMP"),
MaterialUniformBindings = charTo64bitNum("MAT_UNIF"),
MaterialProperties = charTo64bitNum("MAT_PROP"),
@@ -27,7 +27,7 @@
namespace filament {
// update this when a new version of filament wouldn't work with older materials
static constexpr size_t MATERIAL_VERSION = 26;
static constexpr size_t MATERIAL_VERSION = 27;
/**
* Supported shading models
@@ -32,23 +32,31 @@ enum class PostProcessVariant : uint8_t {
TRANSLUCENT
};
// Binding points for uniform buffers and sampler buffers.
// Effectively, these are just names.
enum class BindingPoints : uint8_t {
PER_VIEW = 0, // uniforms/samplers updated per view
PER_RENDERABLE = 1, // uniforms/samplers updated per renderable
// Binding points for uniform buffers
enum class UniformBindingPoints : uint8_t {
PER_VIEW = 0, // uniforms updated per view
PER_RENDERABLE = 1, // uniforms updated per renderable
PER_RENDERABLE_BONES = 2, // bones data, per renderable
PER_RENDERABLE_MORPHING = 3, // morphing uniform/sampler updated per render primitive
LIGHTS = 4, // lights data array
SHADOW = 5, // punctual shadow data
FROXEL_RECORDS = 6,
PER_MATERIAL_INSTANCE = 7, // uniforms/samplers updates per material
PER_MATERIAL_INSTANCE = 7, // uniforms updates per material
// Update utils::Enum::count<>() below when adding values here
// These are limited by CONFIG_BINDING_COUNT (currently 12)
// These are limited by CONFIG_BINDING_COUNT (currently 10)
};
// Binding points for sampler buffers.
enum class SamplerBindingPoints : uint8_t {
PER_VIEW = 0, // samplers updated per view
PER_RENDERABLE_MORPHING = 1, // morphing sampler updated per render primitive
PER_MATERIAL_INSTANCE = 2, // samplers updates per material
// Update utils::Enum::count<>() below when adding values here
// These are limited by CONFIG_SAMPLER_BINDING_COUNT (currently 4)
};
// This value is limited by UBO size, ES3.0 only guarantees 16 KiB.
// Values <= 256, use less CPU and GPU resources.
// It's also limited by the Froxelizer's record buffer data type (uint8_t).
constexpr size_t CONFIG_MAX_LIGHT_COUNT = 256;
constexpr size_t CONFIG_MAX_LIGHT_INDEX = CONFIG_MAX_LIGHT_COUNT - 1;
@@ -61,9 +69,22 @@ constexpr size_t CONFIG_MAX_SHADOW_CASTING_SPOTS = 14;
constexpr size_t CONFIG_MAX_SHADOW_CASCADES = 4;
// The maximum UBO size, in bytes. This value is set to 16 KiB due to the ES3.0 spec.
// Note that this value constrains the maximum number of skinning bones and morph targets.
// Note that this value constrains the maximum number of skinning bones, morph targets,
// instances, and shadow casting spotlights.
constexpr size_t CONFIG_MINSPEC_UBO_SIZE = 16384;
// The maximum number of instances that Filament automatically creates as an optimization.
// Use a much smaller number for WebGL as a workaround for the following Chrome issues:
// https://crbug.com/1348017 Compiling GLSL is very slow with struct arrays
// https://crbug.com/1348363 Lighting looks wrong with D3D11 but not OpenGL
// Note that __EMSCRIPTEN__ is not defined when running matc, but that's okay because we're
// actually using a specification constant.
#if defined(__EMSCRIPTEN__)
constexpr size_t CONFIG_MAX_INSTANCES = 8;
#else
constexpr size_t CONFIG_MAX_INSTANCES = 64;
#endif
// The maximum number of bones that can be associated with a single renderable.
// We store 32 bytes per bone. Must be a power-of-two, and must fit within CONFIG_MINSPEC_UBO_SIZE.
constexpr size_t CONFIG_MAX_BONE_COUNT = 256;
@@ -76,15 +97,16 @@ constexpr size_t CONFIG_MAX_MORPH_TARGET_COUNT = 256;
} // namespace filament
template<>
struct utils::EnableIntegerOperators<filament::BindingPoints> : public std::true_type {};
struct utils::EnableIntegerOperators<filament::UniformBindingPoints> : public std::true_type {};
template<>
struct utils::EnableIntegerOperators<filament::SamplerBindingPoints> : public std::true_type {};
template<>
inline constexpr size_t utils::Enum::count<filament::BindingPoints>() { return 8; }
inline constexpr size_t utils::Enum::count<filament::UniformBindingPoints>() { return 8; }
template<>
inline constexpr size_t utils::Enum::count<filament::SamplerBindingPoints>() { return 3; }
static_assert(utils::Enum::count<filament::BindingPoints>() <= filament::backend::CONFIG_BINDING_COUNT);
static_assert(
filament::BindingPoints::PER_MATERIAL_INSTANCE == utils::Enum::count<filament::BindingPoints>()-1u,
"Dynamically sized sampler buffer must be the last binding point.");
static_assert(utils::Enum::count<filament::UniformBindingPoints>() <= filament::backend::CONFIG_UNIFORM_BINDING_COUNT);
static_assert(utils::Enum::count<filament::SamplerBindingPoints>() <= filament::backend::CONFIG_SAMPLER_BINDING_COUNT);
#endif // TNT_FILAMENT_ENGINE_ENUM_H
@@ -1,56 +0,0 @@
/*
* Copyright (C) 2018 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_DRIVER_SAMPLERBINDINGMAP_H
#define TNT_FILAMENT_DRIVER_SAMPLERBINDINGMAP_H
#include <private/filament/EngineEnums.h>
namespace filament {
class SamplerInterfaceBlock;
// Lookup table from (BlockIndex,LocalOffset) to (GlobalOffset,GroupIndex).
// Also stores the mapping as a flat vector of 4-tuples to make it easy to [de]serialize.
class SamplerBindingMap {
public:
// Assigns a range of finalized binding points to each sampler block.
// If a per-material SIB is provided, then material samplers are also inserted (always at the
// end). The optional material name is used for error reporting only.
void populate(const SamplerInterfaceBlock* perMaterialSib = nullptr,
const char* materialName = nullptr);
// Given a valid Filament binding point and an offset within the block, returns a global unique
// binding index.
uint8_t getSamplerBinding(BindingPoints bindingPoint, uint8_t localOffset) const {
assert_invariant(mSamplerBlockOffsets[+bindingPoint] != UNKNOWN_OFFSET);
return mSamplerBlockOffsets[+bindingPoint] + localOffset;
}
// Gets the global offset of the first sampler in the given sampler block.
uint8_t getBlockOffset(BindingPoints bindingPoint) const {
assert_invariant(mSamplerBlockOffsets[+bindingPoint] != UNKNOWN_OFFSET);
return mSamplerBlockOffsets[+bindingPoint];
}
private:
constexpr static uint8_t UNKNOWN_OFFSET = 0xff;
uint8_t mSamplerBlockOffsets[utils::Enum::count<BindingPoints>()] = { UNKNOWN_OFFSET };
};
} // namespace filament
#endif // TNT_FILAMENT_DRIVER_SAMPLERBINDINGMAP_H
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2022 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_FILABRIDGE_SAMPLERBINDINGS_INFO_H
#define TNT_FILABRIDGE_SAMPLERBINDINGS_INFO_H
#include <backend/DriverEnums.h>
#include <private/filament/EngineEnums.h>
#include <utils/CString.h>
#include <array>
namespace filament {
// binding information about a sampler group
struct SamplerGroupBindingInfo {
constexpr static uint8_t UNKNOWN_OFFSET = 0xff;
// global binding of this block, or UNKNOWN_OFFSET if not used.
uint8_t bindingOffset = UNKNOWN_OFFSET;
// shader stage flags for samplers in this block
backend::ShaderStageFlags shaderStageFlags = backend::ShaderStageFlags::NONE;
// number of samplers in this block. Can be zero.
uint8_t count = 0;
};
// list of binding information for all known binding points
using SamplerGroupBindingInfoList =
std::array<SamplerGroupBindingInfo, utils::Enum::count<SamplerBindingPoints>()>;
// map of sampler shader binding to sampler shader name
using SamplerBindingToNameMap =
std::array<utils::CString, backend::MAX_SAMPLER_COUNT>;
} // namespace filament
#endif //TNT_FILABRIDGE_SAMPLERBINDINGS_INFO_H
@@ -48,12 +48,13 @@ public:
using SamplerParams = backend::SamplerParams;
struct SamplerInfo { // NOLINT(cppcoreguidelines-pro-type-member-init)
utils::CString name; // name of this sampler
uint8_t offset; // offset in "Sampler" of this sampler in the buffer
Type type; // type of this sampler
Format format; // format of this sampler
Precision precision; // precision of this sampler
bool multisample; // multisample capable
utils::CString name; // name of this sampler
utils::CString uniformName; // name of the uniform holding this sampler (needed for glsl)
uint8_t offset; // offset in "Sampler" of this sampler in the buffer
Type type; // type of this sampler
Format format; // format of this sampler
Precision precision; // precision of this sampler
bool multisample; // multisample capable
};
class Builder {
@@ -114,11 +115,12 @@ public:
bool isEmpty() const noexcept { return mSamplersInfoList.empty(); }
static utils::CString getUniformName(const char* group, const char* sampler) noexcept;
static utils::CString generateUniformName(const char* group, const char* sampler) noexcept;
private:
friend class Builder;
explicit SamplerInterfaceBlock(Builder const& builder) noexcept;
utils::CString mName;
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2015 The Android Open Source Project
* Copyright (C) 2022 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.
@@ -14,28 +14,14 @@
* limitations under the License.
*/
#ifndef TNT_FILABRIDGE_SIBGENERATOR_H
#define TNT_FILABRIDGE_SIBGENERATOR_H
#include <private/filament/Variant.h>
#ifndef TNT_FILABRIDGE_SIBSTRUCTS_H
#define TNT_FILABRIDGE_SIBSTRUCTS_H
#include <stdint.h>
#include <stddef.h>
#include "EngineEnums.h"
namespace filament {
class SamplerInterfaceBlock;
class SibGenerator {
public:
static SamplerInterfaceBlock const& getPerViewSib(Variant variant) noexcept;
static SamplerInterfaceBlock const& getPerRenderPrimitiveMorphingSib(Variant variant) noexcept;
static SamplerInterfaceBlock const* getSib(filament::BindingPoints bindingPoint, Variant variant) noexcept;
// When adding a sampler block here, make sure to also update
// FMaterial::getSurfaceProgramSlow and FMaterial::getPostProcessProgramSlow if needed
};
struct PerViewSib {
// indices of each sampler in this SamplerInterfaceBlock (see: getPerViewSib())
static constexpr size_t SHADOW_MAP = 0; // user defined (1024x1024) DEPTH, array
@@ -56,5 +42,6 @@ struct PerRenderPrimitiveMorphingSib {
static constexpr size_t SAMPLER_COUNT = 2;
};
}
#endif // TNT_FILABRIDGE_SIBGENERATOR_H
} // namespace filament
#endif //TNT_FILABRIDGE_SIBSTRUCTS_H
@@ -210,10 +210,10 @@ static_assert(sizeof(PerRenderableData) == 256,
struct alignas(256) PerRenderableUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
static constexpr std::string_view _name{ "ObjectUniforms" };
PerRenderableData data[64];
PerRenderableData data[CONFIG_MAX_INSTANCES];
};
// PerRenderableUib must have an alignment of 256 to be compatible with all versions of GLES.
static_assert(sizeof(PerRenderableUib) <= 16384,
static_assert(sizeof(PerRenderableUib) <= CONFIG_MINSPEC_UBO_SIZE,
"PerRenderableUib exceeds max UBO size");
// ------------------------------------------------------------------------------------------------
@@ -259,7 +259,7 @@ struct ShadowUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
};
ShadowData shadows[CONFIG_MAX_SHADOW_CASTING_SPOTS];
};
static_assert(sizeof(ShadowUib) <= 16384,
static_assert(sizeof(ShadowUib) <= CONFIG_MINSPEC_UBO_SIZE,
"ShadowUib exceeds max UBO size");
// ------------------------------------------------------------------------------------------------
@@ -43,6 +43,7 @@ public:
backend::Precision precision{};
std::string_view structName{};
uint32_t stride{};
std::string_view sizeName{};
};
UniformInterfaceBlock();
@@ -66,6 +67,7 @@ public:
uint32_t size; // size of the array in elements, or 0 if not an array
Precision precision; // precision of this uniform
utils::CString structName; // name of this uniform structure if type is STRUCT
utils::CString sizeName; // name of the size parameter in the shader
// returns offset in bytes of this uniform (at index if an array)
inline size_t getBufferOffset(size_t index = 0) const {
assert_invariant(index < std::max(1u, size));
@@ -46,7 +46,7 @@ SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(
std::string_view samplerName, Type type, Format format,
Precision precision, bool multisample) noexcept {
mEntries.push_back({
{ samplerName.data(), samplerName.size() },
{ samplerName.data(), samplerName.size() }, { },
uint8_t(mEntries.size()), type, format, precision, multisample });
return *this;
}
@@ -84,6 +84,7 @@ SamplerInterfaceBlock::SamplerInterfaceBlock(Builder const& builder) noexcept
assert_invariant(i == e.offset);
SamplerInfo& info = samplersInfoList[i++];
info = e;
info.uniformName = generateUniformName(mName.c_str(), e.name.c_str());
infoMap[{ info.name.data(), info.name.size() }] = info.offset; // info.name.c_str() guaranteed constant
}
assert_invariant(i == samplersInfoList.size());
@@ -97,7 +98,7 @@ const SamplerInterfaceBlock::SamplerInfo* SamplerInterfaceBlock::getSamplerInfo(
return &mSamplersInfoList[pos->second];
}
utils::CString SamplerInterfaceBlock::getUniformName(const char* group, const char* sampler) noexcept {
utils::CString SamplerInterfaceBlock::generateUniformName(const char* group, const char* sampler) noexcept {
char uniformName[256];
// sampler interface block name
@@ -41,7 +41,9 @@ UniformInterfaceBlock::Builder& UniformInterfaceBlock::Builder::add(
mEntries.push_back({
{ item.name.data(), item.name.size() },
0, uint8_t(item.stride), item.type, item.size, item.precision,
{ item.structName.data(), item.structName.size() }});
{ item.structName.data(), item.structName.size() },
{ item.sizeName.data(), item.sizeName.size() }
});
}
return *this;
}
@@ -81,7 +83,7 @@ UniformInterfaceBlock::UniformInterfaceBlock(Builder const& builder) noexcept
offset += padding;
UniformInfo& info = uniformsInfoList[i];
info = { e.name, offset, stride, e.type, e.size, e.precision, e.structName };
info = { e.name, offset, stride, e.type, e.size, e.precision, e.structName, e.sizeName };
// record this uniform info
infoMap[{ info.name.data(), info.name.size() }] = i;
+2
View File
@@ -43,6 +43,8 @@ set(COMMON_SRCS
src/Includes.cpp
src/MaterialBuilder.cpp
src/MaterialVariants.cpp
src/SamplerBindingMap.cpp
src/SibGenerator.cpp
src/UibGenerator.cpp
)
+24 -3
View File
@@ -53,6 +53,8 @@ public:
/**
* High-level hint that works in concert with TargetApi to determine the shader models (used to
* generate GLSL) and final output representations (spirv and/or text).
* When generating the GLSL this is used to differentiate OpenGL from OpenGLES, it is also
* used to make some performance adjustments.
*/
enum class Platform {
DESKTOP,
@@ -60,6 +62,10 @@ public:
ALL
};
/**
* TargetApi defines which language after transpilation will be used, it is used to
* account for some differences between these languages when generating the GLSL.
*/
enum class TargetApi : uint8_t {
OPENGL = 0x01u,
VULKAN = 0x02u,
@@ -67,8 +73,23 @@ public:
ALL = OPENGL | VULKAN | METAL
};
/*
* Generally we generate GLSL that will be converted to SPIRV, optimized and then
* transpiled to the backend's language such as MSL, ESSL300, ESSL410 or SPIRV, in this
* case the generated GLSL uses ESSL310 or ESSL450 and has Vulkan semantics and
* TargetLanguage::SPIRV must be used.
*
* However, in some cases (e.g. when no optimization is asked) we generate the *final* GLSL
* directly, this GLSL must be ESSL300 or ESSL410 and cannot use any Vulkan syntax, for this
* situation we use TargetLanguage::GLSL. In this case TargetApi is guaranteed to be OPENGL.
*
* Note that TargetLanguage::GLSL is not the common case, as it is generally note used in
* release builds.
*
* Also note that glslang performs semantics analysis on whichever GLSL ends up being generated.
*/
enum class TargetLanguage {
GLSL, // GLSL with OpenGL semantics
GLSL, // GLSL with OpenGL 4.1 / OpenGL ES 3.0 semantics
SPIRV // GLSL with Vulkan semantics
};
@@ -106,7 +127,7 @@ protected:
bool mGenerateDebugInfo = false;
utils::bitset32 mShaderModels;
struct CodeGenParams {
int shaderModel;
ShaderModel shaderModel;
TargetApi targetApi;
TargetLanguage targetLanguage;
};
@@ -114,7 +135,7 @@ protected:
// For finding properties and running semantic analysis, we always use the same code gen
// permutation. This is the first permutation generated with default arguments passed to matc.
const CodeGenParams mSemanticCodeGenParams = {
.shaderModel = (int) ShaderModel::MOBILE,
.shaderModel = ShaderModel::MOBILE,
.targetApi = TargetApi::OPENGL,
.targetLanguage = TargetLanguage::SPIRV
};
+36 -35
View File
@@ -29,7 +29,7 @@
#include <filament/MaterialEnums.h>
#include <private/filament/Variant.h>
#include <private/filament/SibGenerator.h>
#include "SibGenerator.h"
#include <utils/Log.h>
@@ -55,15 +55,10 @@ UTILS_NOINLINE
static void generateBindingIndexMap(const GLSLPostProcessor::Config& config,
SamplerInterfaceBlock const& sib, BindingIndexMap& map) {
const auto stageFlags = sib.getStageFlags();
if (hasShaderType(stageFlags, config.shaderType)) {
const auto& infoList = sib.getSamplerInfoList();
for (const auto& info: infoList) {
auto uniformName = SamplerInterfaceBlock::getUniformName(
sib.getName().c_str(), info.name.c_str());
map[uniformName.c_str()] = map.size();
map[info.uniformName.c_str()] = map.size();
}
}
}
@@ -73,9 +68,9 @@ static BindingIndexMap getBindingIndexMap(const GLSLPostProcessor::Config& confi
switch (config.domain) {
case MaterialDomain::SURFACE:
UTILS_NOUNROLL
for (size_t blockIndex = 0; blockIndex < Enum::count<BindingPoints>(); blockIndex++) {
if (blockIndex != BindingPoints::PER_MATERIAL_INSTANCE) {
auto const* sib = SibGenerator::getSib(BindingPoints(blockIndex), config.variant);
for (size_t blockIndex = 0; blockIndex < Enum::count<SamplerBindingPoints>(); blockIndex++) {
if (blockIndex != SamplerBindingPoints::PER_MATERIAL_INSTANCE) {
auto const* sib = SibGenerator::getSib(SamplerBindingPoints(blockIndex), config.variant);
if (sib) {
generateBindingIndexMap(config, *sib, map);
}
@@ -220,14 +215,9 @@ void GLSLPostProcessor::spirvToToMsl(const SpirvBlob *spirv, std::string *outMsl
bool GLSLPostProcessor::process(const std::string& inputShader, Config const& config,
std::string* outputGlsl, SpirvBlob* outputSpirv, std::string* outputMsl) {
using TargetLanguage = MaterialBuilder::TargetLanguage;
// If TargetApi is Vulkan, then we need post-processing even if there's no optimization.
// If we're using framebuffer fetch, we also need to force our compilation target to Vulkan, as
// it is only supported in GLSL for Vulkan.
using TargetApi = MaterialBuilder::TargetApi;
const TargetApi targetApi = (outputSpirv || config.hasFramebufferFetch) ? TargetApi::VULKAN :
TargetApi::OPENGL;
if (targetApi == TargetApi::OPENGL && mOptimization == MaterialBuilder::Optimization::NONE) {
if (config.targetLanguage == TargetLanguage::GLSL) {
*outputGlsl = inputShader;
if (mPrintShaders) {
slog.i << *outputGlsl << io::endl;
@@ -235,16 +225,19 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co
return true;
}
InternalConfig internalConfig;
InternalConfig internalConfig{
.glslOutput = outputGlsl,
.spirvOutput = outputSpirv,
.mslOutput = outputMsl,
};
internalConfig.glslOutput = outputGlsl;
internalConfig.spirvOutput = outputSpirv;
internalConfig.mslOutput = outputMsl;
if (config.shaderType == backend::VERTEX) {
internalConfig.shLang = EShLangVertex;
} else {
internalConfig.shLang = EShLangFragment;
switch (config.shaderType) {
case VERTEX:
internalConfig.shLang = EShLangVertex;
break;
case FRAGMENT:
internalConfig.shLang = EShLangFragment;
break;
}
TProgram program;
@@ -257,9 +250,18 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co
tShader.setStrings(&shaderCString, 1);
internalConfig.langVersion = GLSLTools::glslangVersionFromShaderModel(config.shaderModel);
GLSLTools::prepareShaderParser(targetApi, tShader, internalConfig.shLang,
internalConfig.langVersion, mOptimization);
EShMessages msg = GLSLTools::glslangFlagsFromTargetApi(targetApi);
GLSLTools::prepareShaderParser(config.targetApi, config.targetLanguage, tShader,
internalConfig.shLang, internalConfig.langVersion);
EShMessages msg = GLSLTools::glslangFlagsFromTargetApi(config.targetApi, config.targetLanguage);
if (config.hasFramebufferFetch) {
// FIXME: subpasses require EShMsgVulkanRules, which I think is a mistake.
// SpvRules should be enough.
// I think this could cause the compilation to fail on gl_VertexID.
using Type = std::underlying_type_t<EShMessages>;
msg = EShMessages(Type(msg) | Type(EShMessages::EShMsgVulkanRules));
}
bool ok = tShader.parse(&DefaultTBuiltInResource, internalConfig.langVersion, false, msg);
if (!ok) {
slog.e << tShader.getInfoLog() << io::endl;
@@ -325,15 +327,14 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co
void GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader,
GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const {
using TargetApi = MaterialBuilder::TargetApi;
assert_invariant(bool(internalConfig.spirvOutput) == (config.targetApi != TargetApi::OPENGL));
std::string glsl;
TShader::ForbidIncluder forbidIncluder;
int version = GLSLTools::glslangVersionFromShaderModel(config.shaderModel);
const TargetApi targetApi = internalConfig.spirvOutput ? TargetApi::VULKAN : TargetApi::OPENGL;
EShMessages msg = GLSLTools::glslangFlagsFromTargetApi(targetApi);
const int version = GLSLTools::glslangVersionFromShaderModel(config.shaderModel);
EShMessages msg = GLSLTools::glslangFlagsFromTargetApi(config.targetApi, config.targetLanguage);
bool ok = tShader.preprocess(&DefaultTBuiltInResource, version, ENoProfile, false, false,
msg, &glsl, forbidIncluder);
@@ -351,8 +352,8 @@ void GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader,
const char* shaderCString = glsl.c_str();
spirvShader.setStrings(&shaderCString, 1);
GLSLTools::prepareShaderParser(targetApi, spirvShader,
internalConfig.shLang, internalConfig.langVersion, mOptimization);
GLSLTools::prepareShaderParser(config.targetApi, config.targetLanguage, spirvShader,
internalConfig.shLang, internalConfig.langVersion);
ok = spirvShader.parse(&DefaultTBuiltInResource, internalConfig.langVersion, false, msg);
program.addShader(&spirvShader);
// Even though we only have a single shader stage, linking is still necessary to finalize
+1
View File
@@ -52,6 +52,7 @@ public:
struct Config {
filament::Variant variant;
MaterialBuilder::TargetApi targetApi;
MaterialBuilder::TargetLanguage targetLanguage;
filament::backend::ShaderType shaderType;
filament::backend::ShaderModel shaderModel;
filament::MaterialDomain domain;
+36 -27
View File
@@ -29,7 +29,7 @@
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/UibStructs.h>
#include <private/filament/SibGenerator.h>
#include "SibGenerator.h"
#include "MaterialVariants.h"
@@ -97,19 +97,19 @@ void MaterialBuilderBase::prepare(bool vulkanSemantics) {
// Build a list of codegen permutations, which is useful across all types of material builders.
static_assert(filament::backend::SHADER_MODEL_COUNT == 2);
for (const auto shaderModel : {ShaderModel::MOBILE, ShaderModel::DESKTOP}) {
for (const auto shaderModel: { ShaderModel::MOBILE, ShaderModel::DESKTOP }) {
const auto i = static_cast<uint8_t>(shaderModel);
if (!mShaderModels.test(i)) {
continue; // skip this shader model since it was not requested.
}
if (any(mTargetApi & TargetApi::OPENGL)) {
mCodeGenPermutations.push_back({i, TargetApi::OPENGL, glTargetLanguage});
mCodeGenPermutations.push_back({shaderModel, TargetApi::OPENGL, glTargetLanguage});
}
if (any(mTargetApi & TargetApi::VULKAN)) {
mCodeGenPermutations.push_back({i, TargetApi::VULKAN, TargetLanguage::SPIRV});
mCodeGenPermutations.push_back({shaderModel, TargetApi::VULKAN, TargetLanguage::SPIRV});
}
if (any(mTargetApi & TargetApi::METAL)) {
mCodeGenPermutations.push_back({i, TargetApi::METAL, TargetLanguage::SPIRV});
mCodeGenPermutations.push_back({shaderModel, TargetApi::METAL, TargetLanguage::SPIRV});
}
}
}
@@ -517,8 +517,9 @@ bool MaterialBuilder::findProperties(filament::backend::ShaderType type,
std::string shaderCodeAllProperties = peek(type, mSemanticCodeGenParams, allProperties);
// Populate mProperties with the properties set in the shader.
if (!glslTools.findProperties(type, shaderCodeAllProperties, mProperties,
mSemanticCodeGenParams.targetApi,
ShaderModel(mSemanticCodeGenParams.shaderModel))) {
mSemanticCodeGenParams.targetApi,
mSemanticCodeGenParams.targetLanguage,
mSemanticCodeGenParams.shaderModel)) {
return false;
}
return true;
@@ -561,6 +562,7 @@ bool MaterialBuilder::runSemanticAnalysis(MaterialInfo const& info) noexcept {
GLSLTools glslTools;
TargetApi targetApi = mSemanticCodeGenParams.targetApi;
TargetLanguage targetLanguage = mSemanticCodeGenParams.targetLanguage;
assertSingleTargetApi(targetApi);
if (mEnableFramebufferFetch) {
@@ -570,12 +572,13 @@ bool MaterialBuilder::runSemanticAnalysis(MaterialInfo const& info) noexcept {
ShaderModel model = static_cast<ShaderModel>(mSemanticCodeGenParams.shaderModel);
std::string shaderCode = peek(ShaderType::VERTEX, mSemanticCodeGenParams, mProperties);
bool result = glslTools.analyzeVertexShader(shaderCode, model, mMaterialDomain, targetApi, info);
bool result = glslTools.analyzeVertexShader(shaderCode, model, mMaterialDomain,
targetApi, targetLanguage, info);
if (!result) return false;
shaderCode = peek(ShaderType::FRAGMENT, mSemanticCodeGenParams, mProperties);
result = glslTools.analyzeFragmentShader(shaderCode, model, mMaterialDomain, targetApi,
mCustomSurfaceShading, info);
result = glslTools.analyzeFragmentShader(shaderCode, model, mMaterialDomain,
targetApi, targetLanguage, mCustomSurfaceShading, info);
return result;
#else
return true;
@@ -747,6 +750,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
GLSLPostProcessor::Config config{
.variant = v.variant,
.targetApi = targetApi,
.targetLanguage = targetLanguage,
.shaderType = v.stage,
.shaderModel = shaderModel,
.domain = mMaterialDomain,
@@ -972,7 +976,7 @@ Package MaterialBuilder::build(JobSystem& jobSystem) noexcept {
return Package::invalidPackage();
}
info.samplerBindings.populate(&info.sib, mMaterialName.c_str());
info.samplerBindings.init(mMaterialDomain, &info.sib, mMaterialName.c_str());
// Create chunk tree.
ChunkContainer container;
@@ -1030,7 +1034,7 @@ std::string MaterialBuilder::peek(filament::backend::ShaderType type,
MaterialInfo info;
prepareToBuild(info);
info.samplerBindings.populate(&info.sib, mMaterialName.c_str());
info.samplerBindings.init(mMaterialDomain, &info.sib, mMaterialName.c_str());
if (type == filament::backend::ShaderType::VERTEX) {
return sg.createVertexProgram(ShaderModel(params.shaderModel),
@@ -1047,35 +1051,37 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo&
container.addSimpleChild<const char*>(ChunkType::MaterialName, mMaterialName.c_str_safe());
container.addSimpleChild<uint32_t>(ChunkType::MaterialShaderModels, mShaderModels.getValue());
container.addSimpleChild<uint8_t>(ChunkType::MaterialDomain, static_cast<uint8_t>(mMaterialDomain));
container.addSimpleChild<uint8_t>(ChunkType::MaterialRefraction, static_cast<uint8_t>(mRefractionMode));
container.addSimpleChild<uint8_t>(ChunkType::MaterialRefractionType, static_cast<uint8_t>(mRefractionType));
// note: this chunk is only needed for OpenGL backends, which don't all support layout(binding=)
using namespace filament;
utils::FixedCapacityVector<std::pair<std::string_view, BindingPoints>> list = {
{ PerViewUib::_name, BindingPoints::PER_VIEW },
{ PerRenderableUib::_name, BindingPoints::PER_RENDERABLE },
{ LightsUib::_name, BindingPoints::LIGHTS },
{ ShadowUib::_name, BindingPoints::SHADOW },
{ FroxelRecordUib::_name, BindingPoints::FROXEL_RECORDS },
{ PerRenderableBoneUib::_name, BindingPoints::PER_RENDERABLE_BONES },
{ PerRenderableMorphingUib::_name, BindingPoints::PER_RENDERABLE_MORPHING },
{ info.uib.getName(), BindingPoints::PER_MATERIAL_INSTANCE }
utils::FixedCapacityVector<std::pair<std::string_view, UniformBindingPoints>> list = {
{ PerViewUib::_name, UniformBindingPoints::PER_VIEW },
{ PerRenderableUib::_name, UniformBindingPoints::PER_RENDERABLE },
{ LightsUib::_name, UniformBindingPoints::LIGHTS },
{ ShadowUib::_name, UniformBindingPoints::SHADOW },
{ FroxelRecordUib::_name, UniformBindingPoints::FROXEL_RECORDS },
{ PerRenderableBoneUib::_name, UniformBindingPoints::PER_RENDERABLE_BONES },
{ PerRenderableMorphingUib::_name, UniformBindingPoints::PER_RENDERABLE_MORPHING },
{ info.uib.getName(), UniformBindingPoints::PER_MATERIAL_INSTANCE }
};
container.addChild<MaterialUniformBlockBindingsChunk>(std::move(list));
// UIB
// note: this chunk is needed for Vulkan and GL backends. Metal shouldn't need it (but
// still does as of now).
container.addChild<MaterialSamplerBlockBindingChunk>(info.samplerBindings);
// User Material UIB
container.addChild<MaterialUniformInterfaceBlockChunk>(info.uib);
// SIB
// User Material SIB
container.addChild<MaterialSamplerInterfaceBlockChunk>(info.sib);
// Subpass
// User Subpass
container.addChild<MaterialSubpassInterfaceBlockChunk>(info.subpass);
container.addSimpleChild<bool>(ChunkType::MaterialDoubleSidedSet, mDoubleSidedCapability);
container.addSimpleChild<bool>(ChunkType::MaterialDoubleSided, mDoubleSided);
container.addSimpleChild<uint8_t>(ChunkType::MaterialBlendingMode, static_cast<uint8_t>(mBlendingMode));
container.addSimpleChild<uint8_t>(ChunkType::MaterialTransparencyMode, static_cast<uint8_t>(mTransparencyMode));
container.addSimpleChild<uint8_t>(ChunkType::MaterialReflectionMode, static_cast<uint8_t>(mReflectionMode));
@@ -1087,6 +1093,7 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo&
container.addSimpleChild<uint8_t>(ChunkType::MaterialCullingMode, static_cast<uint8_t>(mCullingMode));
uint64_t properties = 0;
UTILS_NOUNROLL
for (size_t i = 0; i < MATERIAL_PROPERTIES_COUNT; i++) {
if (mProperties[i]) {
properties |= uint64_t(1u) << i;
@@ -1106,6 +1113,8 @@ void MaterialBuilder::writeSurfaceChunks(ChunkContainer& container) const noexce
container.addSimpleChild<bool>(ChunkType::MaterialShadowMultiplier, mShadowMultiplier);
}
container.addSimpleChild<uint8_t>(ChunkType::MaterialRefraction, static_cast<uint8_t>(mRefractionMode));
container.addSimpleChild<uint8_t>(ChunkType::MaterialRefractionType, static_cast<uint8_t>(mRefractionType));
container.addSimpleChild<bool>(ChunkType::MaterialClearCoatIorChange, mClearCoatIorChange);
container.addSimpleChild<uint32_t>(ChunkType::MaterialRequiredAttributes, mRequiredAttributes.getValue());
container.addSimpleChild<bool>(ChunkType::MaterialSpecularAntiAliasing, mSpecularAntiAliasing);
@@ -14,11 +14,14 @@
* limitations under the License.
*/
#include "private/filament/SamplerBindingMap.h"
#include "SamplerBindingMap.h"
#include "private/filament/SamplerInterfaceBlock.h"
#include "SibGenerator.h"
#include <private/filament/SibGenerator.h>
#include <filament/MaterialEnums.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/SibStructs.h>
#include <backend/DriverEnums.h>
@@ -45,36 +48,58 @@ static const char* to_string(ShaderStageFlags stageFlags) noexcept {
return nullptr;
}
void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib,
const char* materialName) {
// TODO: Calculate SamplerBindingMap with a material variant.
// The dummy variant isn't enough for calculating the binding map.
// The material variant affects sampler bindings.
const Variant dummyVariant{};
void SamplerBindingMap::init(MaterialDomain materialDomain,
const SamplerInterfaceBlock* perMaterialSib, const char* materialName) {
assert_invariant(mActiveSamplerCount == 0);
// Note: the material variant affects only the sampler types, but cannot affect
// the actual bindings. For this reason it is okay to use the dummyVariant here.
uint8_t offset = 0;
size_t vertexSamplerCount = 0;
size_t fragmentSamplerCount = 0;
UTILS_NOUNROLL
for (size_t blockIndex = 0; blockIndex < Enum::count<BindingPoints>(); blockIndex++) {
mSamplerBlockOffsets[blockIndex] = offset;
auto processSamplerGroup = [&](SamplerBindingPoints bindingPoint){
SamplerInterfaceBlock const* const sib =
(blockIndex == BindingPoints::PER_MATERIAL_INSTANCE) ?
perMaterialSib : SibGenerator::getSib(BindingPoints(blockIndex), dummyVariant);
(bindingPoint == SamplerBindingPoints::PER_MATERIAL_INSTANCE) ?
perMaterialSib : SibGenerator::getSib(bindingPoint, {});
if (sib) {
const auto& sibFields = sib->getSamplerInfoList();
const auto stageFlags = sib->getStageFlags();
const size_t samplerCount = sibFields.size();
offset += samplerCount;
auto const& list = sib->getSamplerInfoList();
const size_t samplerCount = list.size();
if (any(stageFlags & ShaderStageFlags::VERTEX)) {
vertexSamplerCount += samplerCount;
}
if (any(stageFlags & ShaderStageFlags::FRAGMENT)) {
fragmentSamplerCount += samplerCount;
}
mSamplerBlockOffsets[+bindingPoint] = { offset, stageFlags, uint8_t(samplerCount) };
for (size_t i = 0; i < samplerCount; i++) {
assert_invariant(mSamplerNamesBindingMap[offset + i].empty());
mSamplerNamesBindingMap[offset + i] = list[i].uniformName;
}
offset += samplerCount;
}
};
switch(materialDomain) {
case MaterialDomain::SURFACE:
UTILS_NOUNROLL
for (size_t i = 0; i < Enum::count<SamplerBindingPoints>(); i++) {
processSamplerGroup((SamplerBindingPoints)i);
}
break;
case MaterialDomain::POST_PROCESS:
processSamplerGroup(SamplerBindingPoints::PER_MATERIAL_INSTANCE);
break;
}
mActiveSamplerCount = offset;
// TODO: update this check for feature level 2
const bool isOverflow = vertexSamplerCount > MAX_VERTEX_SAMPLER_COUNT ||
fragmentSamplerCount > MAX_FRAGMENT_SAMPLER_COUNT;
@@ -90,10 +115,10 @@ void SamplerBindingMap::populate(const SamplerInterfaceBlock* perMaterialSib,
UTILS_NOUNROLL
for (size_t blockIndex = 0; blockIndex < Enum::count<BindingPoints>(); blockIndex++) {
for (size_t blockIndex = 0; blockIndex < Enum::count<SamplerBindingPoints>(); blockIndex++) {
SamplerInterfaceBlock const* const sib =
(blockIndex == BindingPoints::PER_MATERIAL_INSTANCE) ?
perMaterialSib : SibGenerator::getSib(BindingPoints(blockIndex), dummyVariant);
(blockIndex == SamplerBindingPoints::PER_MATERIAL_INSTANCE) ?
perMaterialSib : SibGenerator::getSib(SamplerBindingPoints(blockIndex), {});
if (sib) {
auto const& sibFields = sib->getSamplerInfoList();
auto const stageFlagsAsString = to_string(sib->getStageFlags());
+77
View File
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2018 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_DRIVER_SAMPLERBINDINGMAP_H
#define TNT_FILAMENT_DRIVER_SAMPLERBINDINGMAP_H
#include <private/filament/SamplerBindingsInfo.h>
#include <filament/MaterialEnums.h>
namespace filament {
class SamplerInterfaceBlock;
/*
* SamplerBindingMap maps filament's (BindingPoints, offset) to a global offset.
* This global offset is used in shaders to set the `layout(binding=` of each sampler.
*
* It also keeps a map of global offsets to the sampler name in the shader.
*
* SamplerBindingMap is flattened into the material file and used on the filament side to
* create the backend's programs.
*/
class SamplerBindingMap {
public:
using SamplerGroupBindingInfo = SamplerGroupBindingInfo;
// Initializes the SamplerBindingMap.
// Assigns a range of finalized binding points to each sampler block.
// If a per-material SIB is provided, then material samplers are also inserted (always at the
// end). The optional material name is used for error reporting only.
void init(MaterialDomain materialDomain,
const SamplerInterfaceBlock* perMaterialSib = nullptr,
const char* materialName = nullptr);
SamplerGroupBindingInfo const& getSamplerGroupBindingInfo(SamplerBindingPoints bindingPoint) const noexcept {
return mSamplerBlockOffsets[+bindingPoint];
}
// Gets the global offset of the first sampler in the given sampler block.
inline uint8_t getBlockOffset(SamplerBindingPoints bindingPoint) const noexcept {
assert_invariant(mSamplerBlockOffsets[+bindingPoint].bindingOffset != UNKNOWN_OFFSET);
return getSamplerGroupBindingInfo(bindingPoint).bindingOffset;
}
size_t getActiveSamplerCount() const noexcept {
return mActiveSamplerCount;
}
utils::CString const& getSamplerName(size_t binding) const noexcept {
return mSamplerNamesBindingMap[binding];
}
private:
constexpr static uint8_t UNKNOWN_OFFSET = SamplerGroupBindingInfo::UNKNOWN_OFFSET;
SamplerGroupBindingInfoList mSamplerBlockOffsets{};
SamplerBindingToNameMap mSamplerNamesBindingMap{};
uint8_t mActiveSamplerCount = 0;
};
} // namespace filament
#endif // TNT_FILAMENT_DRIVER_SAMPLERBINDINGMAP_H
@@ -14,12 +14,12 @@
* limitations under the License.
*/
#include "private/filament/SibGenerator.h"
#include "SibGenerator.h"
#include "private/filament/Variant.h"
#include "private/filament/EngineEnums.h"
#include <private/filament/SamplerInterfaceBlock.h>
#include "private/filament/SamplerInterfaceBlock.h"
#include "private/filament/SibStructs.h"
namespace filament {
@@ -97,11 +97,11 @@ SamplerInterfaceBlock const& SibGenerator::getPerRenderPrimitiveMorphingSib(Vari
return sib;
}
SamplerInterfaceBlock const* SibGenerator::getSib(BindingPoints bindingPoint, Variant variant) noexcept {
SamplerInterfaceBlock const* SibGenerator::getSib(SamplerBindingPoints bindingPoint, Variant variant) noexcept {
switch (bindingPoint) {
case BindingPoints::PER_VIEW:
case SamplerBindingPoints::PER_VIEW:
return &getPerViewSib(variant);
case BindingPoints::PER_RENDERABLE_MORPHING:
case SamplerBindingPoints::PER_RENDERABLE_MORPHING:
return &getPerRenderPrimitiveMorphingSib(variant);
default:
return nullptr;
+41
View File
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILABRIDGE_SIBGENERATOR_H
#define TNT_FILABRIDGE_SIBGENERATOR_H
#include "private/filament/Variant.h"
#include <stdint.h>
#include <stddef.h>
#include "private/filament/EngineEnums.h"
namespace filament {
class SamplerInterfaceBlock;
class SibGenerator {
public:
static SamplerInterfaceBlock const& getPerViewSib(Variant variant) noexcept;
static SamplerInterfaceBlock const& getPerRenderPrimitiveMorphingSib(Variant variant) noexcept;
static SamplerInterfaceBlock const* getSib(filament::SamplerBindingPoints bindingPoint, Variant variant) noexcept;
// When adding a sampler block here, make sure to also update
// FMaterial::getSurfaceProgramSlow and FMaterial::getPostProcessProgramSlow if needed
};
} // namespace filament
#endif // TNT_FILABRIDGE_SIBGENERATOR_H
+2 -2
View File
@@ -152,8 +152,8 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept {
UniformInterfaceBlock const& UibGenerator::getPerRenderableUib() noexcept {
static UniformInterfaceBlock uib = UniformInterfaceBlock::Builder()
.name(PerRenderableUib::_name)
.add({{ "data", 64, UniformInterfaceBlock::Type::STRUCT, {},
"PerRenderableData", sizeof(PerRenderableData) }})
.add({{ "data", CONFIG_MAX_INSTANCES, UniformInterfaceBlock::Type::STRUCT, {},
"PerRenderableData", sizeof(PerRenderableData), "CONFIG_MAX_INSTANCES" }})
.build();
return uib;
}
@@ -15,20 +15,26 @@
*/
#include "MaterialInterfaceBlockChunk.h"
#include <utility>
#include "filament/MaterialChunkType.h"
#include "../SamplerBindingMap.h"
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/UniformInterfaceBlock.h>
#include <private/filament/SubpassInfo.h>
#include <utility>
using namespace filament;
namespace filamat {
MaterialUniformInterfaceBlockChunk::MaterialUniformInterfaceBlockChunk(UniformInterfaceBlock& uib) :
MaterialUniformInterfaceBlockChunk::MaterialUniformInterfaceBlockChunk(
UniformInterfaceBlock const& uib) :
Chunk(ChunkType::MaterialUib),
mUib(uib){
mUib(uib) {
}
void MaterialUniformInterfaceBlockChunk::flatten(Flattener &f) {
void MaterialUniformInterfaceBlockChunk::flatten(Flattener& f) {
f.writeString(mUib.getName());
auto uibFields = mUib.getUniformInfoList();
f.writeUint64(uibFields.size());
@@ -40,12 +46,15 @@ void MaterialUniformInterfaceBlockChunk::flatten(Flattener &f) {
}
}
MaterialSamplerInterfaceBlockChunk::MaterialSamplerInterfaceBlockChunk(SamplerInterfaceBlock& sib) :
// ------------------------------------------------------------------------------------------------
MaterialSamplerInterfaceBlockChunk::MaterialSamplerInterfaceBlockChunk(
SamplerInterfaceBlock const& sib) :
Chunk(ChunkType::MaterialSib),
mSib(sib) {
}
void MaterialSamplerInterfaceBlockChunk::flatten(Flattener &f) {
void MaterialSamplerInterfaceBlockChunk::flatten(Flattener& f) {
f.writeString(mSib.getName().c_str());
auto sibFields = mSib.getSamplerInfoList();
f.writeUint64(sibFields.size());
@@ -58,12 +67,14 @@ void MaterialSamplerInterfaceBlockChunk::flatten(Flattener &f) {
}
}
MaterialSubpassInterfaceBlockChunk::MaterialSubpassInterfaceBlockChunk(SubpassInfo& subpass) :
// ------------------------------------------------------------------------------------------------
MaterialSubpassInterfaceBlockChunk::MaterialSubpassInterfaceBlockChunk(SubpassInfo const& subpass) :
Chunk(ChunkType::MaterialSubpass),
mSubpass(subpass) {
}
void MaterialSubpassInterfaceBlockChunk::flatten(Flattener &f) {
void MaterialSubpassInterfaceBlockChunk::flatten(Flattener& f) {
f.writeString(mSubpass.block.c_str());
f.writeUint64(mSubpass.isValid ? 1 : 0); // only ever a single subpass for now
if (mSubpass.isValid) {
@@ -76,8 +87,10 @@ void MaterialSubpassInterfaceBlockChunk::flatten(Flattener &f) {
}
}
// ------------------------------------------------------------------------------------------------
MaterialUniformBlockBindingsChunk::MaterialUniformBlockBindingsChunk(
utils::FixedCapacityVector<std::pair<std::string_view, filament::BindingPoints>> list)
utils::FixedCapacityVector<std::pair<std::string_view, filament::UniformBindingPoints>> list)
: Chunk(ChunkType::MaterialUniformBindings),
mBindingList(std::move(list)) {
}
@@ -90,4 +103,36 @@ void MaterialUniformBlockBindingsChunk::flatten(Flattener& f) {
}
}
// ------------------------------------------------------------------------------------------------
MaterialSamplerBlockBindingChunk::MaterialSamplerBlockBindingChunk(
SamplerBindingMap const& samplerBindings)
: Chunk(ChunkType::MaterialSamplerBindings),
mSamplerBindings(samplerBindings) {
}
void MaterialSamplerBlockBindingChunk::flatten(Flattener& f) {
f.writeUint8(utils::Enum::count<SamplerBindingPoints>());
UTILS_NOUNROLL
for (size_t i = 0; i < utils::Enum::count<SamplerBindingPoints>(); i++) {
SamplerBindingPoints bindingPoint = (SamplerBindingPoints)i;
auto const& bindingInfo = mSamplerBindings.getSamplerGroupBindingInfo(bindingPoint);
f.writeUint8(bindingInfo.bindingOffset);
f.writeUint8((uint8_t)bindingInfo.shaderStageFlags);
f.writeUint8(bindingInfo.count);
}
f.writeUint8(mSamplerBindings.getActiveSamplerCount());
UTILS_UNUSED_IN_RELEASE size_t c = 0;
UTILS_NOUNROLL
for (size_t i = 0; i < backend::MAX_SAMPLER_COUNT; i++) {
auto const& uniformName = mSamplerBindings.getSamplerName(i);
if (!uniformName.empty()) {
f.writeUint8((uint8_t)i);
f.writeString(uniformName.c_str());
c++;
}
}
assert_invariant(c == mSamplerBindings.getActiveSamplerCount());
}
} // namespace filamat
@@ -20,55 +20,80 @@
#include "Chunk.h"
#include <private/filament/EngineEnums.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/UniformInterfaceBlock.h>
#include <private/filament/SubpassInfo.h>
#include <utils/FixedCapacityVector.h>
namespace filament {
class SamplerBindingMap;
class SamplerInterfaceBlock;
class UniformInterfaceBlock;
struct SubpassInfo;
} // namespace filament
namespace filamat {
class MaterialUniformInterfaceBlockChunk final : public Chunk {
public:
explicit MaterialUniformInterfaceBlockChunk(filament::UniformInterfaceBlock& uib);
explicit MaterialUniformInterfaceBlockChunk(filament::UniformInterfaceBlock const& uib);
~MaterialUniformInterfaceBlockChunk() final = default;
private:
void flatten(Flattener &) final;
void flatten(Flattener&) final;
filament::UniformInterfaceBlock& mUib;
filament::UniformInterfaceBlock const& mUib;
};
// ------------------------------------------------------------------------------------------------
class MaterialSamplerInterfaceBlockChunk final : public Chunk {
public:
explicit MaterialSamplerInterfaceBlockChunk(filament::SamplerInterfaceBlock& sib);
explicit MaterialSamplerInterfaceBlockChunk(filament::SamplerInterfaceBlock const& sib);
~MaterialSamplerInterfaceBlockChunk() final = default;
private:
void flatten(Flattener &) final;
void flatten(Flattener&) final;
filament::SamplerInterfaceBlock& mSib;
filament::SamplerInterfaceBlock const& mSib;
};
// ------------------------------------------------------------------------------------------------
class MaterialSubpassInterfaceBlockChunk final : public Chunk {
public:
explicit MaterialSubpassInterfaceBlockChunk(filament::SubpassInfo& subpass);
explicit MaterialSubpassInterfaceBlockChunk(filament::SubpassInfo const& subpass);
~MaterialSubpassInterfaceBlockChunk() final = default;
private:
void flatten(Flattener &) final;
void flatten(Flattener&) final;
filament::SubpassInfo& mSubpass;
filament::SubpassInfo const& mSubpass;
};
// ------------------------------------------------------------------------------------------------
class MaterialUniformBlockBindingsChunk final : public Chunk {
public:
explicit MaterialUniformBlockBindingsChunk(
utils::FixedCapacityVector<std::pair<std::string_view, filament::BindingPoints>> list);
utils::FixedCapacityVector<std::pair<std::string_view, filament::UniformBindingPoints>> list);
~MaterialUniformBlockBindingsChunk() final = default;
private:
void flatten(Flattener&) final;
utils::FixedCapacityVector<std::pair<std::string_view, filament::UniformBindingPoints>> mBindingList;
};
// ------------------------------------------------------------------------------------------------
class MaterialSamplerBlockBindingChunk final : public Chunk {
public:
explicit MaterialSamplerBlockBindingChunk(filament::SamplerBindingMap const& samplerBindings);
~MaterialSamplerBlockBindingChunk() final = default;
private:
void flatten(Flattener &) final;
utils::FixedCapacityVector<std::pair<std::string_view, filament::BindingPoints>> mBindingList;
filament::SamplerBindingMap const& mSamplerBindings;
};
} // namespace filamat
+59 -41
View File
@@ -16,11 +16,7 @@
#include "GLSLTools.h"
#include <cstring>
#include <filament/MaterialEnums.h>
#include <private/filament/UniformInterfaceBlock.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <filamat/MaterialBuilder.h>
#include "../shaders/MaterialInfo.h"
@@ -52,10 +48,10 @@ GLSLangCleaner::~GLSLangCleaner() {
SetThreadPoolAllocator(mAllocator);
}
bool GLSLTools::analyzeFragmentShader(const std::string& shaderCode, ShaderModel model,
MaterialBuilder::MaterialDomain materialDomain,
MaterialBuilder::TargetApi targetApi, bool hasCustomSurfaceShading,
MaterialInfo const& info) const noexcept {
bool GLSLTools::analyzeFragmentShader(const std::string& shaderCode,
filament::backend::ShaderModel model, MaterialBuilder::MaterialDomain materialDomain,
MaterialBuilder::TargetApi targetApi, MaterialBuilder::TargetLanguage targetLanguage,
bool hasCustomSurfaceShading, MaterialInfo const& info) noexcept {
// Parse to check syntax and semantic.
const char* shaderCString = shaderCode.c_str();
@@ -65,7 +61,7 @@ bool GLSLTools::analyzeFragmentShader(const std::string& shaderCode, ShaderModel
GLSLangCleaner cleaner;
int version = glslangVersionFromShaderModel(model);
EShMessages msg = glslangFlagsFromTargetApi(targetApi);
EShMessages msg = glslangFlagsFromTargetApi(targetApi, targetLanguage);
bool ok = tShader.parse(&DefaultTBuiltInResource, version, false, msg);
if (!ok) {
utils::slog.e << "ERROR: Unable to parse fragment shader:" << utils::io::endl;
@@ -141,10 +137,10 @@ bool GLSLTools::analyzeFragmentShader(const std::string& shaderCode, ShaderModel
return true;
}
bool GLSLTools::analyzeVertexShader(const std::string& shaderCode, ShaderModel model,
MaterialBuilder::MaterialDomain materialDomain,
MaterialBuilder::TargetApi targetApi,
MaterialInfo const& info) const noexcept {
bool GLSLTools::analyzeVertexShader(const std::string& shaderCode,
filament::backend::ShaderModel model,
MaterialBuilder::MaterialDomain materialDomain, MaterialBuilder::TargetApi targetApi,
MaterialBuilder::TargetLanguage targetLanguage, MaterialInfo const& info) noexcept {
// TODO: After implementing post-process vertex shaders, properly analyze them here.
if (materialDomain == MaterialBuilder::MaterialDomain::POST_PROCESS) {
@@ -159,7 +155,7 @@ bool GLSLTools::analyzeVertexShader(const std::string& shaderCode, ShaderModel m
GLSLangCleaner cleaner;
int version = glslangVersionFromShaderModel(model);
EShMessages msg = glslangFlagsFromTargetApi(targetApi);
EShMessages msg = glslangFlagsFromTargetApi(targetApi, targetLanguage);
bool ok = tShader.parse(&DefaultTBuiltInResource, version, false, msg);
if (!ok) {
utils::slog.e << "ERROR: Unable to parse vertex shader" << utils::io::endl;
@@ -205,6 +201,7 @@ bool GLSLTools::findProperties(
const std::string& shaderCode,
MaterialBuilder::PropertyList& properties,
MaterialBuilder::TargetApi targetApi,
MaterialBuilder::TargetLanguage targetLanguage,
ShaderModel model) const noexcept {
const char* shaderCString = shaderCode.c_str();
@@ -214,7 +211,7 @@ bool GLSLTools::findProperties(
GLSLangCleaner cleaner;
int version = glslangVersionFromShaderModel(model);
EShMessages msg = glslangFlagsFromTargetApi(targetApi);
EShMessages msg = glslangFlagsFromTargetApi(targetApi, targetLanguage);
const TBuiltInResource* builtins = &DefaultTBuiltInResource;
bool ok = tShader.parse(builtins, version, false, msg);
if (!ok) {
@@ -267,12 +264,7 @@ bool GLSLTools::findPropertyWritesOperations(const std::string& functionName, si
}
std::deque<Symbol> symbols;
bool ok = findSymbolsUsage(functionName, *rootNode, symbols);
if (!ok) {
utils::slog.e << "Unable to trace usage of symbols in function '" << functionName
<< utils::io::endl;
return false;
}
findSymbolsUsage(functionName, *rootNode, symbols);
// Iterate over symbols to see if the parameter we are interested in what written.
std::string parameterName = functionMaterialParameters.at(parameterIdx).name;
@@ -283,7 +275,7 @@ bool GLSLTools::findPropertyWritesOperations(const std::string& functionName, si
}
// This is a direct assignment of the variable. X =
if (symbol.getAccesses().size() == 0) {
if (symbol.getAccesses().empty()) {
continue;
}
@@ -295,7 +287,7 @@ bool GLSLTools::findPropertyWritesOperations(const std::string& functionName, si
void GLSLTools::scanSymbolForProperty(Symbol& symbol,
TIntermNode* rootNode,
MaterialBuilder::PropertyList& properties) const noexcept {
for (Access access : symbol.getAccesses()) {
for (const Access& access : symbol.getAccesses()) {
if (access.type == Access::Type::FunctionCall) {
// Do NOT look into prepareMaterial call.
if (access.string.find("prepareMaterial(struct") != std::string::npos) {
@@ -340,7 +332,7 @@ void GLSLTools::scanSymbolForProperty(Symbol& symbol,
}
bool GLSLTools::findSymbolsUsage(const std::string& functionSignature, TIntermNode& root,
std::deque<Symbol>& symbols) const noexcept {
std::deque<Symbol>& symbols) noexcept {
TIntermNode* functionAST = ASTUtils::getFunctionBySignature(functionSignature, root);
ASTUtils::traceSymbols(*functionAST, symbols);
return true;
@@ -359,27 +351,53 @@ int GLSLTools::glslangVersionFromShaderModel(ShaderModel model) {
return version;
}
EShMessages GLSLTools::glslangFlagsFromTargetApi(MaterialBuilder::TargetApi targetApi) {
EShMessages msg = EShMessages::EShMsgDefault;
if (targetApi == MaterialBuilder::TargetApi::VULKAN) {
msg = (EShMessages) (EShMessages::EShMsgVulkanRules | EShMessages::EShMsgSpvRules);
EShMessages GLSLTools::glslangFlagsFromTargetApi(
MaterialBuilder::TargetApi targetApi,
MaterialBuilder::TargetLanguage targetLanguage) {
using TargetApi = MaterialBuilder::TargetApi;
using TargetLanguage = MaterialBuilder::TargetLanguage;
switch (targetLanguage) {
case TargetLanguage::GLSL:
assert_invariant(targetApi == TargetApi::OPENGL);
return EShMessages::EShMsgDefault;
case TargetLanguage::SPIRV:
// issue messages for SPIR-V generation
using Type = std::underlying_type_t<EShMessages>;
auto msg = (Type)EShMessages::EShMsgSpvRules;
if (targetApi == TargetApi::VULKAN) {
// issue messages for Vulkan-requirements of GLSL for SPIR-V
msg |= (Type)EShMessages::EShMsgVulkanRules;
}
if (targetApi == TargetApi::METAL) {
// FIXME: We have to use EShMsgVulkanRules for metal, otherwise compilation will
// choke on gl_VertexIndex.
msg |= (Type)EShMessages::EShMsgVulkanRules;
}
return (EShMessages)msg;
}
return msg;
}
void GLSLTools::prepareShaderParser(MaterialBuilder::TargetApi targetApi, glslang::TShader& shader,
EShLanguage language, int version, filamat::MaterialBuilder::Optimization optimization) {
// We must only setup the SPIRV environment when we actually need to output SPIRV
if (optimization == filamat::MaterialBuilder::Optimization::SIZE ||
optimization == filamat::MaterialBuilder::Optimization::PERFORMANCE) {
void GLSLTools::prepareShaderParser(MaterialBuilder::TargetApi targetApi,
MaterialBuilder::TargetLanguage targetLanguage, glslang::TShader& shader,
EShLanguage stage, int version) {
// We must only set up the SPIRV environment when we actually need to output SPIRV
if (targetLanguage == MaterialBuilder::TargetLanguage::SPIRV) {
shader.setAutoMapBindings(true);
if (targetApi == MaterialBuilder::TargetApi::VULKAN) {
shader.setEnvInput(EShSourceGlsl, language, EShClientVulkan, version);
shader.setEnvClient(EShClientVulkan, EShTargetVulkan_1_0);
} else {
assert(targetApi == MaterialBuilder::TargetApi::OPENGL);
shader.setEnvInput(EShSourceGlsl, language, EShClientOpenGL, version);
shader.setEnvClient(EShClientOpenGL, EShTargetOpenGL_450);
switch (targetApi) {
case MaterialBuilderBase::TargetApi::OPENGL:
shader.setEnvInput(EShSourceGlsl, stage, EShClientOpenGL, version);
shader.setEnvClient(EShClientOpenGL, EShTargetOpenGL_450);
break;
case MaterialBuilderBase::TargetApi::VULKAN:
case MaterialBuilderBase::TargetApi::METAL:
shader.setEnvInput(EShSourceGlsl, stage, EShClientVulkan, version);
shader.setEnvClient(EShClientVulkan, EShTargetVulkan_1_0);
break;
case MaterialBuilderBase::TargetApi::ALL:
// can't happen
break;
}
shader.setEnvTarget(EShTargetSpv, EShTargetSpv_1_0);
}
+24 -21
View File
@@ -35,7 +35,9 @@ namespace filamat {
// Used for symbol tracking during static code analysis.
struct Access {
enum Type {Swizzling, DirectIndexForStruct, FunctionCall};
enum Type {
Swizzling, DirectIndexForStruct, FunctionCall
};
Type type;
std::string string;
size_t parameterIdx = 0; // Only used when type == FunctionCall;
@@ -49,8 +51,8 @@ struct Access {
// Combinations are possible. e.g: foo(material.baseColor.xyz)
class Symbol {
public:
Symbol() {}
Symbol(const std::string& name) {
Symbol() = default;
explicit Symbol(const std::string& name) {
mName = name;
}
@@ -72,7 +74,7 @@ public:
std::string toString() const {
std::string str(mName);
for (Access access: mAccesses) {
for (const Access& access: mAccesses) {
str += ".";
str += access.string;
}
@@ -80,7 +82,7 @@ public:
}
bool hasDirectIndexForStruct() const noexcept {
for (Access access : mAccesses) {
for (const Access& access : mAccesses) {
if (access.type == Access::Type::DirectIndexForStruct) {
return true;
}
@@ -88,8 +90,8 @@ public:
return false;
}
const std::string getDirectIndexStructName() const noexcept {
for (Access access : mAccesses) {
std::string getDirectIndexStructName() const noexcept {
for (const Access& access : mAccesses) {
if (access.type == Access::Type::DirectIndexForStruct) {
return access.string;
}
@@ -121,17 +123,15 @@ public:
// The shader features a material() function AND
// The shader features a prepareMaterial() function AND
// prepareMaterial() is called at some point in material() call chain.
bool analyzeFragmentShader(const std::string& shaderCode,
filament::backend::ShaderModel model,
MaterialBuilder::MaterialDomain materialDomain,
MaterialBuilder::TargetApi targetApi, bool hasCustomSurfaceShading,
MaterialInfo const& info) const noexcept;
static bool analyzeFragmentShader(const std::string& shaderCode,
filament::backend::ShaderModel model, MaterialBuilder::MaterialDomain materialDomain,
MaterialBuilder::TargetApi targetApi, MaterialBuilder::TargetLanguage targetLanguage,
bool hasCustomSurfaceShading, MaterialInfo const& info) noexcept;
bool analyzeVertexShader(const std::string& shaderCode,
static bool analyzeVertexShader(const std::string& shaderCode,
filament::backend::ShaderModel model,
MaterialBuilder::MaterialDomain materialDomain,
MaterialBuilder::TargetApi targetApi,
MaterialInfo const& info) const noexcept;
MaterialBuilder::MaterialDomain materialDomain, MaterialBuilder::TargetApi targetApi,
MaterialBuilder::TargetLanguage targetLanguage, MaterialInfo const& info) noexcept;
// Public for unit tests.
using Property = MaterialBuilder::Property;
@@ -143,14 +143,17 @@ public:
const std::string& shaderCode,
MaterialBuilder::PropertyList& properties,
MaterialBuilder::TargetApi targetApi = MaterialBuilder::TargetApi::OPENGL,
MaterialBuilder::TargetLanguage targetLanguage = MaterialBuilder::TargetLanguage::GLSL,
ShaderModel model = ShaderModel::DESKTOP) const noexcept;
static int glslangVersionFromShaderModel(filament::backend::ShaderModel model);
static EShMessages glslangFlagsFromTargetApi(MaterialBuilder::TargetApi targetApi);
static EShMessages glslangFlagsFromTargetApi(MaterialBuilder::TargetApi targetApi,
MaterialBuilder::TargetLanguage targetLanguage);
static void prepareShaderParser(MaterialBuilder::TargetApi targetApi, glslang::TShader& shader,
EShLanguage language, int version, MaterialBuilder::Optimization optimization);
static void prepareShaderParser(MaterialBuilder::TargetApi targetApi,
MaterialBuilder::TargetLanguage targetLanguage, glslang::TShader& shader,
EShLanguage stage, int version);
static void textureLodBias(glslang::TShader& shader);
@@ -161,8 +164,8 @@ private:
// in a function call.
// Start in the function matching the signature provided and follow all out and inout calls.
// Does NOT recurse to follow function calls.
bool findSymbolsUsage(const std::string& functionSignature, TIntermNode& root,
std::deque<Symbol>& symbols) const noexcept;
static bool findSymbolsUsage(const std::string& functionSignature, TIntermNode& root,
std::deque<Symbol>& symbols) noexcept;
// Determine how a function affect one of its parameter by following all write and function call
+57 -30
View File
@@ -40,26 +40,26 @@ io::sstream& CodeGenerator::generateSeparator(io::sstream& out) {
}
utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, ShaderType type,
MaterialInfo const& info) const {
MaterialInfo const& material) const {
switch (mShaderModel) {
case ShaderModel::MOBILE:
// Vulkan requires version 310 or higher
if (mTargetLanguage == TargetLanguage::SPIRV ||
info.featureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
material.featureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
// Vulkan requires layout locations on ins and outs, which were not supported
// in the OpenGL 4.1 GLSL profile.
out << "#version 310 es\n\n";
} else {
out << "#version 300 es\n\n";
}
if (info.hasExternalSamplers) {
if (material.hasExternalSamplers) {
out << "#extension GL_OES_EGL_image_external_essl3 : require\n\n";
}
out << "#define TARGET_MOBILE\n";
break;
case ShaderModel::DESKTOP:
if (mTargetLanguage == TargetLanguage::SPIRV ||
info.featureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
material.featureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
// Vulkan requires binding specifiers on uniforms and samplers, which were not
// supported in the OpenGL 4.1 GLSL profile.
out << "#version 450 core\n\n";
@@ -95,17 +95,11 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade
out << "#define FILAMENT_OPENGL_SEMANTICS\n";
}
out << '\n';
out << "#define FILAMENT_FEATURE_LEVEL_1 1\n";
out << "#define FILAMENT_FEATURE_LEVEL_2 2\n";
out << "#define FILAMENT_FEATURE_LEVEL FILAMENT_FEATURE_LEVEL_" << (int)info.featureLevel << "\n";
out << '\n';
if (mTargetApi == TargetApi::VULKAN ||
mTargetApi == TargetApi::METAL ||
(mTargetApi == TargetApi::OPENGL && mShaderModel == ShaderModel::DESKTOP) ||
info.featureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
material.featureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
out << "#define FILAMENT_HAS_FEATURE_TEXTURE_GATHER\n";
}
@@ -118,7 +112,13 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade
out << "precision lowp sampler3D;\n";
}
out << SHADERS_COMMON_TYPES_FS_DATA;
// specification constants
out << '\n';
generateSpecificationConstant(out, "BACKEND_FEATURE_LEVEL", 0, 1);
generateSpecificationConstant(out, "CONFIG_MAX_INSTANCES", 1, (int)CONFIG_MAX_INSTANCES);
out << '\n';
out << SHADERS_COMMON_TYPES_GLSL_DATA;
out << "\n";
return out;
@@ -316,7 +316,7 @@ const char* CodeGenerator::getUniformPrecisionQualifier(UniformType type, Precis
}
io::sstream& CodeGenerator::generateUniforms(io::sstream& out, ShaderType type,
BindingPoints binding, const UniformInterfaceBlock& uib) const {
UniformBindingPoints binding, const UniformInterfaceBlock& uib) const {
auto const& infos = uib.getUniformInfoList();
if (infos.empty()) {
return out;
@@ -329,11 +329,23 @@ io::sstream& CodeGenerator::generateUniforms(io::sstream& out, ShaderType type,
Precision uniformPrecision = getDefaultUniformPrecision();
Precision defaultPrecision = getDefaultPrecision(type);
// This constant must match the equivalent in MetalState.h.
static constexpr uint32_t METAL_UNIFORM_BUFFER_BINDING_START = 17u;
out << "\nlayout(";
// TODO: at feature level 2, GLSL should support the binding qualifier
if (mTargetLanguage == TargetLanguage::SPIRV) {
uint32_t bindingIndex = (uint32_t) binding; // avoid char output
out << "binding = " << bindingIndex << ", ";
const auto bindingIndex = (uint32_t) binding; // avoid char output
switch (mTargetApi) {
case TargetApi::METAL:
out << "binding = " << METAL_UNIFORM_BUFFER_BINDING_START + bindingIndex << ", ";
break;
default:
case TargetApi::VULKAN:
out << "binding = " << bindingIndex << ", ";
break;
}
}
out << "std140) uniform " << blockName << " {\n";
for (auto const& info : infos) {
@@ -344,7 +356,11 @@ io::sstream& CodeGenerator::generateUniforms(io::sstream& out, ShaderType type,
if (precision[0] != '\0') out << " ";
out << type << " " << info.name.c_str();
if (info.size > 0) {
out << "[" << info.size << "]";
if (info.sizeName.empty()) {
out << "[" << info.size << "]";
} else {
out << "[" << info.sizeName.c_str() << "]";
}
}
out << ";\n";
}
@@ -361,11 +377,6 @@ io::sstream& CodeGenerator::generateSamplers(
}
for (auto const& info : infos) {
CString uniformName =
SamplerInterfaceBlock::getUniformName(
sib.getName().c_str(), info.name.c_str());
auto type = info.type;
if (type == SamplerType::SAMPLER_EXTERNAL && mShaderModel != ShaderModel::MOBILE) {
// we're generating the shader for the desktop, where we assume external textures
@@ -392,7 +403,7 @@ io::sstream& CodeGenerator::generateSamplers(
out << ") ";
}
out << "uniform " << precision << " " << typeName << " " << uniformName.c_str();
out << "uniform " << precision << " " << typeName << " " << info.uniformName.c_str();
out << ";\n";
}
out << "\n";
@@ -406,7 +417,7 @@ io::sstream& CodeGenerator::generateSubpass(io::sstream& out, SubpassInfo subpas
}
CString subpassName =
SamplerInterfaceBlock::getUniformName(subpass.block.c_str(), subpass.name.c_str());
SamplerInterfaceBlock::generateUniformName(subpass.block.c_str(), subpass.name.c_str());
char const* const typeName = "subpassInput";
// In our Vulkan backend, subpass inputs always live in descriptor set 2. (ignored for GLES)
@@ -435,18 +446,13 @@ void CodeGenerator::fixupExternalSamplers(
// been swapped during a previous optimization step
for (auto const& info : infos) {
if (info.type == SamplerType::SAMPLER_EXTERNAL) {
CString uniformName =
SamplerInterfaceBlock::getUniformName(
sib.getName().c_str(), info.name.c_str());
auto name = std::string("sampler2D ") + uniformName.c_str();
auto name = std::string("sampler2D ") + info.uniformName.c_str();
size_t index = shader.find(name);
if (index != std::string::npos) {
hasExternalSampler = true;
auto newName =
std::string("samplerExternalOES ") + uniformName.c_str();
std::string("samplerExternalOES ") + info.uniformName.c_str();
shader.replace(index, name.size(), newName);
}
}
@@ -491,6 +497,27 @@ io::sstream& CodeGenerator::generateIndexedDefine(io::sstream& out, const char*
return out;
}
utils::io::sstream& CodeGenerator::generateSpecificationConstant(utils::io::sstream& out,
const char* name, uint32_t id, std::variant<int, float, bool> value) const {
static const char* types[] = { "int", "float", "bool" };
if (mTargetLanguage == MaterialBuilderBase::TargetLanguage::SPIRV) {
std::visit([&](auto&& arg) {
out << "layout (constant_id = " << id << ") const "
<< types[value.index()] << " " << name << " = " << arg << ";\n";
}, value);
} else {
std::visit([&](auto&& arg) {
out << "#ifndef SPIRV_CROSS_CONSTANT_ID_" << id << '\n'
<< "#define SPIRV_CROSS_CONSTANT_ID_" << id << " " << arg << '\n'
<< "#endif" << '\n'
<< "const " << types[value.index()] << " " << name << " = SPIRV_CROSS_CONSTANT_ID_" << id
<< ";\n";
}, value);
}
return out;
}
io::sstream& CodeGenerator::generateMaterialProperty(io::sstream& out,
MaterialBuilder::Property property, bool isSet) {
if (isSet) {
+10 -5
View File
@@ -19,19 +19,21 @@
#include <iosfwd>
#include <string>
#include <variant>
#include <utils/compiler.h>
#include <utils/Log.h>
#include <filamat/MaterialBuilder.h>
#include <backend/DriverEnums.h>
#include <private/filament/EngineEnums.h>
#include <filament/MaterialEnums.h>
#include <private/filament/EngineEnums.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/UniformInterfaceBlock.h>
#include <private/filament/SubpassInfo.h>
#include <backend/DriverEnums.h>
#include <utils/sstream.h>
#include <private/filament/Variant.h>
@@ -61,7 +63,7 @@ public:
// generate prolog for the given shader
utils::io::sstream& generateProlog(utils::io::sstream& out, ShaderType type,
MaterialInfo const& info) const;
MaterialInfo const& material) const;
static utils::io::sstream& generateEpilog(utils::io::sstream& out);
@@ -108,7 +110,7 @@ public:
// generate uniforms
utils::io::sstream& generateUniforms(utils::io::sstream& out, ShaderType type,
filament::BindingPoints binding, const filament::UniformInterfaceBlock& uib) const;
filament::UniformBindingPoints binding, const filament::UniformInterfaceBlock& uib) const;
// generate samplers
utils::io::sstream& generateSamplers(utils::io::sstream& out, uint8_t firstBinding,
@@ -130,6 +132,9 @@ public:
static utils::io::sstream& generateIndexedDefine(utils::io::sstream& out, const char* name,
uint32_t index, uint32_t value);
utils::io::sstream& generateSpecificationConstant(utils::io::sstream& out,
const char* name, uint32_t id, std::variant<int, float, bool> value) const;
static utils::io::sstream& generatePostProcessGetters(utils::io::sstream& out, ShaderType type);
static utils::io::sstream& generateGetters(utils::io::sstream& out, ShaderType type);
static utils::io::sstream& generateParameters(utils::io::sstream& out, ShaderType type);
+4 -1
View File
@@ -18,9 +18,12 @@
#define TNT_FILAMAT_MATERIALINFO_H
#include <backend/DriverEnums.h>
#include "../SamplerBindingMap.h"
#include <filament/MaterialEnums.h>
#include <private/filament/UniformInterfaceBlock.h>
#include <private/filament/SamplerBindingMap.h>
#include <private/filament/SamplerInterfaceBlock.h>
#include <private/filament/SubpassInfo.h>
+22 -22
View File
@@ -19,13 +19,13 @@
#include <filament/MaterialEnums.h>
#include <private/filament/EngineEnums.h>
#include <private/filament/SibGenerator.h>
#include <private/filament/Variant.h>
#include <utils/CString.h>
#include "filamat/MaterialBuilder.h"
#include "CodeGenerator.h"
#include "../SibGenerator.h"
#include "../UibGenerator.h"
#include <iterator>
@@ -266,26 +266,26 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel,
// uniforms
cg.generateUniforms(vs, ShaderType::VERTEX,
BindingPoints::PER_VIEW, UibGenerator::getPerViewUib());
UniformBindingPoints::PER_VIEW, UibGenerator::getPerViewUib());
cg.generateUniforms(vs, ShaderType::VERTEX,
BindingPoints::PER_RENDERABLE, UibGenerator::getPerRenderableUib());
UniformBindingPoints::PER_RENDERABLE, UibGenerator::getPerRenderableUib());
if (variant.hasSkinningOrMorphing()) {
cg.generateUniforms(vs, ShaderType::VERTEX,
BindingPoints::PER_RENDERABLE_BONES,
UniformBindingPoints::PER_RENDERABLE_BONES,
UibGenerator::getPerRenderableBonesUib());
cg.generateUniforms(vs, ShaderType::VERTEX,
BindingPoints::PER_RENDERABLE_MORPHING,
UniformBindingPoints::PER_RENDERABLE_MORPHING,
UibGenerator::getPerRenderableMorphingUib());
cg.generateSamplers(vs,
material.samplerBindings.getBlockOffset(BindingPoints::PER_RENDERABLE_MORPHING),
material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_RENDERABLE_MORPHING),
SibGenerator::getPerRenderPrimitiveMorphingSib(variant));
}
cg.generateUniforms(vs, ShaderType::VERTEX,
BindingPoints::PER_MATERIAL_INSTANCE, material.uib);
UniformBindingPoints::PER_MATERIAL_INSTANCE, material.uib);
CodeGenerator::generateSeparator(vs);
// TODO: should we generate per-view SIB in the vertex shader?
cg.generateSamplers(vs,
material.samplerBindings.getBlockOffset(BindingPoints::PER_MATERIAL_INSTANCE),
material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_MATERIAL_INSTANCE),
material.sib);
// shader code
@@ -461,25 +461,25 @@ std::string ShaderGenerator::createFragmentProgram(ShaderModel shaderModel,
// uniforms and samplers
cg.generateUniforms(fs, ShaderType::FRAGMENT,
BindingPoints::PER_VIEW, UibGenerator::getPerViewUib());
UniformBindingPoints::PER_VIEW, UibGenerator::getPerViewUib());
cg.generateUniforms(fs, ShaderType::FRAGMENT,
BindingPoints::PER_RENDERABLE, UibGenerator::getPerRenderableUib());
UniformBindingPoints::PER_RENDERABLE, UibGenerator::getPerRenderableUib());
cg.generateUniforms(fs, ShaderType::FRAGMENT,
BindingPoints::LIGHTS, UibGenerator::getLightsUib());
UniformBindingPoints::LIGHTS, UibGenerator::getLightsUib());
if (litVariants && filament::Variant::isShadowReceiverVariant(variant)) {
cg.generateUniforms(fs, ShaderType::FRAGMENT,
BindingPoints::SHADOW, UibGenerator::getShadowUib());
UniformBindingPoints::SHADOW, UibGenerator::getShadowUib());
}
cg.generateUniforms(fs, ShaderType::FRAGMENT,
BindingPoints::FROXEL_RECORDS, UibGenerator::getFroxelRecordUib());
UniformBindingPoints::FROXEL_RECORDS, UibGenerator::getFroxelRecordUib());
cg.generateUniforms(fs, ShaderType::FRAGMENT,
BindingPoints::PER_MATERIAL_INSTANCE, material.uib);
UniformBindingPoints::PER_MATERIAL_INSTANCE, material.uib);
CodeGenerator::generateSeparator(fs);
cg.generateSamplers(fs,
material.samplerBindings.getBlockOffset(BindingPoints::PER_VIEW),
material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_VIEW),
SibGenerator::getPerViewSib(variant));
cg.generateSamplers(fs,
material.samplerBindings.getBlockOffset(BindingPoints::PER_MATERIAL_INSTANCE),
material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_MATERIAL_INSTANCE),
material.sib);
fs << "float filament_lodBias;\n";
@@ -551,12 +551,12 @@ std::string ShaderGenerator::createPostProcessVertexProgram(ShaderModel sm,
generatePostProcessMaterialVariantDefines(vs, PostProcessVariant(variantKey));
cg.generateUniforms(vs, ShaderType::VERTEX,
BindingPoints::PER_VIEW, UibGenerator::getPerViewUib());
UniformBindingPoints::PER_VIEW, UibGenerator::getPerViewUib());
cg.generateUniforms(vs, ShaderType::VERTEX,
BindingPoints::PER_MATERIAL_INSTANCE, material.uib);
UniformBindingPoints::PER_MATERIAL_INSTANCE, material.uib);
cg.generateSamplers(vs,
material.samplerBindings.getBlockOffset(BindingPoints::PER_MATERIAL_INSTANCE),
material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_MATERIAL_INSTANCE),
material.sib);
CodeGenerator::generatePostProcessCommon(vs, ShaderType::VERTEX);
@@ -588,12 +588,12 @@ std::string ShaderGenerator::createPostProcessFragmentProgram(ShaderModel sm,
}
cg.generateUniforms(fs, ShaderType::FRAGMENT,
BindingPoints::PER_VIEW, UibGenerator::getPerViewUib());
UniformBindingPoints::PER_VIEW, UibGenerator::getPerViewUib());
cg.generateUniforms(fs, ShaderType::FRAGMENT,
BindingPoints::PER_MATERIAL_INSTANCE, material.uib);
UniformBindingPoints::PER_MATERIAL_INSTANCE, material.uib);
cg.generateSamplers(fs,
material.samplerBindings.getBlockOffset(BindingPoints::PER_MATERIAL_INSTANCE),
material.samplerBindings.getBlockOffset(SamplerBindingPoints::PER_MATERIAL_INSTANCE),
material.sib);
// subpass
+127 -98
View File
@@ -45,13 +45,13 @@ static ::testing::AssertionResult PropertyListsMatch(const MaterialBuilder::Prop
}
std::string shaderWithAllProperties(JobSystem& jobSystem, ShaderType type,
const std::string fragmentCode, const std::string vertexCode = "",
const std::string& fragmentCode, const std::string& vertexCode = "",
filamat::MaterialBuilder::Shading shadingModel = filamat::MaterialBuilder::Shading::LIT,
filamat::MaterialBuilder::RefractionMode refractionMode = filamat::MaterialBuilder::RefractionMode::NONE,
filamat::MaterialBuilder::VertexDomain vertexDomain = filamat::MaterialBuilder::VertexDomain::OBJECT) {
MockIncluder includer;
includer
.sourceForInclude("modify_normal.h", "material.normal = vec3(0.8);");
.sourceForInclude("modify_normal.h", "material.normal = vec3(0.8);");
filamat::MaterialBuilder builder;
builder.material(fragmentCode.c_str());
@@ -69,8 +69,11 @@ std::string shaderWithAllProperties(JobSystem& jobSystem, ShaderType type,
// We need to "build" the material to resolve any includes in user code.
builder.build(jobSystem);
return builder.peek(type,
{1, MaterialBuilder::TargetApi::OPENGL, MaterialBuilder::TargetLanguage::GLSL},
return builder.peek(type, {
ShaderModel::MOBILE,
MaterialBuilder::TargetApi::OPENGL,
MaterialBuilder::TargetLanguage::GLSL
},
allProperties);
}
@@ -86,19 +89,17 @@ TEST(StaticCodeAnalysisHelper, getFunctionNameNoParenthesis) {
class MaterialCompiler : public ::testing::Test {
protected:
MaterialCompiler() {
}
MaterialCompiler() = default;
virtual ~MaterialCompiler() {
}
~MaterialCompiler() override = default;
virtual void SetUp() {
void SetUp() override {
jobSystem = std::make_unique<JobSystem>();
jobSystem->adopt();
MaterialBuilder::init();
}
virtual void TearDown() {
void TearDown() override {
jobSystem->emancipate();
MaterialBuilder::shutdown();
}
@@ -113,12 +114,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerNothingDetected) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -137,9 +139,9 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerNothingDetectedVertex) {
fragmentCode, vertexCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::VERTEX, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -155,12 +157,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerNotFollowingINParameters) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -172,12 +175,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerDirectAssign) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::BASE_COLOR)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -201,9 +205,9 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerDirectAssignVertex) {
MaterialBuilder::VertexDomain::DEVICE);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::VERTEX, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::CLIP_SPACE_TRANSFORM)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -216,12 +220,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerDirectAssignWithSwizzling) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::BASE_COLOR)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -239,12 +244,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerSymbolAsOutParameterWithAliasing) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::BASE_COLOR)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -262,12 +268,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerSymbolAsOutParameterWithAliasingAndSw
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::BASE_COLOR)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -290,12 +297,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerSymbolInOutInChainWithDirectIndexInto
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::BASE_COLOR)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -318,12 +326,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerSymbolInOutInChain) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::BASE_COLOR)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -337,12 +346,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerBaseColor) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::BASE_COLOR)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -355,12 +365,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerRoughness) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::ROUGHNESS)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -373,12 +384,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerMetallic) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::METALLIC)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -391,12 +403,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerReflectance) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::REFLECTANCE)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -409,12 +422,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerAmbientOcclusion) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::AMBIENT_OCCLUSION)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -427,12 +441,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerClearCoat) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::CLEAR_COAT)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -447,14 +462,15 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerTransmission) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode, "",
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode,
"",
filamat::MaterialBuilder::Shading::LIT,
filamat::MaterialBuilder::RefractionMode::CUBEMAP);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::ABSORPTION)] = true;
expected[size_t(filamat::MaterialBuilder::Property::TRANSMISSION)] = true;
expected[size_t(filamat::MaterialBuilder::Property::IOR)] = true;
@@ -469,12 +485,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerClearCoatRoughness) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode, "");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode,
"");
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::CLEAR_COAT_ROUGHNESS)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -488,12 +505,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerClearCoatNormal) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode, "");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode,
"");
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::CLEAR_COAT)] = true;
expected[size_t(filamat::MaterialBuilder::Property::CLEAR_COAT_NORMAL)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
@@ -507,13 +525,14 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerThickness) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode, "",
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode,
"",
filamat::MaterialBuilder::Shading::SUBSURFACE);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::THICKNESS)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -526,13 +545,14 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerSubsurfacePower) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode, "",
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode,
"",
filamat::MaterialBuilder::Shading::SUBSURFACE);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::SUBSURFACE_POWER)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -545,13 +565,14 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerSubsurfaceColor) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode, "",
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode,
"",
filamat::MaterialBuilder::Shading::SUBSURFACE);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::SUBSURFACE_COLOR)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -564,12 +585,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerAnisotropicDirection) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::ANISOTROPY_DIRECTION)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -582,12 +604,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerAnisotropic) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::ANISOTROPY)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -600,13 +623,14 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerSheenColor) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode, "",
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode,
"",
filamat::MaterialBuilder::Shading::CLOTH);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::SHEEN_COLOR)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -619,12 +643,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerSheenRoughness) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::SHEEN_ROUGHNESS)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -637,12 +662,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerNormal) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::NORMAL)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -655,12 +681,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerBentNormal) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::BENT_NORMAL)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -673,12 +700,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerOutputFactor) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::POST_LIGHTING_COLOR)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -691,12 +719,13 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerWithinInclude) {
}
)");
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT, fragmentCode);
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderType::FRAGMENT,
fragmentCode);
GLSLTools glslTools;
MaterialBuilder::PropertyList properties {false};
MaterialBuilder::PropertyList properties{ false };
glslTools.findProperties(filament::backend::FRAGMENT, shaderCode, properties);
MaterialBuilder::PropertyList expected {false};
MaterialBuilder::PropertyList expected{ false };
expected[size_t(filamat::MaterialBuilder::Property::NORMAL)] = true;
EXPECT_TRUE(PropertyListsMatch(expected, properties));
}
@@ -131,8 +131,8 @@ private:
mutable std::unordered_map<uint64_t, filament::Material*> mGltfMaterialCache;
filament::Texture* mDefaultMap = nullptr;
filament::Texture* mDefaultNormalMap = nullptr;
float mDefaultMetallic = 0.0;
float mDefaultRoughness = 0.4;
float mDefaultMetallic = 0.0f;
float mDefaultRoughness = 0.4f;
filament::sRGBColor mDefaultEmissive = filament::sRGBColor({0.0f, 0.0f, 0.0f});
std::vector<utils::Entity> mRenderables;
+6 -4
View File
@@ -157,9 +157,10 @@ public:
* Consumes the contents of a glTF 2.0 file and produces a primary asset with one or more
* instances. The primary asset has ownership over the instances.
*
* The returned instances share their textures, material instances, and vertex buffers with the
* primary asset. However each instance has its own unique set of entities, transform
* components, and renderable components. Instances are freed when the primary asset is freed.
* The returned instances share their textures, materials, and vertex buffers with the primary
* asset. However each instance has its own unique set of entities, transform components,
* material instances, and renderable components. Instances are freed when the primary asset is
* freed.
*
* Light components are not instanced, they belong only to the primary asset.
*
@@ -201,7 +202,8 @@ public:
void enableDiagnostics(bool enable = true);
/**
* Destroys the given asset and all of its associated Filament objects.
* Destroys the given asset, all of its associated Filament objects, and all associated
* FilamentInstance objects.
*
* This destroys entities, components, material instances, vertex buffers, index buffers,
* and textures. This does not necessarily immediately free all source data, since
@@ -318,6 +318,11 @@ public:
*/
void detachMaterialInstances();
/**
* Convenience function to get the first instance (which always exists).
*/
FilamentInstance* getInstance() noexcept { return getAssetInstances()[0]; }
/*! \cond PRIVATE */
FilamentInstance** getAssetInstances() noexcept;
@@ -20,6 +20,8 @@
#include <utils/compiler.h>
#include <utils/Entity.h>
#include <filament/Box.h>
namespace filament::gltfio {
class Animator;
@@ -109,6 +111,24 @@ public:
* This is a no-op if the given skin index or target is invalid.
*/
void detachSkin(size_t skinIndex, utils::Entity target) noexcept;
/**
* Resets the AABB on all renderables by manually computing the bounding box.
*
* THIS IS ONLY USEFUL FOR MALFORMED ASSETS THAT DO NOT HAVE MIN/MAX SET UP CORRECTLY.
*
* Does not affect the return value of getBoundingBox() on the owning asset.
* Cannot be called after releaseSourceData() on the owning asset.
* Can only be called after loadResources() or asyncBeginLoad().
*/
void recomputeBoundingBoxes();
/**
* Gets the axis-aligned bounding box from the supplied min / max values in glTF accessors.
*
* If recomputeBoundingBoxes() has been called, then this returns the recomputed AABB.
*/
Aabb getBoundingBox() const noexcept;
};
} // namespace filament::gltfio
@@ -36,8 +36,8 @@ enum class AlphaMode : uint8_t {
// The following struct gets hashed so all padding bits should be explicit.
// Tell the compiler to emit a warning if it adds any padding.
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wpadded"
UTILS_WARNING_PUSH
UTILS_WARNING_ENABLE_PADDED
/**
* \struct MaterialKey MaterialProvider.h gltfio/MaterialProvider.h
@@ -98,7 +98,7 @@ struct alignas(4) MaterialKey {
static_assert(sizeof(MaterialKey) == 16, "MaterialKey has unexpected size.");
#pragma clang diagnostic pop
UTILS_WARNING_POP
bool operator==(const MaterialKey& k1, const MaterialKey& k2);
@@ -49,14 +49,6 @@ struct ResourceConfiguration {
//! If true, adjusts skinning weights to sum to 1. Well formed glTF files do not need this,
//! but it is useful for robustness.
bool normalizeSkinningWeights;
//! If true, computes the bounding boxes of all \c POSITION attibutes. Well formed glTF files
//! do not need this, but it is useful for robustness.
bool recomputeBoundingBoxes;
//! If true, ignores skinning when computing bounding boxes. Implicitly true for instanced
//! assets. Only applicable when recomputeBoundingBoxes is set to true.
bool ignoreBindTransform;
};
/**
@@ -162,7 +154,6 @@ public:
private:
bool loadResources(FFilamentAsset* asset, bool async);
void normalizeSkinningWeights(FFilamentAsset* asset) const;
void updateBoundingBoxes(FFilamentAsset* asset) const;
AssetPool* mPool;
struct Impl;
Impl* pImpl;
+1 -1
View File
@@ -81,7 +81,7 @@ UTILS_PUBLIC inline void decomposeMatrix(const filament::math::mat4f& mat, filam
*rotation = clone.toQuaternion();
} else {
// Set to identity if close to zero
*rotation = quatf(1);
*rotation = quatf(1.0f);
}
}
+75 -67
View File
@@ -76,10 +76,13 @@ struct AnimatorImpl {
TransformManager* transformManager;
vector<float> weights;
FixedCapacityVector<mat4f> crossFade;
void addChannels(const NodeMap& nodeMap, const cgltf_animation& srcAnim, Animation& dst);
void addChannels(const FixedCapacityVector<Entity>& nodeMap, const cgltf_animation& srcAnim,
Animation& dst);
void applyAnimation(const Channel& channel, float t, size_t prevIndex, size_t nextIndex);
void stashCrossFade();
void applyCrossFade(float alpha);
void resetBoneMatrices(FFilamentInstance* instance);
void updateBoneMatrices(FFilamentInstance* instance);
};
static void createSampler(const cgltf_animation_sampler& src, Sampler& dst) {
@@ -298,71 +301,28 @@ void Animator::applyAnimation(size_t animationIndex, float time) const {
}
void Animator::resetBoneMatrices() {
auto renderableManager = mImpl->renderableManager;
auto update = [=](const SkinVector& skins, BoneVector& boneVector) {
for (const auto& skin : skins) {
size_t njoints = skin.joints.size();
boneVector.resize(njoints);
for (const auto& entity : skin.targets) {
auto renderable = renderableManager->getInstance(entity);
if (renderable) {
for (size_t boneIndex = 0; boneIndex < njoints; ++boneIndex) {
boneVector[boneIndex] = mat4f();
}
renderableManager->setBones(renderable, boneVector.data(), boneVector.size());
}
}
}
};
// If this is a single-instance animator, then reset only this instance.
if (mImpl->instance) {
update(mImpl->instance->skins, mImpl->boneMatrices);
} else {
for (FFilamentInstance* instance : mImpl->asset->mInstances) {
update(instance->skins, mImpl->boneMatrices);
}
mImpl->resetBoneMatrices(mImpl->instance);
return;
}
// If this is a broadcast animator, then reset all instances.
for (FFilamentInstance* instance : mImpl->asset->mInstances) {
mImpl->resetBoneMatrices(instance);
}
}
void Animator::updateBoneMatrices() {
auto renderableManager = mImpl->renderableManager;
auto transformManager = mImpl->transformManager;
auto update = [=](const SkinVector& skins, BoneVector& boneVector) {
for (const Skin& skin : skins) {
size_t njoints = skin.joints.size();
boneVector.resize(njoints);
for (Entity entity : skin.targets) {
auto renderable = renderableManager->getInstance(entity);
if (!renderable) {
continue;
}
mat4f inverseGlobalTransform;
auto xformable = transformManager->getInstance(entity);
if (xformable) {
inverseGlobalTransform = inverse(transformManager->getWorldTransform(xformable));
}
for (size_t boneIndex = 0; boneIndex < njoints; ++boneIndex) {
const auto& joint = skin.joints[boneIndex];
TransformManager::Instance jointInstance = transformManager->getInstance(joint);
mat4f globalJointTransform = transformManager->getWorldTransform(jointInstance);
boneVector[boneIndex] =
inverseGlobalTransform *
globalJointTransform *
skin.inverseBindMatrices[boneIndex];
}
renderableManager->setBones(renderable, boneVector.data(), boneVector.size());
}
}
};
// If this is a single-instance animator, then update only this instance.
if (mImpl->instance) {
update(mImpl->instance->skins, mImpl->boneMatrices);
} else {
for (FFilamentInstance* instance : mImpl->asset->mInstances) {
update(instance->skins, mImpl->boneMatrices);
}
mImpl->updateBoneMatrices(mImpl->instance);
return;
}
// If this is a broadcast animator, then update all instances.
for (FFilamentInstance* instance : mImpl->asset->mInstances) {
mImpl->updateBoneMatrices(instance);
}
}
@@ -427,15 +387,16 @@ void AnimatorImpl::applyCrossFade(float alpha) {
recursiveFn(tm.getInstance(asset->mRoot), 0, recursiveFn);
}
void AnimatorImpl::addChannels(const NodeMap& nodeMap, const cgltf_animation& srcAnim,
Animation& dst) {
cgltf_animation_channel* srcChannels = srcAnim.channels;
cgltf_animation_sampler* srcSamplers = srcAnim.samplers;
void AnimatorImpl::addChannels(const FixedCapacityVector<Entity>& nodeMap,
const cgltf_animation& srcAnim, Animation& dst) {
const cgltf_animation_channel* srcChannels = srcAnim.channels;
const cgltf_animation_sampler* srcSamplers = srcAnim.samplers;
const cgltf_node* nodes = asset->mSourceAsset->hierarchy->nodes;
const Sampler* samplers = dst.samplers.data();
for (cgltf_size j = 0, nchans = srcAnim.channels_count; j < nchans; ++j) {
const cgltf_animation_channel& srcChannel = srcChannels[j];
auto iter = nodeMap.find(srcChannel.target_node);
if (UTILS_UNLIKELY(iter == nodeMap.end())) {
Entity targetEntity = nodeMap[srcChannel.target_node - nodes];
if (UTILS_UNLIKELY(!targetEntity)) {
if (GLTFIO_VERBOSE) {
slog.w << "No scene root contains node ";
if (srcChannel.target_node->name) {
@@ -449,7 +410,6 @@ void AnimatorImpl::addChannels(const NodeMap& nodeMap, const cgltf_animation& sr
}
continue;
}
Entity targetEntity = iter.value();
Channel dstChannel;
dstChannel.sourceData = samplers + (srcChannel.sampler - srcSamplers);
dstChannel.targetEntity = targetEntity;
@@ -556,4 +516,52 @@ void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIn
transformManager->setTransform(node, xform);
}
void AnimatorImpl::resetBoneMatrices(FFilamentInstance* instance) {
for (const auto& skin : instance->skins) {
size_t njoints = skin.joints.size();
boneMatrices.resize(njoints);
for (const auto& entity : skin.targets) {
auto renderable = renderableManager->getInstance(entity);
if (renderable) {
for (size_t boneIndex = 0; boneIndex < njoints; ++boneIndex) {
boneMatrices[boneIndex] = mat4f();
}
renderableManager->setBones(renderable, boneMatrices.data(), boneMatrices.size());
}
}
}
}
void AnimatorImpl::updateBoneMatrices(FFilamentInstance* instance) {
assert_invariant(instance->skins.size() == asset->mSkins.size());
size_t skinIndex = 0;
for (const auto& skin : instance->skins) {
const auto& assetSkin = asset->mSkins[skinIndex++];
size_t njoints = skin.joints.size();
boneMatrices.resize(njoints);
for (Entity entity : skin.targets) {
auto renderable = renderableManager->getInstance(entity);
if (!renderable) {
continue;
}
mat4f inverseGlobalTransform;
auto xformable = transformManager->getInstance(entity);
if (xformable) {
inverseGlobalTransform = inverse(transformManager->getWorldTransform(xformable));
}
for (size_t boneIndex = 0; boneIndex < njoints; ++boneIndex) {
const auto& joint = skin.joints[boneIndex];
const mat4f& inverseBindMatrix = assetSkin.inverseBindMatrices[boneIndex];
TransformManager::Instance jointInstance = transformManager->getInstance(joint);
mat4f globalJointTransform = transformManager->getWorldTransform(jointInstance);
boneMatrices[boneIndex] =
inverseGlobalTransform *
globalJointTransform *
inverseBindMatrix;
}
renderableManager->setBones(renderable, boneMatrices.data(), boneMatrices.size());
}
}
}
} // namespace filament::gltfio
+181 -90
View File
@@ -63,10 +63,26 @@ namespace filament::gltfio {
using SceneMask = NodeManager::SceneMask;
void importSkins(const cgltf_data* gltf, const NodeMap& nodeMap, SkinVector& dstSkins);
static const auto FREE_CALLBACK = [](void* mem, size_t, void*) { free(mem); };
// The default glTF material.
static constexpr cgltf_material kDefaultMat = {
.name = (char*) "Default GLTF material",
.has_pbr_metallic_roughness = true,
.has_pbr_specular_glossiness = false,
.has_clearcoat = false,
.has_transmission = false,
.has_volume = false,
.has_ior = false,
.has_specular = false,
.has_sheen = false,
.pbr_metallic_roughness = {
.base_color_factor = {1.0, 1.0, 1.0, 1.0},
.metallic_factor = 1.0,
.roughness_factor = 1.0,
},
};
// Sometimes a glTF bufferview includes unused data at the end (e.g. in skinning.gltf) so we need to
// compute the correct size of the vertex buffer. Filament automatically infers the size of
// driver-level vertex buffers from the attribute data (stride, count, offset) and clients are
@@ -90,6 +106,76 @@ static const char* getNodeName(const cgltf_node* node, const char* defaultNodeNa
return defaultNodeName;
}
static bool primitiveHasVertexColor(const cgltf_primitive* inPrim) {
for (int slot = 0; slot < inPrim->attributes_count; slot++) {
const cgltf_attribute& inputAttribute = inPrim->attributes[slot];
if (inputAttribute.type == cgltf_attribute_type_color) {
return true;
}
}
return false;
}
static LightManager::Type getLightType(const cgltf_light_type light) {
switch (light) {
case cgltf_light_type_max_enum:
case cgltf_light_type_invalid:
assert_invariant(false && "Invalid light type");
return LightManager::Type::DIRECTIONAL;
case cgltf_light_type_directional:
return LightManager::Type::DIRECTIONAL;
case cgltf_light_type_point:
return LightManager::Type::POINT;
case cgltf_light_type_spot:
return LightManager::Type::FOCUSED_SPOT;
}
}
// MaterialInstanceCache
// ---------------------
// Each glTF material definition corresponds to a single MaterialInstance, which are temporarily
// cached when loading a FilamentInstance. If a given glTF material is referenced by multiple
// glTF meshes, then their corresponding Filament primitives will share the same Filament
// MaterialInstance and UvMap. The UvMap is a mapping from each texcoord slot in glTF to one of
// Filament's 2 texcoord sets.
//
// Notes:
// - The Material objects (used to create instances) are cached in MaterialProvider, not here.
// - The cache is not responsible for destroying material instances. They are owned by the asset.
class MaterialInstanceCache {
public:
struct Entry {
MaterialInstance* instance;
UvMap uvmap;
};
MaterialInstanceCache() {}
MaterialInstanceCache(const cgltf_data* hierarchy) :
mHierarchy(hierarchy),
mMaterialInstances(hierarchy->materials_count, Entry{}),
mMaterialInstancesWithVertexColor(hierarchy->materials_count, Entry{}) {}
Entry* getEntry(const cgltf_material** mat, bool vertexColor) {
if (*mat) {
EntryVector& entries = vertexColor ?
mMaterialInstancesWithVertexColor : mMaterialInstances;
const cgltf_material* basePointer = mHierarchy->materials;
return &entries[*mat - basePointer];
}
*mat = &kDefaultMat;
return vertexColor ? &mDefaultMaterialInstanceWithVertexColor : &mDefaultMaterialInstance;
}
private:
using EntryVector = utils::FixedCapacityVector<Entry>;
const cgltf_data* mHierarchy = {};
EntryVector mMaterialInstances;
EntryVector mMaterialInstancesWithVertexColor;
Entry mDefaultMaterialInstance = {};
Entry mDefaultMaterialInstanceWithVertexColor = {};
};
struct FAssetLoader : public AssetLoader {
FAssetLoader(const AssetConfiguration& config) :
mEntityManager(config.entities ? *config.entities : EntityManager::get()),
@@ -102,8 +188,10 @@ struct FAssetLoader : public AssetLoader {
FFilamentAsset* createAsset(const uint8_t* bytes, uint32_t nbytes);
FFilamentAsset* createInstancedAsset(const uint8_t* bytes, uint32_t numBytes,
FilamentInstance** instances, size_t numInstances);
FilamentInstance** instances, size_t numInstances);
FilamentInstance* createInstance(FFilamentAsset* primary);
void importSkins(FFilamentAsset* primary, FFilamentInstance* instance,
const cgltf_data* srcAsset);
static void destroy(FAssetLoader** loader) noexcept {
delete *loader;
@@ -130,10 +218,11 @@ struct FAssetLoader : public AssetLoader {
return mMaterials.getMaterials();
}
private:
void createAsset(const cgltf_data* srcAsset, size_t numInstances);
FFilamentInstance* createInstance(FFilamentAsset* primary, const cgltf_data* srcAsset);
void createEntity(const cgltf_data* srcAsset, const cgltf_node* node, SceneMask scenes,
Entity parent, bool enableLight, FFilamentInstance* instance);
void recurseEntities(const cgltf_data* srcAsset, const cgltf_node* node, SceneMask scenes,
Entity parent, FFilamentInstance* instance);
void createRenderable(const cgltf_data* srcAsset, const cgltf_node* node, Entity entity,
const char* name);
bool createPrimitive(const cgltf_primitive* inPrim, Primitive* outPrim, const UvMap& uvmap,
@@ -144,12 +233,10 @@ struct FAssetLoader : public AssetLoader {
const cgltf_material* inputMat, UvMap* uvmap, bool vertexColor);
void addTextureBinding(MaterialInstance* materialInstance, const char* parameterName,
const cgltf_texture* srcTexture, bool srgb);
bool primitiveHasVertexColor(const cgltf_primitive* inPrim) const;
void createMaterialVariants(const cgltf_data* srcAsset, const cgltf_mesh* mesh, Entity entity,
FFilamentInstance* instance);
static LightManager::Type getLightType(const cgltf_light_type type);
public:
EntityManager& mEntityManager;
RenderableManager& mRenderableManager;
NameComponentManager* const mNameManager;
@@ -164,6 +251,7 @@ struct FAssetLoader : public AssetLoader {
const char* mDefaultNodeName;
bool mError = false;
bool mDiagnosticsEnabled = false;
MaterialInstanceCache mMaterialInstanceCache;
// Weak reference to the largest dummy buffer so far in the current loading phase.
BufferObject* mDummyBufferObject;
@@ -228,16 +316,16 @@ FilamentInstance* FAssetLoader::createInstance(FFilamentAsset* primary) {
slog.e << "There is no scene in the asset." << io::endl;
return nullptr;
}
mMaterialInstanceCache = MaterialInstanceCache(srcAsset);
FFilamentInstance* instance = createInstance(primary, srcAsset);
// Import the skin data. This is normally done by ResourceLoader but dynamically created
// instances are a bit special.
importSkins(primary->mSourceAsset->hierarchy, instance->nodeMap, instance->skins);
if (primary->mAnimator) {
primary->mAnimator->addInstance(instance);
}
primary->mDependencyGraph.refinalize();
primary->mDependencyGraph.commitEdges();
return instance;
}
@@ -256,6 +344,8 @@ void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances)
mResult = new FFilamentAsset(&mEngine, mNameManager, &mEntityManager, &mNodeManager, srcAsset);
mDummyBufferObject = nullptr;
mMaterialInstanceCache = MaterialInstanceCache(srcAsset);
// It is not an error for a glTF file to have zero scenes.
mResult->mScenes.clear();
if (srcAsset->scenes == nullptr) {
@@ -307,7 +397,7 @@ void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances)
mResult->mRenderableCount = 0;
// Create a separate entity hierarchy for each instance. Note that MeshCache (vertex
// buffers and index buffers) and MatInstanceCache (materials and textures) help avoid
// buffers and index buffers) and MaterialInstanceCache (materials and textures) help avoid
// needless duplication of resources.
for (size_t index = 0; index < numInstances; ++index) {
if (createInstance(mResult, srcAsset) == nullptr) {
@@ -325,10 +415,10 @@ void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances)
// Find every unique resource URI and store a pointer to any of the cgltf-owned cstrings
// that match the URI. These strings get freed during releaseSourceData().
tsl::robin_map<std::string_view, const char*> resourceUris;
tsl::robin_set<std::string_view> resourceUris;
auto addResourceUri = [&resourceUris](const char* uri) {
if (uri) {
resourceUris[uri] = uri;
resourceUris.insert(uri);
}
};
for (cgltf_size i = 0, len = srcAsset->buffers_count; i < len; ++i) {
@@ -338,8 +428,8 @@ void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances)
addResourceUri(srcAsset->images[i].uri);
}
mResult->mResourceUris.reserve(resourceUris.size());
for (const auto& pair : resourceUris) {
mResult->mResourceUris.push_back(pair.second);
for (std::string_view uri : resourceUris) {
mResult->mResourceUris.push_back(uri.data());
}
if (mError) {
@@ -356,12 +446,9 @@ FFilamentInstance* FAssetLoader::createInstance(FFilamentAsset* primary,
mTransformManager.create(instanceRoot, rootTransform);
// Create an instance object, which is a just a lightweight wrapper around a vector of
// entities and a lazily created animator.
FFilamentInstance* instance = new FFilamentInstance;
instance->root = instanceRoot;
instance->animator = nullptr;
instance->owner = primary;
primary->mInstances.push_back(instance);
// entities and an animator. The creation of animator is triggered from ResourceLoader
// because it could require external bin data.
FFilamentInstance* instance = new FFilamentInstance(instanceRoot, primary);
// Check if the asset has variants.
instance->variants.reserve(srcAsset->variants_count);
@@ -371,13 +458,20 @@ FFilamentInstance* FAssetLoader::createInstance(FFilamentAsset* primary,
// For each scene root, recursively create all entities.
for (const auto& pair : mRootNodes) {
createEntity(srcAsset, pair.first, pair.second, instanceRoot, false, instance);
recurseEntities(srcAsset, pair.first, pair.second, instanceRoot, instance);
}
importSkins(primary, instance, srcAsset);
// This needs to stay near the end of the method to allow detection of the first instance.
primary->mInstances.push_back(instance);
instance->boundingBox = primary->mBoundingBox;
return instance;
}
void FAssetLoader::createEntity(const cgltf_data* srcAsset, const cgltf_node* node,
SceneMask scenes, Entity parent, bool enableLight, FFilamentInstance* instance) {
void FAssetLoader::recurseEntities(const cgltf_data* srcAsset, const cgltf_node* node,
SceneMask scenes, Entity parent, FFilamentInstance* instance) {
NodeManager& nm = mNodeManager;
const Entity entity = mEntityManager.create();
nm.create(entity);
@@ -409,7 +503,7 @@ void FAssetLoader::createEntity(const cgltf_data* srcAsset, const cgltf_node* no
// Update the asset's entity list and private node mapping.
mResult->mEntities.push_back(entity);
instance->entities.push_back(entity);
instance->nodeMap[node] = entity;
instance->nodeMap[node - srcAsset->nodes] = entity;
const char* name = getNodeName(node, mDefaultNodeName);
@@ -432,7 +526,12 @@ void FAssetLoader::createEntity(const cgltf_data* srcAsset, const cgltf_node* no
}
}
if (node->light && enableLight) {
// TODO: refactor loading into 2 passes: the first pass creates all objects that are shared
// amongst instances (VertexBuffer, IndexBuffer, etc) while the second pass creates the
// lightweight ECS stuff (Renderables, Transformables) for each instance. This will allow
// users to create assets with zero instances, which is a feature request from Google.
if (node->light && mResult->mInstances.empty()) {
createLight(node->light, entity);
}
@@ -441,7 +540,7 @@ void FAssetLoader::createEntity(const cgltf_data* srcAsset, const cgltf_node* no
}
for (cgltf_size i = 0, len = node->children_count; i < len; ++i) {
createEntity(srcAsset, node->children[i], scenes, entity, enableLight, instance);
recurseEntities(srcAsset, node->children[i], scenes, entity, instance);
}
}
@@ -453,17 +552,17 @@ void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node
auto thisTransform = mTransformManager.getInstance(entity);
mat4f worldTransform = mTransformManager.getWorldTransform(thisTransform);
cgltf_size nprims = mesh->primitives_count;
RenderableManager::Builder builder(nprims);
const cgltf_size primitiveCount = mesh->primitives_count;
RenderableManager::Builder builder(primitiveCount);
// If the mesh is already loaded, obtain the list of Filament VertexBuffer / IndexBuffer objects
// that were already generated (one for each primitive), otherwise allocate a new list of
// pointers for the primitives.
auto iter = mResult->mMeshCache.find(mesh);
if (iter == mResult->mMeshCache.end()) {
mResult->mMeshCache[mesh].resize(nprims);
FixedCapacityVector<Primitive>& prims = mResult->mMeshCache[mesh - srcAsset->meshes];
if (prims.empty()) {
prims = FixedCapacityVector<Primitive>(primitiveCount, Primitive {});
}
Primitive* outputPrim = mResult->mMeshCache[mesh].data();
Primitive* outputPrim = prims.data();
const cgltf_primitive* inputPrim = &mesh->primitives[0];
Aabb aabb;
@@ -473,7 +572,9 @@ void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node
builder.morphing(numMorphTargets);
// For each prim, create a Filament VertexBuffer, IndexBuffer, and MaterialInstance.
for (cgltf_size index = 0; index < nprims; ++index, ++outputPrim, ++inputPrim) {
// The VertexBuffer and IndexBuffer objects are cached for possible re-use, but MaterialInstance
// is not.
for (cgltf_size index = 0; index < primitiveCount; ++index, ++outputPrim, ++inputPrim) {
RenderableManager::PrimitiveType primType;
if (!getPrimitiveType(inputPrim->type, &primType)) {
slog.e << "Unsupported primitive type in " << name << io::endl;
@@ -539,8 +640,7 @@ void FAssetLoader::createRenderable(const cgltf_data* srcAsset, const cgltf_node
}
// Per the spec, glTF models must have valid mix / max annotations for position attributes.
// However in practice these can be missing and we should be as robust as other glTF viewers.
// If desired, clients can enable the "recomputeBoundingBoxes" feature in ResourceLoader.
// If desired, clients can call "recomputeBoundingBoxes()" in FilamentInstance.
Box box = Box().set(aabb.min, aabb.max);
if (box.isEmpty()) {
slog.w << "Missing bounding box in " << name << io::endl;
@@ -587,7 +687,8 @@ void FAssetLoader::createMaterialVariants(const cgltf_data* srcAsset, const cglt
break;
}
bool hasVertexColor = primitiveHasVertexColor(srcPrim);
MaterialInstance* mi = createMaterialInstance(srcAsset, material, &uvmap, hasVertexColor);
MaterialInstance* mi =
createMaterialInstance(srcAsset, material, &uvmap, hasVertexColor);
assert_invariant(mi);
if (!mi) {
mError = true;
@@ -766,7 +867,8 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
cgltf_size targetsCount = inPrim->targets_count;
if (targetsCount > MAX_MORPH_TARGETS) {
utils::slog.w << "WARNING: Exceeded max morph target count of " << MAX_MORPH_TARGETS << utils::io::endl;
utils::slog.w << "WARNING: Exceeded max morph target count of "
<< MAX_MORPH_TARGETS << utils::io::endl;
targetsCount = MAX_MORPH_TARGETS;
}
@@ -982,32 +1084,13 @@ void FAssetLoader::createCamera(const cgltf_camera* camera, Entity entity) {
MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsset,
const cgltf_material* inputMat, UvMap* uvmap, bool vertexColor) {
intptr_t key = ((intptr_t) inputMat) ^ (vertexColor ? 1 : 0);
auto iter = mResult->mMatInstanceCache.find(key);
if (iter != mResult->mMatInstanceCache.end()) {
*uvmap = iter->second.uvmap;
return iter->second.instance;
MaterialInstanceCache::Entry* const cacheEntry =
mMaterialInstanceCache.getEntry(&inputMat, vertexColor);
if (cacheEntry->instance) {
*uvmap = cacheEntry->uvmap;
return cacheEntry->instance;
}
// The default glTF material.
static const cgltf_material kDefaultMat = {
.name = (char*) "Default GLTF material",
.has_pbr_metallic_roughness = true,
.has_pbr_specular_glossiness = false,
.has_clearcoat = false,
.has_transmission = false,
.has_volume = false,
.has_ior = false,
.has_specular = false,
.has_sheen = false,
.pbr_metallic_roughness = {
.base_color_factor = {1.0, 1.0, 1.0, 1.0},
.metallic_factor = 1.0,
.roughness_factor = 1.0,
},
};
inputMat = inputMat ? inputMat : &kDefaultMat;
auto mrConfig = inputMat->pbr_metallic_roughness;
auto sgConfig = inputMat->pbr_specular_glossiness;
auto ccConfig = inputMat->clearcoat;
@@ -1123,7 +1206,8 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse
mi->setMaskThreshold(inputMat->alpha_cutoff);
}
float3 emissiveFactor(inputMat->emissive_factor[0], inputMat->emissive_factor[1], inputMat->emissive_factor[2]);
const float* emissive = &inputMat->emissive_factor[0];
float3 emissiveFactor(emissive[0], emissive[1], emissive[2]);
if (inputMat->has_emissive_strength) {
emissiveFactor *= inputMat->emissive_strength.emissive_strength;
}
@@ -1211,7 +1295,8 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse
}
}
if (matkey.hasClearCoatRoughnessTexture) {
addTextureBinding(mi, "clearCoatRoughnessMap", ccConfig.clearcoat_roughness_texture.texture, false);
addTextureBinding(mi, "clearCoatRoughnessMap",
ccConfig.clearcoat_roughness_texture.texture, false);
if (matkey.hasTextureTransforms) {
const cgltf_texture_transform& uvt = ccConfig.clearcoat_roughness_texture.transform;
auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
@@ -1219,7 +1304,8 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse
}
}
if (matkey.hasClearCoatNormalTexture) {
addTextureBinding(mi, "clearCoatNormalMap", ccConfig.clearcoat_normal_texture.texture, false);
addTextureBinding(mi, "clearCoatNormalMap",
ccConfig.clearcoat_normal_texture.texture, false);
if (matkey.hasTextureTransforms) {
const cgltf_texture_transform& uvt = ccConfig.clearcoat_normal_texture.transform;
auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
@@ -1243,7 +1329,8 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse
}
}
if (matkey.hasSheenRoughnessTexture) {
addTextureBinding(mi, "sheenRoughnessMap", shConfig.sheen_roughness_texture.texture, false);
addTextureBinding(mi, "sheenRoughnessMap",
shConfig.sheen_roughness_texture.texture, false);
if (matkey.hasTextureTransforms) {
const cgltf_texture_transform& uvt = shConfig.sheen_roughness_texture.transform;
auto uvmat = matrixFromUvTransform(uvt.offset, uvt.rotation, uvt.scale);
@@ -1303,7 +1390,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse
inputMat->emissive_strength.emissive_strength : 1.0f);
}
mResult->mMatInstanceCache[key] = {mi, *uvmap};
*cacheEntry = { mi, *uvmap };
return mi;
}
@@ -1315,6 +1402,7 @@ void FAssetLoader::addTextureBinding(MaterialInstance* materialInstance, const c
#endif
return;
}
TextureSampler dstSampler;
auto srcSampler = srcTexture->sampler;
if (srcSampler) {
@@ -1327,43 +1415,46 @@ void FAssetLoader::addTextureBinding(MaterialInstance* materialInstance, const c
dstSampler.setWrapModeS(TextureSampler::WrapMode::REPEAT);
dstSampler.setWrapModeT(TextureSampler::WrapMode::REPEAT);
// These defaults are up the implementation but since we try to provide mipmaps,
// These defaults are up to the implementation but since we try to provide mipmaps,
// we might as well use them. In practice the conformance models look awful without
// using mipmapping by default.
dstSampler.setMagFilter(TextureSampler::MagFilter::LINEAR);
dstSampler.setMinFilter(TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR);
}
mResult->mTextureSlots.push_back({
.texture = srcTexture,
const intptr_t textureIndex = srcTexture - mResult->mSourceAsset->hierarchy->textures;
mResult->addTextureSlot({
.sourceTexture = (size_t) textureIndex,
.materialInstance = materialInstance,
.materialParameter = parameterName,
.sampler = dstSampler,
.srgb = srgb
});
mResult->mDependencyGraph.addEdge(materialInstance, parameterName);
}
bool FAssetLoader::primitiveHasVertexColor(const cgltf_primitive* inPrim) const {
for (int slot = 0; slot < inPrim->attributes_count; slot++) {
const cgltf_attribute& inputAttribute = inPrim->attributes[slot];
if (inputAttribute.type == cgltf_attribute_type_color) {
return true;
void FAssetLoader::importSkins(FFilamentAsset* primary,
FFilamentInstance* instance, const cgltf_data* gltf) {
instance->skins.reserve(gltf->skins_count);
instance->skins.resize(gltf->skins_count);
const auto& nodeMap = instance->nodeMap;
for (cgltf_size i = 0, len = gltf->nodes_count; i < len; ++i) {
const cgltf_node& node = gltf->nodes[i];
Entity entity = nodeMap[i];
if (node.skin && entity) {
int skinIndex = node.skin - &gltf->skins[0];
instance->skins[skinIndex].targets.insert(entity);
}
}
return false;
}
for (cgltf_size i = 0, len = gltf->skins_count; i < len; ++i) {
FFilamentInstance::Skin& dstSkin = instance->skins[i];
const cgltf_skin& srcSkin = gltf->skins[i];
LightManager::Type FAssetLoader::getLightType(const cgltf_light_type light) {
switch (light) {
case cgltf_light_type_max_enum:
case cgltf_light_type_invalid:
case cgltf_light_type_directional:
return LightManager::Type::DIRECTIONAL;
case cgltf_light_type_point:
return LightManager::Type::POINT;
case cgltf_light_type_spot:
return LightManager::Type::FOCUSED_SPOT;
// Build a list of transformables for this skin, one for each joint.
dstSkin.joints = FixedCapacityVector<Entity>(srcSkin.joints_count);
for (cgltf_size i = 0, len = srcSkin.joints_count; i < len; ++i) {
dstSkin.joints[i] = nodeMap[srcSkin.joints[i] - gltf->nodes];
}
}
}
+16 -31
View File
@@ -16,6 +16,8 @@
#include "DependencyGraph.h"
#include <utils/Panic.h>
using namespace filament;
using namespace utils;
@@ -34,38 +36,22 @@ size_t DependencyGraph::popRenderables(Entity* result, size_t count) noexcept {
}
void DependencyGraph::addEdge(Entity entity, MaterialInstance* mi) {
// Permit adding an Entity-Material edge to a finalized graph as long as the material is already
// known. Since we already encountered this material instance, we already know what textures it
// is associated with.
assert(!mFinalized || mMaterialToEntity.find(mi) != mMaterialToEntity.end());
mMaterialToEntity[mi].insert(entity);
mEntityToMaterial[entity].materials.insert(mi);
}
void DependencyGraph::addEdge(MaterialInstance* mi, const char* parameter) {
assert(!mFinalized);
if (auto iter = mMaterialToTexture.find(mi); iter != mMaterialToTexture.end()) {
const tsl::robin_map<std::string, TextureNode*>& params = iter.value().params;
if (params.find(parameter) != params.end()) {
return;
}
}
mMaterialToTexture[mi].params[parameter] = nullptr;
}
// During finalization, the structure of the glTF is known but we have not yet created texture
// objects. Find all non-textured entities and immediately add mark them as ready.
void DependencyGraph::finalize() {
assert(!mFinalized);
for (const auto& pair : mMaterialToEntity) {
auto mi = pair.first;
if (mMaterialToTexture.find(mi) == mMaterialToTexture.end()) {
markAsReady(mi);
}
}
mFinalized = true;
}
void DependencyGraph::refinalize() {
assert(mFinalized);
for (const auto& pair : mMaterialToEntity) {
auto material = pair.first;
void DependencyGraph::commitEdges() {
for (const auto& [material, entities] : mMaterialToEntity) {
if (mMaterialToTexture.find(material) == mMaterialToTexture.end()) {
markAsReady(material);
} else {
@@ -75,7 +61,7 @@ void DependencyGraph::refinalize() {
}
void DependencyGraph::addEdge(Texture* texture, MaterialInstance* material, const char* parameter) {
assert(texture && !mFinalized);
assert_invariant(texture);
mTextureToMaterial[texture].insert(material);
mMaterialToTexture.at(material).params.at(parameter) = getStatus(texture);
}
@@ -85,9 +71,8 @@ void DependencyGraph::checkReadiness(Material* material) {
// Check this material's texture parameters, there are 5 in the worst case.
bool materialIsReady = true;
for (const auto& pair : status.params) {
assert(pair.second && "Parameter-to-Texture edge is missing.");
if (!pair.second->ready) {
for (const auto& [name, texture] : status.params) {
if (!texture || !texture->ready) {
materialIsReady = false;
break;
}
@@ -100,7 +85,7 @@ void DependencyGraph::checkReadiness(Material* material) {
}
void DependencyGraph::markAsReady(Texture* texture) {
assert(texture && mFinalized);
assert_invariant(texture);
mTextureNodes.at(texture)->ready = true;
// Iterate over the materials associated with this texture to check if any have become ready.
@@ -115,7 +100,7 @@ void DependencyGraph::markAsReady(MaterialInstance* material) {
auto& entities = mMaterialToEntity.at(material);
for (auto entity : entities) {
auto& status = mEntityToMaterial.at(entity);
assert(status.numReadyMaterials <= status.materials.size());
assert_invariant(status.numReadyMaterials <= status.materials.size());
if (status.numReadyMaterials == status.materials.size()) {
continue;
}
@@ -126,7 +111,7 @@ void DependencyGraph::markAsReady(MaterialInstance* material) {
}
DependencyGraph::TextureNode* DependencyGraph::getStatus(Texture* texture) {
assert(texture);
assert_invariant(texture);
auto iter = mTextureNodes.find(texture);
if (iter == mTextureNodes.end()) {
TextureNode* status = (mTextureNodes[texture] = std::make_unique<TextureNode>()).get();
+11 -25
View File
@@ -55,11 +55,11 @@ namespace filament::gltfio {
* Texture Texture Texture
*
* Note that the left-most entity in the above graph has no textures, so it becomes ready as soon as
* finalize is called.
* commitEdges is called.
*/
class DependencyGraph {
public:
using Material = filament::MaterialInstance;
using Material = MaterialInstance;
using Entity = utils::Entity;
// Pops up to "count" ready-to-render entities off the queue.
@@ -68,23 +68,16 @@ public:
// If "result" is null, returns the number of available entities.
size_t popRenderables(Entity* result, size_t count) noexcept;
// These are called during the initial asset loader phase.
void addEdge(Entity entity, Material* material);
void addEdge(Material* material, const char* parameter);
void addEdge(filament::Texture* texture, Material* material, const char* parameter);
void addEdge(Texture* texture, Material* material, const char* parameter);
// Marks the end of synchronous asset loading.
//
// At this point, the graph enters a finalized state and all non-textured entities are
// immediately marked as "ready". However textures are not yet fully decoded.
//
// After finalization, the only nodes that can be added to the graph are entities.
void finalize();
// Commits a set of edges to the graph. This simply triggers a check to see if
// any entities are already ready, e.g. if any entities are non-textured.
void commitEdges();
// Marks the given texture as being fully decoded, with all miplevels initialized.
//
// This can only be called on a finalized graph.
void markAsReady(filament::Texture* texture);
void markAsReady(Texture* texture);
// Marks the material as ready, but due to an error.
//
@@ -92,15 +85,9 @@ public:
// dependencies will never become available.
void markAsError(Material* material) { markAsReady(material); }
// Re-checks the readiness of all entities after finalization.
//
// This exists only to support dynamic instancing. It is slower than finalize() because it
// checks the readiness of existing materials.
void refinalize();
private:
struct TextureNode {
filament::Texture* texture;
Texture* texture;
bool ready;
};
@@ -115,21 +102,20 @@ private:
void checkReadiness(Material* material);
void markAsReady(Material* material);
TextureNode* getStatus(filament::Texture* texture);
TextureNode* getStatus(Texture* texture);
// The following maps contain the directed edges in the graph.
tsl::robin_map<Entity, EntityNode, Entity::Hasher> mEntityToMaterial;
tsl::robin_map<Material*, tsl::robin_set<Entity, Entity::Hasher>> mMaterialToEntity;
tsl::robin_map<Material*, MaterialNode> mMaterialToTexture;
tsl::robin_map<filament::Texture*, tsl::robin_set<Material*>> mTextureToMaterial;
tsl::robin_map<Texture*, tsl::robin_set<Material*>> mTextureToMaterial;
// Each texture (and its readiness flag) can be referenced from multiple nodes, so we own
// a collection of wrapper objects in the following map. This uses std::unique_ptr to allow
// nodes to refer to a texture wrapper using a stable weak pointer.
tsl::robin_map<filament::Texture*, std::unique_ptr<TextureNode>> mTextureNodes;
tsl::robin_map<Texture*, std::unique_ptr<TextureNode>> mTextureNodes;
std::queue<Entity> mReadyRenderables;
bool mFinalized = false;
};
} // namespace filament::gltfio
+71 -52
View File
@@ -44,7 +44,6 @@
#include "DracoCache.h"
#include "FFilamentInstance.h"
#include <tsl/robin_map.h>
#include <tsl/htrie_map.h>
#include <vector>
@@ -78,17 +77,20 @@ struct BufferSlot {
const cgltf_accessor* accessor;
cgltf_attribute_type attribute;
int bufferIndex; // for vertex buffer and morph target buffer only
filament::VertexBuffer* vertexBuffer;
filament::IndexBuffer* indexBuffer;
filament::MorphTargetBuffer* morphTargetBuffer;
VertexBuffer* vertexBuffer;
IndexBuffer* indexBuffer;
MorphTargetBuffer* morphTargetBuffer;
};
// Encapsulates a connection between Texture and MaterialInstance.
// Stores a connection between Texture and MaterialInstance; consumed by resource loader so that it
// can call "setParameter" on the given MaterialInstance after the Texture has been created.
// Since material instances are not typically shared between FilamentInstance, the slots are a
// unified list across all instances that exist before creation of Texture objects.
struct TextureSlot {
const cgltf_texture* texture;
filament::MaterialInstance* materialInstance;
size_t sourceTexture; // index into cgltf_texture
MaterialInstance* materialInstance;
const char* materialParameter;
filament::TextureSampler sampler;
TextureSampler sampler;
bool srgb;
};
@@ -97,38 +99,26 @@ struct TextureSlot {
// If a given glTF mesh is referenced by multiple glTF nodes, then it generates a separate Filament
// renderable for each of those nodes. All renderables generated by a given mesh share a common set
// of VertexBuffer and IndexBuffer objects. To achieve the sharing behavior, the loader maintains a
// small cache. The cache keys are glTF mesh definitions and the cache entries are lists of
// primitives, where a "primitive" is a reference to a Filament VertexBuffer and IndexBuffer.
// small cache. There is one cache entry per cgltf_mesh. Each entry is a list of primitives, where a
// "primitive" is a reference to a Filament VertexBuffer and IndexBuffer.
struct Primitive {
filament::VertexBuffer* vertices = nullptr;
filament::IndexBuffer* indices = nullptr;
filament::Aabb aabb; // object-space bounding box
VertexBuffer* vertices = nullptr;
IndexBuffer* indices = nullptr;
Aabb aabb; // object-space bounding box
UvMap uvmap; // mapping from each glTF UV set to either UV0 or UV1 (8 bytes)
filament::MorphTargetBuffer* targets = nullptr;
MorphTargetBuffer* targets = nullptr;
};
using MeshCache = tsl::robin_map<const cgltf_mesh*, std::vector<Primitive>>;
// MatInstanceCache
// ----------------
// Each glTF material definition corresponds to a single filament::MaterialInstance, which are
// temporarily cached during loading. The filament::Material objects that are used to create instances are
// cached in MaterialProvider. If a given glTF material is referenced by multiple glTF meshes, then
// their corresponding filament primitives will share the same Filament MaterialInstance and UvMap.
// The UvMap is a mapping from each texcoord slot in glTF to one of Filament's 2 texcoord sets.
struct MaterialEntry {
filament::MaterialInstance* instance;
UvMap uvmap;
};
using MatInstanceCache = tsl::robin_map<intptr_t, MaterialEntry>;
using MeshCache = utils::FixedCapacityVector<utils::FixedCapacityVector<Primitive>>;
struct FFilamentAsset : public FilamentAsset {
FFilamentAsset(filament::Engine* engine, utils::NameComponentManager* names,
FFilamentAsset(Engine* engine, utils::NameComponentManager* names,
utils::EntityManager* entityManager, NodeManager* nodeManager,
const cgltf_data* srcAsset) :
mEngine(engine), mNameManager(names), mEntityManager(entityManager),
mNodeManager(nodeManager) {
mSourceAsset.reset(new SourceAsset {(cgltf_data*)srcAsset});
}
mNodeManager(nodeManager),
mSourceAsset(new SourceAsset {(cgltf_data*)srcAsset}),
mTextureBindings(srcAsset->textures_count, 0),
mMeshCache(srcAsset->meshes_count) {}
~FFilamentAsset();
@@ -176,11 +166,11 @@ struct FFilamentAsset : public FilamentAsset {
return mMaterialInstances.size();
}
const filament::MaterialInstance* const* getMaterialInstances() const noexcept {
const MaterialInstance* const* getMaterialInstances() const noexcept {
return mMaterialInstances.data();
}
filament::MaterialInstance* const* getMaterialInstances() noexcept {
MaterialInstance* const* getMaterialInstances() noexcept {
return mMaterialInstances.data();
}
@@ -192,7 +182,7 @@ struct FFilamentAsset : public FilamentAsset {
return mResourceUris.data();
}
filament::Aabb getBoundingBox() const noexcept {
Aabb getBoundingBox() const noexcept {
return mBoundingBox;
}
@@ -222,7 +212,7 @@ struct FFilamentAsset : public FilamentAsset {
utils::Entity getWireframe() noexcept;
filament::Engine* getEngine() const noexcept {
Engine* getEngine() const noexcept {
return mEngine;
}
@@ -246,7 +236,7 @@ struct FFilamentAsset : public FilamentAsset {
return mScenes[sceneIndex].c_str();
}
void addEntitiesToScene(filament::Scene& targetScene, const Entity* entities, size_t count,
void addEntitiesToScene(Scene& targetScene, const Entity* entities, size_t count,
SceneMask sceneFilter);
void detachFilamentComponents() noexcept {
@@ -259,19 +249,41 @@ struct FFilamentAsset : public FilamentAsset {
// end public API
void attachTexture(filament::Texture* texture) {
void attachTexture(Texture* texture) {
mTextures.push_back(texture);
}
void bindTexture(const TextureSlot& tb, filament::Texture* texture) {
void bindTexture(const TextureSlot& tb, Texture* texture) {
assert_invariant(texture);
tb.materialInstance->setParameter(tb.materialParameter, texture, tb.sampler);
mDependencyGraph.addEdge(texture, tb.materialInstance, tb.materialParameter);
mTextureBindings[tb.sourceTexture] = texture;
}
// If a Filament Texture already exists for the given slot, go ahead and bind it to the
// given material instance. (This can occur when creating new FilamentInstances)
// Otherwise, stash the binding information so that ResourceLoader can trigger the bind.
void addTextureSlot(const TextureSlot& tb) {
if (mResourcesLoaded) {
Texture* texture = mTextureBindings[tb.sourceTexture];
assert_invariant(texture);
tb.materialInstance->setParameter(tb.materialParameter, texture, tb.sampler);
// Intentionally omit adding a dependency graph edge here. We do not need progressive
// reveal for multiple FilamentInstance.
} else {
mTextureSlots.push_back(tb);
mDependencyGraph.addEdge(tb.materialInstance, tb.materialParameter);
}
}
void createAnimators();
filament::Engine* const mEngine;
struct Skin {
utils::CString name;
utils::FixedCapacityVector<math::mat4f> inverseBindMatrices;
};
Engine* const mEngine;
utils::NameComponentManager* const mNameManager;
utils::EntityManager* const mEntityManager;
NodeManager* const mNodeManager;
@@ -279,15 +291,16 @@ struct FFilamentAsset : public FilamentAsset {
std::vector<utils::Entity> mLightEntities;
std::vector<utils::Entity> mCameraEntities;
size_t mRenderableCount = 0;
std::vector<filament::MaterialInstance*> mMaterialInstances;
std::vector<filament::VertexBuffer*> mVertexBuffers;
std::vector<filament::BufferObject*> mBufferObjects;
std::vector<filament::IndexBuffer*> mIndexBuffers;
std::vector<filament::MorphTargetBuffer*> mMorphTargetBuffers;
std::vector<filament::Texture*> mTextures;
std::vector<MaterialInstance*> mMaterialInstances;
std::vector<VertexBuffer*> mVertexBuffers;
std::vector<BufferObject*> mBufferObjects;
std::vector<IndexBuffer*> mIndexBuffers;
std::vector<MorphTargetBuffer*> mMorphTargetBuffers;
std::vector<Texture*> mTextures;
utils::FixedCapacityVector<Skin> mSkins;
utils::FixedCapacityVector<Variant> mVariants;
utils::FixedCapacityVector<utils::CString> mScenes;
filament::Aabb mBoundingBox;
Aabb mBoundingBox;
utils::Entity mRoot;
std::vector<FFilamentInstance*> mInstances;
Animator* mAnimator = nullptr;
@@ -320,13 +333,19 @@ struct FFilamentAsset : public FilamentAsset {
using SourceHandle = std::shared_ptr<SourceAsset>;
SourceHandle mSourceAsset;
// Transient source data that can freed via releaseSourceData:
// Mapping from cgltf_texture to Texture* is required when creating new instances.
utils::FixedCapacityVector<Texture*> mTextureBindings;
// Resource URIs can be queried by the end user.
utils::FixedCapacityVector<const char*> mResourceUris;
// The mapping from cgltf_mesh to VertexBuffer* (etc) is required when creating new instances.
MeshCache mMeshCache;
// Asset information that is produced by AssetLoader and consumed by ResourceLoader:
std::vector<BufferSlot> mBufferSlots;
std::vector<TextureSlot> mTextureSlots;
std::vector<const char*> mResourceUris;
std::vector<std::pair<const cgltf_primitive*, filament::VertexBuffer*> > mPrimitives;
MatInstanceCache mMatInstanceCache;
MeshCache mMeshCache;
std::vector<std::pair<const cgltf_primitive*, VertexBuffer*> > mPrimitives;
};
FILAMENT_UPCAST(FilamentAsset)
+30 -25
View File
@@ -25,7 +25,6 @@
#include <math/mat4.h>
#include <tsl/robin_map.h>
#include <tsl/robin_set.h>
#include <vector>
@@ -43,25 +42,10 @@ namespace filament::gltfio {
struct FFilamentAsset;
class Animator;
struct Skin {
utils::CString name;
// The inverse bind matrices and joints arrays must have the same count. Each element
// corresponds to a single bone. We considered using the ECS to store these, but this would be
// complicated because a single node might be used as a bone in more than one skin, and its
// inverse bind matrix might be unique in each of these skins.
utils::FixedCapacityVector<filament::math::mat4f> inverseBindMatrices;
utils::FixedCapacityVector<utils::Entity> joints;
// The set of all nodes that are influenced by this skin.
// This is initially gleaned from the glTF file using the "skin" attribute of each node.
tsl::robin_set<utils::Entity, utils::Entity::Hasher> targets;
};
struct VariantMapping {
utils::Entity renderable;
size_t primitiveIndex;
filament::MaterialInstance* material;
MaterialInstance* material;
};
struct Variant {
@@ -69,17 +53,37 @@ struct Variant {
std::vector<VariantMapping> mappings;
};
using SkinVector = std::vector<Skin>;
using NodeMap = tsl::robin_map<const cgltf_node*, utils::Entity>;
struct FFilamentInstance : public FilamentInstance {
FFilamentInstance(utils::Entity root, FFilamentAsset* owner);
// The per-instance skin structure caches information to allow animation to be applied
// efficiently at run time. Note that shared immutable data, such as the skin name and inverse
// bind transforms, are stored in FFilamentAsset.
struct Skin {
// The list of entities whose transform components define the joints of the skin.
utils::FixedCapacityVector<utils::Entity> joints;
// The set of all entities that are influenced by this skin.
// This is initially derived from the glTF, but users can dynamically add or remove targets.
tsl::robin_set<utils::Entity, utils::Entity::Hasher> targets;
};
const utils::Entity root;
FFilamentAsset* const owner;
std::vector<utils::Entity> entities;
utils::FixedCapacityVector<Variant> variants;
utils::Entity root;
Animator* animator;
FFilamentAsset* owner;
SkinVector skins;
NodeMap nodeMap;
Animator* animator = nullptr;
utils::FixedCapacityVector<Skin> skins;
// Note that nodeMap is yet another a vector of entities, but unlike the "entities" field, it
// may be sparsely populated. This is used as a simple mapping between cgltf_node and Entity,
// and therefore has the same size as the number of cgltf_node in the original asset. We
// considered using the ECS for this, but we need Node => Entity, not the other way around.
// This is discarded after the animator is created.
utils::FixedCapacityVector<utils::Entity> nodeMap;
Aabb boundingBox;
void createAnimator();
Animator* getAnimator() const noexcept;
size_t getSkinCount() const noexcept;
@@ -89,6 +93,7 @@ struct FFilamentInstance : public FilamentInstance {
void attachSkin(size_t skinIndex, utils::Entity target) noexcept;
void detachSkin(size_t skinIndex, utils::Entity target) noexcept;
void applyMaterialVariant(size_t variantIndex) noexcept;
void recomputeBoundingBoxes();
};
FILAMENT_UPCAST(FilamentInstance)
+4 -9
View File
@@ -160,18 +160,13 @@ Entity FFilamentAsset::getWireframe() noexcept {
void FFilamentAsset::releaseSourceData() noexcept {
// To ensure that all possible memory is freed, we reassign to new containers rather than
// calling clear(). With many container types (such as robin_map), clearing is a fast
// operation that merely frees the storage for the items.
mMatInstanceCache = {};
// calling clear(). With many container types, clearing is a fast operation that merely frees
// the storage for the items but not the actual container.
mTextureBindings = {};
mTextureSlots = {};
mMeshCache = {};
mResourceUris = {};
mPrimitives = {};
mBufferSlots = {};
mTextureSlots = {};
mSourceAsset.reset();
for (FFilamentInstance* instance : mInstances) {
instance->nodeMap = {};
}
}
const char* FFilamentAsset::getName(utils::Entity entity) const noexcept {
+204 -1
View File
@@ -19,13 +19,20 @@
#include <gltfio/Animator.h>
#include <utils/JobSystem.h>
#include <utils/Log.h>
using namespace filament;
using namespace filament::math;
using namespace utils;
namespace filament::gltfio {
FFilamentInstance::FFilamentInstance(Entity root, FFilamentAsset* owner) :
root(root),
owner(owner),
nodeMap(owner->mSourceAsset->hierarchy->nodes_count, Entity()) {}
Animator* FFilamentInstance::getAnimator() const noexcept {
assert_invariant(animator);
return animator;
@@ -34,6 +41,7 @@ Animator* FFilamentInstance::getAnimator() const noexcept {
void FFilamentInstance::createAnimator() {
assert_invariant(animator == nullptr);
animator = new Animator(owner, this);
nodeMap = {};
}
size_t FFilamentInstance::getSkinCount() const noexcept {
@@ -44,7 +52,7 @@ const char* FFilamentInstance::getSkinNameAt(size_t skinIndex) const noexcept {
if (skins.size() <= skinIndex) {
return nullptr;
}
return skins[skinIndex].name.c_str();
return owner->mSkins[skinIndex].name.c_str();
}
size_t FFilamentInstance::getJointCountAt(size_t skinIndex) const noexcept {
@@ -87,6 +95,194 @@ void FFilamentInstance::applyMaterialVariant(size_t variantIndex) noexcept {
}
}
void FFilamentInstance::recomputeBoundingBoxes() {
ASSERT_PRECONDITION(owner->mSourceAsset,
"Do not call releaseSourceData before recomputeBoundingBoxes");
ASSERT_PRECONDITION(owner->mResourcesLoaded,
"Do not call recomputeBoundingBoxes before loadResources or asyncBeginLoad");
auto& rm = owner->mEngine->getRenderableManager();
auto& tm = owner->mEngine->getTransformManager();
// The purpose of the root node is to give the client a place for custom transforms.
// Since it is not part of the source model, it should be ignored when computing the
// bounding box.
TransformManager::Instance root = tm.getInstance(owner->getRoot());
utils::FixedCapacityVector<Entity> modelRoots(tm.getChildCount(root));
tm.getChildren(root, modelRoots.data(), modelRoots.size());
for (auto e : modelRoots) {
tm.setParent(tm.getInstance(e), 0);
}
struct Prim {
cgltf_primitive const* prim;
Entity node;
ssize_t skinIndex;
};
auto computeBoundingBox = [](const cgltf_primitive* prim) -> Aabb {
Aabb aabb;
for (cgltf_size slot = 0; slot < prim->attributes_count; slot++) {
const cgltf_attribute& attr = prim->attributes[slot];
const cgltf_accessor* accessor = attr.data;
const size_t dim = cgltf_num_components(accessor->type);
if (attr.type == cgltf_attribute_type_position && dim >= 3) {
utils::FixedCapacityVector<float> unpacked(accessor->count * dim);
cgltf_accessor_unpack_floats(accessor, unpacked.data(), unpacked.size());
for (cgltf_size i = 0, j = 0, n = accessor->count; i < n; ++i, j += dim) {
float3 pt(unpacked[j + 0], unpacked[j + 1], unpacked[j + 2]);
aabb.min = min(aabb.min, pt);
aabb.max = max(aabb.max, pt);
}
break;
}
}
return aabb;
};
auto computeBoundingBoxSkinned = [&tm, this](const Prim& prim) -> Aabb {
FixedCapacityVector<float3> verts;
FixedCapacityVector<uint4> joints;
FixedCapacityVector<float4> weights;
for (cgltf_size slot = 0, n = prim.prim->attributes_count; slot < n; ++slot) {
const cgltf_attribute& attr = prim.prim->attributes[slot];
const cgltf_accessor& accessor = *attr.data;
switch (attr.type) {
case cgltf_attribute_type_position:
verts = FixedCapacityVector<float3>(accessor.count);
cgltf_accessor_unpack_floats(&accessor, &verts.data()->x, accessor.count * 3);
break;
case cgltf_attribute_type_joints: {
FixedCapacityVector<float4> tmp(accessor.count);
cgltf_accessor_unpack_floats(&accessor, &tmp.data()->x, accessor.count * 4);
joints = FixedCapacityVector<uint4>(accessor.count);
for (size_t i = 0, n = accessor.count; i < n; ++i) {
joints[i] = uint4(tmp[i]);
}
break;
}
case cgltf_attribute_type_weights:
weights = FixedCapacityVector<float4>(accessor.count);
cgltf_accessor_unpack_floats(&accessor, &weights.data()->x, accessor.count * 4);
break;
default:
break;
}
}
Aabb aabb;
TransformManager::Instance transformable = tm.getInstance(prim.node);
const mat4f inverseGlobalTransform = inverse(tm.getWorldTransform(transformable));
const Skin& instanceSkin = skins[prim.skinIndex];
const FFilamentAsset::Skin& assetSkin = owner->mSkins[prim.skinIndex];
for (size_t i = 0, n = verts.size(); i < n; i++) {
mat4f tmp = mat4f(0.0f);
for (size_t j = 0; j < 4; j++) {
size_t jointIndex = joints[i][j];
Entity jointEntity = instanceSkin.joints[jointIndex];
mat4f globalJointTransform = tm.getWorldTransform(tm.getInstance(jointEntity));
mat4f inverseBindMatrix = assetSkin.inverseBindMatrices[jointIndex];
tmp += weights[i][j] * globalJointTransform * inverseBindMatrix;
}
const mat4f skinMatrix = inverseGlobalTransform * tmp;
const float3 point = verts[i];
// NOTE: Filament's vertex shader assumes that last row is [0,0,0,1]
// so we make the same assumption in the following transformation.
const float3 skinnedPoint =
point.x * skinMatrix[0].xyz +
point.y * skinMatrix[1].xyz +
point.z * skinMatrix[2].xyz +
skinMatrix[3].xyz;
aabb.min = min(aabb.min, skinnedPoint);
aabb.max = max(aabb.max, skinnedPoint);
}
return aabb;
};
// Collect all mesh primitives that we wish to find bounds for. For each mesh primitive, we also
// collect the skin it is bound to (nullptr if not skinned) for bounds computation.
size_t primCount = 0;
const cgltf_data* hierarchy = owner->mSourceAsset->hierarchy;
const cgltf_node* nodes = hierarchy->nodes;
for (size_t i = 0, n = hierarchy->nodes_count; i < n; ++i) {
if (const cgltf_mesh* mesh = nodes[i].mesh; mesh) {
primCount += mesh->primitives_count;
}
}
auto primitives = FixedCapacityVector<Prim>::with_capacity(primCount);
const cgltf_skin* baseSkin = &hierarchy->skins[0];
for (size_t i = 0, n = hierarchy->nodes_count; i < n; ++i) {
const cgltf_node& node = nodes[i];
const Entity entity = nodeMap[i];
if (const cgltf_mesh* mesh = node.mesh; mesh) {
for (cgltf_size j = 0, nprims = mesh->primitives_count; j < nprims; ++j) {
primitives.push_back({
.prim = &mesh->primitives[j],
.node = entity,
.skinIndex = node.skin ? (node.skin - baseSkin) : -1
});
}
}
}
// Kick off a bounding box job for every primitive.
FixedCapacityVector<Aabb> bounds(primitives.size());
JobSystem& js = owner->mEngine->getJobSystem();
JobSystem::Job* parent = js.createJob();
for (size_t i = 0; i < primitives.size(); ++i) {
Aabb& result = bounds[i];
const Prim& prim = primitives[i];
if (primitives[i].skinIndex < 0) {
js.run(jobs::createJob(js, parent, [&prim, &result, computeBoundingBox] {
result = computeBoundingBox(prim.prim);
}));
} else {
js.run(jobs::createJob(js, parent, [&prim, &result, computeBoundingBoxSkinned] {
result = computeBoundingBoxSkinned(prim);
}));
}
}
js.runAndWait(parent);
// Compute the asset-level bounding box.
size_t primIndex = 0;
Aabb assetBounds;
for (size_t i = 0, n = owner->mSourceAsset->hierarchy->nodes_count; i < n; ++i) {
const cgltf_node& node = nodes[i];
const Entity entity = nodeMap[i];
if (const cgltf_mesh* mesh = node.mesh; mesh) {
// Find the object-space bounds for the renderable by unioning the bounds of each prim.
Aabb aabb;
for (cgltf_size j = 0, nprims = mesh->primitives_count; j < nprims; ++j) {
Aabb primBounds = bounds[primIndex++];
aabb.min = min(aabb.min, primBounds.min);
aabb.max = max(aabb.max, primBounds.max);
}
auto renderable = rm.getInstance(entity);
rm.setAxisAlignedBoundingBox(renderable, Box().set(aabb.min, aabb.max));
// Transform this bounding box, then update the asset-level bounding box.
auto transformable = tm.getInstance(entity);
const mat4f worldTransform = tm.getWorldTransform(transformable);
const Aabb transformed = aabb.transform(worldTransform);
assetBounds.min = min(assetBounds.min, transformed.min);
assetBounds.max = max(assetBounds.max, transformed.max);
}
}
// Restore the root node.
for (auto e : modelRoots) {
tm.setParent(tm.getInstance(e), root);
}
boundingBox = assetBounds;
}
FilamentAsset* FilamentInstance::getAsset() const noexcept {
return upcast(this)->owner;
}
@@ -136,5 +332,12 @@ void FilamentInstance::detachSkin(size_t skinIndex, Entity target) noexcept {
return upcast(this)->detachSkin(skinIndex, target);
}
void FilamentInstance::recomputeBoundingBoxes() {
return upcast(this)->recomputeBoundingBoxes();
}
Aabb FilamentInstance::getBoundingBox() const noexcept {
return upcast(this)->boundingBox;
}
} // namespace filament::gltfio
+46 -252
View File
@@ -67,15 +67,11 @@ struct ResourceLoader::Impl {
Impl(const ResourceConfiguration& config) :
mEngine(config.engine),
mNormalizeSkinningWeights(config.normalizeSkinningWeights),
mRecomputeBoundingBoxes(config.recomputeBoundingBoxes),
mGltfPath(config.gltfPath ? config.gltfPath : ""),
mIgnoreBindTransform(config.ignoreBindTransform) {}
mGltfPath(config.gltfPath ? config.gltfPath : "") {}
Engine* const mEngine;
const bool mNormalizeSkinningWeights;
const bool mRecomputeBoundingBoxes;
const std::string mGltfPath;
const bool mIgnoreBindTransform;
// User-provided resource data with URI string keys, populated with addResourceData().
// This is used on platforms without traditional file systems, such as Android, iOS, and WebGL.
@@ -117,47 +113,6 @@ static void uploadCallback(void* buffer, size_t size, void* user) {
delete event;
}
void importSkins(const cgltf_data* gltf, const NodeMap& nodeMap, SkinVector& dstSkins) {
dstSkins.resize(gltf->skins_count);
for (cgltf_size i = 0, len = gltf->nodes_count; i < len; ++i) {
const cgltf_node& node = gltf->nodes[i];
if (node.skin) {
int skinIndex = node.skin - &gltf->skins[0];
Entity entity = nodeMap.at(&node);
dstSkins[skinIndex].targets.insert(entity);
}
}
for (cgltf_size i = 0, len = gltf->skins_count; i < len; ++i) {
Skin& dstSkin = dstSkins[i];
const cgltf_skin& srcSkin = gltf->skins[i];
if (srcSkin.name) {
dstSkin.name = CString(srcSkin.name);
}
// Build a list of transformables for this skin, one for each joint.
dstSkin.joints = FixedCapacityVector<Entity>(srcSkin.joints_count);
for (cgltf_size i = 0, len = srcSkin.joints_count; i < len; ++i) {
auto iter = nodeMap.find(srcSkin.joints[i]);
assert_invariant(iter != nodeMap.end());
dstSkin.joints[i] = iter->second;
}
// Retain a copy of the inverse bind matrices because the source blob could be evicted later.
const cgltf_accessor* srcMatrices = srcSkin.inverse_bind_matrices;
dstSkin.inverseBindMatrices = FixedCapacityVector<mat4f>(srcSkin.joints_count);
if (srcMatrices) {
auto dstMatrices = (uint8_t*) dstSkin.inverseBindMatrices.data();
uint8_t* bytes = (uint8_t*) srcMatrices->buffer_view->buffer->data;
if (!bytes) {
slog.w << "Empty animation buffer, have resources been loaded yet?" << io::endl;
continue;
}
auto srcBuffer = (void*) (bytes + srcMatrices->offset + srcMatrices->buffer_view->offset);
memcpy(dstMatrices, srcBuffer, srcSkin.joints_count * sizeof(mat4f));
}
}
}
static void convertBytesToShorts(uint16_t* dst, const uint8_t* src, size_t count) {
for (size_t i = 0; i < count; ++i) {
dst[i] = src[i];
@@ -212,8 +167,7 @@ static void decodeDracoMeshes(FFilamentAsset* asset) {
};
// Go through every primitive and check if it has a Draco mesh.
for (auto& pair : asset->mPrimitives) {
const cgltf_primitive* prim = pair.first;
for (auto& [prim, vertexBuffer] : asset->mPrimitives) {
if (!prim->has_draco_mesh_compression) {
continue;
}
@@ -222,7 +176,6 @@ static void decodeDracoMeshes(FFilamentAsset* asset) {
// If an error occurs, we can simply set the primitive's associated VertexBuffer to null.
// This does not cause a leak because it is a weak reference.
auto& vertexBuffer = pair.second;
// Check if we have already decoded this mesh.
DracoMesh* mesh = dracoCache->findOrCreateMesh(draco.buffer_view);
@@ -433,23 +386,35 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
// tangent generation.
decodeDracoMeshes(asset);
// Normalize skinning weights, then "import" each skin into the asset by building a mapping of
// skins to their affected entities.
// For each skin, optionally normalize skinning weights and store a copy of the bind matrices.
if (gltf->skins_count > 0) {
if (pImpl->mNormalizeSkinningWeights) {
normalizeSkinningWeights(asset);
}
// NOTE: This takes care of up-front instances, but dynamically added instances also
// need to import the skin data, which is done in AssetLoader.
for (FFilamentInstance* instance : asset->mInstances) {
importSkins(gltf, instance->nodeMap, instance->skins);
asset->mSkins.reserve(gltf->skins_count);
for (cgltf_size i = 0, len = gltf->skins_count; i < len; ++i) {
const cgltf_skin& srcSkin = gltf->skins[i];
CString name;
if (srcSkin.name) {
name = CString(srcSkin.name);
}
const cgltf_accessor* srcMatrices = srcSkin.inverse_bind_matrices;
FixedCapacityVector<mat4f> inverseBindMatrices(srcSkin.joints_count);
if (srcMatrices) {
uint8_t* bytes = (uint8_t*) srcMatrices->buffer_view->buffer->data;
assert_invariant(bytes);
uint8_t* srcBuffer = bytes + srcMatrices->offset + srcMatrices->buffer_view->offset;
memcpy((uint8_t*) inverseBindMatrices.data(),
(const void*) srcBuffer, srcSkin.joints_count * sizeof(mat4f));
}
FFilamentAsset::Skin skin {
.name = std::move(name),
.inverseBindMatrices = std::move(inverseBindMatrices),
};
asset->mSkins.emplace_back(std::move(skin));
}
}
if (pImpl->mRecomputeBoundingBoxes) {
updateBoundingBoxes(asset);
}
Engine& engine = *pImpl->mEngine;
// Upload VertexBuffer and IndexBuffer data to the GPU.
@@ -527,6 +492,9 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
// we need to generate the contents of a GPU buffer by processing one or more CPU buffer(s).
pImpl->computeTangents(asset);
asset->mBufferSlots = {};
asset->mPrimitives = {};
// If any decoding jobs are still underway from a previous load, wait for them to finish.
for (const auto& iter : pImpl->mTextureProviders) {
iter.second->waitForCompletion();
@@ -537,8 +505,8 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
pImpl->createTextures(asset, async);
// Non-textured renderables are now considered ready, and we can guarantee that no new
// materials or textures will be added. notify the dependency graph.
asset->mDependencyGraph.finalize();
// materials or textures will be added. Notify the dependency graph.
asset->mDependencyGraph.commitEdges();
asset->createAnimators();
@@ -587,9 +555,9 @@ void ResourceLoader::asyncUpdateLoad() {
}
Texture* ResourceLoader::Impl::getOrCreateTexture(FFilamentAsset* asset, const TextureSlot& tb) {
const cgltf_texture* srcTexture = tb.texture;
const cgltf_image* image = srcTexture->basisu_image ?
srcTexture->basisu_image : srcTexture->image;
const cgltf_texture& srcTexture = asset->mSourceAsset->hierarchy->textures[tb.sourceTexture];
const cgltf_image* image = srcTexture.basisu_image ?
srcTexture.basisu_image : srcTexture.image;
const cgltf_buffer_view* bv = image->buffer_view;
const char* uri = image->uri;
@@ -686,7 +654,7 @@ Texture* ResourceLoader::Impl::getOrCreateTexture(FFilamentAsset* asset, const T
}
if (!texture) {
const char* name = srcTexture->name ? srcTexture->name : uri;
const char* name = srcTexture.name ? srcTexture.name : uri;
slog.e << "Unable to create texture " << name << ": "
<< provider->getPushMessage() << io::endl;
asset->mDependencyGraph.markAsError(tb.materialInstance);
@@ -707,7 +675,7 @@ void ResourceLoader::Impl::cancelTextureDecoding() {
void ResourceLoader::Impl::createTextures(FFilamentAsset* asset, bool async) {
// Create new texture objects if they are not cached and kick off decoding jobs.
mRemainingTextureDownloads = 0;
for (auto slot : asset->mTextureSlots) {
for (const TextureSlot& slot : asset->mTextureSlots) {
if (Texture* texture = getOrCreateTexture(asset, slot)) {
asset->bindTexture(slot, texture);
}
@@ -744,29 +712,27 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) {
// Create a job description for each triangle-based primitive.
using Params = TangentsJob::Params;
std::vector<Params> jobParams;
for (auto pair : asset->mPrimitives) {
if (UTILS_UNLIKELY(pair.first->type != cgltf_primitive_type_triangles)) {
for (auto [prim, vb] : asset->mPrimitives) {
if (UTILS_UNLIKELY(prim->type != cgltf_primitive_type_triangles)) {
continue;
}
VertexBuffer* vb = pair.second;
auto iter = baseTangents.find(vb);
if (iter != baseTangents.end()) {
jobParams.emplace_back(Params {{ pair.first }, {vb, nullptr, iter->second }});
jobParams.emplace_back(Params {{ prim }, {vb, nullptr, iter->second }});
}
}
// Create a job description for morph targets.
const NodeMap& nodeMap = asset->mInstances[0]->nodeMap;
for (auto iter : nodeMap) {
cgltf_node const* node = iter.first;
cgltf_mesh const* mesh = node->mesh;
if (UTILS_UNLIKELY(!mesh || !mesh->weights_count)) {
for (size_t i = 0, n = asset->mSourceAsset->hierarchy->meshes_count; i < n; ++i) {
const cgltf_mesh& mesh = asset->mSourceAsset->hierarchy->meshes[i];
const FixedCapacityVector<Primitive>& prims = asset->mMeshCache[i];
if (0 == mesh.weights_count) {
continue;
}
for (cgltf_size pindex = 0, pcount = mesh->primitives_count; pindex < pcount; ++pindex) {
const cgltf_primitive& prim = mesh->primitives[pindex];
const auto& gltfioPrim = asset->mMeshCache.at(mesh)[pindex];
MorphTargetBuffer* tb = gltfioPrim.targets;
for (cgltf_size tindex = 0, tcount = prim.targets_count; tindex < tcount; ++ tindex) {
for (cgltf_size pindex = 0, pcount = mesh.primitives_count; pindex < pcount; ++pindex) {
const cgltf_primitive& prim = mesh.primitives[pindex];
MorphTargetBuffer* tb = prims[pindex].targets;
for (cgltf_size tindex = 0, tcount = prim.targets_count; tindex < tcount; ++tindex) {
const cgltf_morph_target& target = prim.targets[tindex];
bool hasNormals = false;
for (cgltf_size aindex = 0; aindex < target.attributes_count; aindex++) {
@@ -854,176 +820,4 @@ void ResourceLoader::normalizeSkinningWeights(FFilamentAsset* asset) const {
}
}
void ResourceLoader::updateBoundingBoxes(FFilamentAsset* asset) const {
SYSTRACE_CALL();
auto& rm = pImpl->mEngine->getRenderableManager();
auto& tm = pImpl->mEngine->getTransformManager();
const NodeMap& nodeMap = asset->mInstances[0]->nodeMap;
// The purpose of the root node is to give the client a place for custom transforms.
// Since it is not part of the source model, it should be ignored when computing the
// bounding box.
TransformManager::Instance root = tm.getInstance(asset->getRoot());
std::vector<Entity> modelRoots(tm.getChildCount(root));
tm.getChildren(root, modelRoots.data(), modelRoots.size());
for (auto e : modelRoots) {
tm.setParent(tm.getInstance(e), 0);
}
auto computeBoundingBox = [](const cgltf_primitive* prim, Aabb* result) {
Aabb aabb;
for (cgltf_size slot = 0; slot < prim->attributes_count; slot++) {
const cgltf_attribute& attr = prim->attributes[slot];
const cgltf_accessor* accessor = attr.data;
const size_t dim = cgltf_num_components(accessor->type);
if (attr.type == cgltf_attribute_type_position && dim >= 3) {
std::vector<float> unpacked(accessor->count * dim);
cgltf_accessor_unpack_floats(accessor, unpacked.data(), unpacked.size());
for (cgltf_size i = 0, j = 0, n = accessor->count; i < n; ++i, j += dim) {
float3 pt(unpacked[j + 0], unpacked[j + 1], unpacked[j + 2]);
aabb.min = min(aabb.min, pt);
aabb.max = max(aabb.max, pt);
}
break;
}
}
*result = aabb;
};
struct Prim {
cgltf_primitive const* prim;
Skin const* skin;
Entity node;
};
auto computeBoundingBoxSkinned = [&](const Prim& prim, Aabb* result) {
FixedCapacityVector<float3> verts;
FixedCapacityVector<uint4> joints;
FixedCapacityVector<float4> weights;
for (cgltf_size slot = 0, n = prim.prim->attributes_count; slot < n; ++slot) {
const cgltf_attribute& attr = prim.prim->attributes[slot];
const cgltf_accessor& accessor = *attr.data;
switch (attr.type) {
case cgltf_attribute_type_position:
verts = FixedCapacityVector<float3>(accessor.count);
cgltf_accessor_unpack_floats(&accessor, &verts.data()->x, accessor.count * 3);
break;
case cgltf_attribute_type_joints: {
FixedCapacityVector<float4> tmp(accessor.count);
cgltf_accessor_unpack_floats(&accessor, &tmp.data()->x, accessor.count * 4);
joints = FixedCapacityVector<uint4>(accessor.count);
for (size_t i = 0, n = accessor.count; i < n; ++i) {
joints[i] = uint4(tmp[i]);
}
break;
}
case cgltf_attribute_type_weights:
weights = FixedCapacityVector<float4>(accessor.count);
cgltf_accessor_unpack_floats(&accessor, &weights.data()->x, accessor.count * 4);
break;
default:
break;
}
}
Aabb aabb;
TransformManager::Instance transformable = tm.getInstance(prim.node);
const mat4f inverseGlobalTransform = inverse(tm.getWorldTransform(transformable));
for (size_t i = 0, n = verts.size(); i < n; i++) {
float3 point = verts[i];
mat4f tmp = mat4f(0.0f);
for (size_t j = 0; j < 4; j++) {
size_t jointIndex = joints[i][j];
Entity jointEntity = prim.skin->joints[jointIndex];
mat4f globalJointTransform = tm.getWorldTransform(tm.getInstance(jointEntity));
mat4f inverseBindMatrix = prim.skin->inverseBindMatrices[jointIndex];
tmp += weights[i][j] * globalJointTransform * inverseBindMatrix;
}
mat4f skinMatrix = inverseGlobalTransform * tmp;
if (!pImpl->mNormalizeSkinningWeights) {
skinMatrix /= skinMatrix[3].w;
}
float3 skinnedPoint = (point.x * skinMatrix[0] +
point.y * skinMatrix[1] + point.z * skinMatrix[2] + skinMatrix[3]).xyz;
aabb.min = min(aabb.min, skinnedPoint);
aabb.max = max(aabb.max, skinnedPoint);
}
*result = aabb;
};
// Collect all mesh primitives that we wish to find bounds for. For each mesh primitive, we also
// collect the skin it is bound to (nullptr if not skinned) for bounds computation.
size_t primCount = 0;
for (auto iter : nodeMap) {
const cgltf_mesh* mesh = iter.first->mesh;
if (mesh) {
primCount += mesh->primitives_count;
}
}
auto primitives = FixedCapacityVector<Prim>::with_capacity(primCount);
const cgltf_skin* baseSkin = &asset->mSourceAsset->hierarchy->skins[0];
for (auto iter : nodeMap) {
const cgltf_mesh* mesh = iter.first->mesh;
if (mesh) {
for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) {
primitives.push_back({&mesh->primitives[index], nullptr, iter.second});
}
if (cgltf_skin* const skin = iter.first->skin; skin) {
primitives.back().skin = &asset->mInstances[0]->skins[skin - baseSkin];
}
}
}
// Kick off a bounding box job for every primitive.
FixedCapacityVector<Aabb> bounds(primitives.size());
JobSystem* js = &pImpl->mEngine->getJobSystem();
JobSystem::Job* parent = js->createJob();
for (size_t i = 0; i < primitives.size(); ++i) {
Aabb* result = &bounds[i];
if (pImpl->mIgnoreBindTransform || primitives[i].skin == nullptr) {
cgltf_primitive const* prim = primitives[i].prim;
js->run(jobs::createJob(*js, parent, [prim, result, computeBoundingBox] {
computeBoundingBox(prim, result);
}));
} else {
const Prim& prim = primitives[i];
js->run(jobs::createJob(*js, parent, [&prim, result, computeBoundingBoxSkinned] {
computeBoundingBoxSkinned(prim, result);
}));
}
}
js->runAndWait(parent);
// Compute the asset-level bounding box.
size_t primIndex = 0;
Aabb assetBounds;
for (auto iter : nodeMap) {
const cgltf_mesh* mesh = iter.first->mesh;
if (mesh) {
// Find the object-space bounds for the renderable by unioning the bounds of each prim.
Aabb aabb;
for (cgltf_size index = 0, nprims = mesh->primitives_count; index < nprims; ++index) {
Aabb primBounds = bounds[primIndex++];
aabb.min = min(aabb.min, primBounds.min);
aabb.max = max(aabb.max, primBounds.max);
}
auto renderable = rm.getInstance(iter.second);
rm.setAxisAlignedBoundingBox(renderable, Box().set(aabb.min, aabb.max));
// Transform this bounding box, then update the asset-level bounding box.
auto transformable = tm.getInstance(iter.second);
const mat4f worldTransform = tm.getWorldTransform(transformable);
const Aabb transformed = aabb.transform(worldTransform);
assetBounds.min = min(assetBounds.min, transformed.min);
assetBounds.max = max(assetBounds.max, transformed.max);
}
}
for (auto e : modelRoots) {
tm.setParent(tm.getInstance(e), root);
}
asset->mBoundingBox = assetBounds;
}
} // namespace filament::gltfio
+3 -3
View File
@@ -45,8 +45,8 @@ struct TangentsJob {
// The context of the procedure. These fields are not used by the procedure but are provided as
// a convenience to clients. You can think of this as a scratch space for clients.
struct Context {
filament::VertexBuffer* const vb;
filament::MorphTargetBuffer* const tb;
VertexBuffer* const vb;
MorphTargetBuffer* const tb;
const uint8_t slot;
};
@@ -54,7 +54,7 @@ struct TangentsJob {
// should remember to free it.
struct OutputParams {
cgltf_size vertexCount;
filament::math::short4* results;
math::short4* results;
};
// Clients might want to track the jobs in an array, so the arguments are bundled into a struct.

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