diff --git a/CMakeLists.txt b/CMakeLists.txt index 352038ad7ff..0bb6196ea9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -583,6 +583,7 @@ add_subdirectory(${LIBRARIES}/filameshio) add_subdirectory(${LIBRARIES}/geometry) add_subdirectory(${LIBRARIES}/gltfio) add_subdirectory(${LIBRARIES}/ibl) +add_subdirectory(${LIBRARIES}/iblprefilter) add_subdirectory(${LIBRARIES}/image) add_subdirectory(${LIBRARIES}/math) add_subdirectory(${LIBRARIES}/mathio) diff --git a/README.md b/README.md index d2605f4423e..046c66c022a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ repositories { } dependencies { - implementation 'com.google.android.filament:filament-android:1.9.22' + implementation 'com.google.android.filament:filament-android:1.9.23' } ``` @@ -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.22' +pod 'Filament', '~> 1.9.23' ``` ### Snapshots diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d76c013e759..3f79182ca7b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,7 +5,18 @@ A new header is inserted each time a *tag* is created. ## Next release (main branch) -- engine: Add new `Renderer::renderStandaloneView()` API. +## v1.9.23 + +- Vulkan: various fixes. +- android: fix crash seen using VSM with MSAA on Adreno devices. +- engine: Add `Engine::getEntityManager()`. +- engine: Fix desktop crash seen with some GPU drivers. +- engine: improve importance sampling. +- gltfio: robustness improvements for Draco meshes. +- libs: Add new Transcoder API for C++ clients (part of `libgeometry`). +- libs: New `iblprefilter` library to compute IBL pre-integration on the GPU using filament. +- materials: Fix documentation for `getNormalizedViewportCoord`. +- samples: fix rendertarget sample crash on launch. ## v1.9.22 diff --git a/android/filament-android/CMakeLists.txt b/android/filament-android/CMakeLists.txt index 8c89028b858..09012db638b 100644 --- a/android/filament-android/CMakeLists.txt +++ b/android/filament-android/CMakeLists.txt @@ -97,7 +97,6 @@ target_link_libraries(filament-jni PRIVATE backend PRIVATE filaflat PRIVATE filabridge - PRIVATE geometry PRIVATE ibl-lite PRIVATE log PRIVATE GLESv3 @@ -105,6 +104,10 @@ target_link_libraries(filament-jni PRIVATE android PRIVATE jnigraphics PRIVATE utils + + # libgeometry is PUBLIC because gltfio uses it. + PUBLIC geometry + $<$:matdbg> $<$:bluevk> $<$:vkshaders> diff --git a/android/filament-android/src/main/cpp/Engine.cpp b/android/filament-android/src/main/cpp/Engine.cpp index a49b78e9fef..0ae8a52b000 100644 --- a/android/filament-android/src/main/cpp/Engine.cpp +++ b/android/filament-android/src/main/cpp/Engine.cpp @@ -312,3 +312,9 @@ Java_com_google_android_filament_Engine_nGetJobSystem(JNIEnv*, jclass, jlong nat Engine* engine = (Engine*) nativeEngine; return (jlong) &engine->getJobSystem(); } + +extern "C" JNIEXPORT jlong JNICALL +Java_com_google_android_filament_Engine_nGetEntityManager(JNIEnv*, jclass, jlong nativeEngine) { + Engine* engine = (Engine*) nativeEngine; + return (jlong) &engine->getEntityManager(); +} diff --git a/android/filament-android/src/main/java/com/google/android/filament/Engine.java b/android/filament-android/src/main/java/com/google/android/filament/Engine.java index 16476131b79..e58a0a31113 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Engine.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Engine.java @@ -110,6 +110,7 @@ public class Engine { @NonNull private final TransformManager mTransformManager; @NonNull private final LightManager mLightManager; @NonNull private final RenderableManager mRenderableManager; + @NonNull private final EntityManager mEntityManager; /** * Denotes a backend @@ -142,6 +143,7 @@ private Engine(long nativeEngine) { mTransformManager = new TransformManager(nGetTransformManager(nativeEngine)); mLightManager = new LightManager(nGetLightManager(nativeEngine)); mRenderableManager = new RenderableManager(nGetRenderableManager(nativeEngine)); + mEntityManager = new EntityManager(nGetEntityManager(nativeEngine)); } /** @@ -707,4 +709,5 @@ private static void assertDestroy(boolean success) { private static native long nGetLightManager(long nativeEngine); private static native long nGetRenderableManager(long nativeEngine); private static native long nGetJobSystem(long nativeEngine); + private static native long nGetEntityManager(long nativeEngine); } diff --git a/android/filament-android/src/main/java/com/google/android/filament/EntityManager.java b/android/filament-android/src/main/java/com/google/android/filament/EntityManager.java index cf83ecc41d6..cd586de9e46 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/EntityManager.java +++ b/android/filament-android/src/main/java/com/google/android/filament/EntityManager.java @@ -31,6 +31,10 @@ private static class Holder { private EntityManager() { } + EntityManager(long nativeEntityManager) { + mNativeObject = nativeEntityManager; + } + @NonNull public static EntityManager get() { return Holder.INSTANCE; diff --git a/android/filament-android/src/main/java/com/google/android/filament/RenderTarget.java b/android/filament-android/src/main/java/com/google/android/filament/RenderTarget.java index 3f26759d146..17370a24cbf 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/RenderTarget.java +++ b/android/filament-android/src/main/java/com/google/android/filament/RenderTarget.java @@ -52,10 +52,10 @@ public long getNativeObject() { */ public enum AttachmentPoint { COLOR, - DEPTH, COLOR1, COLOR2, - COLOR3 + COLOR3, + DEPTH } /** diff --git a/android/filament-android/src/main/java/com/google/android/filament/Skybox.java b/android/filament-android/src/main/java/com/google/android/filament/Skybox.java index c642867c11c..f33425c8d7d 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/Skybox.java +++ b/android/filament-android/src/main/java/com/google/android/filament/Skybox.java @@ -113,10 +113,11 @@ public Builder showSun(boolean show) { } /** - * Sets the Skybox intensity when no {@link IndirectLight} is set + * Sets the Skybox intensity when no {@link IndirectLight} is set on the + * {@link Scene}. * - *

This call is ignored when an {@link IndirectLight} is set, otherwise it is used in - * its place.

+ *

This call is ignored when an {@link IndirectLight} is set on the {@link Scene}, and + * the intensity of the {@link IndirectLight} is used instead.

