Merge branch 'rc/1.9.8' into release

This commit is contained in:
Benjamin Doherty
2020-11-09 09:28:58 -08:00
41 changed files with 607 additions and 274 deletions

View File

@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.9.7'
implementation 'com.google.android.filament:filament-android:1.9.8'
}
```
@@ -63,7 +63,7 @@ A much smaller alternative to `filamat-android` that can only generate OpenGL sh
iOS projects can use CocoaPods to install the latest release:
```
pod 'Filament', '~> 1.9.7'
pod 'Filament', '~> 1.9.8'
```
### Snapshots

View File

@@ -5,6 +5,18 @@ A new header is inserted each time a *tag* is created.
## Next release (main branch)
## v1.9.8
- Fix a few Fence-related bugs
- gltfio: add createInstance() to AssetLoader.
- gltfio: fix ASAN issue when consuming invalid animation.
- gltfio: do not segfault on invalid primitives.
- gltfio: add safety checks to getAnimator.
- gltfio: fix segfault when consuming invalid file.
- Vulkan: various internal refactoring and improvements
- mathio: add ostream operator for quaternions.
- Fix color grading not applied when dithering is off.
## v1.9.7
- Vulkan: improvements to the ReadPixels implementation.

View File

@@ -152,6 +152,32 @@ public class AssetLoader {
return new FilamentAsset(mEngine, nativeAsset);
}
/**
* Adds a new instance to an instanced asset.
*
* Use this with caution. It is more efficient to pre-allocate a max number of instances, and
* gradually add them to the scene as needed. Instances can also be "recycled" by removing and
* re-adding them to the scene.
*
* NOTE: destroyInstance() does not exist because gltfio favors flat arrays for storage of
* entity lists and instance lists, which would be slow to shift. We also wish to discourage
* create/destroy churn, as noted above.
*
* This cannot be called after FilamentAsset#releaseSourceData().
* This cannot be called on a non-instanced asset.
* Animation is not supported in new instances.
* See also AssetLoader#createInstancedAsset().
*/
@Nullable
@SuppressWarnings("unused")
public FilamentInstance createInstance(@NonNull FilamentAsset asset) {
long nativeInstance = nCreateInstance(mNativeObject, asset.getNativeObject());
if (nativeInstance == 0) {
return null;
}
return new FilamentInstance(nativeInstance);
}
/**
* Allows clients to enable diagnostic shading on newly-loaded assets.
*/
@@ -175,6 +201,7 @@ public class AssetLoader {
private static native long nCreateAssetFromJson(long nativeLoader, Buffer buffer, int remaining);
private static native long nCreateInstancedAsset(long nativeLoader, Buffer buffer, int remaining,
long[] nativeInstances);
private static native long nCreateInstance(long nativeLoader, long nativeAsset);
private static native void nEnableDiagnostics(long nativeLoader, boolean enable);
private static native void nDestroyAsset(long nativeLoader, long nativeAsset);
}

View File

@@ -197,7 +197,11 @@ public class FilamentAsset {
if (mAnimator != null) {
return mAnimator;
}
mAnimator = new Animator(nGetAnimator(getNativeObject()));
long nativeAnimator = nGetAnimator(getNativeObject());
if (nativeAnimator == 0) {
throw new IllegalStateException("Unable to create animator");
}
mAnimator = new Animator(nativeAnimator);
return mAnimator;
}
@@ -215,6 +219,7 @@ public class FilamentAsset {
*
* This should only be called after ResourceLoader#loadResources().
* If using Animator, this should be called after getAnimator().
* If this is an instanced asset, this prevents creation of new instances.
*/
public void releaseSourceData() {
nReleaseSourceData(mNativeObject);

View File

@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.9.7
VERSION_NAME=1.9.8
POM_DESCRIPTION=Real-time physically based rendering engine for Android.

View File

@@ -684,6 +684,7 @@ void MetalFence::onSignal(MetalFenceSignalBlock block) {
FenceStatus MetalFence::wait(uint64_t timeoutNs) {
if (@available(macOS 10.14, iOS 12, *)) {
std::unique_lock<std::mutex> guard(state->mutex);
timeoutNs = std::min(timeoutNs, (uint64_t) std::chrono::nanoseconds::max().count());
while (state->status == FenceStatus::TIMEOUT_EXPIRED) {
if (timeoutNs == 0 ||
state->cv.wait_for(guard, std::chrono::nanoseconds(timeoutNs)) ==

View File

@@ -281,7 +281,7 @@ bool VulkanBinder::getOrCreatePipeline(VkPipeline* pipeline) noexcept {
pipelineCreateInfo.pDynamicState = &dynamicState;
// Filament assumes consistent blend state across all color attachments.
mColorBlendState.attachmentCount = mPipelineKey.rasterState.getColorTargetCount;
mColorBlendState.attachmentCount = mPipelineKey.rasterState.colorTargetCount;
for (auto& target : mColorBlendAttachments) {
target = mPipelineKey.rasterState.blending;
}
@@ -333,7 +333,7 @@ void VulkanBinder::bindRasterState(const RasterState& rasterState) noexcept {
VkPipelineMultisampleStateCreateInfo& ms0 = mPipelineKey.rasterState.multisampling;
const VkPipelineMultisampleStateCreateInfo& ms1 = rasterState.multisampling;
if (
mPipelineKey.rasterState.getColorTargetCount != rasterState.getColorTargetCount ||
mPipelineKey.rasterState.colorTargetCount != rasterState.colorTargetCount ||
raster0.polygonMode != raster1.polygonMode ||
raster0.cullMode != raster1.cullMode ||
raster0.frontFace != raster1.frontFace ||

View File

@@ -103,7 +103,7 @@ public:
VkPipelineColorBlendAttachmentState blending;
VkPipelineDepthStencilStateCreateInfo depthStencil;
VkPipelineMultisampleStateCreateInfo multisampling;
uint32_t getColorTargetCount;
uint32_t colorTargetCount;
};
static_assert(std::is_pod<RasterState>::value, "RasterState must be a POD for fast hashing.");

View File

@@ -31,10 +31,13 @@
#pragma clang diagnostic pop
#include "VulkanContext.h"
#include "VulkanHandles.h"
#include "VulkanUtility.h"
#include <utils/Panic.h>
#define FILAMENT_VULKAN_CHECK_BLIT_FORMAT 0
namespace filament {
namespace backend {
@@ -831,5 +834,120 @@ VkImageLayout getTextureLayout(TextureUsage usage) {
return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
static void blit(VkImageAspectFlags aspect, VkFilter filter, VulkanContext* context,
const VulkanRenderTarget* srcTarget, VulkanAttachment src, VulkanAttachment dst,
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2], VkCommandBuffer cmdbuffer) {
const VkImageBlit blitRegions[1] = {{
.srcSubresource = { aspect, src.level, src.layer, 1 },
.srcOffsets = { srcRect[0], srcRect[1] },
.dstSubresource = { aspect, dst.level, dst.layer, 1 },
.dstOffsets = { dstRect[0], dstRect[1] }
}};
const VkExtent2D srcExtent = srcTarget->getExtent();
const VkImageResolve resolveRegions[1] = {{
.srcSubresource = { aspect, src.level, src.layer, 1 },
.srcOffset = srcRect[0],
.dstSubresource = { aspect, dst.level, dst.layer, 1 },
.dstOffset = dstRect[0],
.extent = { srcExtent.width, srcExtent.height, 1 }
}};
VulkanTexture::transitionImageLayout(cmdbuffer, src.image, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, src.level, 1, 1, aspect);
VulkanTexture::transitionImageLayout(cmdbuffer, dst.image, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, dst.level, 1, 1, aspect);
if (src.texture && src.texture->samples > 1 && dst.texture && dst.texture->samples == 1) {
vkCmdResolveImage(cmdbuffer, src.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, resolveRegions);
} else {
vkCmdBlitImage(cmdbuffer, src.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blitRegions, filter);
}
if (src.texture) {
VulkanTexture::transitionImageLayout(cmdbuffer, src.image, VK_IMAGE_LAYOUT_UNDEFINED,
getTextureLayout(src.texture->usage), src.level, 1, 1, aspect);
} else if (!context->currentSurface->headlessQueue) {
VulkanTexture::transitionImageLayout(cmdbuffer, src.image, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, src.level, 1, 1, aspect);
}
// Determine the desired texture layout for the destination while ensuring that the default
// render target is supported, which has no associated texture.
const VkImageLayout desiredLayout = dst.texture ? getTextureLayout(dst.texture->usage) :
getSwapContext(*context).attachment.layout;
VulkanTexture::transitionImageLayout(cmdbuffer, dst.image, VK_IMAGE_LAYOUT_UNDEFINED,
desiredLayout, dst.level, 1, 1, aspect);
}
void blitDepth(VulkanContext* context, const VulkanRenderTarget* dstTarget,
const VkOffset3D dstRect[2], const VulkanRenderTarget* srcTarget,
const VkOffset3D srcRect[2]) {
const VulkanAttachment src = srcTarget->getDepth();
const VulkanAttachment dst = dstTarget->getDepth();
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
#if FILAMENT_VULKAN_CHECK_BLIT_FORMAT
const VkPhysicalDevice gpu = context->physicalDevice;
VkFormatProperties info;
vkGetPhysicalDeviceFormatProperties(gpu, src.format, &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
"Depth format is not blittable")) {
return;
}
vkGetPhysicalDeviceFormatProperties(gpu, dst.format, &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
"Depth format is not blittable")) {
return;
}
#endif
if (!context->currentCommands) {
VkCommandBuffer cmdbuf = acquireWorkCommandBuffer(*context);
blit(aspect, VK_FILTER_NEAREST, context, srcTarget, src, dst, srcRect, dstRect, cmdbuf);
flushWorkCommandBuffer(*context);
} else {
VkCommandBuffer cmdbuf = context->currentCommands->cmdbuffer;
blit(aspect, VK_FILTER_NEAREST, context, srcTarget, src, dst, srcRect, dstRect, cmdbuf);
}
}
void blitColor(VulkanContext* context, const VulkanRenderTarget* dstTarget,
const VkOffset3D dstRect[2], const VulkanRenderTarget* srcTarget,
const VkOffset3D srcRect[2], VkFilter filter, int targetIndex) {
const VulkanAttachment src = srcTarget->getColor(targetIndex);
const VulkanAttachment dst = dstTarget->getColor(0);
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT;
#if FILAMENT_VULKAN_CHECK_BLIT_FORMAT
const VkPhysicalDevice gpu = context->physicalDevice;
VkFormatProperties info;
vkGetPhysicalDeviceFormatProperties(gpu, src.format, &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
"Source format is not blittable")) {
return;
}
vkGetPhysicalDeviceFormatProperties(gpu, dst.format, &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
"Destination format is not blittable")) {
return;
}
#endif
if (!context->currentCommands) {
VkCommandBuffer cmdbuf = acquireWorkCommandBuffer(*context);
blit(aspect, filter, context, srcTarget, src, dst, srcRect, dstRect, cmdbuf);
flushWorkCommandBuffer(*context);
} else {
VkCommandBuffer cmdbuf = context->currentCommands->cmdbuffer;
blit(aspect, filter, context, srcTarget, src, dst, srcRect, dstRect, cmdbuf);
}
}
} // namespace filament
} // namespace backend

View File

@@ -47,6 +47,7 @@ constexpr VkAllocationCallbacks* VKALLOC = nullptr;
constexpr static const int VK_REQUIRED_VERSION_MAJOR = 1;
constexpr static const int VK_REQUIRED_VERSION_MINOR = 0;
struct VulkanRenderTarget;
struct VulkanSurfaceContext;
struct VulkanTexture;
@@ -176,6 +177,14 @@ void flushWorkCommandBuffer(VulkanContext& context);
void createFinalDepthBuffer(VulkanContext& context, VulkanSurfaceContext& sc, VkFormat depthFormat);
VkImageLayout getTextureLayout(TextureUsage usage);
void blitDepth(VulkanContext* context, const VulkanRenderTarget* dstTarget,
const VkOffset3D dstRect[2], const VulkanRenderTarget* srcTarget,
const VkOffset3D srcRect[2]);
void blitColor(VulkanContext* context, const VulkanRenderTarget* dstTarget,
const VkOffset3D dstRect[2], const VulkanRenderTarget* srcTarget,
const VkOffset3D srcRect[2], VkFilter filter, int index);
} // namespace filament
} // namespace backend

View File

@@ -1461,104 +1461,41 @@ void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, V
Handle<HwRenderTarget> src, Viewport srcRect, SamplerMagFilter filter) {
VulkanRenderTarget* dstTarget = handle_cast<VulkanRenderTarget>(mHandleMap, dst);
VulkanRenderTarget* srcTarget = handle_cast<VulkanRenderTarget>(mHandleMap, src);
const int targetIndex = 0; // TODO: support MRT in blit
// In debug builds, verify that the two render targets have blittable formats.
#ifndef NDEBUG
const VkPhysicalDevice gpu = mContext.physicalDevice;
VkFormatProperties info;
vkGetPhysicalDeviceFormatProperties(gpu, srcTarget->getColor(targetIndex).format, &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
"Source format is not blittable")) {
return;
}
vkGetPhysicalDeviceFormatProperties(gpu, dstTarget->getColor(targetIndex).format, &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
"Destination format is not blittable")) {
return;
}
if (any(buffers & TargetBufferFlags::DEPTH)) {
utils::slog.w << "Depth blits are not yet supported." << utils::io::endl;
}
#endif
VkFilter vkfilter = filter == SamplerMagFilter::NEAREST ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
const VkExtent2D srcExtent = srcTarget->getExtent();
const VkExtent2D dstExtent = dstTarget->getExtent();
const int32_t srcLeft = std::min(srcRect.left, (int32_t) srcExtent.width);
const int32_t srcBottom = std::min(srcRect.bottom, (int32_t) srcExtent.height);
const int32_t srcRight = std::min(srcRect.left + srcRect.width, srcExtent.width);
const int32_t srcTop = std::min(srcRect.bottom + srcRect.height, srcExtent.height);
const uint32_t srcLevel = srcTarget->getColor(targetIndex).level;
const uint32_t srcLayer = srcTarget->getColor(targetIndex).layer;
const VkOffset3D srcOffsets[2] = { { srcLeft, srcBottom, 0 }, { srcRight, srcTop, 1 }};
const VkExtent2D dstExtent = dstTarget->getExtent();
const int32_t dstLeft = std::min(dstRect.left, (int32_t) dstExtent.width);
const int32_t dstBottom = std::min(dstRect.bottom, (int32_t) dstExtent.height);
const int32_t dstRight = std::min(dstRect.left + dstRect.width, dstExtent.width);
const int32_t dstTop = std::min(dstRect.bottom + dstRect.height, dstExtent.height);
const uint32_t dstLevel = dstTarget->getColor(targetIndex).level;
const uint32_t dstLayer = dstTarget->getColor(targetIndex).layer;
const VkOffset3D dstOffsets[2] = { { dstLeft, dstBottom, 0 }, { dstRight, dstTop, 1 }};
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT;
if (any(buffers & TargetBufferFlags::DEPTH) && srcTarget->hasDepth() && dstTarget->hasDepth()) {
blitDepth(&mContext, dstTarget, dstOffsets, srcTarget, srcOffsets);
}
const VkImageBlit blitRegions[1] = {{
.srcSubresource = { aspect, srcLevel, srcLayer, 1 },
.srcOffsets = { { srcLeft, srcBottom, 0 }, { srcRight, srcTop, 1 }},
.dstSubresource = { aspect, dstLevel, dstLayer, 1 },
.dstOffsets = { { dstLeft, dstBottom, 0 }, { dstRight, dstTop, 1 }}
}};
if (any(buffers & TargetBufferFlags::COLOR0)) {
blitColor(&mContext, dstTarget, dstOffsets, srcTarget, srcOffsets, vkfilter, 0);
}
const VkImageResolve resolveRegions[1] = {{
.srcSubresource = { aspect, srcLevel, srcLayer, 1 },
.srcOffset = { srcLeft, srcBottom, 0 },
.dstSubresource = { aspect, dstLevel, dstLayer, 1 },
.dstOffset = { dstLeft, dstBottom, 0 },
.extent = { srcExtent.width, srcExtent.height, 1 }
}};
if (any(buffers & TargetBufferFlags::COLOR1)) {
blitColor(&mContext, dstTarget, dstOffsets, srcTarget, srcOffsets, vkfilter, 1);
}
const VulkanTexture* srcTexture = srcTarget->getColor(targetIndex).texture;
const VulkanTexture* dstTexture = dstTarget->getColor(targetIndex).texture;
if (any(buffers & TargetBufferFlags::COLOR2)) {
blitColor(&mContext, dstTarget, dstOffsets, srcTarget, srcOffsets, vkfilter, 2);
}
auto vkblit = [=](VkCommandBuffer cmdbuffer) {
VkImage srcImage = srcTarget->getColor(targetIndex).image;
VulkanTexture::transitionImageLayout(cmdbuffer, srcImage, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, srcLevel, 1, 1, aspect);
VkImage dstImage = dstTarget->getColor(targetIndex).image;
VulkanTexture::transitionImageLayout(cmdbuffer, dstImage, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, dstLevel, 1, 1, aspect);
if (srcTexture && srcTexture->samples > 1 && dstTexture && dstTexture->samples == 1) {
vkCmdResolveImage(cmdbuffer, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstImage,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, resolveRegions);
} else {
vkCmdBlitImage(cmdbuffer, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstImage,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blitRegions,
filter == SamplerMagFilter::NEAREST ? VK_FILTER_NEAREST : VK_FILTER_LINEAR);
}
if (srcTexture) {
VulkanTexture::transitionImageLayout(cmdbuffer, srcImage, VK_IMAGE_LAYOUT_UNDEFINED,
getTextureLayout(srcTexture->usage), srcLevel, 1, 1, aspect);
} else if (!mContext.currentSurface->headlessQueue) {
VulkanTexture::transitionImageLayout(cmdbuffer, srcImage, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, srcLevel, 1, 1, aspect);
}
// Determine the desired texture layout for the destination while ensuring that the default
// render target is supported, which has no associated texture.
const VkImageLayout desiredLayout = dstTexture ? getTextureLayout(dstTexture->usage) :
getSwapContext(mContext).attachment.layout;
VulkanTexture::transitionImageLayout(cmdbuffer, dstImage, VK_IMAGE_LAYOUT_UNDEFINED,
desiredLayout, dstLevel, 1, 1, aspect);
};
if (!mContext.currentCommands) {
vkblit(acquireWorkCommandBuffer(mContext));
flushWorkCommandBuffer(mContext);
} else {
vkblit(mContext.currentCommands->cmdbuffer);
if (any(buffers & TargetBufferFlags::COLOR3)) {
blitColor(&mContext, dstTarget, dstOffsets, srcTarget, srcOffsets, vkfilter, 3);
}
}
@@ -1622,7 +1559,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
vkraster.depthBiasConstantFactor = depthOffset.constant;
vkraster.depthBiasSlopeFactor = depthOffset.slope;
mContext.rasterState.getColorTargetCount = rt->getColorTargetCount();
mContext.rasterState.colorTargetCount = rt->getColorTargetCount(mContext.currentRenderPass);
VulkanBinder::ProgramBundle shaderHandles = program->bundle;

View File

@@ -120,6 +120,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
return iter->second.handle;
}
const bool isSwapChain = config.colorLayout[0] == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
const bool hasSubpasses = config.subpassMask != 0;
// Set up some const aliases for terseness.
const VkAttachmentLoadOp kClear = VK_ATTACHMENT_LOAD_OP_CLEAR;
@@ -138,7 +139,12 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
struct { VkImageLayout subpass, initial, final; } colorLayouts[MRT::TARGET_COUNT];
if (isSwapChain) {
colorLayouts[0].subpass = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorLayouts[0].initial = discard ? VK_IMAGE_LAYOUT_UNDEFINED : colorLayouts[0].subpass;
// It is legal to always use UNDEFINED for "initial", but we wish to avoid warnings
// when the load op is LOAD.
colorLayouts[0].initial = discard ? VK_IMAGE_LAYOUT_UNDEFINED :
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorLayouts[0].final = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
} else {
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
@@ -149,7 +155,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
}
VkAttachmentReference inputAttachmentRef[MRT::TARGET_COUNT] = {};
VkAttachmentReference colorAttachmentRef[MRT::TARGET_COUNT] = {};
VkAttachmentReference colorAttachmentRefs[2][MRT::TARGET_COUNT] = {};
VkAttachmentReference resolveAttachmentRef[MRT::TARGET_COUNT] = {};
VkAttachmentReference depthAttachmentRef = {};
@@ -157,14 +163,15 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
VkSubpassDescription subpasses[2] = {{
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.pColorAttachments = colorAttachmentRef,
.pInputAttachments = nullptr,
.pColorAttachments = colorAttachmentRefs[0],
.pResolveAttachments = resolveAttachmentRef,
.pDepthStencilAttachment = hasDepth ? &depthAttachmentRef : nullptr
},
{
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.pInputAttachments = inputAttachmentRef,
.pColorAttachments = colorAttachmentRef,
.pColorAttachments = colorAttachmentRefs[1],
.pResolveAttachments = resolveAttachmentRef,
.pDepthStencilAttachment = hasDepth ? &depthAttachmentRef : nullptr
}};
@@ -174,24 +181,6 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
// Note that this needs to have the same ordering as the corollary array in getFramebuffer.
VkAttachmentDescription attachments[MRT::TARGET_COUNT + MRT::TARGET_COUNT + 1] = {};
// Determine the number of color attachments based on whether the format has been initialized.
int colorAttachmentCount = 0;
for (VkFormat format : config.colorFormat) {
if (format != VK_FORMAT_UNDEFINED) {
++colorAttachmentCount;
}
}
subpasses[0].colorAttachmentCount = colorAttachmentCount;
subpasses[1].colorAttachmentCount = colorAttachmentCount;
// Nulling out the zero-sized lists is necessary to avoid VK_ERROR_OUT_OF_HOST_MEMORY on Adreno.
if (colorAttachmentCount == 0) {
subpasses[0].pColorAttachments = nullptr;
subpasses[0].pResolveAttachments = nullptr;
subpasses[1].pColorAttachments = nullptr;
subpasses[1].pResolveAttachments = nullptr;
}
// We support 2 subpasses, which means we need to supply 1 dependency struct.
VkSubpassDependency dependencies[1] = {{
.srcSubpass = 0,
@@ -207,32 +196,56 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.attachmentCount = 0u,
.pAttachments = attachments,
.subpassCount = config.subpassMask ? 2u : 1u,
.subpassCount = hasSubpasses ? 2u : 1u,
.pSubpasses = subpasses,
.dependencyCount = config.subpassMask ? 1u : 0u,
.dependencyCount = hasSubpasses ? 1u : 0u,
.pDependencies = dependencies
};
int attachmentIndex = 0;
// Populate the Color Attachments.
VkAttachmentReference* pColorAttachment = colorAttachmentRef;
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
if (config.colorFormat[i] == VK_FORMAT_UNDEFINED) {
continue;
}
TargetBufferFlags flag = TargetBufferFlags(int(TargetBufferFlags::COLOR0) << i);
bool clear = any(config.clear & flag);
bool discard = any(config.discardStart & flag);
if (config.subpassMask & (1 << i)) {
int subpassInputIndex = subpasses[1].inputAttachmentCount++;
inputAttachmentRef[subpassInputIndex].layout = colorLayouts[i].subpass;
inputAttachmentRef[subpassInputIndex].attachment = attachmentIndex;
const VkImageLayout subpassLayout = colorLayouts[i].subpass;
uint32_t index;
if (!hasSubpasses) {
index = subpasses[0].colorAttachmentCount++;
colorAttachmentRefs[0][index].layout = subpassLayout;
colorAttachmentRefs[0][index].attachment = attachmentIndex;
} else {
// The Driver API consolidates all color attachments from the first and second subpasses
// into a single list, and uses a bitmask to mark attachments that belong only to the
// second subpass and should be available as inputs. All color attachments in the first
// subpass are automatically made available to the second subpass.
// If there are subpasses, we require the input attachment to be the first attachment.
// Breaking this assumption would likely require enhancements to the Driver API in order
// to supply Vulkan with all the information needed.
assert(config.subpassMask == 1);
if (config.subpassMask & (1 << i)) {
index = subpasses[0].colorAttachmentCount++;
colorAttachmentRefs[0][index].layout = subpassLayout;
colorAttachmentRefs[0][index].attachment = attachmentIndex;
index = subpasses[1].inputAttachmentCount++;
inputAttachmentRef[index].layout = subpassLayout;
inputAttachmentRef[index].attachment = attachmentIndex;
}
index = subpasses[1].colorAttachmentCount++;
colorAttachmentRefs[1][index].layout = subpassLayout;
colorAttachmentRefs[1][index].attachment = attachmentIndex;
}
pColorAttachment->layout = colorLayouts[i].subpass;
pColorAttachment->attachment = attachmentIndex;
++pColorAttachment;
const TargetBufferFlags flag = TargetBufferFlags(int(TargetBufferFlags::COLOR0) << i);
const bool clear = any(config.clear & flag);
const bool discard = any(config.discardStart & flag);
attachments[attachmentIndex++] = {
.format = config.colorFormat[i],
@@ -246,6 +259,14 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
};
}
// Nulling out the zero-sized lists is necessary to avoid VK_ERROR_OUT_OF_HOST_MEMORY on Adreno.
if (subpasses[0].colorAttachmentCount == 0) {
subpasses[0].pColorAttachments = nullptr;
subpasses[0].pResolveAttachments = nullptr;
subpasses[1].pColorAttachments = nullptr;
subpasses[1].pResolveAttachments = nullptr;
}
// Populate the Resolve Attachments.
VkAttachmentReference* pResolveAttachment = resolveAttachmentRef;
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
@@ -304,7 +325,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
utils::slog.d << "Created render pass " << renderPass << " with "
<< "samples = " << int(config.samples) << ", "
<< "depth = " << (hasDepth ? 1 : 0) << ", "
<< "colorAttachmentCount = " << colorAttachmentCount
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount
<< utils::io::endl;
#endif

View File

@@ -77,7 +77,7 @@ VulkanProgram::VulkanProgram(VulkanContext& context, const Program& builder) noe
#if FILAMENT_VULKAN_VERBOSE
utils::slog.d << "Created VulkanProgram " << builder.getName().c_str()
<< ", variant = (" << utils::io::hex
<< builder.getVariant() << utils::io::dec << "), "
<< (int) builder.getVariant() << utils::io::dec << "), "
<< "shaders = (" << bundle.vertex << ", " << bundle.fragment << ")"
<< utils::io::endl;
#endif
@@ -374,14 +374,18 @@ VulkanAttachment VulkanRenderTarget::getMsaaDepth() const {
return mMsaaDepthAttachment;
}
int VulkanRenderTarget::getColorTargetCount() const {
int VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) const {
if (!mOffscreen) {
return 1;
}
int count = 0;
for (int i = 0; i < MRT::TARGET_COUNT; i++) {
if (mColor[i].format != VK_FORMAT_UNDEFINED) {
++count;
if (mColor[i].format == VK_FORMAT_UNDEFINED) {
continue;
}
// NOTE: This must be consistent with VkRenderPass construction (see VulkanFboCache).
if (!(pass.subpassMask & (1 << i)) || pass.currentSubpass == 1) {
count++;
}
}
return count;
@@ -769,6 +773,7 @@ VkImageView VulkanTexture::getImageView(int level, int layer, VkImageAspectFlags
}
// TODO: replace the last 4 args with VkImageSubresourceRange
// TODO: replace this function with a flexible thin wrapper over image barrier creation
void VulkanTexture::transitionImageLayout(VkCommandBuffer cmd, VkImage image,
VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t miplevel,
uint32_t layerCount, uint32_t levelCount, VkImageAspectFlags aspect) {
@@ -812,6 +817,7 @@ void VulkanTexture::transitionImageLayout(VkCommandBuffer cmd, VkImage image,
// We support PRESENT as a target layout to allow blitting from the swap chain.
// See also makeSwapChainPresentable().
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.dstAccessMask = 0;

View File

@@ -58,9 +58,10 @@ struct VulkanRenderTarget : private HwRenderTarget {
VulkanAttachment getMsaaColor(int target) const;
VulkanAttachment getDepth() const;
VulkanAttachment getMsaaDepth() const;
int getColorTargetCount() const;
int getColorTargetCount(const VulkanRenderPass& pass) const;
bool invalidate();
uint8_t getSamples() const { return mSamples; }
bool hasDepth() const { return mDepth.format != VK_FORMAT_UNDEFINED; }
private:
VulkanAttachment mColor[MRT::TARGET_COUNT] = {};
VulkanAttachment mDepth = {};

View File

@@ -129,7 +129,7 @@ TEST_F(BackendTest, ReadPixels) {
const size_t width = readRect.width, height = readRect.height;
LinearImage image(width, height, 4);
if (format == PixelDataFormat::RGBA && type == PixelDataType::UBYTE) {
image = toLinear<uint8_t>(width, height, width * 4, (uint8_t*) pixelData);
image = toLinearWithAlpha<uint8_t>(width, height, width * 4, (uint8_t*) pixelData);
}
if (format == PixelDataFormat::RGBA && type == PixelDataType::FLOAT) {
memcpy(image.getPixelRef(), pixelData, width * height * sizeof(math::float4));

View File

@@ -71,6 +71,7 @@ FenceStatus FFence::waitAndDestroy(FFence* fence, Mode mode) noexcept {
UTILS_NOINLINE
FenceStatus FFence::wait(Mode mode, uint64_t timeout) noexcept {
ASSERT_PRECONDITION(UTILS_HAS_THREADING || timeout == 0, "Non-zero timeout requires threads.");
timeout = std::min(timeout, (uint64_t) ns::max().count());
FEngine& engine = mEngine;

View File

@@ -119,8 +119,8 @@ fragment {
#else
postProcess.color = dithered;
#endif
postProcess.tonemappedOutput = postProcess.color;
}
postProcess.tonemappedOutput = postProcess.color;
}
}

View File

@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.9.7"
spec.version = "1.9.8"
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.9.7/filament-v1.9.7-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.9.8/filament-v1.9.8-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {

View File

@@ -154,19 +154,19 @@ public:
/**
* Consumes the contents of a glTF 2.0 file and produces a primary asset with one or more
* instances.
* 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 automatically freed when the primary asset is freed.
* 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.
*
* Light components are not instanced, they belong only to the primary asset.
*
* Clients must use ResourceLoader to load resources on the primary asset.
*
* The entity accessors and renderable stack in the returned FilamentAsset represent the union
* of all entities across all instances. Use the individual FilamentInstance objects to access
* each partition of entities. Similarly, the Animator in the primary asset controls all
* The entity accessor and renderable stack API in the primary asset can be used to control the
* union of all instances. The individual FilamentInstance objects can be used to access each
* instance's partition of entities. Similarly, the Animator in the primary asset controls all
* instances. To animate instances individually, use FilamentInstance::getAnimator().
*
* @param bytes the contents of a glTF 2.0 file (JSON or GLB)
@@ -178,6 +178,24 @@ public:
FilamentAsset* createInstancedAsset(const uint8_t* bytes, uint32_t numBytes,
FilamentInstance** instances, size_t numInstances);
/**
* Adds a new instance to an instanced asset.
*
* Use this with caution. It is more efficient to pre-allocate a max number of instances, and
* gradually add them to the scene as needed. Instances can also be "recycled" by removing and
* re-adding them to the scene.
*
* NOTE: destroyInstance() does not exist because gltfio favors flat arrays for storage of
* entity lists and instance lists, which would be slow to shift. We also wish to discourage
* create/destroy churn, as noted above.
*
* This cannot be called after FilamentAsset::releaseSourceData().
* This cannot be called on a non-instanced asset.
* Animation is not supported in new instances.
* See also AssetLoader::createInstancedAsset().
*/
FilamentInstance* createInstance(FilamentAsset* primary);
/**
* Takes a pointer to an opaque pipeline object and returns a bundle of Filament objects.
*

View File

@@ -215,6 +215,7 @@ public:
*
* This should only be called after ResourceLoader::loadResources().
* If using Animator, this should be called after getAnimator().
* If this is an instanced asset, this prevents creation of new instances.
*/
void releaseSourceData() noexcept;

View File

@@ -139,13 +139,49 @@ static void setTransformType(const cgltf_animation_channel& src, Channel& dst) {
}
}
static bool validateAnimation(const cgltf_animation& anim) {
for (cgltf_size j = 0; j < anim.channels_count; ++j) {
const cgltf_animation_channel& channel = anim.channels[j];
const cgltf_animation_sampler* sampler = channel.sampler;
if (!channel.target_node) {
continue;
}
if (!channel.sampler) {
return false;
}
cgltf_size components = 1;
if (channel.target_path == cgltf_animation_path_type_weights) {
if (!channel.target_node->mesh || !channel.target_node->mesh->primitives_count) {
return false;
}
components = channel.target_node->mesh->primitives[0].targets_count;
}
cgltf_size values = sampler->interpolation == cgltf_interpolation_type_cubic_spline ? 3 : 1;
if (sampler->input->count * components * values != sampler->output->count) {
return false;
}
}
return true;
}
Animator::Animator(FFilamentAsset* asset, FFilamentInstance* instance) {
assert(asset->mResourcesLoaded && !asset->mIsReleased);
mImpl = new AnimatorImpl();
mImpl->asset = asset;
mImpl->instance = instance;
mImpl->renderableManager = &asset->mEngine->getRenderableManager();
mImpl->transformManager = &asset->mEngine->getTransformManager();
const cgltf_data* srcAsset = asset->mSourceAsset;
const cgltf_animation* srcAnims = srcAsset->animations;
for (cgltf_size i = 0, len = srcAsset->animations_count; i < len; ++i) {
const cgltf_animation& anim = srcAnims[i];
if (!validateAnimation(anim)) {
slog.e << "Disabling animation due to validation failure." << io::endl;
return;
}
}
auto addChannels = [](const NodeMap& nodeMap, const cgltf_animation& srcAnim, Animation& dst) {
cgltf_animation_channel* srcChannels = srcAnim.channels;
cgltf_animation_sampler* srcSamplers = srcAnim.samplers;
@@ -162,8 +198,6 @@ Animator::Animator(FFilamentAsset* asset, FFilamentInstance* instance) {
};
// Loop over the glTF animation definitions.
const cgltf_data* srcAsset = asset->mSourceAsset;
const cgltf_animation* srcAnims = srcAsset->animations;
mImpl->animations.resize(srcAsset->animations_count);
for (cgltf_size i = 0, len = srcAsset->animations_count; i < len; ++i) {
const cgltf_animation& srcAnim = srcAnims[i];
@@ -189,7 +223,7 @@ Animator::Animator(FFilamentAsset* asset, FFilamentInstance* instance) {
// Import each glTF channel into a custom data structure.
if (instance) {
addChannels(instance->nodeMap, srcAnim, dstAnim);
} else if (asset->mInstances.empty()) {
} else if (!asset->isInstanced()) {
addChannels(asset->mNodeMap, srcAnim, dstAnim);
} else {
for (FFilamentInstance* instance : asset->mInstances) {
@@ -379,7 +413,7 @@ void Animator::updateBoneMatrices() {
if (mImpl->instance) {
update(mImpl->instance->skins, mImpl->boneMatrices);
} else if (mImpl->asset->mInstances.empty()) {
} else if (!mImpl->asset->isInstanced()) {
update(mImpl->asset->mSkins, mImpl->boneMatrices);
} else {
for (FFilamentInstance* instance : mImpl->asset->mInstances) {

View File

@@ -60,33 +60,6 @@ namespace gltfio {
static const auto FREE_CALLBACK = [](void* mem, size_t, void*) { free(mem); };
// MeshCache
// ---------
// 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.
struct Primitive {
VertexBuffer* vertices = nullptr;
IndexBuffer* indices = nullptr;
Aabb aabb; // object-space bounding box
};
using MeshCache = tsl::robin_map<const cgltf_mesh*, std::vector<Primitive>>;
// MatInstanceCache
// ----------------
// Each glTF material definition corresponds to a single filament::MaterialInstance, which are
// cached here in the loader. 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 {
MaterialInstance* instance;
UvMap uvmap;
};
using MatInstanceCache = tsl::robin_map<intptr_t, MaterialEntry>;
// 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
@@ -124,6 +97,7 @@ struct FAssetLoader : public AssetLoader {
FFilamentAsset* createAssetFromBinary(const uint8_t* bytes, uint32_t nbytes);
FFilamentAsset* createInstancedAsset(const uint8_t* bytes, uint32_t numBytes,
FilamentInstance** instances, size_t numInstances);
FilamentInstance* createInstance(FFilamentAsset* primary);
bool createAssets(const uint8_t* bytes, uint32_t numBytes, FilamentAsset** assets,
size_t numAssets);
@@ -149,6 +123,7 @@ struct FAssetLoader : public AssetLoader {
}
void createAsset(const cgltf_data* srcAsset, size_t numInstances);
FilamentInstance* createInstance(FFilamentAsset* primary, const cgltf_scene* scene);
void createEntity(const cgltf_node* node, Entity parent, bool enableLight,
FFilamentInstance* instance);
void createRenderable(const cgltf_node* node, Entity entity, const char* name);
@@ -171,10 +146,8 @@ struct FAssetLoader : public AssetLoader {
MaterialProvider* mMaterials;
Engine* mEngine;
// The loader owns a few transient mappings used only for the current asset being loaded.
// Transient state used only for the asset currently being loaded:
FFilamentAsset* mResult;
MatInstanceCache mMatInstanceCache;
MeshCache mMeshCache;
const char* mDefaultNodeName;
bool mError = false;
bool mDiagnosticsEnabled = false;
@@ -246,6 +219,26 @@ FFilamentAsset* FAssetLoader::createInstancedAsset(const uint8_t* bytes, uint32_
return mResult;
}
FilamentInstance* FAssetLoader::createInstance(FFilamentAsset* primary) {
if (primary->mIsReleased) {
slog.e << "Source data has been released; asset is frozen." << io::endl;
return nullptr;
}
if (!primary->isInstanced()) {
slog.e << "Cannot add an instance to a non-instanced asset." << io::endl;
return nullptr;
}
const cgltf_data* srcAsset = primary->mSourceAsset;
const cgltf_scene* scene = srcAsset->scene ? srcAsset->scene : srcAsset->scenes;
if (!scene) {
slog.e << "There is no scene in the asset." << io::endl;
return nullptr;
}
FilamentInstance* instance = createInstance(primary, scene);
primary->mDependencyGraph.refinalize();
return instance;
}
void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances) {
SYSTRACE_CALL();
#if !GLTFIO_DRACO_SUPPORTED
@@ -280,36 +273,17 @@ void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances)
createEntity(nodes[i], mResult->mRoot, true, nullptr);
}
} else {
// Create a separate entity hierarchy for each instance. Note that mMeshCache (vertex
// buffers and index buffers) and mMatInstanceCache (materials and textures) help avoid
// Create a separate entity hierarchy for each instance. Note that MeshCache (vertex
// buffers and index buffers) and MatInstanceCache (materials and textures) help avoid
// needless duplication of resources.
for (size_t index = 0; index < numInstances; ++index) {
// Create a root node within each instance that is a child of the primary root.
auto rootTransform = mTransformManager.getInstance(mResult->mRoot);
Entity instanceRoot = mEntityManager.create();
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 = mResult;
mResult->mInstances.push_back(instance);
// For each scene root, recursively create all entities.
for (cgltf_size i = 0, len = scene->nodes_count; i < len; ++i) {
cgltf_node** nodes = scene->nodes;
createEntity(nodes[i], instanceRoot, index == 0, instance);
if (createInstance(mResult, scene) == nullptr) {
mError = true;
break;
}
}
}
if (mError) {
delete mResult;
mResult = nullptr;
}
// 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, const char*> resourceUris;
@@ -329,10 +303,32 @@ void FAssetLoader::createAsset(const cgltf_data* srcAsset, size_t numInstances)
mResult->mResourceUris.push_back(pair.second);
}
// We're done with the import, so free up transient bookkeeping resources.
mMatInstanceCache.clear();
mMeshCache.clear();
mError = false;
if (mError) {
delete mResult;
mResult = nullptr;
mError = false;
}
}
FilamentInstance* FAssetLoader::createInstance(FFilamentAsset* primary, const cgltf_scene* scene) {
auto rootTransform = mTransformManager.getInstance(primary->mRoot);
Entity instanceRoot = mEntityManager.create();
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);
// For each scene root, recursively create all entities.
for (cgltf_size i = 0, len = scene->nodes_count; i < len; ++i) {
cgltf_node** nodes = scene->nodes;
createEntity(nodes[i], instanceRoot, false, instance);
}
return instance;
}
void FAssetLoader::createEntity(const cgltf_node* node, Entity parent, bool enableLight,
@@ -406,11 +402,11 @@ void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity, const
// 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 = mMeshCache.find(mesh);
if (iter == mMeshCache.end()) {
mMeshCache[mesh].resize(nprims);
auto iter = mResult->mMeshCache.find(mesh);
if (iter == mResult->mMeshCache.end()) {
mResult->mMeshCache[mesh].resize(nprims);
}
Primitive* outputPrim = mMeshCache[mesh].data();
Primitive* outputPrim = mResult->mMeshCache[mesh].data();
const cgltf_primitive* inputPrim = &mesh->primitives[0];
Aabb aabb;
@@ -516,7 +512,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
};
// In glTF, each primitive may or may not have an index buffer.
IndexBuffer* indices;
IndexBuffer* indices = nullptr;
const cgltf_accessor* accessor = inPrim->indices;
if (accessor) {
IndexBuffer::IndexType indexType;
@@ -533,7 +529,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
BufferSlot slot = { accessor };
slot.indexBuffer = indices;
addBufferSlot(slot);
} else {
} else if (inPrim->attributes_count > 0) {
// If a primitive does not have an index buffer, generate a trivial one now.
const uint32_t vertexCount = inPrim->attributes[0].data->count;
@@ -710,6 +706,11 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out
}
}
if (vertexCount == 0) {
slog.e << "Empty vertex buffer in " << name << io::endl;
return false;
}
vbb.vertexCount(vertexCount);
// If an ubershader is used, then we provide a single dummy buffer for all unfulfilled vertex
@@ -849,8 +850,8 @@ void FAssetLoader::createCamera(const cgltf_camera* camera, Entity entity) {
MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_material* inputMat,
UvMap* uvmap, bool vertexColor) {
intptr_t key = ((intptr_t) inputMat) ^ (vertexColor ? 1 : 0);
auto iter = mMatInstanceCache.find(key);
if (iter != mMatInstanceCache.end()) {
auto iter = mResult->mMatInstanceCache.find(key);
if (iter != mResult->mMatInstanceCache.end()) {
*uvmap = iter->second.uvmap;
return iter->second.instance;
}
@@ -1074,7 +1075,7 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_material* inp
}
}
mMatInstanceCache[key] = {mi, *uvmap};
mResult->mMatInstanceCache[key] = {mi, *uvmap};
return mi;
}
@@ -1157,6 +1158,10 @@ FilamentAsset* AssetLoader::createInstancedAsset(const uint8_t* bytes, uint32_t
return upcast(this)->createInstancedAsset(bytes, numBytes, instances, numInstances);
}
FilamentInstance* AssetLoader::createInstance(FilamentAsset* asset) {
return upcast(this)->createInstance(upcast(asset));
}
FilamentAsset* AssetLoader::createAssetFromHandle(const void* handle) {
const cgltf_data* sourceAsset = (const cgltf_data*) handle;
upcast(this)->createAsset(sourceAsset, 0);

View File

@@ -34,7 +34,12 @@ size_t DependencyGraph::popRenderables(Entity* result, size_t count) noexcept {
}
void DependencyGraph::addEdge(Entity entity, MaterialInstance* mi) {
assert(!mFinalized);
// 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);
}
@@ -57,12 +62,42 @@ void DependencyGraph::finalize() {
mFinalized = true;
}
void DependencyGraph::refinalize() {
assert(mFinalized);
for (auto pair : mMaterialToEntity) {
auto material = pair.first;
if (mMaterialToTexture.find(material) == mMaterialToTexture.end()) {
markAsReady(material);
} else {
checkReadiness(material);
}
}
}
void DependencyGraph::addEdge(Texture* texture, MaterialInstance* material, const char* parameter) {
assert(mFinalized);
mTextureToMaterial[texture].insert(material);
mMaterialToTexture.at(material).params.at(parameter) = getStatus(texture);
}
void DependencyGraph::checkReadiness(Material* material) {
auto& status = mMaterialToTexture.at(material);
// Check this material's texture parameters, there are 5 in the worst case.
bool materialIsReady = true;
for (auto pair : status.params) {
if (!pair.second->ready) {
materialIsReady = false;
break;
}
}
// If all of its textures are ready, then the material has become ready.
if (materialIsReady) {
markAsReady(material);
}
}
void DependencyGraph::markAsReady(Texture* texture) {
assert(texture && mFinalized);
mTextureNodes.at(texture)->ready = true;
@@ -71,21 +106,7 @@ void DependencyGraph::markAsReady(Texture* texture) {
// This is O(n2) but the inner loop is always small.
auto& materials = mTextureToMaterial.at(texture);
for (auto material : materials) {
auto& status = mMaterialToTexture.at(material);
// Check this material's texture parameters, there are 5 in the worst case.
bool materialIsReady = true;
for (auto pair : status.params) {
if (!pair.second->ready) {
materialIsReady = false;
break;
}
}
// If all of its textures are ready, then the material has become ready.
if (materialIsReady) {
markAsReady(material);
}
checkReadiness(material);
}
}
@@ -93,7 +114,10 @@ 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(status.numReadyMaterials <= status.materials.size());
if (status.numReadyMaterials == status.materials.size()) {
continue;
}
if (++status.numReadyMaterials == status.materials.size()) {
mReadyRenderables.push(entity);
}

View File

@@ -34,7 +34,7 @@ namespace gltfio {
/**
* Internal graph that enables FilamentAsset to discover "ready-to-render" entities by tracking
* the Texture objects that each entity depends on.
* the loading status of Texture objects that each entity depends on.
*
* Renderables connect to a set of material instances, which in turn connect to a set of parameter
* names, which in turn connect to a set of texture objects. These relationships are not easily
@@ -72,8 +72,13 @@ public:
void addEdge(Material* material, const char* parameter);
// This is called at the end of the initial asset loading phase.
// Makes a guarantee that no new material nodes or parameter nodes will be added to the graph.
void finalize();
// This can be called after finalization to allow for dynamic addition of entities.
// It is slower than finalize() because it checks the readiness of existing materials.
void refinalize();
// These are called after textures have created and decoded.
void addEdge(filament::Texture* texture, Material* material, const char* parameter);
void markAsReady(filament::Texture* texture);
@@ -93,6 +98,7 @@ private:
size_t numReadyMaterials = 0;
};
void checkReadiness(Material* material);
void markAsReady(Material* material);
TextureNode* getStatus(filament::Texture* texture);

View File

@@ -28,6 +28,8 @@
#include <filament/TransformManager.h>
#include <filament/VertexBuffer.h>
#include <gltfio/MaterialProvider.h>
#include <math/mat4.h>
#include <utils/Entity.h>
@@ -73,6 +75,33 @@ struct TextureSlot {
bool srgb;
};
// MeshCache
// ---------
// 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.
struct Primitive {
filament::VertexBuffer* vertices = nullptr;
filament::IndexBuffer* indices = nullptr;
filament::Aabb aabb; // object-space bounding box
};
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>;
struct FFilamentAsset : public FilamentAsset {
FFilamentAsset(filament::Engine* engine, utils::NameComponentManager* names,
utils::EntityManager* entityManager) :
@@ -183,6 +212,10 @@ struct FFilamentAsset : public FilamentAsset {
mDependencyGraph.addEdge(texture, tb.materialInstance, tb.materialParameter);
}
bool isInstanced() const {
return mInstances.size() > 0;
}
filament::Engine* mEngine;
utils::NameComponentManager* mNameManager;
utils::EntityManager* mEntityManager;
@@ -218,6 +251,9 @@ struct FFilamentAsset : public FilamentAsset {
const cgltf_data* mSourceAsset = nullptr;
NodeMap mNodeMap; // unused for instanced assets
std::vector<std::pair<const cgltf_primitive*, filament::VertexBuffer*> > mPrimitives;
MatInstanceCache mMatInstanceCache;
MeshCache mMeshCache;
bool mIsReleased = false;
};
FILAMENT_UPCAST(FilamentAsset)

View File

@@ -18,7 +18,6 @@
#define GLTFIO_FFILAMENTINSTANCE_H
#include <gltfio/FilamentInstance.h>
#include <gltfio/Animator.h>
#include <utils/Entity.h>
@@ -36,6 +35,7 @@ struct cgltf_node;
namespace gltfio {
struct FFilamentAsset;
class Animator;
struct Skin {
std::string name;
@@ -54,13 +54,7 @@ struct FFilamentInstance : public FilamentInstance {
FFilamentAsset* owner;
SkinVector skins;
NodeMap nodeMap;
Animator* getAnimator() noexcept {
if (!animator) {
animator = new Animator(owner, this);
}
return animator;
}
Animator* getAnimator() noexcept;
};
FILAMENT_UPCAST(FilamentInstance)

View File

@@ -19,6 +19,7 @@
#include <gltfio/Animator.h>
#include <utils/EntityManager.h>
#include <utils/Log.h>
#include <utils/NameComponentManager.h>
#include "Wireframe.h"
@@ -70,6 +71,14 @@ FFilamentAsset::~FFilamentAsset() {
Animator* FFilamentAsset::getAnimator() noexcept {
if (!mAnimator) {
if (!mResourcesLoaded) {
slog.e << "Cannot create animator before resource loading." << io::endl;
return nullptr;
}
if (mIsReleased) {
slog.e << "Cannot create animator from frozen asset." << io::endl;
return nullptr;
}
mAnimator = new Animator(this, nullptr);
}
return mAnimator;
@@ -83,9 +92,12 @@ Entity FFilamentAsset::getWireframe() noexcept {
}
void FFilamentAsset::releaseSourceData() noexcept {
mIsReleased = true;
// 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 = {};
mMeshCache = {};
mResourceUris = {};
mNodeMap = {};
mPrimitives = {};

View File

@@ -15,14 +15,32 @@
*/
#include "FFilamentInstance.h"
#include "FFilamentAsset.h"
#include <gltfio/Animator.h>
#include <utils/Log.h>
using namespace filament;
using namespace utils;
namespace gltfio {
Animator* FFilamentInstance::getAnimator() noexcept {
if (!animator) {
if (!owner->mResourcesLoaded) {
slog.e << "Cannot create animator before resource loading." << io::endl;
return nullptr;
}
if (owner->mIsReleased) {
slog.e << "Cannot create animator from frozen asset." << io::endl;
return nullptr;
}
animator = new Animator(owner, this);
}
return animator;
}
size_t FilamentInstance::getEntityCount() const noexcept {
return upcast(this)->entities.size();
}

View File

@@ -296,7 +296,6 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
if (asset->mResourcesLoaded) {
return false;
}
asset->mResourcesLoaded = true;
mPool->addAsset(asset);
const cgltf_data* gltf = asset->mSourceAsset;
cgltf_options options {};
@@ -389,7 +388,7 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
if (pImpl->mNormalizeSkinningWeights) {
normalizeSkinningWeights(asset);
}
if (asset->mInstances.empty()) {
if (!asset->isInstanced()) {
importSkins(gltf, asset->mNodeMap, asset->mSkins);
} else {
for (FFilamentInstance* instance : asset->mInstances) {
@@ -444,8 +443,9 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
asset->mDependencyGraph.finalize();
pImpl->mCurrentAsset = asset;
// Finally, load image files and create Filament Textures.
return pImpl->createTextures(async);
// Finally, create Filament Textures and begin loading image files.
asset->mResourcesLoaded = pImpl->createTextures(async);
return asset->mResourcesLoaded;
}
bool ResourceLoader::asyncBeginLoad(FilamentAsset* asset) {
@@ -996,7 +996,7 @@ void ResourceLoader::updateBoundingBoxes(FFilamentAsset* asset) const {
SYSTRACE_CALL();
auto& rm = pImpl->mEngine->getRenderableManager();
auto& tm = pImpl->mEngine->getTransformManager();
NodeMap& nodeMap = asset->mInstances.empty() ? asset->mNodeMap : asset->mInstances[0]->nodeMap;
NodeMap& nodeMap = asset->isInstanced() ? asset->mInstances[0]->nodeMap : asset->mNodeMap;
// 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

View File

@@ -347,7 +347,7 @@ inline LinearImage fromLinearToRGBM(const LinearImage& image) {
}
template<typename T>
static LinearImage toLinear(size_t w, size_t h, size_t bpr, const uint8_t* src) {
static LinearImage toLinearWithAlpha(size_t w, size_t h, size_t bpr, const uint8_t* src) {
LinearImage result(w, h, 4);
filament::math::float4* d = reinterpret_cast<filament::math::float4*>(result.getPixelRef(0, 0));
for (size_t y = 0; y < h; ++y) {
@@ -361,6 +361,21 @@ static LinearImage toLinear(size_t w, size_t h, size_t bpr, const uint8_t* src)
return result;
}
template<typename T>
static LinearImage toLinear(size_t w, size_t h, size_t bpr, const uint8_t* src) {
LinearImage result(w, h, 3);
filament::math::float3* d = reinterpret_cast<filament::math::float3*>(result.getPixelRef(0, 0));
for (size_t y = 0; y < h; ++y) {
T const* p = reinterpret_cast<T const*>(src + y * bpr);
for (size_t x = 0; x < w; ++x, p += 3) {
filament::math::float3 sRGB(p[0], p[1], p[2]);
sRGB /= std::numeric_limits<T>::max();
*d++ = sRGBToLinear(sRGB);
}
}
return result;
}
} // namespace Image
#endif // IMAGE_COLORTRANSFORM_H_

View File

@@ -20,6 +20,8 @@
namespace filament {
namespace math {
namespace details { template<typename T> class TQuaternion; }
template<typename T>
std::ostream& operator<<(std::ostream& out, const details::TVec2<T>& v) noexcept;
@@ -38,5 +40,8 @@ std::ostream& operator<<(std::ostream& out, const details::TMat33<T>& v) noexcep
template<typename T>
std::ostream& operator<<(std::ostream& out, const details::TMat44<T>& v) noexcept;
template<typename T>
std::ostream& operator<<(std::ostream& out, const details::TQuaternion<T>& v) noexcept;
} // namespace math
} // namespace filament

View File

@@ -112,6 +112,11 @@ std::ostream& operator<<(std::ostream& out, const details::TMat44<T>& v) noexcep
return printMatrix(out, v.asArray(), 4, 4);
}
template<typename T>
std::ostream& operator<<(std::ostream& out, const details::TQuaternion<T>& v) noexcept {
return printQuat(out, v);
}
template std::ostream& operator<<(std::ostream& out, const details::TVec2<double>& v) noexcept;
template std::ostream& operator<<(std::ostream& out, const details::TVec2<float>& v) noexcept;
template std::ostream& operator<<(std::ostream& out, const details::TVec2<half>& v) noexcept;
@@ -154,5 +159,9 @@ template std::ostream& operator<<(std::ostream& out, const details::TMat33<float
template std::ostream& operator<<(std::ostream& out, const details::TMat44<double>& v) noexcept;
template std::ostream& operator<<(std::ostream& out, const details::TMat44<float>& v) noexcept;
template std::ostream& operator<<(std::ostream& out, const details::TQuaternion<double>& v) noexcept;
template std::ostream& operator<<(std::ostream& out, const details::TQuaternion<float>& v) noexcept;
template std::ostream& operator<<(std::ostream& out, const details::TQuaternion<half>& v) noexcept;
} // namespace math
} // namespace filament

View File

@@ -68,7 +68,7 @@ public:
std::cv_status wait_until(std::unique_lock<Mutex>& lock,
const std::chrono::time_point<std::chrono::steady_clock, D>& timeout_time) noexcept {
// convert to nanoseconds
int64_t ns = std::chrono::duration<int64_t, std::nano>(timeout_time.time_since_epoch()).count();
uint64_t ns = std::chrono::duration<uint64_t, std::nano>(timeout_time.time_since_epoch()).count();
using sec_t = decltype(timespec::tv_sec);
using nsec_t = decltype(timespec::tv_nsec);
timespec ts{ sec_t(ns / 1000000000), nsec_t(ns % 1000000000) };
@@ -79,7 +79,7 @@ public:
std::cv_status wait_until(std::unique_lock<Mutex>& lock,
const std::chrono::time_point<std::chrono::system_clock, D>& timeout_time) noexcept {
// convert to nanoseconds
int64_t ns = std::chrono::duration<int64_t, std::nano>(timeout_time.time_since_epoch()).count();
uint64_t ns = std::chrono::duration<uint64_t, std::nano>(timeout_time.time_since_epoch()).count();
using sec_t = decltype(timespec::tv_sec);
using nsec_t = decltype(timespec::tv_nsec);
timespec ts{ sec_t(ns / 1000000000), nsec_t(ns % 1000000000) };

View File

@@ -410,7 +410,8 @@ void SimpleViewer::updateUserInterface() {
ImGui::Unindent();
}
if (mAnimator->getAnimationCount() > 0 && ImGui::CollapsingHeader("Animation")) {
if (mAnimator && mAnimator->getAnimationCount() > 0 &&
ImGui::CollapsingHeader("Animation")) {
ImGui::Indent();
int selectedAnimation = mCurrentAnimation;
ImGui::RadioButton("Disable", &selectedAnimation, 0);

View File

@@ -51,8 +51,6 @@ using namespace filament::viewer;
using namespace gltfio;
using namespace utils;
using InstanceHandle = FilamentInstance*;
struct App {
Engine* engine;
SimpleViewer* viewer;
@@ -63,9 +61,8 @@ struct App {
MaterialProvider* materials;
MaterialSource materialSource = GENERATE_SHADERS;
ResourceLoader* resourceLoader = nullptr;
int numInstances = 5;
int instanceToAnimate = -1;
InstanceHandle* instances;
std::vector<FilamentInstance*> instances;
};
static const char* DEFAULT_IBL = "default_env";
@@ -132,7 +129,7 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) {
app->instanceToAnimate = atoi(arg.c_str());
break;
case 'n':
app->numInstances = atoi(arg.c_str());
app->instances.resize(atoi(arg.c_str()));
break;
case 'i':
app->config.iblDirectory = arg;
@@ -142,6 +139,9 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) {
break;
}
}
if (app->instances.empty()) {
app->instances.resize(5);
}
return optind;
}
@@ -184,8 +184,8 @@ int main(int argc, char** argv) {
}
// Parse the glTF file and create Filament entities.
app.asset = app.loader->createInstancedAsset(buffer.data(), buffer.size(), app.instances,
app.numInstances);
app.asset = app.loader->createInstancedAsset(buffer.data(), buffer.size(),
app.instances.data(), app.instances.size());
buffer.clear();
buffer.shrink_to_fit();
@@ -213,7 +213,6 @@ int main(int argc, char** argv) {
if (app.instanceToAnimate > -1) {
app.instances[app.instanceToAnimate]->getAnimator();
}
app.asset->releaseSourceData();
auto ibl = FilamentApp::get().getIBL();
if (ibl) {
@@ -221,6 +220,20 @@ int main(int argc, char** argv) {
}
};
auto arrangeIntoCircle = [&app]() {
auto& tcm = app.engine->getTransformManager();
auto extent = app.asset->getBoundingBox().extent();
float max_extent = std::max(std::max(extent.x, extent.y), extent.z);
auto translation = mat4f::translation(float3(max_extent, 0, 0));
for (size_t inst = 0; inst < app.instances.size(); ++inst) {
FilamentInstance* instance = app.instances[inst];
auto transformRoot = tcm.getInstance(instance->getRoot());
float theta = inst * 2.0 * M_PI / app.instances.size();
auto rotation = mat4f::rotation(theta, float3(0, 0, 1));
tcm.setTransform(transformRoot, rotation * translation);
}
};
auto setup = [&](Engine* engine, View* view, Scene* scene) {
app.engine = engine;
app.names = new NameComponentManager(EntityManager::get());
@@ -228,28 +241,15 @@ int main(int argc, char** argv) {
app.materials = (app.materialSource == GENERATE_SHADERS) ?
createMaterialGenerator(engine) : createUbershaderLoader(engine);
app.loader = AssetLoader::create({engine, app.materials, app.names });
app.instances = new InstanceHandle[app.numInstances];
if (filename.isEmpty()) {
app.asset = app.loader->createInstancedAsset(
GLTF_VIEWER_DAMAGEDHELMET_DATA, GLTF_VIEWER_DAMAGEDHELMET_SIZE,
app.instances, app.numInstances);
app.instances.data(), app.instances.size());
} else {
loadAsset(filename);
}
// Arrange all instances into a circle.
auto& tcm = engine->getTransformManager();
auto extent = app.asset->getBoundingBox().extent();
float max_extent = std::max(std::max(extent.x, extent.y), extent.z);
auto translation = mat4f::translation(float3(max_extent, 0, 0));
for (size_t inst = 0; inst < app.numInstances; ++inst) {
FilamentInstance* instance = app.instances[inst];
auto transformRoot = tcm.getInstance(instance->getRoot());
float theta = inst * 2.0 * M_PI / app.numInstances;
auto rotation = mat4f::rotation(theta, float3(0, 0, 1));
tcm.setTransform(transformRoot, rotation * translation);
}
arrangeIntoCircle();
loadResources(filename);
};
@@ -262,11 +262,9 @@ int main(int argc, char** argv) {
delete app.names;
AssetLoader::destroy(&app.loader);
delete[] app.instances;
};
auto animate = [&app](Engine* engine, View* view, double now) {
auto animate = [&app, arrangeIntoCircle](Engine* engine, View* view, double now) {
app.resourceLoader->asyncUpdateLoad();
FilamentInstance* instance = nullptr;
if (app.instanceToAnimate > -1) {
@@ -274,6 +272,14 @@ int main(int argc, char** argv) {
}
app.viewer->populateScene(app.asset, true, instance);
app.viewer->applyAnimation(now);
static double previous = 0.0;
if (now - previous > 1.0) {
FilamentInstance* instance = app.loader->createInstance(app.asset);
app.instances.push_back(instance);
arrangeIntoCircle();
previous = now;
}
};
auto gui = [&app](Engine* engine, View* view) { };

View File

@@ -1,3 +1,7 @@
#if defined(HAS_VSM)
layout(location = 0) out vec4 fragColor;
#endif
//------------------------------------------------------------------------------
// Depth
//------------------------------------------------------------------------------

View File

@@ -31,4 +31,4 @@ LAYOUT_LOCATION(11) in highp vec4 vertex_lightSpacePosition;
LAYOUT_LOCATION(12) in highp vec4 vertex_spotLightSpacePosition[MAX_SHADOW_CASTING_SPOTS];
#endif
layout(location = 0) out vec4 fragColor;
// Note that fragColor is an output and is not declared here; see main.fs and depth_main.fs

View File

@@ -1,3 +1,5 @@
layout(location = 0) out vec4 fragColor;
#if defined(MATERIAL_HAS_POST_LIGHTING_COLOR)
void blendPostLightingColor(const MaterialInputs material, inout vec4 color) {
#if defined(POST_LIGHTING_BLEND_MODE_OPAQUE)

View File

@@ -568,6 +568,7 @@ export class gltfio$AssetLoader {
public createInstancedAsset(urlOrBuffer: BufferReference,
instances: (gltfio$FilamentInstance | null)[]): gltfio$FilamentAsset;
public destroyAsset(asset: gltfio$FilamentAsset): void;
public createInstance(asset: gltfio$FilamentAsset): (gltfio$FilamentInstance | null);
public delete(): void;
}

View File

@@ -1782,6 +1782,10 @@ class_<AssetLoader>("gltfio$AssetLoader")
buffer.bd->size, instances.data(), numInstances);
}), allow_raw_pointers())
// createInstance ::method::
// Adds a new instance to an instanced asset.
.function("createInstance", &AssetLoader::createInstance, allow_raw_pointers())
// destroyAsset ::method::
// Destroys the given asset and all of its associated Filament objects. This includes
// components, material instances, vertex buffers, index buffers, and textures.

View File

@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.9.7",
"version": "1.9.8",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",