* * @param envIntensity Scale factor applied to the skybox texel values such that * the result is in lux, or lumen/m^2 (default = 30000) diff --git a/android/gradle.properties b/android/gradle.properties index 3641d9240da..8c23bd442f0 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.google.android.filament -VERSION_NAME=1.9.22 +VERSION_NAME=1.9.23 POM_DESCRIPTION=Real-time physically based rendering engine for Android. diff --git a/docs/Materials.md.html b/docs/Materials.md.html index 464ff9714e5..24aadc23b33 100644 --- a/docs/Materials.md.html +++ b/docs/Materials.md.html @@ -1936,7 +1936,7 @@ **getWorldNormalVector()** | float3 | Normalized normal in world space, after bump mapping (must be used after `prepareMaterial()`) **getWorldGeometricNormalVector()** | float3 | Normalized normal in world space, before bump mapping (can be used before `prepareMaterial()`) **getWorldReflectedVector()** | float3 | Reflection of the view vector about the normal (must be used after `prepareMaterial()`) -**getNormalizedViewportCoord()** | float2 | Normalized viewport position (i.e. clip-space position normalized to [0, 1], can be used before `prepareMaterial()`) +**getNormalizedViewportCoord()** | float3 | Normalized viewport position (i.e. NDC coordinates normalized to [0, 1], can be used before `prepareMaterial()`) **getNdotV()** | float | The result of `dot(normal, view)`, always strictly greater than 0 (must be used after `prepareMaterial()`) **getColor()** | float4 | Interpolated color of the fragment, if the color attribute is required **getUV0()** | float2 | First interpolated set of UV coordinates, only available if the uv0 attribute is required diff --git a/filament/backend/include/backend/TargetBufferInfo.h b/filament/backend/include/backend/TargetBufferInfo.h index eb0be33272f..80266e09839 100644 --- a/filament/backend/include/backend/TargetBufferInfo.h +++ b/filament/backend/include/backend/TargetBufferInfo.h @@ -54,10 +54,10 @@ class TargetBufferInfo { class MRT { public: - static constexpr int TARGET_COUNT = 4; + static constexpr int MAX_SUPPORTED_RENDER_TARGET_COUNT = 4; private: - TargetBufferInfo mInfos[TARGET_COUNT]; + TargetBufferInfo mInfos[MAX_SUPPORTED_RENDER_TARGET_COUNT]; public: TargetBufferInfo const& operator[](size_t i) const noexcept { diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index 1a4ef6b4ce9..8c7cac89c07 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -160,7 +160,9 @@ mContext->bufferPool->gc(); // If we acquired a drawable for this frame, ensure that we release it here. - mContext->currentDrawSwapChain->releaseDrawable(); + if (mContext->currentDrawSwapChain) { + mContext->currentDrawSwapChain->releaseDrawable(); + } CVMetalTextureCacheFlush(mContext->textureCache, 0); @@ -270,12 +272,12 @@ uint8_t samples, backend::MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) { - MetalRenderTarget::Attachment colorAttachments[4] = {{ 0 }}; - for (size_t i = 0; i < 4; i++) { + MetalRenderTarget::Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {{ nil }}; + for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { const auto& buffer = color[i]; if (!buffer.handle) { ASSERT_POSTCONDITION(none(targetBufferFlags & getMRTColorFlag(i)), - "The COLOR%d flag was specified, but no color texture provided.", i); + "The COLOR%u flag was specified, but no color texture provided.", i); continue; } @@ -288,7 +290,7 @@ colorAttachments[i].layer = color[i].layer; } - MetalRenderTarget::Attachment depthAttachment = { 0 }; + MetalRenderTarget::Attachment depthAttachment = { nil }; if (depth.handle) { auto depthTexture = handle_cast(mHandleMap, depth.handle); ASSERT_PRECONDITION(depthTexture->texture, @@ -1155,8 +1157,8 @@ ASSERT_PRECONDITION(program->isValid, "Attempting to draw with an invalid Metal program."); // Pipeline state - MTLPixelFormat colorPixelFormat[4] = { MTLPixelFormatInvalid }; - for (size_t i = 0; i < 4; i++) { + MTLPixelFormat colorPixelFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { MTLPixelFormatInvalid }; + for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { const auto& attachment = mContext->currentRenderTarget->getDrawColorAttachment(i); if (!attachment) { continue; diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index f2a9647d12f..3d50341cc01 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -224,7 +224,7 @@ class MetalRenderTarget : public HwRenderTarget { }; MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height, uint8_t samples, - Attachment colorAttachments[MRT::TARGET_COUNT], Attachment depthAttachment); + Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT], Attachment depthAttachment); explicit MetalRenderTarget(MetalContext* context) : HwRenderTarget(0, 0), context(context), defaultRenderTarget(true) {} @@ -248,11 +248,11 @@ class MetalRenderTarget : public HwRenderTarget { bool defaultRenderTarget = false; uint8_t samples = 1; - Attachment color[MRT::TARGET_COUNT] = {}; + Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; Attachment depth = {}; // "Sidecar" textures used to implement automatic MSAA resolve. - id multisampledColor[MRT::TARGET_COUNT] = { 0 }; + id multisampledColor[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { 0 }; id multisampledDepth = nil; }; diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index 0a799aec9c1..92a8e422eca 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -701,13 +701,13 @@ static MTLPixelFormat decidePixelFormat(id device, TextureFormat form } MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height, - uint8_t samples, Attachment colorAttachments[MRT::TARGET_COUNT], Attachment depthAttachment) : + uint8_t samples, Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT], Attachment depthAttachment) : HwRenderTarget(width, height), context(context), samples(samples) { // If we were given a single-sampled texture but the samples parameter is > 1, we create // multisampled sidecar textures and do a resolve automatically. const bool msaaResolve = samples > 1; - for (size_t i = 0; i < MRT::TARGET_COUNT; i++) { + for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (!colorAttachments[i]) { continue; } @@ -748,7 +748,7 @@ static MTLPixelFormat decidePixelFormat(id device, TextureFormat form const auto discardFlags = params.flags.discardEnd; - for (size_t i = 0; i < MRT::TARGET_COUNT; i++) { + for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { Attachment attachment = getDrawColorAttachment(i); if (!attachment) { continue; @@ -807,7 +807,7 @@ static MTLPixelFormat decidePixelFormat(id device, TextureFormat form } MetalRenderTarget::Attachment MetalRenderTarget::getDrawColorAttachment(size_t index) { - assert_invariant(index < MRT::TARGET_COUNT); + assert_invariant(index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT); Attachment result = color[index]; if (index == 0 && defaultRenderTarget) { assert_invariant(context->currentDrawSwapChain); @@ -817,7 +817,7 @@ static MTLPixelFormat decidePixelFormat(id device, TextureFormat form } MetalRenderTarget::Attachment MetalRenderTarget::getReadColorAttachment(size_t index) { - assert_invariant(index < MRT::TARGET_COUNT); + assert_invariant(index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT); Attachment result = color[index]; if (index == 0 && defaultRenderTarget) { assert_invariant(context->currentReadSwapChain); diff --git a/filament/backend/src/metal/MetalState.h b/filament/backend/src/metal/MetalState.h index 2cee9d0e602..7f714ca4001 100644 --- a/filament/backend/src/metal/MetalState.h +++ b/filament/backend/src/metal/MetalState.h @@ -216,7 +216,7 @@ struct PipelineState { id vertexFunction = nil; // 8 bytes id fragmentFunction = nil; // 8 bytes VertexDescription vertexDescription; // 528 bytes - MTLPixelFormat colorAttachmentPixelFormat[4] = { MTLPixelFormatInvalid }; // 32 bytes + MTLPixelFormat colorAttachmentPixelFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { MTLPixelFormatInvalid }; // 32 bytes MTLPixelFormat depthAttachmentPixelFormat = MTLPixelFormatInvalid; // 8 bytes NSUInteger sampleCount = 1; // 8 bytes BlendState blendState; // 56 bytes @@ -228,7 +228,7 @@ struct PipelineState { this->vertexFunction == rhs.vertexFunction && this->fragmentFunction == rhs.fragmentFunction && this->vertexDescription == rhs.vertexDescription && - std::equal(this->colorAttachmentPixelFormat, this->colorAttachmentPixelFormat + 4, + std::equal(this->colorAttachmentPixelFormat, this->colorAttachmentPixelFormat + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT, rhs.colorAttachmentPixelFormat) && this->depthAttachmentPixelFormat == rhs.depthAttachmentPixelFormat && this->sampleCount == rhs.sampleCount && diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp index 5904665a776..033bf542217 100644 --- a/filament/backend/src/opengl/OpenGLContext.cpp +++ b/filament/backend/src/opengl/OpenGLContext.cpp @@ -80,6 +80,7 @@ OpenGLContext::OpenGLContext() noexcept { // On Adreno (As of 3/20) timer query seem to return the CPU time, not the // GPU time. bugs.dont_use_timer_query = true; + bugs.disable_sidecar_blit_into_texture_array = true; } else if (strstr(renderer, "Mali")) { bugs.vao_doesnt_store_element_array_buffer_binding = true; if (strstr(renderer, "Mali-T")) { diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index d82da9d91c6..3f3283f8324 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -180,6 +180,10 @@ class OpenGLContext { // Some drivers don't implement timer queries correctly bool dont_use_timer_query = false; + + // Some drivers can't blit from a sidecar renderbuffer into a layer of a texture array. + // This technique is used for VSM with MSAA turned on. + bool disable_sidecar_blit_into_texture_array = false; } bugs; // state getters -- as needed. diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index b119ac716da..262b43ea408 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -937,8 +937,17 @@ void OpenGLDriver::framebufferTexture(backend::TargetBufferInfo const& binfo, attachmentTypeNotSupportedByMSRTT = true; } + // There's a bug with certain drivers preventing us from emulating + // EXT_multisampled_render_to_texture when the texture is a TEXTURE_2D_ARRAY, so we'll simply + // fall back to non-MSAA rendering. + const bool disableMultisampling = + gl.bugs.disable_sidecar_blit_into_texture_array && + rt->gl.samples > 1 && t->samples <= 1 && + target == GL_TEXTURE_2D_ARRAY; + if (rt->gl.samples <= 1 || - (rt->gl.samples > 1 && t->samples > 1 && gl.features.multisample_texture)) { + (rt->gl.samples > 1 && t->samples > 1 && gl.features.multisample_texture) || + disableMultisampling) { // on GL3.2 / GLES3.1 and above multisample is handled when creating the texture. // If multisampled textures are not supported and we end-up here, things should // still work, albeit without MSAA. @@ -1170,8 +1179,8 @@ void OpenGLDriver::createRenderTargetR(Handle rth, rt->targets = targets; if (any(targets & TargetBufferFlags::COLOR_ALL)) { - GLenum bufs[4] = { GL_NONE }; - for (size_t i = 0; i < 4; i++) { + GLenum bufs[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { GL_NONE }; + for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (any(targets & getMRTColorFlag(i))) { rt->gl.color[i].texture = handle_cast(color[i].handle); rt->gl.color[i].level = color[i].level; @@ -1179,7 +1188,7 @@ void OpenGLDriver::createRenderTargetR(Handle rth, bufs[i] = GL_COLOR_ATTACHMENT0 + i; } } - glDrawBuffers(4, bufs); + glDrawBuffers(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT, bufs); CHECK_GL_ERROR(utils::slog.e) } diff --git a/filament/backend/src/vulkan/VulkanBinder.h b/filament/backend/src/vulkan/VulkanBinder.h index 6c4eebce0ff..cb13fa554a4 100644 --- a/filament/backend/src/vulkan/VulkanBinder.h +++ b/filament/backend/src/vulkan/VulkanBinder.h @@ -76,7 +76,7 @@ class VulkanBinder { public: static constexpr uint32_t UBUFFER_BINDING_COUNT = Program::UNIFORM_BINDING_COUNT; static constexpr uint32_t SAMPLER_BINDING_COUNT = backend::MAX_SAMPLER_COUNT; - static constexpr uint32_t TARGET_BINDING_COUNT = MRT::TARGET_COUNT; + static constexpr uint32_t TARGET_BINDING_COUNT = MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; static constexpr uint32_t SHADER_MODULE_COUNT = 2; static constexpr uint32_t VERTEX_ATTRIBUTE_COUNT = backend::MAX_VERTEX_ATTRIBUTE_COUNT; @@ -234,7 +234,7 @@ class VulkanBinder { VkDescriptorImageInfo mDescriptorInputAttachments[TARGET_BINDING_COUNT]; VkWriteDescriptorSet mDescriptorWrites[ UBUFFER_BINDING_COUNT + SAMPLER_BINDING_COUNT + TARGET_BINDING_COUNT]; - VkPipelineColorBlendAttachmentState mColorBlendAttachments[MRT::TARGET_COUNT]; + VkPipelineColorBlendAttachmentState mColorBlendAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // Current bindings are divided into two "keys" which are composed of a mix of actual values // (e.g., blending is OFF) and weak references to Vulkan objects (e.g., shader programs and diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 8866687b600..575f0db999d 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -555,8 +555,8 @@ void VulkanDriver::createDefaultRenderTargetR(Handle rth, int) { void VulkanDriver::createRenderTargetR(Handle rth, TargetBufferFlags targets, uint32_t width, uint32_t height, uint8_t samples, backend::MRT color, TargetBufferInfo depth, TargetBufferInfo stencil) { - VulkanAttachment colorTargets[MRT::TARGET_COUNT] = {}; - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + VulkanAttachment colorTargets[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (color[i].handle) { colorTargets[i].texture = handle_cast(mHandleMap, color[i].handle); } @@ -1040,7 +1040,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP .samples = rt->getSamples(), .subpassMask = uint8_t(params.subpassMask) }; - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { rpkey.colorLayout[i] = rt->getColor(i).layout; rpkey.colorFormat[i] = rt->getColor(i).format; VulkanTexture* texture = rt->getColor(i).texture; @@ -1060,7 +1060,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP .layers = 1, .samples = rpkey.samples }; - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (rt->getColor(i).format == VK_FORMAT_UNDEFINED) { fbkey.color[i] = VK_NULL_HANDLE; fbkey.resolve[i] = VK_NULL_HANDLE; @@ -1087,7 +1087,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP // The current command buffer now owns a reference to the render target and its attachments. mDisposer.acquire(rt, mContext.currentCommands->resources); mDisposer.acquire(depth.texture, mContext.currentCommands->resources); - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { mDisposer.acquire(rt->getColor(i).texture, mContext.currentCommands->resources); } @@ -1104,12 +1104,12 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP rt->transformClientRectToPlatform(&renderPassInfo.renderArea); - VkClearValue clearValues[MRT::TARGET_COUNT + MRT::TARGET_COUNT + 1] = {}; + VkClearValue clearValues[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 1] = {}; // NOTE: clearValues must be populated in the same order as the attachments array in // VulkanFboCache::getFramebuffer. Values must be provided regardless of whether Vulkan is // actually clearing that particular target. - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (fbkey.color[i]) { VkClearValue& clearValue = clearValues[renderPassInfo.clearValueCount++]; clearValue.color.float32[0] = params.clearColor.r; @@ -1119,7 +1119,7 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP } } // Resolve attachments are not cleared but still have entries in the list, so skip over them. - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (rpkey.needsResolveMask & (1u << i)) { renderPassInfo.clearValueCount++; } @@ -1652,7 +1652,11 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle r const uint32_t bufferCount = prim.vertexBuffer->attributes.size(); for (uint32_t attribIndex = 0; attribIndex < bufferCount; attribIndex++) { Attribute attrib = prim.vertexBuffer->attributes[attribIndex]; - VkFormat vkformat = getVkFormat(attrib.type, attrib.flags & Attribute::FLAG_NORMALIZED); + + const bool isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET; + const bool isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED; + + VkFormat vkformat = getVkFormat(attrib.type, isNormalized, isInteger); // HACK: Re-use the positions buffer as a dummy buffer for disabled attributes. Filament's // vertex shaders declare all attributes as either vec4 or uvec4 (the latter for bone @@ -1660,7 +1664,6 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle r // a dummy type of either R8G8B8A8_UINT or R8G8B8A8_SNORM, depending on whether the shader // expects to receive floats or ints. if (attrib.buffer == Attribute::BUFFER_UNUSED) { - const bool isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET; vkformat = isInteger ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_SNORM; attrib = prim.vertexBuffer->attributes[0]; } diff --git a/filament/backend/src/vulkan/VulkanFboCache.cpp b/filament/backend/src/vulkan/VulkanFboCache.cpp index 07afa3fe2c4..1ae12040c69 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.cpp +++ b/filament/backend/src/vulkan/VulkanFboCache.cpp @@ -35,7 +35,7 @@ bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1, if (k1.subpassMask != k2.subpassMask) return false; if (k1.depthLayout != k2.depthLayout) return false; if (k1.depthFormat != k2.depthFormat) return false; - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (k1.colorLayout[i] != k2.colorLayout[i]) return false; if (k1.colorFormat[i] != k2.colorFormat[i]) return false; } @@ -49,7 +49,7 @@ bool VulkanFboCache::FboKeyEqualFn::operator()(const FboKey& k1, const FboKey& k if (k1.layers != k2.layers) return false; if (k1.samples != k2.samples) return false; if (k1.depth != k2.depth) return false; - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (k1.color[i] != k2.color[i]) return false; if (k1.resolve[i] != k2.resolve[i]) return false; } @@ -73,7 +73,7 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config) noexcept { // The attachment list contains: Color Attachments, Resolve Attachments, and Depth Attachment. // For simplicity, create an array that can hold the maximum possible number of attachments. // Note that this needs to have the same ordering as the corollary array in getRenderPass. - VkImageView attachments[MRT::TARGET_COUNT + MRT::TARGET_COUNT + 1]; + VkImageView attachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 1]; uint32_t attachmentCount = 0; for (VkImageView attachment : config.color) { if (attachment) { @@ -138,7 +138,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { // thrashing the layout. Note that pipeline barriers are more powerful than render passes for // performing layout transitions, because they allow for per-miplevel transitions. const bool discard = any(config.discardStart & TargetBufferFlags::COLOR); - struct { VkImageLayout subpass, initial, final; } colorLayouts[MRT::TARGET_COUNT]; + struct { VkImageLayout subpass, initial, final; } colorLayouts[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; if (isSwapChain) { colorLayouts[0].subpass = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; @@ -149,16 +149,16 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { colorLayouts[0].final = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } else { - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { colorLayouts[i].subpass = config.colorLayout[i]; colorLayouts[i].initial = config.colorLayout[i]; colorLayouts[i].final = config.colorLayout[i]; } } - VkAttachmentReference inputAttachmentRef[MRT::TARGET_COUNT] = {}; - VkAttachmentReference colorAttachmentRefs[2][MRT::TARGET_COUNT] = {}; - VkAttachmentReference resolveAttachmentRef[MRT::TARGET_COUNT] = {}; + VkAttachmentReference inputAttachmentRef[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; + VkAttachmentReference colorAttachmentRefs[2][MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; + VkAttachmentReference resolveAttachmentRef[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; VkAttachmentReference depthAttachmentRef = {}; const bool hasDepth = config.depthFormat != VK_FORMAT_UNDEFINED; @@ -181,7 +181,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { // The attachment list contains: Color Attachments, Resolve Attachments, and Depth Attachment. // For simplicity, create an array that can hold the maximum possible number of attachments. // Note that this needs to have the same ordering as the corollary array in getFramebuffer. - VkAttachmentDescription attachments[MRT::TARGET_COUNT + MRT::TARGET_COUNT + 1] = {}; + VkAttachmentDescription attachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 1] = {}; // We support 2 subpasses, which means we need to supply 1 dependency struct. VkSubpassDependency dependencies[1] = {{ @@ -207,7 +207,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { int attachmentIndex = 0; // Populate the Color Attachments. - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (config.colorFormat[i] == VK_FORMAT_UNDEFINED) { continue; } @@ -271,7 +271,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept { // Populate the Resolve Attachments. VkAttachmentReference* pResolveAttachment = resolveAttachmentRef; - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (config.colorFormat[i] == VK_FORMAT_UNDEFINED) { continue; } diff --git a/filament/backend/src/vulkan/VulkanFboCache.h b/filament/backend/src/vulkan/VulkanFboCache.h index 646fe423643..f1fc59dd608 100644 --- a/filament/backend/src/vulkan/VulkanFboCache.h +++ b/filament/backend/src/vulkan/VulkanFboCache.h @@ -39,8 +39,8 @@ class VulkanFboCache { // RenderPassKey is a small POD representing the immutable state that is used to construct // a VkRenderPass. It is hashed and used as a lookup key. struct alignas(8) RenderPassKey { - VkImageLayout colorLayout[MRT::TARGET_COUNT]; // 16 bytes - VkFormat colorFormat[MRT::TARGET_COUNT]; // 16 bytes + VkImageLayout colorLayout[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 16 bytes + VkFormat colorFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 16 bytes VkImageLayout depthLayout; // 4 bytes VkFormat depthFormat; // 4 bytes TargetBufferFlags clear : 8; // 1 byte @@ -72,8 +72,8 @@ class VulkanFboCache { uint16_t height; // 2 bytes uint16_t layers; // 2 bytes uint16_t samples; // 2 bytes - VkImageView color[MRT::TARGET_COUNT]; // 32 bytes - VkImageView resolve[MRT::TARGET_COUNT]; // 32 bytes + VkImageView color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 32 bytes + VkImageView resolve[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 32 bytes VkImageView depth; // 8 bytes }; struct FboVal { diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index 5f8d75aee0d..a97d66afa02 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -110,13 +110,13 @@ VulkanRenderTarget::VulkanRenderTarget(VulkanContext& context) : HwRenderTarget( mContext(context), mOffscreen(false), mSamples(1) {} VulkanRenderTarget::VulkanRenderTarget(VulkanContext& context, uint32_t width, uint32_t height, - uint8_t samples, VulkanAttachment color[MRT::TARGET_COUNT], + uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT], VulkanAttachment depthStencil[2], VulkanStagePool& stagePool) : HwRenderTarget(width, height), mContext(context), mOffscreen(true), mSamples(samples) { // For each color attachment, create (or fetch from cache) a VkImageView that selects a specific // miplevel and array layer. - for (int index = 0; index < MRT::TARGET_COUNT; index++) { + for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) { const VulkanAttachment& spec = color[index]; mColor[index] = createAttachment(spec); VulkanTexture* texture = spec.texture; @@ -146,7 +146,7 @@ VulkanRenderTarget::VulkanRenderTarget(VulkanContext& context, uint32_t width, u const int depth = 1; // Create sidecar MSAA textures for color attachments. - for (int index = 0; index < MRT::TARGET_COUNT; index++) { + for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) { const VulkanAttachment& spec = color[index]; VulkanTexture* texture = spec.texture; if (texture && texture->samples == 1) { @@ -183,7 +183,7 @@ VulkanRenderTarget::VulkanRenderTarget(VulkanContext& context, uint32_t width, u } VulkanRenderTarget::~VulkanRenderTarget() { - for (int index = 0; index < MRT::TARGET_COUNT; index++) { + for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) { if (mMsaaAttachments[index].texture != mColor[index].texture) { delete mMsaaAttachments[index].texture; } @@ -381,7 +381,7 @@ int VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) const return 1; } int count = 0; - for (int i = 0; i < MRT::TARGET_COUNT; i++) { + for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (mColor[i].format == VK_FORMAT_UNDEFINED) { continue; } diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index e7fa20dfaac..8eb07a4ebf2 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -44,7 +44,7 @@ struct VulkanProgram : public HwProgram { struct VulkanRenderTarget : private HwRenderTarget { // Creates an offscreen render target. VulkanRenderTarget(VulkanContext& context, uint32_t width, uint32_t height, uint8_t samples, - VulkanAttachment color[MRT::TARGET_COUNT], VulkanAttachment depthStencil[2], + VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT], VulkanAttachment depthStencil[2], VulkanStagePool& stagePool); // Creates a special "default" render target (i.e. associated with the swap chain) @@ -65,12 +65,12 @@ struct VulkanRenderTarget : private HwRenderTarget { bool hasDepth() const { return mDepth.format != VK_FORMAT_UNDEFINED; } private: - VulkanAttachment mColor[MRT::TARGET_COUNT] = {}; + VulkanAttachment mColor[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; VulkanAttachment mDepth = {}; VulkanContext& mContext; const bool mOffscreen; const uint8_t mSamples; - VulkanAttachment mMsaaAttachments[MRT::TARGET_COUNT] = {}; + VulkanAttachment mMsaaAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; VulkanAttachment mMsaaDepthAttachment = {}; }; diff --git a/filament/backend/src/vulkan/VulkanUtility.cpp b/filament/backend/src/vulkan/VulkanUtility.cpp index 4b7de731f0e..0d851c419a7 100644 --- a/filament/backend/src/vulkan/VulkanUtility.cpp +++ b/filament/backend/src/vulkan/VulkanUtility.cpp @@ -33,7 +33,7 @@ void createSemaphore(VkDevice device, VkSemaphore *semaphore) { ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateSemaphore error."); } -VkFormat getVkFormat(ElementType type, bool normalized) { +VkFormat getVkFormat(ElementType type, bool normalized, bool integer) { using ElementType = ElementType; if (normalized) { switch (type) { @@ -64,19 +64,19 @@ VkFormat getVkFormat(ElementType type, bool normalized) { } switch (type) { // Single Component Types - case ElementType::BYTE: return VK_FORMAT_R8_SINT; - case ElementType::UBYTE: return VK_FORMAT_R8_UINT; - case ElementType::SHORT: return VK_FORMAT_R16_SINT; - case ElementType::USHORT: return VK_FORMAT_R16_UINT; + case ElementType::BYTE: return integer ? VK_FORMAT_R8_SINT : VK_FORMAT_R8_SSCALED; + case ElementType::UBYTE: return integer ? VK_FORMAT_R8_UINT : VK_FORMAT_R8_USCALED; + case ElementType::SHORT: return integer ? VK_FORMAT_R16_SINT : VK_FORMAT_R16_SSCALED; + case ElementType::USHORT: return integer ? VK_FORMAT_R16_UINT : VK_FORMAT_R16_USCALED; case ElementType::HALF: return VK_FORMAT_R16_SFLOAT; case ElementType::INT: return VK_FORMAT_R32_SINT; case ElementType::UINT: return VK_FORMAT_R32_UINT; case ElementType::FLOAT: return VK_FORMAT_R32_SFLOAT; // Two Component Types - case ElementType::BYTE2: return VK_FORMAT_R8G8_SINT; - case ElementType::UBYTE2: return VK_FORMAT_R8G8_UINT; - case ElementType::SHORT2: return VK_FORMAT_R16G16_SINT; - case ElementType::USHORT2: return VK_FORMAT_R16G16_UINT; + case ElementType::BYTE2: return integer ? VK_FORMAT_R8G8_SINT : VK_FORMAT_R8G8_SSCALED; + case ElementType::UBYTE2: return integer ? VK_FORMAT_R8G8_UINT : VK_FORMAT_R8G8_USCALED; + case ElementType::SHORT2: return integer ? VK_FORMAT_R16G16_SINT : VK_FORMAT_R16G16_SSCALED; + case ElementType::USHORT2: return integer ? VK_FORMAT_R16G16_UINT : VK_FORMAT_R16G16_USCALED; case ElementType::HALF2: return VK_FORMAT_R16G16_SFLOAT; case ElementType::FLOAT2: return VK_FORMAT_R32G32_SFLOAT; // Three Component Types @@ -87,10 +87,10 @@ VkFormat getVkFormat(ElementType type, bool normalized) { case ElementType::HALF3: return VK_FORMAT_R16G16B16_SFLOAT; // NOT MINSPEC case ElementType::FLOAT3: return VK_FORMAT_R32G32B32_SFLOAT; // Four Component Types - case ElementType::BYTE4: return VK_FORMAT_R8G8B8A8_SINT; - case ElementType::UBYTE4: return VK_FORMAT_R8G8B8A8_UINT; - case ElementType::SHORT4: return VK_FORMAT_R16G16B16A16_SINT; - case ElementType::USHORT4: return VK_FORMAT_R16G16B16A16_UINT; + case ElementType::BYTE4: return integer ? VK_FORMAT_R8G8B8A8_SINT : VK_FORMAT_R8G8B8A8_SSCALED; + case ElementType::UBYTE4: return integer ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_USCALED; + case ElementType::SHORT4: return integer ? VK_FORMAT_R16G16B16A16_SINT : VK_FORMAT_R16G16B16A16_SSCALED; + case ElementType::USHORT4: return integer ? VK_FORMAT_R16G16B16A16_UINT : VK_FORMAT_R16G16B16A16_USCALED; case ElementType::HALF4: return VK_FORMAT_R16G16B16A16_SFLOAT; case ElementType::FLOAT4: return VK_FORMAT_R32G32B32A32_SFLOAT; } diff --git a/filament/backend/src/vulkan/VulkanUtility.h b/filament/backend/src/vulkan/VulkanUtility.h index 189b48533c3..e6393a7966e 100644 --- a/filament/backend/src/vulkan/VulkanUtility.h +++ b/filament/backend/src/vulkan/VulkanUtility.h @@ -25,7 +25,7 @@ namespace filament { namespace backend { void createSemaphore(VkDevice device, VkSemaphore* semaphore); -VkFormat getVkFormat(ElementType type, bool normalized); +VkFormat getVkFormat(ElementType type, bool normalized, bool integer); VkFormat getVkFormat(TextureFormat format); uint32_t getBytesPerPixel(TextureFormat format); VkCompareOp getCompareOp(SamplerCompareFunc func); diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index 301424a96bc..c31ba295f09 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -23,6 +23,7 @@ namespace utils { class Entity; +class EntityManager; class JobSystem; } // namespace utils @@ -303,10 +304,24 @@ class UTILS_PUBLIC Engine { */ static void destroy(Engine* engine); + /** + * @return EntityManager used by filament + */ + utils::EntityManager& getEntityManager() noexcept; + + /** + * @return RenderableManager reference + */ RenderableManager& getRenderableManager() noexcept; + /** + * @return LightManager reference + */ LightManager& getLightManager() noexcept; + /** + * @return TransformManager reference + */ TransformManager& getTransformManager() noexcept; /** @@ -477,16 +492,18 @@ class UTILS_PUBLIC Engine { UTILS_DEPRECATED void destroy(const Camera* camera); - /** - * Invokes one iteration of the render loop, used only on single-threaded platforms. - * - * This should be called every time the windowing system needs to paint (e.g. at 60 Hz). - */ + /** + * Invokes one iteration of the render loop, used only on single-threaded platforms. + * + * This should be called every time the windowing system needs to paint (e.g. at 60 Hz). + */ void execute(); - /** - * Retrieves the job system that the Engine has ownership over. - */ + /** + * Retrieves the job system that the Engine has ownership over. + * + * @return JobSystem used by filament + */ utils::JobSystem& getJobSystem() noexcept; DebugRegistry& getDebugRegistry() noexcept; diff --git a/filament/include/filament/RenderTarget.h b/filament/include/filament/RenderTarget.h index 51dd77b09cf..fd2d207b9db 100644 --- a/filament/include/filament/RenderTarget.h +++ b/filament/include/filament/RenderTarget.h @@ -22,6 +22,7 @@ #include #include +#include #include @@ -51,15 +52,13 @@ class UTILS_PUBLIC RenderTarget : public FilamentAPI { */ enum AttachmentPoint { COLOR0 = 0, //!< identifies the 1st color attachment - COLOR1 = 2, //!< identifies the 2nd color attachment - COLOR2 = 3, //!< identifies the 3rd color attachment - COLOR3 = 4, //!< identifies the 4th color attachment - DEPTH = 1, //!< identifies the depth attachment + COLOR1 = 1, //!< identifies the 2nd color attachment + COLOR2 = 2, //!< identifies the 3rd color attachment + COLOR3 = 3, //!< identifies the 4th color attachment + DEPTH = backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT, //!< identifies the depth attachment COLOR = COLOR0, //!< identifies the 1st color attachment }; - static constexpr size_t ATTACHMENT_COUNT = 5; - //! Use Builder to construct a RenderTarget object instance class Builder : public BuilderBase { friend struct BuilderDetails; diff --git a/filament/include/filament/Skybox.h b/filament/include/filament/Skybox.h index 6279e493dec..bf4342a43e0 100644 --- a/filament/include/filament/Skybox.h +++ b/filament/include/filament/Skybox.h @@ -104,12 +104,11 @@ class UTILS_PUBLIC Skybox : public FilamentAPI { */ Builder& showSun(bool show) noexcept; - /** - * Skybox intensity when no IndirectLight is set - * - * This call is ignored when an IndirectLight is set, otherwise it is used in its place. + * Skybox intensity when no IndirectLight is set on the Scene. * + * This call is ignored when an IndirectLight is set on the Scene, and the intensity + * of the IndirectLight is used instead. * * @param envIntensity Scale factor applied to the skybox texel values such that * the result is in lux, or lumen/m^2 (default = 30000) diff --git a/filament/include/filament/VertexBuffer.h b/filament/include/filament/VertexBuffer.h index 4cd677dad21..51c2c247bc8 100644 --- a/filament/include/filament/VertexBuffer.h +++ b/filament/include/filament/VertexBuffer.h @@ -116,6 +116,9 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI { * @warning VertexAttribute::TANGENTS must be specified as a quaternion and is how normals * are specified. * + * @warning Not all backends support 3-component attributes that are not floats. For help + * with conversion, see geometry::Transcoder. + * * @see VertexAttribute * * This is a no-op if the \p attribute is an invalid enum. diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 8ffdfc5f1b5..0135f5def7b 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -1007,6 +1007,10 @@ void Engine::flushAndWait() { upcast(this)->flushAndWait(); } +utils::EntityManager& Engine::getEntityManager() noexcept { + return upcast(this)->getEntityManager(); +} + RenderableManager& Engine::getRenderableManager() noexcept { return upcast(this)->getRenderableManager(); } diff --git a/filament/src/RenderTarget.cpp b/filament/src/RenderTarget.cpp index fc2a1a62119..2abc74f710c 100644 --- a/filament/src/RenderTarget.cpp +++ b/filament/src/RenderTarget.cpp @@ -28,7 +28,7 @@ namespace filament { using namespace backend; struct RenderTarget::BuilderDetails { - FRenderTarget::Attachment mAttachments[RenderTarget::ATTACHMENT_COUNT] = {}; + FRenderTarget::Attachment mAttachments[FRenderTarget::ATTACHMENT_COUNT] = {}; uint32_t mWidth{}; uint32_t mHeight{}; uint8_t mSamples = 1; // currently not settable in the public facing API @@ -127,21 +127,11 @@ FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& build } }; - if (mAttachments[COLOR0].texture) { - mAttachmentMask |= TargetBufferFlags::COLOR0; - setAttachment(mrt[0], COLOR0); - } - if (mAttachments[COLOR1].texture) { - mAttachmentMask |= TargetBufferFlags::COLOR1; - setAttachment(mrt[1], COLOR1); - } - if (mAttachments[COLOR2].texture) { - mAttachmentMask |= TargetBufferFlags::COLOR2; - setAttachment(mrt[2], COLOR2); - } - if (mAttachments[COLOR3].texture) { - mAttachmentMask |= TargetBufferFlags::COLOR3; - setAttachment(mrt[3], COLOR3); + for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { + if (mAttachments[i].texture) { + mAttachmentMask |= getMRTColorFlag(i); + setAttachment(mrt[i], (AttachmentPoint)i); + } } if (mAttachments[DEPTH].texture) { mAttachmentMask |= TargetBufferFlags::DEPTH; diff --git a/filament/src/details/RenderTarget.h b/filament/src/details/RenderTarget.h index 6b07bf69e72..2992c950c67 100644 --- a/filament/src/details/RenderTarget.h +++ b/filament/src/details/RenderTarget.h @@ -57,7 +57,8 @@ class FRenderTarget : public RenderTarget { private: friend class RenderTarget; - Attachment mAttachments[RenderTarget::ATTACHMENT_COUNT]; + static constexpr size_t ATTACHMENT_COUNT = backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 1; + Attachment mAttachments[ATTACHMENT_COUNT]; HwHandle mHandle{}; backend::TargetBufferFlags mAttachmentMask = {}; }; diff --git a/filament/src/fg2/FrameGraphRenderPass.h b/filament/src/fg2/FrameGraphRenderPass.h index 47548acd551..61d86b89e8c 100644 --- a/filament/src/fg2/FrameGraphRenderPass.h +++ b/filament/src/fg2/FrameGraphRenderPass.h @@ -20,6 +20,7 @@ #include "fg2/FrameGraphTexture.h" #include +#include #include @@ -30,11 +31,12 @@ namespace filament { * These are transient objects that exist inside a pass only. */ struct FrameGraphRenderPass { + static constexpr size_t ATTACHMENT_COUNT = backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 2; struct Attachments { union { - FrameGraphId array[6] = {}; + FrameGraphId array[ATTACHMENT_COUNT] = {}; struct { - FrameGraphId color[4]; + FrameGraphId color[backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]; FrameGraphId depth; FrameGraphId stencil; }; diff --git a/filament/src/fg2/PassNode.cpp b/filament/src/fg2/PassNode.cpp index e81d4ab14e3..4ffc7ce38f2 100644 --- a/filament/src/fg2/PassNode.cpp +++ b/filament/src/fg2/PassNode.cpp @@ -91,7 +91,7 @@ uint32_t RenderPassNode::declareRenderTarget(FrameGraph& fg, FrameGraph::Builder auto incomingEdges = dependencyGraph.getIncomingEdges(this); auto outgoingEdges = dependencyGraph.getOutgoingEdges(this); - for (size_t i = 0; i < 6; i++) { + for (size_t i = 0; i < RenderPassData::ATTACHMENT_COUNT; i++) { if (descriptor.attachments.array[i]) { data.attachmentInfo[i] = attachments.array[i]; @@ -126,15 +126,6 @@ uint32_t RenderPassNode::declareRenderTarget(FrameGraph& fg, FrameGraph::Builder void RenderPassNode::resolve() noexcept { using namespace backend; - const TargetBufferFlags flags[6] = { - TargetBufferFlags::COLOR0, - TargetBufferFlags::COLOR1, - TargetBufferFlags::COLOR2, - TargetBufferFlags::COLOR3, - TargetBufferFlags::DEPTH, - TargetBufferFlags::STENCIL - }; - for (auto& rt : mRenderTargetData) { uint32_t minWidth = std::numeric_limits::max(); @@ -148,20 +139,21 @@ void RenderPassNode::resolve() noexcept { ImportedRenderTarget* pImportedRenderTarget = nullptr; - for (size_t i = 0; i < 6; i++) { + for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 2; i++) { if (rt.descriptor.attachments.array[i]) { + const TargetBufferFlags target = getMRTColorFlag(i); - rt.targetBufferFlags |= flags[i]; + rt.targetBufferFlags |= target; // start by discarding all the attachments we have // (we could set to ALL, but this is cleaner) - rt.backend.params.flags.discardStart |= flags[i]; - rt.backend.params.flags.discardEnd |= flags[i]; + rt.backend.params.flags.discardStart |= target; + rt.backend.params.flags.discardEnd |= target; if (rt.outgoing[i] && rt.outgoing[i]->hasActiveReaders()) { - rt.backend.params.flags.discardEnd &= ~flags[i]; + rt.backend.params.flags.discardEnd &= ~target; } if (rt.incoming[i] && rt.incoming[i]->hasActiveWriters()) { - rt.backend.params.flags.discardStart &= ~flags[i]; + rt.backend.params.flags.discardStart &= ~target; } VirtualResource* pResource = mFrameGraph.getResource(rt.descriptor.attachments.array[i]); @@ -239,11 +231,22 @@ void RenderPassNode::RenderPassData::devirtualize(FrameGraph& fg, assert_invariant(any(targetBufferFlags)); if (UTILS_LIKELY(!imported)) { - TargetBufferInfo info[6] = {}; - for (size_t i = 0; i < 6; i++) { + MRT colorInfo{}; + for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { if (attachmentInfo[i]) { auto const* pResource = static_cast const*>( fg.getResource(attachmentInfo[i])); + colorInfo[i].handle = pResource->resource.handle; + colorInfo[i].level = pResource->subResourceDescriptor.level; + colorInfo[i].layer = pResource->subResourceDescriptor.layer; + } + } + + TargetBufferInfo info[2] = {}; + for (size_t i = 0; i < 2; i++) { + if (attachmentInfo[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + i]) { + auto const* pResource = static_cast const*>( + fg.getResource(attachmentInfo[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + i])); info[i].handle = pResource->resource.handle; info[i].level = pResource->subResourceDescriptor.level; info[i].layer = pResource->subResourceDescriptor.layer; @@ -255,8 +258,7 @@ void RenderPassNode::RenderPassData::devirtualize(FrameGraph& fg, backend.params.viewport.width, backend.params.viewport.height, descriptor.samples, - { info[0], info[1], info[2], info[3] }, - info[4], info[5]); + colorInfo,info[0], info[1]); } } diff --git a/filament/src/fg2/details/PassNode.h b/filament/src/fg2/details/PassNode.h index af04bf64c39..142b2dcbbbc 100644 --- a/filament/src/fg2/details/PassNode.h +++ b/filament/src/fg2/details/PassNode.h @@ -21,8 +21,11 @@ #include "fg2/details/Utilities.h" #include "fg2/FrameGraph.h" #include "fg2/FrameGraphRenderPass.h" + #include "private/backend/DriverApiForward.h" +#include + #include namespace utils { @@ -63,13 +66,14 @@ class RenderPassNode : public PassNode { public: class RenderPassData { public: + static constexpr size_t ATTACHMENT_COUNT = backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 2; const char* name = {}; FrameGraphRenderPass::Descriptor descriptor; bool imported = false; backend::TargetBufferFlags targetBufferFlags = {}; - FrameGraphId attachmentInfo[6] = {}; - ResourceNode* incoming[6] = {}; // nodes of the incoming attachments - ResourceNode* outgoing[6] = {}; // nodes of the outgoing attachments + FrameGraphId attachmentInfo[ATTACHMENT_COUNT] = {}; + ResourceNode* incoming[ATTACHMENT_COUNT] = {}; // nodes of the incoming attachments + ResourceNode* outgoing[ATTACHMENT_COUNT] = {}; // nodes of the outgoing attachments struct { backend::Handle target; backend::RenderPassParams params; diff --git a/filament/src/materials/antiAliasing/taa.mat b/filament/src/materials/antiAliasing/taa.mat index c70a0ee2443..e96f72ca97b 100644 --- a/filament/src/materials/antiAliasing/taa.mat +++ b/filament/src/materials/antiAliasing/taa.mat @@ -75,7 +75,7 @@ fragment { /* Configuration mobile/desktop */ -#if defined(TARGET_MOBILE) +#if FILAMENT_QUALITY == FILAMENT_QUALITY_LOW #define BOX_CLIPPING BOX_CLIPPING_ACCURATE #define BOX_TYPE BOX_TYPE_VARIANCE #define USE_YCoCg 0 diff --git a/filament/src/materials/bloom/bloomUpsample.mat b/filament/src/materials/bloom/bloomUpsample.mat index e17882279f6..f5565b1003a 100644 --- a/filament/src/materials/bloom/bloomUpsample.mat +++ b/filament/src/materials/bloom/bloomUpsample.mat @@ -35,7 +35,7 @@ fragment { float lod = materialParams.level; highp vec2 uv = variable_vertex.xy; -#if defined(TARGET_MOBILE) +#if FILAMENT_QUALITY == FILAMENT_QUALITY_LOW highp vec4 d = vec4(materialParams.resolution.zw, -materialParams.resolution.zw) * 0.5; vec3 c; c = textureLod(materialParams_source, uv + d.zw, lod).rgb; diff --git a/filament/src/materials/dof/dof.mat b/filament/src/materials/dof/dof.mat index a89c9fb18fc..2cb0f0c428b 100644 --- a/filament/src/materials/dof/dof.mat +++ b/filament/src/materials/dof/dof.mat @@ -132,7 +132,7 @@ float getMipLevel(const float ringCount, const float kernelSizeInPixels) { // median pass. With round() below + noise, most gaps are handled by the median pass, // and the quality is much better overall. -#if defined(TARGET_MOBILE) +#if FILAMENT_QUALITY == FILAMENT_QUALITY_LOW // on mobile, the mip level is not used in computations in the shader, // so we just let the texture unit pick the the "nearest" mipmap level. #else @@ -156,7 +156,7 @@ float sampleWeight(const float coc, const float mip) { // The high resolution pixel radius is sqrt(2) * 0.5. // x2 to scale to 1/4 res, x 2^mip for the current mip. // ^2 for the area. -#if defined(TARGET_MOBILE) +#if FILAMENT_QUALITY == FILAMENT_QUALITY_LOW const float pixelRadiusSquared = 2.0; // (sqrt(2) * 0.5 * 2.0)^2 #else float pixelRadiusSquared = 2.0 * pow(2.0, mip); // 2^(mip+1) @@ -166,7 +166,7 @@ float sampleWeight(const float coc, const float mip) { float intersection(const float border, const float absCoc, const float mip) { // there is very little visible difference, so use the cheaper version on mobile -#if defined(TARGET_MOBILE) +#if FILAMENT_QUALITY == FILAMENT_QUALITY_LOW return saturate((absCoc - border) + 0.5); #else return saturate((absCoc - border) * pow(0.5, mip) + 0.5); @@ -597,7 +597,7 @@ void backgroundTile(inout vec4 background, inout float bgOpacity, float kernelArea = sampleWeight(kernelSize * ((ringCountGather + 0.5) / ringCountGather), mip); bgOpacity = saturate(prev.cw * rcp(kernelArea)); -#if !defined(TARGET_MOBILE) +#if FILAMENT_QUALITY == FILAMENT_QUALITY_HIGH bgOpacity *= smoothstep(MAX_IN_FOCUS_COC, 2.0, kernelSize); #endif background = prev.c * (rcpOrZeroHighp(prev.cw) * bgOpacity); diff --git a/ios/CocoaPods/Filament.podspec b/ios/CocoaPods/Filament.podspec index 0124a6e6108..25fc99c0982 100644 --- a/ios/CocoaPods/Filament.podspec +++ b/ios/CocoaPods/Filament.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = "Filament" - spec.version = "1.9.22" + spec.version = "1.9.23" 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.22/filament-v1.9.22-ios.tgz" } + spec.source = { :http => "https://github.com/google/filament/releases/download/v1.9.23/filament-v1.9.23-ios.tgz" } # Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon. spec.pod_target_xcconfig = { diff --git a/libs/filamat/include/filamat/MaterialBuilder.h b/libs/filamat/include/filamat/MaterialBuilder.h index a7a27fc91a6..64edbce3c8f 100644 --- a/libs/filamat/include/filamat/MaterialBuilder.h +++ b/libs/filamat/include/filamat/MaterialBuilder.h @@ -562,7 +562,7 @@ class UTILS_PUBLIC MaterialBuilder : public MaterialBuilderBase { using VariableList = utils::CString[MATERIAL_VARIABLES_COUNT]; using OutputList = std::vector; - static constexpr size_t MAX_COLOR_OUTPUT = filament::backend::MRT::TARGET_COUNT; + static constexpr size_t MAX_COLOR_OUTPUT = filament::backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; static constexpr size_t MAX_DEPTH_OUTPUT = 1; static_assert(MAX_COLOR_OUTPUT == 4, "When updating MRT::TARGET_COUNT, manually update post_process_inputs.fs" diff --git a/libs/filamat/src/shaders/CodeGenerator.cpp b/libs/filamat/src/shaders/CodeGenerator.cpp index c7c97e43206..75fa57068f1 100644 --- a/libs/filamat/src/shaders/CodeGenerator.cpp +++ b/libs/filamat/src/shaders/CodeGenerator.cpp @@ -78,12 +78,24 @@ io::sstream& CodeGenerator::generateProlog(io::sstream& out, ShaderType type, out << "#define TARGET_LANGUAGE_SPIRV\n"; } + out << '\n'; + out << "#define FILAMENT_QUALITY_LOW 0\n"; + out << "#define FILAMENT_QUALITY_HIGH 1\n"; + out << "#if defined(TARGET_MOBILE)\n"; + out << "#define FILAMENT_QUALITY FILAMENT_QUALITY_LOW\n"; + out << "#else\n"; + out << "#define FILAMENT_QUALITY FILAMENT_QUALITY_HIGH\n"; + out << "#endif\n"; + out << '\n'; + Precision defaultPrecision = getDefaultPrecision(type); const char* precision = getPrecisionQualifier(defaultPrecision, Precision::DEFAULT); out << "precision " << precision << " float;\n"; out << "precision " << precision << " int;\n"; - out << "precision lowp sampler2DArray;\n"; - out << "precision lowp sampler3D;\n"; + if (mShaderModel == ShaderModel::GL_ES_30) { + out << "precision lowp sampler2DArray;\n"; + out << "precision lowp sampler3D;\n"; + } out << SHADERS_COMMON_TYPES_FS_DATA; diff --git a/libs/geometry/CMakeLists.txt b/libs/geometry/CMakeLists.txt index e755177f5bc..cc959567e26 100644 --- a/libs/geometry/CMakeLists.txt +++ b/libs/geometry/CMakeLists.txt @@ -9,10 +9,12 @@ set(PUBLIC_HDR_DIR include) # ================================================================================================== set(PUBLIC_HDRS include/geometry/SurfaceOrientation.h + include/geometry/Transcoder.h ) set(SRCS src/SurfaceOrientation.cpp + src/Transcoder.cpp ) # ================================================================================================== @@ -41,3 +43,11 @@ endif() # ================================================================================================== install(TARGETS ${TARGET} ARCHIVE DESTINATION lib/${DIST_DIR}) install(DIRECTORY ${PUBLIC_HDR_DIR}/geometry DESTINATION include) + +# ================================================================================================== +# Tests +# ================================================================================================== +if (NOT ANDROID AND NOT WEBGL AND NOT IOS) + add_executable(test_transcoder tests/test_transcoder.cpp) + target_link_libraries(test_transcoder PRIVATE ${TARGET} gtest) +endif() diff --git a/libs/geometry/include/geometry/Transcoder.h b/libs/geometry/include/geometry/Transcoder.h new file mode 100644 index 00000000000..0c77c941702 --- /dev/null +++ b/libs/geometry/include/geometry/Transcoder.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2021 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_GEOMETRY_TRANSCODER_H +#define TNT_GEOMETRY_TRANSCODER_H + +#include + +#include +#include + +namespace filament { +namespace geometry { + +enum class ComponentType { + BYTE, //!< If normalization is enabled, this maps from [-127,127] to [-1,+1] + UBYTE, //!< If normalization is enabled, this maps from [0,255] to [0, +1] + SHORT, //!< If normalization is enabled, this maps from [-32767,32767] to [-1,+1] + USHORT, //!< If normalization is enabled, this maps from [0,65535] to [0, +1] + HALF, //!< 1 sign bit, 5 exponent bits, and 5 mantissa bits. +}; + +/** + * Creates a function object that can convert vertex attribute data into tightly packed floats. + * + * This is especially useful for 3-component formats which are not supported by all backends. + * e.g. The Vulkan minspec includes float3 but not short3. + * + * Usage Example: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * using filament::geometry::Transcoder; + * using filament::geometry::ComponentType; + * + * Transcoder transcode({ + * .componentType = ComponentType::BYTE, + * .normalized = true, + * .componentCount = 3, + * .inputStrideBytes = 0 + * }); + * + * transcode(outputPtr, inputPtr, count); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * The interpretation of signed normalized data is consistent with Vulkan and OpenGL ES 3.0+. + * Note that this slightly differs from earlier versions of OpenGL ES. For example, a signed byte + * value of -127 maps exactly to -1.0f under ES3 and VK rules, but not ES2. + */ +class UTILS_PUBLIC Transcoder { +public: + /** + * Describes the format of all input data that get passed to this transcoder object. + */ + struct Config { + ComponentType componentType; + bool normalized; + uint32_t componentCount; + uint32_t inputStrideBytes = 0; //!< If stride is 0, the transcoder assumes tight packing. + }; + + /** + * Creates an immutable function object with the specified configuration. + * + * The config is not passed by const reference to allow for type inference at the call site. + */ + Transcoder(Config config) noexcept : mConfig(config) {} + + /** + * Converts arbitrary data into tightly packed 32-bit floating point values. + * + * If target is non-null, writes up to "count" items into target and returns the number of bytes + * actually written. + * + * If target is null, returns the number of bytes required. + * + * @param target Client owned area to write into, or null for a size query + * @param source Pointer to the data to read from (does not get retained) + * @param count The maximum number of items to write (i.e. number of float3 values, not bytes) + * @return Number of bytes required to contain "count" items after conversion to packed floats + * + */ + size_t operator()(float* UTILS_RESTRICT target, void const* UTILS_RESTRICT source, + size_t count) const noexcept; + +private: + const Config mConfig; +}; + +} // namespace geometry +} // namespace filament + +#endif // TNT_GEOMETRY_TRANSCODER_H diff --git a/libs/geometry/src/SurfaceOrientation.cpp b/libs/geometry/src/SurfaceOrientation.cpp index 478bb08d1bd..47fc2a7910b 100644 --- a/libs/geometry/src/SurfaceOrientation.cpp +++ b/libs/geometry/src/SurfaceOrientation.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -226,6 +227,7 @@ SurfaceOrientation* OrientationBuilderImpl::buildWithUvs() { memset(tan2.data(), 0, sizeof(float3) * vertexCount); for (size_t a = 0; a < triangleCount; ++a) { uint3 tri = triangles16 ? uint3(triangles16[a]) : triangles32[a]; + assert_invariant(tri.x < vertexCount && tri.y < vertexCount && tri.z < vertexCount); const float3& v1 = positions[tri.x]; const float3& v2 = positions[tri.y]; const float3& v3 = positions[tri.z]; @@ -334,6 +336,7 @@ SurfaceOrientation* OrientationBuilderImpl::buildWithFlatNormals() { float3* normals = new float3[vertexCount]; for (size_t a = 0; a < triangleCount; ++a) { const uint3 tri = triangles16 ? uint3(triangles16[a]) : triangles32[a]; + assert_invariant(tri.x < vertexCount && tri.y < vertexCount && tri.z < vertexCount); const float3 v1 = positions[tri.x]; const float3 v2 = positions[tri.y]; const float3 v3 = positions[tri.z]; diff --git a/libs/geometry/src/Transcoder.cpp b/libs/geometry/src/Transcoder.cpp new file mode 100644 index 00000000000..ae3582f3e50 --- /dev/null +++ b/libs/geometry/src/Transcoder.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +using filament::math::half; + +namespace filament { +namespace geometry { + +// The internal workhorse function of the Transcoder, which takes arbitrary input but always +// produced packed floats. We expose a more readable interface than this to users, who often have +// untyped blobs of interleaved data. Note that this variant takes an arbitrary number of +// components, we also have a fixed-size variant for better compiler output. +template +void convert(float* UTILS_RESTRICT target, void const* UTILS_RESTRICT source, size_t count, + int componentCount, int srcStride) noexcept { + constexpr float scale = 1.0f / float(NORMALIZATION_FACTOR); + uint8_t const* srcBytes = (uint8_t const*) source; + for (size_t i = 0; i < count; ++i, target += componentCount, srcBytes += srcStride) { + SOURCE_TYPE const* src = (SOURCE_TYPE const*) srcBytes; + for (int n = 0; n < componentCount; ++n) { + target[n] = float(src[n]) * scale; + } + } +} + +template +void convert(float* UTILS_RESTRICT target, void const* UTILS_RESTRICT source, size_t count, + int srcStride) noexcept { + constexpr float scale = 1.0f / float(NORMALIZATION_FACTOR); + uint8_t const* srcBytes = (uint8_t const*) source; + for (size_t i = 0; i < count; ++i, target += NUM_COMPONENTS, srcBytes += srcStride) { + SOURCE_TYPE const* src = (SOURCE_TYPE const*) srcBytes; + for (int n = 0; n < NUM_COMPONENTS; ++n) { + target[n] = float(src[n]) * scale; + } + } +} + +// Similar to "convert" but clamps the result to -1, which is required for normalized signed types. +// For example, -128 can be represented in SBYTE but is outside the permitted range and should +// therefore be clamped. For more information, see the Vulkan spec under the section "Conversion +// from Normalized Fixed-Point to Floating-Point". +template +void convertClamped(float* UTILS_RESTRICT target, void const* UTILS_RESTRICT source, size_t count, + int componentCount, int srcStride) noexcept { + constexpr float scale = 1.0f / float(NORMALIZATION_FACTOR); + uint8_t const* srcBytes = (uint8_t const*) source; + for (size_t i = 0; i < count; ++i, target += componentCount, srcBytes += srcStride) { + SOURCE_TYPE const* src = (SOURCE_TYPE const*) srcBytes; + for (int n = 0; n < componentCount; ++n) { + const float value = float(src[n]) * scale; + target[n] = value < -1.0f ? -1.0f : value; + } + } +} + +template +void convertClamped(float* UTILS_RESTRICT target, void const* UTILS_RESTRICT source, size_t count, + int srcStride) noexcept { + constexpr float scale = 1.0f / float(NORMALIZATION_FACTOR); + uint8_t const* srcBytes = (uint8_t const*) source; + for (size_t i = 0; i < count; ++i, target += NUM_COMPONENTS, srcBytes += srcStride) { + SOURCE_TYPE const* src = (SOURCE_TYPE const*) srcBytes; + for (int n = 0; n < NUM_COMPONENTS; ++n) { + const float value = float(src[n]) * scale; + target[n] = value < -1.0f ? -1.0f : value; + } + } +} + +size_t Transcoder::operator()(float* UTILS_RESTRICT target, void const* UTILS_RESTRICT source, + size_t count) const noexcept { + const size_t required = count * mConfig.componentCount * sizeof(float); + if (target == nullptr) { + return required; + } + const uint32_t comp = mConfig.componentCount; + switch (mConfig.componentType) { + case ComponentType::BYTE: { + const uint32_t stride = mConfig.inputStrideBytes ? mConfig.inputStrideBytes : comp; + if (mConfig.normalized) { + if (comp == 2) { + convertClamped(target, source, count, stride); + } else if (comp == 3) { + convertClamped(target, source, count, stride); + } else { + convertClamped(target, source, count, comp, stride); + } + } else { + if (comp == 2) { + convert(target, source, count, stride); + } else if (comp == 3) { + convert(target, source, count, stride); + } else { + convert(target, source, count, comp, stride); + } + } + return required; + } + case ComponentType::UBYTE: { + const uint32_t stride = mConfig.inputStrideBytes ? mConfig.inputStrideBytes : comp; + if (mConfig.normalized) { + if (comp == 2) { + convert(target, source, count, stride); + } else if (comp == 3) { + convert(target, source, count, stride); + } else { + convert(target, source, count, comp, stride); + } + } else { + if (comp == 2) { + convert(target, source, count, stride); + } else if (comp == 3) { + convert(target, source, count, stride); + } else { + convert(target, source, count, comp, stride); + } + } + return required; + } + case ComponentType::SHORT: { + const uint32_t stride = mConfig.inputStrideBytes ? mConfig.inputStrideBytes : (2 * comp); + if (mConfig.normalized) { + if (comp == 2) { + convertClamped(target, source, count, stride); + } else if (comp == 3) { + convertClamped(target, source, count, stride); + } else { + convertClamped(target, source, count, comp, stride); + } + } else { + if (comp == 2) { + convert(target, source, count, stride); + } else if (comp == 3) { + convert(target, source, count, stride); + } else { + convert(target, source, count, comp, stride); + } + } + return required; + } + case ComponentType::USHORT: { + const uint32_t stride = mConfig.inputStrideBytes ? mConfig.inputStrideBytes : (2 * comp); + if (mConfig.normalized) { + if (comp == 2) { + convert(target, source, count, stride); + } else if (comp == 3) { + convert(target, source, count, stride); + } else { + convert(target, source, count, comp, stride); + } + } else { + if (comp == 2) { + convert(target, source, count, stride); + } else if (comp == 3) { + convert(target, source, count, stride); + } else { + convert(target, source, count, comp, stride); + } + } + return required; + } + case ComponentType::HALF: { + const uint32_t stride = mConfig.inputStrideBytes ? mConfig.inputStrideBytes : (2 * comp); + uint8_t const* srcBytes = (uint8_t const*) source; + for (size_t i = 0; i < count; ++i, target += comp, srcBytes += stride) { + half const* src = (half const*) srcBytes; + for (int n = 0; n < comp; ++n) { + target[n] = float(src[n]); + } + } + return required; + } + } + return 0; +} + +} // namespace geometry +} // namespace filament diff --git a/libs/geometry/tests/test_transcoder.cpp b/libs/geometry/tests/test_transcoder.cpp new file mode 100644 index 00000000000..5ebf893ed93 --- /dev/null +++ b/libs/geometry/tests/test_transcoder.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include + +using filament::math::half; +using filament::geometry::Transcoder; +using filament::geometry::ComponentType; + +class TranscoderTest : public testing::Test {}; + +struct Vertex { + uint8_t b0, b1, b2, b3; + uint16_t s0, s1, s2; + half h0; +}; + +static constexpr int count = 2; + +static const Vertex vbuffer[count] = { + { + 0x00, 0xff, 0x7f, 0x81, + 0x0000, 0xffff, 0x7fff, + half(-0.5f) + }, + { + 0x00, 0xff, 0x7f, 0x80, // 0x80 is -128 when interpreted as int8_t + 0x0000, 0x8000, 0x7fff, + half(1.0f) + } +}; + +TEST_F(TranscoderTest, Normalized) { + float result[count * 4]; + + // UNSIGNED BYTES + + Transcoder transcodeBytes({ + .componentType = ComponentType::UBYTE, + .normalized = true, + .componentCount = 4u, + .inputStrideBytes = sizeof(Vertex) + }); + + transcodeBytes(result, vbuffer, count); + + ASSERT_EQ(result[0], 0.0f); + ASSERT_EQ(result[1], 1.0f); + ASSERT_NEAR(result[2], 0.50f, 0.005f); + ASSERT_NEAR(result[3], 0.51f, 0.005f); + + ASSERT_EQ(result[4], 0.0f); + ASSERT_EQ(result[5], 1.0f); + ASSERT_NEAR(result[6], 0.50f, 0.005f); + ASSERT_NEAR(result[7], 0.50f, 0.005f); + + // SIGNED BYTES (twos-complement) + + Transcoder transcodeSignedBytes({ + .componentType = ComponentType::BYTE, + .normalized = true, + .componentCount = 4u, + .inputStrideBytes = sizeof(Vertex) + }); + + transcodeSignedBytes(result, vbuffer, count); + + ASSERT_EQ(result[0], 0.0f); + ASSERT_NEAR(result[1], -0.01f, 0.005f); + ASSERT_EQ(result[2], +1.0f); + ASSERT_EQ(result[3], -1.0f); + ASSERT_EQ(result[7], -1.0f); +} + +TEST_F(TranscoderTest, NonNormalized) { + float result[count * 3]; + char const* srcBytes = (char const*) vbuffer; + + // SIGNED SHORTS (twos-complement) + + Transcoder transcodeShorts({ + .componentType = ComponentType::SHORT, + .normalized = false, + .componentCount = 3u, + .inputStrideBytes = sizeof(Vertex) + }); + + size_t written = transcodeShorts(result, srcBytes + 4, count); + ASSERT_EQ(written, sizeof(result)); + + ASSERT_EQ(result[0], 0.0f); + ASSERT_EQ(result[1], -1.0f); + ASSERT_EQ(result[2], 32767.0f); + ASSERT_EQ(result[4], -32768.0f); + + // HALF + + Transcoder transcodeHalf({ + .componentType = ComponentType::HALF, + .normalized = false, // <= this field is ignored for HALF + .componentCount = 1u, + .inputStrideBytes = sizeof(Vertex) + }); + + transcodeHalf(result, srcBytes + 10, count); + ASSERT_EQ(result[0], -0.5f); + ASSERT_EQ(result[1], 1.0f); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/libs/gltfio/src/AssetLoader.cpp b/libs/gltfio/src/AssetLoader.cpp index 0f5f10a72ca..d7457f97679 100644 --- a/libs/gltfio/src/AssetLoader.cpp +++ b/libs/gltfio/src/AssetLoader.cpp @@ -644,15 +644,17 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out } VertexBuffer::AttributeType fatype; - if (!getElementType(accessor->type, accessor->component_type, &fatype)) { + VertexBuffer::AttributeType actualType; + if (!getElementType(accessor->type, accessor->component_type, &fatype, &actualType)) { slog.e << "Unsupported accessor type in " << name << io::endl; return false; } + const int stride = (fatype == actualType) ? accessor->stride : 0; // The cgltf library provides a stride value for all accessors, even though they do not // exist in the glTF file. It is computed from the type and the stride of the buffer view. // As a convenience, cgltf also replaces zero (default) stride with the actual stride. - vbb.attribute(semantic, slot, fatype, 0, accessor->stride); + vbb.attribute(semantic, slot, fatype, 0, stride); vbb.normalized(semantic, accessor->normalized); addBufferSlot({accessor, atype, slot++}); } @@ -709,13 +711,15 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive* inPrim, Primitive* out outPrim->aabb.max = max(outPrim->aabb.max, float3(maxp[0], maxp[1], maxp[2])); VertexBuffer::AttributeType fatype; - if (!getElementType(accessor->type, accessor->component_type, &fatype)) { + VertexBuffer::AttributeType actualType; + if (!getElementType(accessor->type, accessor->component_type, &fatype, &actualType)) { slog.e << "Unsupported accessor type in " << name << io::endl; return false; } + const int stride = (fatype == actualType) ? accessor->stride : 0; VertexAttribute attr = (VertexAttribute) (basePositionAttr + targetIndex); - vbb.attribute(attr, slot, fatype, 0, accessor->stride); + vbb.attribute(attr, slot, fatype, 0, stride); vbb.normalized(attr, accessor->normalized); addBufferSlot({accessor, atype, slot++, morphId}); } diff --git a/libs/gltfio/src/DracoCache.cpp b/libs/gltfio/src/DracoCache.cpp index 57eaf7ea8aa..aadc32e84d4 100644 --- a/libs/gltfio/src/DracoCache.cpp +++ b/libs/gltfio/src/DracoCache.cpp @@ -117,10 +117,10 @@ DracoMesh* DracoMesh::decode(const uint8_t* data, size_t dataSize) { return new DracoMesh(new DracoMeshDetails { std::move(meshStatus).value() }); } -void DracoMesh::getFaceIndices(cgltf_accessor* target) const { +bool DracoMesh::getFaceIndices(cgltf_accessor* target) const { // Return early if we've already decompressed this data. if (target->buffer_view) { - return; + return true; } draco::Mesh* mesh = mDetails->mesh.get(); @@ -131,7 +131,7 @@ void DracoMesh::getFaceIndices(cgltf_accessor* target) const { if (target->count != count) { slog.e << "The glTF accessor wants " << target->count << " indices, " << "but the decoded Draco mesh has " << count << " indices." << io::endl; - return; + return false; } cgltf_buffer_view* view = new cgltf_buffer_view; @@ -149,8 +149,9 @@ void DracoMesh::getFaceIndices(cgltf_accessor* target) const { case cgltf_component_type_r_8u: convertFaces(target, mesh); break; default: slog.e << "Unexpected component type for Draco indices." << io::endl; - break; + return false; } + return true; } bool DracoMesh::getVertexAttributes(uint32_t attributeId, cgltf_accessor* target) const { @@ -173,11 +174,12 @@ bool DracoMesh::getVertexAttributes(uint32_t attributeId, cgltf_accessor* target // DracoMesh. uint32_t count = mesh->num_points(); if (target->count != count) { - slog.w << "The glTF accessor wants " << target->count << " vertices, " + slog.e << "The glTF accessor wants " << target->count << " vertices, " << "but the decoded Draco mesh has " << count << " vertices." << io::endl; - if (target->count < count) { - count = target->count; - } + + // It is tempting to degrade gracefully by processing only the lesser of the two + // counts, but doing so would lead to invalid indices in the index buffer. + return false; } cgltf_buffer_view* view = new cgltf_buffer_view; @@ -209,7 +211,7 @@ bool DracoMesh::getVertexAttributes(uint32_t attributeId, cgltf_accessor* target DracoMesh::~DracoMesh() {} struct DracoMeshDetails {}; DracoMesh* DracoMesh::decode(const uint8_t* data, size_t dataSize) { return nullptr; } -void DracoMesh::getFaceIndices(cgltf_accessor* target) const {} +bool DracoMesh::getFaceIndices(cgltf_accessor* target) const {} bool DracoMesh::getVertexAttributes(uint32_t attributeId, cgltf_accessor* target) const { return false; diff --git a/libs/gltfio/src/DracoCache.h b/libs/gltfio/src/DracoCache.h index 2547ca3f73f..88963da8d79 100644 --- a/libs/gltfio/src/DracoCache.h +++ b/libs/gltfio/src/DracoCache.h @@ -44,7 +44,11 @@ class DracoCache { // Decodes a Draco mesh upon construction and retains the results. // -// For convenience, the decoded attributes and indices are written into glTF accessor structs. +// The DracoMesh API leverages cgltf accessor structs in a way that bears explanation. These are +// read / write parameters that tell the decoder where to write the decoded data, and what format +// is desired. The buffer_view in the accessor should be null unless decompressed data is already +// loaded. This tells the decoder that it should create a buffer_view and a buffer. The buffer +// view, the buffer, and the buffer's data are all automatically freed when DracoMesh is destroyed. // // Note that in the gltfio architecture, the AssetLoader has the job of constructing VertexBuffer // objects while the ResourceLoader has the job of populating them asychronously. This means that @@ -54,7 +58,7 @@ class DracoCache { class DracoMesh { public: static DracoMesh* decode(const uint8_t* compressedData, size_t compressedSize); - void getFaceIndices(cgltf_accessor* destination) const; + bool getFaceIndices(cgltf_accessor* destination) const; bool getVertexAttributes(uint32_t attributeId, cgltf_accessor* destination) const; ~DracoMesh(); private: diff --git a/libs/gltfio/src/GltfEnums.h b/libs/gltfio/src/GltfEnums.h index f3820109979..ad6a34aacec 100644 --- a/libs/gltfio/src/GltfEnums.h +++ b/libs/gltfio/src/GltfEnums.h @@ -134,28 +134,42 @@ inline bool getPrimitiveType(cgltf_primitive_type in, return false; } +// This converts a cgltf component type into a Filament Attribute type. +// +// This function has two out parameters. One result is a safe "permitted type" which we know is +// universally accepted across GPU's and backends, but may require conversion (see Transcoder). The +// other result is the "actual type" which requires no conversion. +// +// Returns false if the given component type is invalid. inline bool getElementType(cgltf_type type, cgltf_component_type ctype, - filament::VertexBuffer::AttributeType* atype) { + filament::VertexBuffer::AttributeType* permitType, + filament::VertexBuffer::AttributeType* actualType) { switch (type) { case cgltf_type_scalar: switch (ctype) { case cgltf_component_type_r_8: - *atype = filament::VertexBuffer::AttributeType::BYTE; + *permitType = filament::VertexBuffer::AttributeType::BYTE; + *actualType = filament::VertexBuffer::AttributeType::BYTE; return true; case cgltf_component_type_r_8u: - *atype = filament::VertexBuffer::AttributeType::UBYTE; + *permitType = filament::VertexBuffer::AttributeType::UBYTE; + *actualType = filament::VertexBuffer::AttributeType::UBYTE; return true; case cgltf_component_type_r_16: - *atype = filament::VertexBuffer::AttributeType::SHORT; + *permitType = filament::VertexBuffer::AttributeType::SHORT; + *actualType = filament::VertexBuffer::AttributeType::SHORT; return true; case cgltf_component_type_r_16u: - *atype = filament::VertexBuffer::AttributeType::USHORT; + *permitType = filament::VertexBuffer::AttributeType::USHORT; + *actualType = filament::VertexBuffer::AttributeType::USHORT; return true; case cgltf_component_type_r_32u: - *atype = filament::VertexBuffer::AttributeType::UINT; + *permitType = filament::VertexBuffer::AttributeType::UINT; + *actualType = filament::VertexBuffer::AttributeType::UINT; return true; case cgltf_component_type_r_32f: - *atype = filament::VertexBuffer::AttributeType::FLOAT; + *permitType = filament::VertexBuffer::AttributeType::FLOAT; + *actualType = filament::VertexBuffer::AttributeType::FLOAT; return true; default: return false; @@ -164,19 +178,24 @@ inline bool getElementType(cgltf_type type, cgltf_component_type ctype, case cgltf_type_vec2: switch (ctype) { case cgltf_component_type_r_8: - *atype = filament::VertexBuffer::AttributeType::BYTE2; + *permitType = filament::VertexBuffer::AttributeType::BYTE2; + *actualType = filament::VertexBuffer::AttributeType::BYTE2; return true; case cgltf_component_type_r_8u: - *atype = filament::VertexBuffer::AttributeType::UBYTE2; + *permitType = filament::VertexBuffer::AttributeType::UBYTE2; + *actualType = filament::VertexBuffer::AttributeType::UBYTE2; return true; case cgltf_component_type_r_16: - *atype = filament::VertexBuffer::AttributeType::SHORT2; + *permitType = filament::VertexBuffer::AttributeType::SHORT2; + *actualType = filament::VertexBuffer::AttributeType::SHORT2; return true; case cgltf_component_type_r_16u: - *atype = filament::VertexBuffer::AttributeType::USHORT2; + *permitType = filament::VertexBuffer::AttributeType::USHORT2; + *actualType = filament::VertexBuffer::AttributeType::USHORT2; return true; case cgltf_component_type_r_32f: - *atype = filament::VertexBuffer::AttributeType::FLOAT2; + *permitType = filament::VertexBuffer::AttributeType::FLOAT2; + *actualType = filament::VertexBuffer::AttributeType::FLOAT2; return true; default: return false; @@ -185,19 +204,24 @@ inline bool getElementType(cgltf_type type, cgltf_component_type ctype, case cgltf_type_vec3: switch (ctype) { case cgltf_component_type_r_8: - *atype = filament::VertexBuffer::AttributeType::BYTE3; + *permitType = filament::VertexBuffer::AttributeType::FLOAT3; + *actualType = filament::VertexBuffer::AttributeType::BYTE3; return true; case cgltf_component_type_r_8u: - *atype = filament::VertexBuffer::AttributeType::UBYTE3; + *permitType = filament::VertexBuffer::AttributeType::FLOAT3; + *actualType = filament::VertexBuffer::AttributeType::UBYTE3; return true; case cgltf_component_type_r_16: - *atype = filament::VertexBuffer::AttributeType::SHORT3; + *permitType = filament::VertexBuffer::AttributeType::FLOAT3; + *actualType = filament::VertexBuffer::AttributeType::SHORT3; return true; case cgltf_component_type_r_16u: - *atype = filament::VertexBuffer::AttributeType::USHORT3; + *permitType = filament::VertexBuffer::AttributeType::FLOAT3; + *actualType = filament::VertexBuffer::AttributeType::USHORT3; return true; case cgltf_component_type_r_32f: - *atype = filament::VertexBuffer::AttributeType::FLOAT3; + *permitType = filament::VertexBuffer::AttributeType::FLOAT3; + *actualType = filament::VertexBuffer::AttributeType::FLOAT3; return true; default: return false; @@ -206,19 +230,24 @@ inline bool getElementType(cgltf_type type, cgltf_component_type ctype, case cgltf_type_vec4: switch (ctype) { case cgltf_component_type_r_8: - *atype = filament::VertexBuffer::AttributeType::BYTE4; + *permitType = filament::VertexBuffer::AttributeType::BYTE4; + *actualType = filament::VertexBuffer::AttributeType::BYTE4; return true; case cgltf_component_type_r_8u: - *atype = filament::VertexBuffer::AttributeType::UBYTE4; + *permitType = filament::VertexBuffer::AttributeType::UBYTE4; + *actualType = filament::VertexBuffer::AttributeType::UBYTE4; return true; case cgltf_component_type_r_16: - *atype = filament::VertexBuffer::AttributeType::SHORT4; + *permitType = filament::VertexBuffer::AttributeType::SHORT4; + *actualType = filament::VertexBuffer::AttributeType::SHORT4; return true; case cgltf_component_type_r_16u: - *atype = filament::VertexBuffer::AttributeType::USHORT4; + *permitType = filament::VertexBuffer::AttributeType::USHORT4; + *actualType = filament::VertexBuffer::AttributeType::USHORT4; return true; case cgltf_component_type_r_32f: - *atype = filament::VertexBuffer::AttributeType::FLOAT4; + *permitType = filament::VertexBuffer::AttributeType::FLOAT4; + *actualType = filament::VertexBuffer::AttributeType::FLOAT4; return true; default: return false; @@ -230,4 +259,11 @@ inline bool getElementType(cgltf_type type, cgltf_component_type ctype, return false; } +inline bool requiresConversion(cgltf_type type, cgltf_component_type ctype) { + filament::VertexBuffer::AttributeType permitted; + filament::VertexBuffer::AttributeType actual; + bool supported = getElementType(type, ctype, &permitted, &actual); + return supported && permitted != actual; +} + #endif // GLTFIO_GLTFENUMS_H diff --git a/libs/gltfio/src/ResourceLoader.cpp b/libs/gltfio/src/ResourceLoader.cpp index 9427eecd010..8a56b286268 100644 --- a/libs/gltfio/src/ResourceLoader.cpp +++ b/libs/gltfio/src/ResourceLoader.cpp @@ -17,6 +17,7 @@ #include #include +#include "GltfEnums.h" #include "FFilamentAsset.h" #include "TangentsJob.h" #include "upcast.h" @@ -28,6 +29,8 @@ #include #include +#include + #include #include #include @@ -53,6 +56,9 @@ using namespace filament; using namespace filament::math; using namespace utils; +using filament::geometry::Transcoder; +using filament::geometry::ComponentType; + static const auto FREE_CALLBACK = [](void* mem, size_t, void*) { free(mem); }; namespace { @@ -185,6 +191,33 @@ static void convertBytesToShorts(uint16_t* dst, const uint8_t* src, size_t count } } +static ComponentType getComponentType(const cgltf_accessor* accessor) { + switch (accessor->component_type) { + case cgltf_component_type_r_8: return ComponentType::BYTE; + case cgltf_component_type_r_8u: return ComponentType::UBYTE; + case cgltf_component_type_r_16: return ComponentType::SHORT; + case cgltf_component_type_r_16u: return ComponentType::USHORT; + default: + // This should be unreachable because other types do not require conversion. + assert_invariant(false); + return {}; + } +} + +static void convertToFloats(float* dest, const cgltf_accessor* accessor) { + const uint32_t dim = cgltf_num_components(accessor->type); + const size_t floatsSize = accessor->count * sizeof(float) * dim; + Transcoder transcode({ + .componentType = getComponentType(accessor), + .normalized = bool(accessor->normalized), + .componentCount = dim, + .inputStrideBytes = uint32_t(accessor->stride) + }); + auto bufferData = (const uint8_t*) accessor->buffer_view->buffer->data; + const uint8_t* source = computeBindingOffset(accessor) + bufferData; + transcode(dest, source, accessor->count); +} + static void decodeDracoMeshes(FFilamentAsset* asset) { DracoCache* dracoCache = &asset->mSourceAsset->dracoCache; @@ -200,25 +233,30 @@ static void decodeDracoMeshes(FFilamentAsset* asset) { }; // Go through every primitive and check if it has a Draco mesh. - for (auto pair : asset->mPrimitives) { + for (auto& pair : asset->mPrimitives) { const cgltf_primitive* prim = pair.first; - VertexBuffer* vb = pair.second; if (!prim->has_draco_mesh_compression) { continue; } const cgltf_draco_mesh_compression& draco = prim->draco_mesh_compression; + // 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); if (!mesh) { - slog.w << "Cannot decompress mesh, Draco decoding error." << io::endl; + slog.e << "Cannot decompress mesh, Draco decoding error." << io::endl; + vertexBuffer = nullptr; continue; } // Copy over the decompressed data, converting the data type if necessary. - if (prim->indices) { - mesh->getFaceIndices(prim->indices); + if (prim->indices && !mesh->getFaceIndices(prim->indices)) { + vertexBuffer = nullptr; + continue; } // Go through each attribute in the decompressed mesh. @@ -237,7 +275,10 @@ static void decodeDracoMeshes(FFilamentAsset* asset) { } // Copy over the decompressed data, converting the data type if necessary. - mesh->getVertexAttributes(id, accessor); + if (!mesh->getVertexAttributes(id, accessor)) { + vertexBuffer = nullptr; + break; + } } } } @@ -432,6 +473,17 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) { const uint8_t* data = computeBindingOffset(accessor) + bufferData; const uint32_t size = computeBindingSize(accessor); if (slot.vertexBuffer) { + if (requiresConversion(accessor->type, accessor->component_type)) { + const size_t dim = cgltf_num_components(accessor->type); + const size_t floatsSize = accessor->count * sizeof(float) * dim; + float* floatsData = (float*) malloc(floatsSize); + convertToFloats(floatsData, accessor); + BufferObject* bo = BufferObject::Builder().size(floatsSize).build(engine); + asset->mBufferObjects.push_back(bo); + bo->setBuffer(engine, BufferDescriptor(floatsData, floatsSize, FREE_CALLBACK)); + slot.vertexBuffer->setBufferObjectAt(engine, slot.bufferIndex, bo); + continue; + } BufferObject* bo = BufferObject::Builder().size(size).build(engine); asset->mBufferObjects.push_back(bo); bo->setBuffer(engine, BufferDescriptor(data, size, diff --git a/libs/iblprefilter/CMakeLists.txt b/libs/iblprefilter/CMakeLists.txt new file mode 100644 index 00000000000..fb13b8552b9 --- /dev/null +++ b/libs/iblprefilter/CMakeLists.txt @@ -0,0 +1,136 @@ +cmake_minimum_required(VERSION 3.10) +project(filament C ASM) + +set(TARGET filament-iblprefilter) +set(PUBLIC_HDR_DIR include) +set(GENERATION_ROOT ${CMAKE_CURRENT_BINARY_DIR}) +set(RESOURCE_DIR "${GENERATION_ROOT}/generated/resources") +set(MATERIAL_DIR "${GENERATION_ROOT}/generated/material") + +# ================================================================================================== +# Sources and headers +# ================================================================================================== +set(PUBLIC_HDRS + include/filament/iblprefilter/IBLPrefilterContext.h +) + +set(SRCS + src/IBLPrefilterContext.cpp +) + +set(PRIVATE_HDRS +) + +set(MATERIAL_SRCS + src/materials/generateKernel.mat + src/materials/iblprefilter.mat +) + +# Embed the binary resource blob for materials. +get_resgen_vars(${RESOURCE_DIR} iblprefilter_materials) +list(APPEND PRIVATE_HDRS ${RESGEN_HEADER}) +list(APPEND SRCS ${RESGEN_SOURCE}) + +# ================================================================================================== +# Definitions +# ================================================================================================== +# "2" corresponds to SYSTRACE_TAG_FILAMENT (See: utils/Systrace.h) +add_definitions(-DSYSTRACE_TAG=2 ) + +# ================================================================================================== +# Generate all .filamat: default material, skyboxes, and post-process +# ================================================================================================== + +if (CMAKE_CROSSCOMPILING) + include(${IMPORT_EXECUTABLES}) +endif() + +set(MATERIAL_BINS) +file(MAKE_DIRECTORY ${MATERIAL_DIR}) + +foreach (mat_src ${MATERIAL_SRCS}) + get_filename_component(localname "${mat_src}" NAME_WE) + get_filename_component(fullname "${mat_src}" ABSOLUTE) + set(output_path "${MATERIAL_DIR}/${localname}.filamat") + + add_custom_command( + OUTPUT ${output_path} + COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname} + MAIN_DEPENDENCY ${fullname} + DEPENDS matc + COMMENT "Compiling material ${mat_src} to ${output_path}" + ) + list(APPEND MATERIAL_BINS ${output_path}) +endforeach() + +add_custom_command( + OUTPUT ${RESGEN_OUTPUTS} + COMMAND resgen ${RESGEN_FLAGS} ${MATERIAL_BINS} + DEPENDS resgen ${MATERIAL_BINS} + COMMENT "Aggregating compiled materials" +) + +if (DEFINED RESGEN_SOURCE_FLAGS) + set_source_files_properties(${RESGEN_SOURCE} PROPERTIES COMPILE_FLAGS ${RESGEN_SOURCE_FLAGS}) +endif() + +# ================================================================================================== +# Includes & target definition +# ================================================================================================== +# specify where our headers are +include_directories(${PUBLIC_HDR_DIR}) +include_directories(${GENERATION_ROOT}) +include_directories(src) + +# we're building a library +add_library(${TARGET} STATIC ${PRIVATE_HDRS} ${PUBLIC_HDRS} ${SRCS}) + +# specify where the public headers of this library are +target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR}) + +# ================================================================================================== +# Dependencies +# ================================================================================================== + +target_link_libraries(${TARGET} PUBLIC math) +target_link_libraries(${TARGET} PUBLIC utils) +target_link_libraries(${TARGET} PUBLIC filament) + +# ================================================================================================== +# Compiler flags +# ================================================================================================== +if (MSVC) + set(OPTIMIZATION_FLAGS + /fp:fast + ) +elseif(WEBGL) + # Avoid strict-vtable-pointers here, it is broken in WebAssembly. + set(OPTIMIZATION_FLAGS -fvisibility=hidden -fvisibility-inlines-hidden) +else() + set(OPTIMIZATION_FLAGS + -ffast-math + -ffp-contract=fast + # TODO: aggressive vectorization is currently broken on Android + # -fslp-vectorize-aggressive + -fvisibility=hidden + -fvisibility-inlines-hidden + -fstrict-vtable-pointers + ) +endif() + +target_compile_options(${TARGET} PRIVATE + ${FILAMENT_WARNINGS} + $<$:${OPTIMIZATION_FLAGS}> + $<$,$>:${DARWIN_OPTIMIZATION_FLAGS}> +) + +target_link_libraries(${TARGET} PRIVATE + $<$,$>:${LINUX_LINKER_OPTIMIZATION_FLAGS}> +) + +# ================================================================================================== +# Installation +# ================================================================================================== +set(INSTALL_TYPE ARCHIVE) +install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR}) +install(DIRECTORY ${PUBLIC_HDR_DIR}/filament/iblprefilter DESTINATION include) diff --git a/libs/iblprefilter/README.md b/libs/iblprefilter/README.md new file mode 100644 index 00000000000..d22294a8790 --- /dev/null +++ b/libs/iblprefilter/README.md @@ -0,0 +1,43 @@ +# IBL Prefilter + +This library can be used to generate the `reflections` texture used by filament's `IndirectLight` +class. It is similar to the `cmgen` tool except that all computations are performed on the GPU and +are therefore significantly faster. `cmgen` however offers more functionalities. + +`IBL Prefilter` is designed entirely as a client of filament, that is, it only uses filament +public APIs. + +## Library and headers + +The library is called `libfilament-iblprefilter.a` and its public headers can be found in +``. + +## Performance + +Expect a total processing time of about 100ms to 300ms for a 5-levels 256 x 256 cubemap with 1024 +samples. + +## Example + +```c++ +#include +#include + +using namespace filament; + +Engine* engine = Engine::create(); + +// create an IBLPrefilterContext, keep it around if several cubemap will be processed. +IBLPrefilterContext context(engine); + +// create the specular (reflections) filter. This operation generates the kernel, so it's important +// to keep it around if it will be reused for several cubemaps. +IBLPrefilterContext::SpecularFilter filter(context); + +// launch the heaver computation. Expect 100-100ms on the GPU. +Texture* texture = filter(environment_cubemap); + +IndirectLight* indirectLight = IndirectLight::Builder() + .reflections(texture) + .build(engine); +``` diff --git a/libs/iblprefilter/include/filament/iblprefilter/IBLPrefilterContext.h b/libs/iblprefilter/include/filament/iblprefilter/IBLPrefilterContext.h new file mode 100644 index 00000000000..cb1d56ea073 --- /dev/null +++ b/libs/iblprefilter/include/filament/iblprefilter/IBLPrefilterContext.h @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2021 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_IBL_PREFILTER_IBLPREFILTER_H +#define TNT_IBL_PREFILTER_IBLPREFILTER_H + +#include + +#include + +namespace filament { +class Engine; +class View; +class Scene; +class Renderer; +class Material; +class MaterialInstance; +class VertexBuffer; +class IndexBuffer; +class Camera; +class Texture; +} // namespace filament + +/** + * IBLPrefilterContext creates and initializes GPU state common to all environment map filters + * supported. Typically, only one instance per filament Engine of this object needs to exist. + * + * Usage Example: + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * #include + * using namespace filament; + * + * Engine* engine = Engine::create(); + * + * IBLPrefilterContext context(engine); + * IBLPrefilterContext::SpecularFilter filter(context); + * Texture* texture = filter(environment_cubemap); + * + * IndirectLight* indirectLight = IndirectLight::Builder() + * .reflections(texture) + * .build(engine); + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +class IBLPrefilterContext { +public: + + /** + * Creates an IBLPrefilter context. + * @param engine filament engine to use + */ + IBLPrefilterContext(filament::Engine& engine); + + /** + * Destroys all GPU resources created during initialization. + */ + ~IBLPrefilterContext() noexcept; + + // not copyable + IBLPrefilterContext(IBLPrefilterContext const&) = delete; + IBLPrefilterContext& operator=(IBLPrefilterContext const&) = delete; + + // movable + IBLPrefilterContext(IBLPrefilterContext&& rhs) noexcept; + IBLPrefilterContext& operator=(IBLPrefilterContext&& rhs); + + // ------------------------------------------------------------------------------------------- + + /** + * SpecularFilter is a GPU based implementation of the specular probe pre-integration filter. + * An instance of SpecularFilter is needed per filter configuration. A filter configuration + * contains the filter's kernel and sample count. + */ + class SpecularFilter { + public: + enum class Kernel : uint8_t { + D_GGX, // Trowbridge-reitz distribution + }; + + /** + * Filter configuration. + */ + struct Config { + uint16_t sampleCount = 1024u; //!< filter sample count (max 2048) + uint8_t levelCount = 5u; //!< number of roughness levels + Kernel kernel = Kernel::D_GGX; //!< filter kernel + }; + + /** + * Filtering options for the current environment. + */ + struct Options { + float hdrLinear = 1024.0f; //!< no HDR compression up to this value + float hdrMax = 16384.0f; //!< HDR compression between hdrLinear and hdrMax + float lodOffset = 1.0f; //!< Good values are 1.0 or 2.0. Higher values help with heavily HDR inputs. + bool generateMipmap = true; //!< set to false if the environment map already has mipmaps + }; + + /** + * Creates a filter. + * @param context IBLPrefilterContext to use + * @param config Configuration of the filter + */ + SpecularFilter(IBLPrefilterContext& context, Config config); + + /** + * Creates a filter with the default configuration. + * @param context IBLPrefilterContext to use + */ + explicit SpecularFilter(IBLPrefilterContext& context); + + /** + * Destroys all GPU resources created during initialization. + */ + ~SpecularFilter() noexcept; + + SpecularFilter(SpecularFilter const&) = delete; + SpecularFilter& operator=(SpecularFilter const&) = delete; + SpecularFilter(SpecularFilter&& rhs) noexcept; + SpecularFilter& operator=(SpecularFilter&& rhs); + + /** + * Generates a prefiltered cubemap. + * @param options Options for this environment + * @param environmentCubemap Environment cubemap (input). Can't be null. + * This cubemap must be SAMPLEABLE and must have all its + * levels allocated. If Options.generateMipmap is true, + * the mipmap levels will be overwritten, otherwise + * it is assumed that all levels are correctly initialized. + * @param outReflectionsTexture Output prefiltered texture or, if null, it is + * automatically created with some default parameters. + * outReflectionsTexture must be a cubemap, it must have + * at least COLOR_ATTACHMENT and SAMPLEABLE usages and at + * least the same number of levels than requested by Config. + * @return returns outReflectionsTexture + */ + filament::Texture* operator()(Options options, + filament::Texture const* environmentCubemap, + filament::Texture* outReflectionsTexture = nullptr); + + /** + * Generates a prefiltered cubemap. + * @param environmentCubemap Environment cubemap (input). Can't be null. + * This cubemap must be SAMPLEABLE and must have all its + * levels allocated. All mipmap levels will be overwritten. + * @param outReflectionsTexture Output prefiltered texture or, if null, it is + * automatically created with some default parameters. + * outReflectionsTexture must be a cubemap, it must have + * at least COLOR_ATTACHMENT and SAMPLEABLE usages and at + * least the same number of levels than requested by Config. + * @return returns outReflectionsTexture + */ + filament::Texture* operator()( + filament::Texture const* environmentCubemap, + filament::Texture* outReflectionsTexture = nullptr); + + // TODO: option for progressive filtering + + // TODO: add a callback for when the processing is done? + + private: + friend class Instance; + filament::Texture* createReflectionsTexture(); + IBLPrefilterContext& mContext; + filament::Material* mKernelMaterial = nullptr; + filament::Texture* mKernelTexture = nullptr; + float* mKernelWeightArray = nullptr; + uint32_t mSampleCount = 0u; + uint8_t mLevelCount = 1u; + }; + +private: + friend class Filter; + filament::Engine& mEngine; + filament::Renderer* mRenderer{}; + filament::Scene* mScene{}; + filament::VertexBuffer* mVertexBuffer{}; + filament::IndexBuffer* mIndexBuffer{}; + filament::Camera* mCamera{}; + utils::Entity mFullScreenQuadEntity{}; + utils::Entity mCameraEntity{}; + filament::View* mView{}; + filament::Material* mIntegrationMaterial{}; +}; + +#endif //TNT_IBL_PREFILTER_IBLPREFILTER_H diff --git a/libs/iblprefilter/src/IBLPrefilterContext.cpp b/libs/iblprefilter/src/IBLPrefilterContext.cpp new file mode 100644 index 00000000000..913b9751a75 --- /dev/null +++ b/libs/iblprefilter/src/IBLPrefilterContext.cpp @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "filament/iblprefilter/IBLPrefilterContext.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "generated/resources/iblprefilter_materials.h" + +using namespace filament::math; +using namespace filament; + +constexpr static float4 sFullScreenTriangleVertices[3] = { + { -1.0f, -1.0f, 1.0f, 1.0f }, + { 3.0f, -1.0f, 1.0f, 1.0f }, + { -1.0f, 3.0f, 1.0f, 1.0f } +}; + +constexpr static const uint16_t sFullScreenTriangleIndices[3] = { 0, 1, 2 }; + +static float DistributionGGX(float NoH, float linearRoughness) noexcept { + // NOTE: (aa-1) == (a-1)(a+1) produces better fp accuracy + float a = linearRoughness; + float f = (a - 1) * ((a + 1) * (NoH * NoH)) + 1; + return (a * a) / ((float)F_PI * f * f); +} + +static float3 hemisphereImportanceSampleDggx(float2 u, float a) { // pdf = D(a) * cosTheta + const float phi = 2.0f * (float)F_PI * u.x; + // NOTE: (aa-1) == (a-1)(a+1) produces better fp accuracy + const float cosTheta2 = (1 - u.y) / (1 + (a + 1) * ((a - 1) * u.y)); + const float cosTheta = std::sqrt(cosTheta2); + const float sinTheta = std::sqrt(1 - cosTheta2); + return { sinTheta * std::cos(phi), sinTheta * std::sin(phi), cosTheta }; +} + +inline math::float2 hammersley(uint32_t i, float iN) { + constexpr float tof = 0.5f / 0x80000000U; + uint32_t bits = i; + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return { i * iN, bits * tof }; +} + +static float lodToPerceptualRoughness(float lod) noexcept { + // Inverse perceptualRoughness-to-LOD mapping: + // The LOD-to-perceptualRoughness mapping is a quadratic fit for + // log2(perceptualRoughness)+iblMaxMipLevel when iblMaxMipLevel is 4. + // We found empirically that this mapping works very well for a 256 cubemap with 5 levels used, + // but also scales well for other iblMaxMipLevel values. + const float a = 2.0f; + const float b = -1.0f; + return (lod != 0) + ? saturate((std::sqrt(a * a + 4.0f * b * lod) - a) / (2.0f * b)) + : 0.0f; +} + +template +static inline constexpr T log4(T x) { + return std::log2(x) * T(0.5); +} + + +IBLPrefilterContext::IBLPrefilterContext(Engine& engine) + : mEngine(engine) { + utils::EntityManager& em = utils::EntityManager::get(); + mCameraEntity = em.create(); + mFullScreenQuadEntity = em.create(); + + mIntegrationMaterial = Material::Builder().package( + IBLPREFILTER_MATERIALS_IBLPREFILTER_DATA, + IBLPREFILTER_MATERIALS_IBLPREFILTER_SIZE).build(engine); + + mVertexBuffer = VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(1) + .attribute(VertexAttribute::POSITION, 0, + VertexBuffer::AttributeType::FLOAT4, 0) + .build(engine); + + mIndexBuffer = IndexBuffer::Builder() + .indexCount(3) + .bufferType(IndexBuffer::IndexType::USHORT) + .build(engine); + + + mVertexBuffer->setBufferAt(engine, 0, + { sFullScreenTriangleVertices, sizeof(sFullScreenTriangleVertices) }); + + mIndexBuffer->setBuffer(engine, + { sFullScreenTriangleIndices, sizeof(sFullScreenTriangleIndices) }); + + + RenderableManager::Builder(1) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, mVertexBuffer, mIndexBuffer) + .material(0, mIntegrationMaterial->getDefaultInstance()) + .culling(false) + .castShadows(false) + .receiveShadows(false) + .build(engine, mFullScreenQuadEntity); + + mView = engine.createView(); + mScene = engine.createScene(); + mRenderer = engine.createRenderer(); + mCamera = engine.createCamera(mCameraEntity); + + mScene->addEntity(mFullScreenQuadEntity); + + View* const view = mView; + view->setCamera(mCamera); + view->setScene(mScene); + view->setScreenSpaceRefractionEnabled(false); + view->setShadowingEnabled(false); + view->setPostProcessingEnabled(false); + view->setFrustumCullingEnabled(false); +} + +IBLPrefilterContext::~IBLPrefilterContext() noexcept { + utils::EntityManager& em = utils::EntityManager::get(); + auto& engine = mEngine; + engine.destroy(mView); + engine.destroy(mScene); + engine.destroy(mRenderer); + engine.destroy(mVertexBuffer); + engine.destroy(mIndexBuffer); + engine.destroy(mIntegrationMaterial); + engine.destroy(mFullScreenQuadEntity); + engine.destroyCameraComponent(mCameraEntity); + em.destroy(mFullScreenQuadEntity); +} + + +IBLPrefilterContext::IBLPrefilterContext(IBLPrefilterContext&& rhs) noexcept + : mEngine(rhs.mEngine) { + this->operator=(std::move(rhs)); +} + +IBLPrefilterContext& IBLPrefilterContext::operator=(IBLPrefilterContext&& rhs) { + using std::swap; + if (this != & rhs) { + swap(mRenderer, rhs.mRenderer); + swap(mScene, rhs.mScene); + swap(mVertexBuffer, rhs.mVertexBuffer); + swap(mIndexBuffer, rhs.mIndexBuffer); + swap(mCamera, rhs.mCamera); + swap(mFullScreenQuadEntity, rhs.mFullScreenQuadEntity); + swap(mCameraEntity, rhs.mCameraEntity); + swap(mIntegrationMaterial, rhs.mIntegrationMaterial); + swap(mView, rhs.mView); + } + return *this; +} + +// ------------------------------------------------------------------------------------------------ + +IBLPrefilterContext::SpecularFilter::SpecularFilter(IBLPrefilterContext& context, Config config) + : mContext(context) { + SYSTRACE_CALL(); + using namespace backend; + + Engine& engine = mContext.mEngine; + View* const view = mContext.mView; + Renderer* const renderer = mContext.mRenderer; + + + mSampleCount = std::min(config.sampleCount, uint16_t(2048)); + mLevelCount = std::max(config.levelCount, uint8_t(1u)); + + mKernelMaterial = Material::Builder().package( + IBLPREFILTER_MATERIALS_GENERATEKERNEL_DATA, + IBLPREFILTER_MATERIALS_GENERATEKERNEL_SIZE).build(engine); + + + // { L.x, L.y, L.z, lod } + mKernelTexture = Texture::Builder() + .sampler(Texture::Sampler::SAMPLER_2D) + .format(Texture::InternalFormat::RGBA16F) + .usage(Texture::Usage::SAMPLEABLE | Texture::Usage::COLOR_ATTACHMENT) + .width(mLevelCount) + .height(mSampleCount) + .build(engine); + + MaterialInstance* const mi = mKernelMaterial->getDefaultInstance(); + mi->setParameter("size", uint2{ mLevelCount, mSampleCount }); + mi->setParameter("sampleBits", uint32_t(std::log2(mSampleCount) + 0.5f)); + mi->setParameter("sampleCount", float(mSampleCount)); + mi->setParameter("oneOverLevelsMinusOne", 1.0f / (mLevelCount - 1.0f)); + + RenderableManager& rcm = engine.getRenderableManager(); + rcm.setMaterialInstanceAt( + rcm.getInstance(mContext.mFullScreenQuadEntity), 0, mi); + + RenderTarget* const rt = RenderTarget::Builder() + .texture(RenderTarget::AttachmentPoint::COLOR0, mKernelTexture) + .build(engine); + + view->setRenderTarget(rt); + view->setViewport({ 0, 0, mLevelCount, mSampleCount }); + + renderer->renderStandaloneView(view); + + engine.destroy(rt); + + // the code below must match the shader in generateKernel.mat + // this is a little bit unfortunate that we have to compute the weightSum here, but it's + // not too heavy. + const uint32_t levelCount = mLevelCount; + const float sampleCount = mSampleCount; + mKernelWeightArray = new float[mLevelCount]; + for (uint32_t lod = 0 ; lod < levelCount; lod++) { + SYSTRACE_NAME("computeFilterLOD"); + const float perceptualRoughness = lodToPerceptualRoughness(saturate(lod / (levelCount - 1.0f))); + const float roughness = perceptualRoughness * perceptualRoughness; + const uint32_t effectiveSampleCount = (lod == 0) ? 1u : sampleCount; + float weight = 0.0f; + for (size_t i = 0; i < effectiveSampleCount; i++) { + const float2 u = hammersley(uint32_t(i), 1.0f / float(effectiveSampleCount)); + const float3 H = hemisphereImportanceSampleDggx(u, roughness); + const float NoH2 = H.z * H.z; + const float NoL = saturate(2 * NoH2 - 1); + weight += NoL; + } + assert_invariant(lod < mLevelCount); + mKernelWeightArray[lod] = weight; + } +} + +UTILS_NOINLINE +IBLPrefilterContext::SpecularFilter::SpecularFilter(IBLPrefilterContext& context) + : SpecularFilter(context, {}) { +} + +IBLPrefilterContext::SpecularFilter::~SpecularFilter() noexcept { + Engine& engine = mContext.mEngine; + engine.destroy(mKernelTexture); + engine.destroy(mKernelMaterial); + delete [] mKernelWeightArray; +} + +IBLPrefilterContext::SpecularFilter::SpecularFilter(SpecularFilter&& rhs) noexcept + : mContext(rhs.mContext) { + this->operator=(std::move(rhs)); +} + +IBLPrefilterContext::SpecularFilter& IBLPrefilterContext::SpecularFilter::operator=(SpecularFilter&& rhs) { + using std::swap; + if (this != & rhs) { + swap(mKernelTexture, rhs.mKernelTexture); + swap(mKernelWeightArray, rhs.mKernelWeightArray); + mSampleCount = rhs.mSampleCount; + mLevelCount = rhs.mLevelCount; + } + return *this; +} + +Texture* IBLPrefilterContext::SpecularFilter::createReflectionsTexture() { + Engine& engine = mContext.mEngine; + + const uint8_t levels = mLevelCount; + + // default texture is 256 or larger to accommodate the level count requested + const uint32_t dim = std::max(256u, 1u << (levels - 1u)); + + Texture* const outCubemap = Texture::Builder() + .sampler(Texture::Sampler::SAMPLER_CUBEMAP) + .format(Texture::InternalFormat::R11F_G11F_B10F) + .usage(Texture::Usage::COLOR_ATTACHMENT | Texture::Usage::SAMPLEABLE) + .width(dim).height(dim).levels(levels) + .build(engine); + + return outCubemap; +} + +UTILS_NOINLINE +filament::Texture* IBLPrefilterContext::SpecularFilter::operator()( + filament::Texture const* environmentCubemap, + filament::Texture* outReflectionsTexture) { + return operator()({}, environmentCubemap, outReflectionsTexture); +} + +filament::Texture* IBLPrefilterContext::SpecularFilter::operator()( + IBLPrefilterContext::SpecularFilter::Options options, + filament::Texture const* environmentCubemap, + filament::Texture* outReflectionsTexture) { + + SYSTRACE_CALL(); + using namespace backend; + + ASSERT_PRECONDITION(environmentCubemap != nullptr, "outReflectionsTexture is null!"); + + ASSERT_PRECONDITION(environmentCubemap->getTarget() == Texture::Sampler::SAMPLER_CUBEMAP, + "outReflectionsTexture must be a cubemap."); + + UTILS_UNUSED_IN_RELEASE + const uint8_t maxLevelCount = uint8_t(std::log2(environmentCubemap->getWidth()) + 0.5f) + 1u; + + ASSERT_PRECONDITION(environmentCubemap->getLevels() == maxLevelCount, + "outReflectionsTexture must have %u mipmap levels allocated.", +maxLevelCount); + + if (outReflectionsTexture == nullptr) { + outReflectionsTexture = createReflectionsTexture(); + } + + ASSERT_PRECONDITION(mLevelCount <= outReflectionsTexture->getLevels(), + "outReflectionsTexture has %u levels but %u are requested.", + +outReflectionsTexture->getLevels(), +mLevelCount); + + const TextureCubemapFace faces[2][3] = { + { TextureCubemapFace::POSITIVE_X, TextureCubemapFace::POSITIVE_Y, TextureCubemapFace::POSITIVE_Z }, + { TextureCubemapFace::NEGATIVE_X, TextureCubemapFace::NEGATIVE_Y, TextureCubemapFace::NEGATIVE_Z } + }; + + Engine& engine = mContext.mEngine; + View* const view = mContext.mView; + Renderer* const renderer = mContext.mRenderer; + MaterialInstance* const mi = mContext.mIntegrationMaterial->getDefaultInstance(); + + RenderableManager& rcm = engine.getRenderableManager(); + rcm.setMaterialInstanceAt( + rcm.getInstance(mContext.mFullScreenQuadEntity), 0, mi); + + const uint32_t sampleCount = mSampleCount; + const float linear = options.hdrLinear; + const float compress = options.hdrMax; + const uint8_t levels = outReflectionsTexture->getLevels(); + uint32_t dim = outReflectionsTexture->getWidth(); + const float omegaP = (4.0f * f::PI) / float(6 * dim * dim); + + TextureSampler environmentSampler; + environmentSampler.setMagFilter(SamplerMagFilter::LINEAR); + environmentSampler.setMinFilter(SamplerMinFilter::LINEAR_MIPMAP_LINEAR); + + mi->setParameter("environment", environmentCubemap, environmentSampler); + mi->setParameter("kernel", mKernelTexture, TextureSampler{ SamplerMagFilter::NEAREST }); + mi->setParameter("compress", float2{ linear, compress }); + mi->setParameter("lodOffset", options.lodOffset - log4(omegaP)); + + if (options.generateMipmap) { + // We need mipmaps for prefiltering + environmentCubemap->generateMipmaps(engine); + } + + RenderTarget::Builder builder; + builder.texture(RenderTarget::AttachmentPoint::COLOR0, outReflectionsTexture) + .texture(RenderTarget::AttachmentPoint::COLOR1, outReflectionsTexture) + .texture(RenderTarget::AttachmentPoint::COLOR2, outReflectionsTexture); + + for (size_t lod = 0; lod < levels; lod++) { + SYSTRACE_NAME("executeFilterLOD"); + + mi->setParameter("sampleCount", uint32_t(lod == 0 ? 1u : sampleCount)); + mi->setParameter("attachmentLevel", uint32_t(lod)); + mi->setParameter("invKernelWeight", 1.0f / mKernelWeightArray[lod]); + + if (lod == levels - 1) { + // this is the last lod, use a more agressive filtering because this level is also + // used for the diffuse brdf by filament, and we need it to be very smooth. + // So we set the lod offset to at least 2. + mi->setParameter("lodOffset", std::max(2.0f, options.lodOffset) - log4(omegaP)); + } + + builder.mipLevel(RenderTarget::AttachmentPoint::COLOR0, lod) + .mipLevel(RenderTarget::AttachmentPoint::COLOR1, lod) + .mipLevel(RenderTarget::AttachmentPoint::COLOR2, lod); + + view->setViewport({ 0, 0, dim, dim }); + + for (size_t i = 0; i < 2; i++) { + mi->setParameter("side", i == 0 ? 1.0f : -1.0f); + + builder.face(RenderTarget::AttachmentPoint::COLOR0, faces[i][0]) + .face(RenderTarget::AttachmentPoint::COLOR1, faces[i][1]) + .face(RenderTarget::AttachmentPoint::COLOR2, faces[i][2]); + + RenderTarget* const rt = builder.build(engine); + view->setRenderTarget(rt); + renderer->renderStandaloneView(view); + engine.destroy(rt); + } + + dim >>= 1; + } + + return outReflectionsTexture; +} diff --git a/libs/iblprefilter/src/materials/generateKernel.mat b/libs/iblprefilter/src/materials/generateKernel.mat new file mode 100644 index 00000000000..28b60c953f4 --- /dev/null +++ b/libs/iblprefilter/src/materials/generateKernel.mat @@ -0,0 +1,118 @@ +material { + name : generateKernel, + parameters : [ + { + type : uint2, + name : size, + precision: high + }, + { + type : uint, + name : sampleBits, + }, + { + type : float, + name : sampleCount, + }, + { + type : float, + name : oneOverLevelsMinusOne, + } + ], + outputs : [ + { + name : weight, + target : color, + type : float4 + } + ], + variables : [ + vertex + ], + domain : postprocess, + depthWrite : false, + depthCulling : false +} + +vertex { + void postProcessVertex(inout PostProcessVertexInputs postProcess) { + postProcess.vertex.xy = postProcess.normalizedUV * vec2(materialParams.size); + } +} + +fragment { + + void dummy() {} + +precision highp float; + +float log4(const float x) { + return log2(x) * 0.5; +} + +vec2 hammersley(const uint index, const float sampleCount, const uint sampleBits) { + // Compute Hammersley sequence + float invNumSamples = 1.0 / sampleCount; + uint i = index; + uint t = i; + uint bits = 0u; + for (uint j = 0u; j < sampleBits; j++) { + bits = bits * 2u + (t - (2u * (t / 2u))); + t /= 2u; + } + return vec2(float(i), float(bits)) * invNumSamples; +} + +float DistributionGGX(const float NoH, const float a) { + // NOTE: (aa-1) == (a-1)(a+1) produces better fp accuracy + float f = (a - 1.0) * ((a + 1.0) * (NoH * NoH)) + 1.0; + return (a * a) / (PI * f * f); +} + +vec3 hemisphereImportanceSampleDggx(const vec2 u, const float a) { // pdf = D(a) * cosTheta + float phi = 2.0 * PI * u.x; + // NOTE: (aa-1) == (a-1)(a+1) produces better fp accuracy + float cosTheta2 = (1.0 - u.y) / (1.0 + (a + 1.0) * ((a - 1.0) * u.y)); + float cosTheta = sqrt(cosTheta2); + float sinTheta = sqrt(1.0 - cosTheta2); + return vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); +} + +vec4 computeWeight(const uint index, const float roughness) { + float sampleCount = materialParams.sampleCount; + vec2 u = hammersley(index, sampleCount, materialParams.sampleBits); + vec3 H = hemisphereImportanceSampleDggx(u, roughness); + float NoH = H.z; + float NoH2 = H.z * H.z; + float NoL = saturate(2.0 * NoH2 - 1.0); + vec3 L = vec3(2.0 * NoH * H.x, 2.0 * NoH * H.y, NoL); + float pdf = DistributionGGX(NoH, roughness) * 0.25; + float invOmegaS = sampleCount * pdf; + float l = -log4(invOmegaS); + return vec4(L, l); +} + +float lodToPerceptualRoughness(const float lod) { + // Inverse perceptualRoughness-to-LOD mapping: + // The LOD-to-perceptualRoughness mapping is a quadratic fit for + // log2(perceptualRoughness)+iblMaxMipLevel when iblMaxMipLevel is 4. + // We found empirically that this mapping works very well for a 256 cubemap with 5 levels used, + // but also scales well for other iblMaxMipLevel values. + const float a = 2.0f; + const float b = -1.0f; + return (lod != 0.0) ? saturate((sqrt(a * a + 4.0 * b * lod) - a) / (2.0 * b)) : 0.0; +} + +void postProcess(inout PostProcessInputs postProcess) { + vec2 uv = variable_vertex.xy; // interpolated at pixel's center + + float lod = floor(uv.x); + uint index = uint(uv.y); + + float perceptualRoughness = lodToPerceptualRoughness(saturate(lod * materialParams.oneOverLevelsMinusOne)); + float roughness = perceptualRoughness * perceptualRoughness; + + postProcess.weight = computeWeight(index, roughness); +} + +} diff --git a/libs/iblprefilter/src/materials/iblprefilter.mat b/libs/iblprefilter/src/materials/iblprefilter.mat new file mode 100644 index 00000000000..36c6e646ce7 --- /dev/null +++ b/libs/iblprefilter/src/materials/iblprefilter.mat @@ -0,0 +1,164 @@ +material { + name : iblprefilter, + parameters : [ + { + type : samplerCubemap, + name : environment, + precision: medium + }, + { + type : sampler2d, + name : kernel, + precision: high + }, + { + type : float2, + name : compress, + precision: medium + }, + { + type : float, + name : side, + precision: medium + }, + { + type : float, + name : invKernelWeight, + precision: high + }, + { + type : float, + name : lodOffset, + precision: medium + }, + { + type : uint, + name : sampleCount, + precision: medium + }, + { + type : uint, + name : attachmentLevel, + precision: medium + } + ], + outputs : [ + { + name : outx, + target : color, + type : float3 + }, + { + name : outy, + target : color, + type : float3 + }, + { + name : outz, + target : color, + type : float3 + } + ], + variables : [ + vertex + ], + domain : postprocess, + depthWrite : false, + depthCulling : false +} + +vertex { + void postProcessVertex(inout PostProcessVertexInputs postProcess) { + postProcess.vertex.xy = postProcess.normalizedUV; + } +} + +fragment { + +void dummy() {} + +precision highp float; + +mat3 tangentSpace(const vec3 N) { + const vec3 up = vec3(0, 0, 1); + mat3 R; + R[0] = normalize(cross(up, N)); + R[1] = cross(N, R[0]); + R[2] = N; + return R; +} + +float random(const highp vec2 w) { + const vec3 m = vec3(0.06711056, 0.00583715, 52.9829189); + return fract(m.z * fract(dot(w, m.xy))); +} + +// See: http://graphicrants.blogspot.com/2013/12/tone-mapping.html, By Brian Karis +vec3 compress(const vec3 color, const float linear, const float compressed) { + const mediump vec3 rec709 = vec3(0.2126, 0.7152, 0.0722); + float luma = dot(color, rec709); // REC 709 + float s = 1.0; + if (luma > linear) { + s = ((linear * linear - compressed * luma) / ((2.0 * linear - compressed - luma) * luma)); + } + return color * s; +} + +vec3 sampleEnvironment(mediump samplerCube environment, highp vec3 r, mediump float lod) { + mediump float linear = materialParams.compress.x; + mediump float compressed = materialParams.compress.y; + mediump vec3 c = textureLod(environment, r, lod).rgb; + return compress(c, linear, compressed); + // return c * (compressed / (compressed + luma)); // cheaper + // return clamp(c, 0.0, compressed); // cheapest +} + +void postProcess(inout PostProcessInputs postProcess) { + vec2 uv = variable_vertex.xy; // interpolated at pixel's center + vec2 p = uv * 2.0 - 1.0; + float side = materialParams.side; + + // compute the view (and normal, since v = n) direction for each face + vec3 rx = normalize(vec3( side, -p.y, side * -p.x)); + vec3 ry = normalize(vec3( p.x, side, side * p.y)); + vec3 rz = normalize(vec3(side * p.x, -p.y, side)); + + // random rotation around r + mediump float a = 2.0 * PI * random(gl_FragCoord.xy); + mediump float c = cos(a); + mediump float s = sin(a); + mat3 R; + R[0] = vec3( c, s, 0); + R[1] = vec3(-s, c, 0); + R[2] = vec3( 0, 0, 1); + + // compute the rotation by which to transform our sample locations for each face + mat3 Tx = tangentSpace(rx) * R; + mat3 Ty = tangentSpace(ry) * R; + mat3 Tz = tangentSpace(rz) * R; + + // accumulated environment light for each face + vec3 Lx = vec3(0); + vec3 Ly = vec3(0); + vec3 Lz = vec3(0); + + for (uint i = 0u ; i < materialParams.sampleCount ; i++) { + // { L, lod }, with L.z == NoL + mediump vec4 entry = texelFetch(materialParams_kernel, ivec2(materialParams.attachmentLevel, i), 0); + float l = entry.w + materialParams.lodOffset; // we don't need to clamp, the h/w does it for us + Lx += sampleEnvironment(materialParams_environment, Tx * entry.xyz, l) * entry.z; + Ly += sampleEnvironment(materialParams_environment, Ty * entry.xyz, l) * entry.z; + Lz += sampleEnvironment(materialParams_environment, Tz * entry.xyz, l) * entry.z; + } + + highp float invKernelWeight = materialParams.invKernelWeight; + Lx *= invKernelWeight; + Ly *= invKernelWeight; + Lz *= invKernelWeight; + + postProcess.outx = Lx; + postProcess.outy = Ly; + postProcess.outz = Lz; +} + +} diff --git a/libs/viewer/src/SimpleViewer.cpp b/libs/viewer/src/SimpleViewer.cpp index 312a74fd199..ace24f0f9a5 100644 --- a/libs/viewer/src/SimpleViewer.cpp +++ b/libs/viewer/src/SimpleViewer.cpp @@ -384,10 +384,12 @@ void SimpleViewer::setIndirectLight(filament::IndirectLight* ibl, if (ibl) { float3 d = filament::IndirectLight::getDirectionEstimate(sh3); float4 c = filament::IndirectLight::getColorEstimate(sh3, d); - mSettings.lighting.sunlightDirection = d; - mSettings.lighting.sunlightColor = c.rgb; - mSettings.lighting.sunlightIntensity = c[3] * ibl->getIntensity(); - updateIndirectLight(); + if (!std::isnan(d.x * d.y * d.z)) { + mSettings.lighting.sunlightDirection = d; + mSettings.lighting.sunlightColor = c.rgb; + mSettings.lighting.sunlightIntensity = c[3] * ibl->getIntensity(); + updateIndirectLight(); + } } } diff --git a/shaders/src/brdf.fs b/shaders/src/brdf.fs index 2c239e80f1f..dd6ead1a699 100644 --- a/shaders/src/brdf.fs +++ b/shaders/src/brdf.fs @@ -28,7 +28,7 @@ #define BRDF_DIFFUSE DIFFUSE_LAMBERT -#if defined(TARGET_MOBILE) +#if FILAMENT_QUALITY == FILAMENT_QUALITY_LOW #define BRDF_SPECULAR_D SPECULAR_D_GGX #define BRDF_SPECULAR_V SPECULAR_V_SMITH_GGX_FAST #define BRDF_SPECULAR_F SPECULAR_F_SCHLICK @@ -172,7 +172,7 @@ float visibility(float roughness, float NoV, float NoL) { vec3 fresnel(const vec3 f0, float LoH) { #if BRDF_SPECULAR_F == SPECULAR_F_SCHLICK -#if defined(TARGET_MOBILE) +#if FILAMENT_QUALITY == FILAMENT_QUALITY_LOW return F_Schlick(f0, LoH); // f90 = 1.0 #else float f90 = saturate(dot(f0, vec3(50.0 * 0.33))); diff --git a/shaders/src/common_material.fs b/shaders/src/common_material.fs index 8356f0b1886..53cc8acb0d8 100644 --- a/shaders/src/common_material.fs +++ b/shaders/src/common_material.fs @@ -54,7 +54,7 @@ float f0ToIor(float f0) { vec3 f0ClearCoatToSurface(const vec3 f0) { // Approximation of iorTof0(f0ToIor(f0), 1.5) // This assumes that the clear coat layer has an IOR of 1.5 -#if defined(TARGET_MOBILE) +#if FILAMENT_QUALITY == FILAMENT_QUALITY_LOW return saturate(f0 * (f0 * 0.526868 + 0.529324) - 0.0482256); #else return saturate(f0 * (f0 * (0.941892 - 0.263008 * f0) + 0.346479) - 0.0285998); diff --git a/shaders/src/dithering.fs b/shaders/src/dithering.fs index 96eb18bed4f..74163cf9653 100644 --- a/shaders/src/dithering.fs +++ b/shaders/src/dithering.fs @@ -9,11 +9,7 @@ #define DITHERING_TRIANGLE_NOISE 3 #define DITHERING_TRIANGLE_NOISE_RGB 4 -#if defined(TARGET_MOBILE) - #define DITHERING_OPERATOR DITHERING_TRIANGLE_NOISE -#else - #define DITHERING_OPERATOR DITHERING_TRIANGLE_NOISE -#endif +#define DITHERING_OPERATOR DITHERING_TRIANGLE_NOISE //------------------------------------------------------------------------------ // Noise diff --git a/shaders/src/getters.fs b/shaders/src/getters.fs index b946faa9a4f..d4d660fcf8b 100644 --- a/shaders/src/getters.fs +++ b/shaders/src/getters.fs @@ -101,7 +101,7 @@ highp vec3 getLightSpacePosition() { * @public-api */ highp vec3 getNormalizedViewportCoord() { - // make sure handle our reversed-z + // make sure to handle our reversed-z return vec3(shading_normalizedViewportCoord, 1.0 - gl_FragCoord.z); } diff --git a/shaders/src/light_directional.fs b/shaders/src/light_directional.fs index ae882784405..97af3334297 100644 --- a/shaders/src/light_directional.fs +++ b/shaders/src/light_directional.fs @@ -2,7 +2,7 @@ // Directional light evaluation //------------------------------------------------------------------------------ -#if !defined(TARGET_MOBILE) +#if FILAMENT_QUALITY == FILAMENT_QUALITY_HIGH #define SUN_AS_AREA_LIGHT #endif diff --git a/shaders/src/light_indirect.fs b/shaders/src/light_indirect.fs index f95b11bd97c..ce4659edd60 100644 --- a/shaders/src/light_indirect.fs +++ b/shaders/src/light_indirect.fs @@ -72,7 +72,7 @@ vec3 Irradiance_RoughnessOne(const vec3 n) { vec3 diffuseIrradiance(const vec3 n) { if (frameUniforms.iblSH[0].x == 65504.0) { -#if defined(TARGET_MOBILE) +#if FILAMENT_QUALITY == FILAMENT_QUALITY_LOW return Irradiance_RoughnessOne(n); #else ivec2 s = textureSize(light_iblSpecular, int(frameUniforms.iblRoughnessOneLevel)); @@ -166,20 +166,16 @@ vec3 getReflectedVector(const PixelParams pixel, const vec3 n) { #if IBL_INTEGRATION == IBL_INTEGRATION_IMPORTANCE_SAMPLING vec2 hammersley(uint index) { - // Compute Hammersley sequence - // TODO: these should come from uniforms - // TODO: we should do this with logical bit operations const uint numSamples = uint(IBL_INTEGRATION_IMPORTANCE_SAMPLING_COUNT); - const uint numSampleBits = uint(log2(float(numSamples))); const float invNumSamples = 1.0 / float(numSamples); - uint i = uint(index); - uint t = i; - uint bits = 0u; - for (uint j = 0u; j < numSampleBits; j++) { - bits = bits * 2u + (t - (2u * (t / 2u))); - t /= 2u; - } - return vec2(float(i), float(bits)) * invNumSamples; + const float tof = 0.5 / float(0x80000000U); + uint bits = index; + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return vec2(float(index) * invNumSamples, float(bits) * tof); } vec3 importanceSamplingNdfDggx(vec2 u, float roughness) { @@ -192,6 +188,14 @@ vec3 importanceSamplingNdfDggx(vec2 u, float roughness) { return vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); } +vec3 hemisphereCosSample(vec2 u) { + float phi = 2.0f * PI * u.x; + float cosTheta2 = 1.0 - u.y; + float cosTheta = sqrt(cosTheta2); + float sinTheta = sqrt(1.0 - cosTheta2); + return vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); +} + vec3 importanceSamplingVNdfDggx(vec2 u, float roughness, vec3 v) { // See: "A Simpler and Exact Sampling Routine for the GGX Distribution of Visible Normals", Eric Heitz float alpha = roughness; @@ -219,62 +223,67 @@ vec3 importanceSamplingVNdfDggx(vec2 u, float roughness, vec3 v) { return h; } -float prefilteredImportanceSampling(float ipdf, float iblRoughnessOneLevel) { +float prefilteredImportanceSampling(float ipdf, float omegaP) { // See: "Real-time Shading with Filtered Importance Sampling", Jaroslav Krivanek // Prefiltering doesn't work with anisotropy const float numSamples = float(IBL_INTEGRATION_IMPORTANCE_SAMPLING_COUNT); const float invNumSamples = 1.0 / float(numSamples); - float dim = float(textureSize(light_iblSpecular, 0).x); - float omegaP = (4.0 * PI) / (6.0 * dim * dim); - float invOmegaP = 1.0 / omegaP; const float K = 4.0; float omegaS = invNumSamples * ipdf; - float mipLevel = clamp(log2(K * omegaS * invOmegaP) * 0.5, 0.0, iblRoughnessOneLevel); + float mipLevel = log2(K * omegaS / omegaP) * 0.5; // log4 return mipLevel; } -vec3 isEvaluateIBL(const PixelParams pixel, vec3 n, vec3 v, float NoV) { - // TODO: for a true anisotropic BRDF, we need a real tangent space - vec3 up = abs(n.z) < 0.9999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); +vec3 isEvaluateSpecularIBL(const PixelParams pixel, vec3 n, vec3 v, float NoV) { + const uint numSamples = uint(IBL_INTEGRATION_IMPORTANCE_SAMPLING_COUNT); + const float invNumSamples = 1.0 / float(numSamples); + const vec3 up = vec3(0.0, 0.0, 1.0); - mat3 tangentToWorld; - tangentToWorld[0] = normalize(cross(up, n)); - tangentToWorld[1] = cross(n, tangentToWorld[0]); - tangentToWorld[2] = n; + // TODO: for a true anisotropic BRDF, we need a real tangent space + // tangent space + mat3 T; + T[0] = normalize(cross(up, n)); + T[1] = cross(n, T[0]); + T[2] = n; + + // Random rotation around N per pixel + const vec3 m = vec3(0.06711056, 0.00583715, 52.9829189); + float a = 2.0 * PI * fract(m.z * fract(dot(gl_FragCoord.xy, m.xy))); + float c = cos(a); + float s = sin(a); + mat3 R; + R[0] = vec3( c, s, 0); + R[1] = vec3(-s, c, 0); + R[2] = vec3( 0, 0, 1); + T *= R; float roughness = pixel.roughness; - float a2 = roughness * roughness; - - float iblRoughnessOneLevel = frameUniforms.iblRoughnessOneLevel; - const uint numSamples = uint(IBL_INTEGRATION_IMPORTANCE_SAMPLING_COUNT); - const float invNumSamples = 1.0 / float(numSamples); + float dim = float(textureSize(light_iblSpecular, 0).x); + float omegaP = (4.0 * PI) / (6.0 * dim * dim); vec3 indirectSpecular = vec3(0.0); for (uint i = 0u; i < numSamples; i++) { vec2 u = hammersley(i); - vec3 h = tangentToWorld * importanceSamplingNdfDggx(u, roughness); + vec3 h = T * importanceSamplingNdfDggx(u, roughness); // Since anisotropy doesn't work with prefiltering, we use the same "faux" anisotropy // we do when we use the prefiltered cubemap vec3 l = getReflectedVector(pixel, v, h); // Compute this sample's contribution to the brdf - float NoL = dot(n, l); + float NoL = saturate(dot(n, l)); if (NoL > 0.0) { float NoH = dot(n, h); - float LoH = max(dot(l, h), 0.0); + float LoH = saturate(dot(l, h)); // PDF inverse (we must use D_GGX() here, which is used to generate samples) float ipdf = (4.0 * LoH) / (D_GGX(roughness, NoH, h) * NoH); - - float mipLevel = prefilteredImportanceSampling(ipdf, iblRoughnessOneLevel); - - // we use texture() instead of textureLod() to take advantage of mipmapping - vec3 L = decodeDataForIBL(texture(light_iblSpecular, l, mipLevel)); + float mipLevel = prefilteredImportanceSampling(ipdf, omegaP); + vec3 L = decodeDataForIBL(textureLod(light_iblSpecular, l, mipLevel)); float D = distribution(roughness, NoH, h); float V = visibility(roughness, NoV, NoL); - vec3 F = fresnel(pixel.f0, LoH); + vec3 F = fresnel(pixel.f0, LoH); vec3 Fr = F * (D * V * NoL * ipdf * invNumSamples); indirectSpecular += (Fr * L); @@ -284,6 +293,56 @@ vec3 isEvaluateIBL(const PixelParams pixel, vec3 n, vec3 v, float NoV) { return indirectSpecular; } +vec3 isEvaluateDiffuseIBL(const PixelParams pixel, vec3 n, vec3 v) { + const uint numSamples = uint(IBL_INTEGRATION_IMPORTANCE_SAMPLING_COUNT); + const float invNumSamples = 1.0 / float(numSamples); + const vec3 up = vec3(0.0, 0.0, 1.0); + + // TODO: for a true anisotropic BRDF, we need a real tangent space + // tangent space + mat3 T; + T[0] = normalize(cross(up, n)); + T[1] = cross(n, T[0]); + T[2] = n; + + // Random rotation around N per pixel + const vec3 m = vec3(0.06711056, 0.00583715, 52.9829189); + float a = 2.0 * PI * fract(m.z * fract(dot(gl_FragCoord.xy, m.xy))); + float c = cos(a); + float s = sin(a); + mat3 R; + R[0] = vec3( c, s, 0); + R[1] = vec3(-s, c, 0); + R[2] = vec3( 0, 0, 1); + T *= R; + + float dim = float(textureSize(light_iblSpecular, 0).x); + float omegaP = (4.0 * PI) / (6.0 * dim * dim); + + vec3 indirectDiffuse = vec3(0.0); + for (uint i = 0u; i < numSamples; i++) { + vec2 u = hammersley(i); + vec3 h = T * hemisphereCosSample(u); + + // Since anisotropy doesn't work with prefiltering, we use the same "faux" anisotropy + // we do when we use the prefiltered cubemap + vec3 l = getReflectedVector(pixel, v, h); + + // Compute this sample's contribution to the brdf + float NoL = saturate(dot(n, l)); + if (NoL > 0.0) { + // PDF inverse (we must use D_GGX() here, which is used to generate samples) + float ipdf = PI / NoL; + // we have to bias the mipLevel (+1) to help with very strong highlights + float mipLevel = prefilteredImportanceSampling(ipdf, omegaP) + 1.0; + vec3 L = decodeDataForIBL(textureLod(light_iblSpecular, l, mipLevel)); + indirectDiffuse += L; + } + } + + return indirectDiffuse * invNumSamples; // we bake 1/PI here, which cancels out +} + void isEvaluateClearCoatIBL(const PixelParams pixel, float specularAO, inout vec3 Fd, inout vec3 Fr) { #if defined(MATERIAL_HAS_CLEAR_COAT) #if defined(MATERIAL_HAS_NORMAL) || defined(MATERIAL_HAS_CLEAR_COAT_NORMAL) @@ -308,7 +367,7 @@ void isEvaluateClearCoatIBL(const PixelParams pixel, float specularAO, inout vec p.anisotropy = 0.0; #endif - vec3 clearCoatLobe = isEvaluateIBL(p, clearCoatNormal, shading_view, clearCoatNoV); + vec3 clearCoatLobe = isEvaluateSpecularIBL(p, clearCoatNormal, shading_view, clearCoatNoV); Fr += clearCoatLobe * (specularAO * pixel.clearCoat); #endif } @@ -521,7 +580,7 @@ void evaluateIBL(const MaterialInputs material, const PixelParams pixel, inout v Fr = E * prefilteredRadiance(r, pixel.perceptualRoughness); #elif IBL_INTEGRATION == IBL_INTEGRATION_IMPORTANCE_SAMPLING vec3 E = vec3(0.0); // TODO: fix for importance sampling - Fr = isEvaluateIBL(pixel, shading_normal, shading_view, shading_NoV); + Fr = isEvaluateSpecularIBL(pixel, shading_normal, shading_view, shading_NoV); #endif Fr *= singleBounceAO(specularAO) * pixel.energyCompensation; @@ -535,7 +594,11 @@ void evaluateIBL(const MaterialInputs material, const PixelParams pixel, inout v vec3 diffuseNormal = shading_normal; #endif +#if IBL_INTEGRATION == IBL_INTEGRATION_PREFILTERED_CUBEMAP vec3 diffuseIrradiance = diffuseIrradiance(diffuseNormal); +#elif IBL_INTEGRATION == IBL_INTEGRATION_IMPORTANCE_SAMPLING + vec3 diffuseIrradiance = isEvaluateDiffuseIBL(pixel, diffuseNormal, shading_view); +#endif vec3 Fd = pixel.diffuseColor * diffuseIrradiance * (1.0 - E) * diffuseBRDF; // sheen layer diff --git a/shaders/src/shadowing.fs b/shaders/src/shadowing.fs index f2f658ef9a2..b883493a248 100644 --- a/shaders/src/shadowing.fs +++ b/shaders/src/shadowing.fs @@ -15,15 +15,9 @@ #define SHADOW_RECEIVER_PLANE_DEPTH_BIAS_MIN_SAMPLING_METHOD SHADOW_SAMPLING_PCF_MEDIUM -#ifdef TARGET_MOBILE - #define SHADOW_SAMPLING_METHOD SHADOW_SAMPLING_PCF_LOW - #define SHADOW_SAMPLING_ERROR SHADOW_SAMPLING_ERROR_DISABLED - #define SHADOW_RECEIVER_PLANE_DEPTH_BIAS SHADOW_RECEIVER_PLANE_DEPTH_BIAS_DISABLED -#else - #define SHADOW_SAMPLING_METHOD SHADOW_SAMPLING_PCF_LOW - #define SHADOW_SAMPLING_ERROR SHADOW_SAMPLING_ERROR_DISABLED - #define SHADOW_RECEIVER_PLANE_DEPTH_BIAS SHADOW_RECEIVER_PLANE_DEPTH_BIAS_DISABLED -#endif +#define SHADOW_SAMPLING_METHOD SHADOW_SAMPLING_PCF_LOW +#define SHADOW_SAMPLING_ERROR SHADOW_SAMPLING_ERROR_DISABLED +#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS SHADOW_RECEIVER_PLANE_DEPTH_BIAS_DISABLED #if SHADOW_SAMPLING_ERROR == SHADOW_SAMPLING_ERROR_ENABLED #undef SHADOW_RECEIVER_PLANE_DEPTH_BIAS diff --git a/web/docs/Pipfile b/web/docs/Pipfile index de60550f7df..d4d70a7bc2e 100644 --- a/web/docs/Pipfile +++ b/web/docs/Pipfile @@ -8,7 +8,7 @@ verify_ssl = true [packages] jsbeautifier = "*" mistletoe = "*" -Pygments = "*" +Pygments = ">=2.7.4" [requires] python_version = "3.9" diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index bbed7f0866d..e7694c7f165 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -512,6 +512,12 @@ class_("Engine") return &engine->getRenderableManager(); }), allow_raw_pointers()) + /// getEntityManager ::method:: + /// ::retval:: an instance of [utils::EntityManager] + .function("getEntityManager", EMBIND_LAMBDA(utils::EntityManager*, (Engine* engine), { + return &engine->getEntityManager(); + }), allow_raw_pointers()) + /// createSwapChain ::method:: /// ::retval:: an instance of [SwapChain] .function("createSwapChain", (SwapChain* (*)(Engine*)) [] diff --git a/web/filament-js/package.json b/web/filament-js/package.json index 52005fdedb6..b203f7a979d 100644 --- a/web/filament-js/package.json +++ b/web/filament-js/package.json @@ -1,6 +1,6 @@ { "name": "filament", - "version": "1.9.22", + "version": "1.9.23", "description": "Real-time physically based rendering engine", "main": "filament.js", "module": "filament.js",