Compare commits

...
Author SHA1 Message Date
Powei Feng b7244464a3 test: fix VK backend FeedbackLoops test 2024-02-22 11:34:03 -08:00
Powei Feng b066678a37 matdbg: fix backend selection 2023-11-27 16:54:49 -08:00
Powei Feng b10d5c58aa backend: Add linux as platform for tests (#7375)
- Also fixed a leak in `test_FeedbackLoops`
2023-11-27 15:48:31 -08:00
Powei Feng 000bff5ce1 Release Filament 1.47.0 2023-11-27 14:19:52 -08:00
Powei Feng 47d45a64fa Update MATERIAL_VERSION to 47 2023-11-27 11:40:00 -08:00
Eliza Velasquez 2bd48f58ff Enable optimizations for ESSL 1.0 code
The CL introducing the ESSL 1.0 chunk in materials inadvertently disabled
optimizations for said code. This commit reintroduces those optimizations and
fixes associated bugs which manifested. In particular, spirv-cross was
generating uints for bools; this has been fixed with a hack. Additionally,
spirv-cross is now compiled with exceptions enabled so that matc can gracefully
fail and show the code which failed to compile rather than abruptly aborting.
2023-11-16 14:59:01 -08:00
Mathias Agopian f2f4f556b5 fix a lot of issues with the backend tests
With these fixes failing tests are:

OpenGL:
BackendTest.FeedbackLoops

Metal:
BackendTest.ColorResolve
BackendTest.BufferObjectUpdateWithOffset
BasicStencilBufferTest.StencilBufferMSAA

Vulkan:
Many failures still
2023-11-16 14:56:07 -08:00
Mathias Agopian 5aa15f061b fix scissor/viewport state tracking 2023-11-16 13:13:27 -08:00
Eliza Velasquez 63db4f0bf0 Fix typo in depth_main.fs 2023-11-16 12:57:54 -08:00
Eliza Velasquez 7797f5af38 Incorporate feedback 2023-11-16 12:47:46 -08:00
Eliza Velasquez f9def09d17 Update NEW_RELEASE_NOTES.md 2023-11-16 12:47:46 -08:00
Eliza Velasquez 494934f34e Add option to matc to disable ESSL 1.0 codegen 2023-11-16 12:47:46 -08:00
Eliza Velasquez caae42fdf6 Enable preprocessor optimization of ESSL 1.0
Since #7358 is blocked by an upstream spirv-cross issue, we can at least do a
bit of preprocessor optimization for ESSL 1.0 code in the meantime and introduce
the FILAMENT_EFFECTIVE_VERSION preprocessor definitions.
2023-11-16 12:47:46 -08:00
Benjamin Doherty 8f6c4841cb Fix LoadImage backend tests 2023-11-15 14:42:25 -08:00
Ben Doherty 5d0c06e3f2 Remove debug code in MetalShaderCompiler (#7368) 2023-11-15 13:50:59 -08:00
Ben Doherty 7d31a7f7ea Move stereoscopic debug options to Settings framework (#7347) 2023-11-15 10:51:58 -08:00
Powei Feng 4f4a439f8b Release Filament 1.46.0 2023-11-14 21:34:31 -08:00
Eliza Velasquez 2306b56ea7 Add FILAMENT_ENABLE_FEATURE_LEVEL_0 option
This allows clients to selectively exclude feature level 0 support.
2023-11-14 16:50:44 -08:00
Antonio MaioranoandPowei Feng eebb40d30d Add missing include for latest glslang (#7365)
This change in glslang removes the include of "intermediate.h" from
GlslangToSpv.h:
https://github.com/KhronosGroup/glslang/commit/62de186c3383feef067feb6ea578f79977910722

As a result, the definition of "class TIntermediate" is removed, and
will fail compilation of MaterialCompiler.cpp when glslang is updated to
a version including the aforementioned change. We fix this by adding an
explicit include to this header in MaterialCompiler.cpp.

Co-authored-by: Powei Feng <powei@google.com>
2023-11-14 15:23:39 -08:00
Eliza Velasquez 766e4f2ec9 Fix some ES2 issues
Fix specification of mipmaps when generating textures.

Fix oversight where emulated UBOs would not replace uniforms when swapped out
for another.
2023-11-14 13:39:01 -08:00
Powei Feng b0a584c915 Small compilation fixes (#7363)
- gltfio: Enable -Wall -Werror for gltfio_core
 - gltfio: Fix various errors that were missed warnings
 - matdbg: switch from std::atomic_uint64_t to
   std::atomic<uint64_t> for older clang
2023-11-14 13:26:48 -08:00
Powei Feng d5bab43ceb Revert "engine: move setFrontFaceWindingInverted from View to MaterialInstance (#7331)" (#7360)
This reverts commit 038f07cb34.
2023-11-13 22:26:03 -08:00
Mathias Agopian 485ae1b324 add Material.compile and Engine.flush java bindings (#7348)
* Add Material.compile() Java binding.

* Add Engine.flush() java binding

* Add Scene.forEach java binding

update the Android gltf-viewer sample to precompile all variants of all
materials in the scene, similarly to the desktop sample.
2023-11-13 10:43:52 -08:00
Powei Feng 75e8961109 Update Material_VERSION to 45 2023-11-13 10:37:16 -08:00
Mathias Agopian 0e9b2eda0a Fix (again) the shadow transform
- we were not using the correct field in ShadowMapManager
- we were not computing the transform correctly, it should applied
  after the local transform, not before.

FIXES=[299310624]
2023-11-10 15:33:06 -08:00
Mathias Agopian a900bc69fb don't precompile variants in gltfio
we recently added calls to Material::compile in gltfio to precompile
materials are they are discovered. that wasn't a good call, because 
this should be the responsibility of the app, not of gltfio, at least
not without an option.

This is now done in gltf_viewer. We need something similar for 
Android.

Bugs #7318, #7336
2023-11-10 15:30:44 -08:00
Ben Doherty edbfefda0a Fix Java FeatureLevel Enum (#7344) 2023-11-10 14:41:17 -08:00
Mathias Agopian 1a0b5ddc14 fix [Pixel]BufferDescriptor functor callbacks
and attempt to make functor<->callback code less confusing.
2023-11-10 13:15:29 -08:00
Mathias Agopian e8bed52b3f repair visible shadow status
FIXES=[309519433]
2023-11-10 08:58:48 -08:00
Ben Doherty 06e8b1d689 Support up to 4 side-by-side stereoscopic eyes (#7328) 2023-11-09 15:34:50 -08:00
Benjamin Doherty 9c351b28ab Release Filament 1.45.1 2023-11-09 10:09:36 -08:00
Ben Doherty cbc3bb3326 Fix mix-precision quaternion conversions (#7339) 2023-11-08 12:38:57 -08:00
Powei Feng 0eb851ff8c Update debug.cpp (#7337) 2023-11-08 10:55:28 -08:00
Powei Feng 038f07cb34 engine: move setFrontFaceWindingInverted from View to MaterialInstance (#7331)
Moving setFrontFaceWindingInverted to MaterialInstance will enable
finer control over face inversion and aligns better with Vulkan's
pipeline definition (see VkGraphicsPipelineCreateInfo).
2023-11-06 14:06:00 -08:00
Sungun Park 62adb234d1 Cleanup RenderPassNode::declareRenderTarget (#7332)
There's no functional change. Remove an unused local variable
`outgoingEdges` to save CPU resource. Tidy up an usage of local variable
to slightly improve readability.
2023-11-06 13:22:30 -08:00
Powei Feng 53d8d2a41f vk: fix headless semaphore wait logic (#7335)
We removed the no-op queue submit for headless in PR #7264. This
means that a semaphore will not be waited on and caused a
validation error.  Here, we simply don't acquire that semaphore
for present.

Also reorganzied the pSempaphores array code for better
readability.

Fixes #7334
2023-11-03 17:14:31 -07:00
Mathias Agopian 974a69a273 update remote ui and web samples 2023-11-02 07:15:46 -07:00
Mathias Agopian 7d694ee85e repair gltf_viewer drag&drop
Drag and dropping a gltf folder was broken:
- the handle didn't find the gltf file on drag&drop
- the ResourceLoader cached the asset path
- don't exit(1) when drag&dropping an invalid file
2023-11-02 06:00:45 -07:00
Powei Feng 46e6664c3d matdbg: fix matinfo use case (#7325)
- Make sure matinfo works by selecting a default backend in the
   absence of activeShaders.
 - Add options to select backend in matinfo mode.
 - Workaround cursor misplacement for monaco
 - Refactor menu sections into a common element.
2023-11-01 21:42:42 -07:00
117 changed files with 1827 additions and 674 deletions
+13
View File
@@ -41,6 +41,8 @@ option(FILAMENT_LINUX_IS_MOBILE "Treat Linux as Mobile" OFF)
option(FILAMENT_ENABLE_ASAN_UBSAN "Enable Address and Undefined Behavior Sanitizers" OFF)
option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON)
set(FILAMENT_NDK_VERSION "" CACHE STRING
"Android NDK version or version prefix to be used when building for Android."
)
@@ -65,6 +67,9 @@ set(FILAMENT_METAL_HANDLE_ARENA_SIZE_IN_MB "8" CACHE STRING
"Size of the Metal handle arena, default 8."
)
# Enable exceptions by default in spirv-cross.
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF)
# ==================================================================================================
# CMake policies
# ==================================================================================================
@@ -337,6 +342,7 @@ endif()
if (CYGWIN)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON)
endif()
if (MSVC)
@@ -373,6 +379,7 @@ endif()
# saved by -fno-exception and 10 KiB saved by -fno-rtti).
if (ANDROID OR IOS OR WEBGL)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-exceptions -fno-rtti")
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON)
if (ANDROID OR WEBGL)
# Omitting unwind info prevents the generation of readable stack traces in crash reports on iOS
@@ -384,6 +391,7 @@ endif()
# std::visit, which is not supported on iOS 11.0 when exceptions are enabled.
if (IOS)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-exceptions")
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON)
endif()
# With WebGL, we disable RTTI even for debug builds because we pass emscripten::val back and forth
@@ -523,6 +531,11 @@ if (FILAMENT_SUPPORTS_METAL)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -a metal)
endif()
# Disable ESSL 1.0 code generation.
if (NOT FILAMENT_ENABLE_FEATURE_LEVEL_0)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -1)
endif()
# Enable debug info (preserves names in SPIR-V)
if (FILAMENT_ENABLE_MATDBG)
set(MATC_OPT_FLAGS ${MATC_OPT_FLAGS} -d)
-9
View File
@@ -7,12 +7,3 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- engine: Allow instantiating Engine at a given feature level via `Engine::Builder::featureLevel`
- matc: Enable `GL_OES_standard_derivatives` extension in ESSL 1.0 shaders
- matc: Fix code generation of double sided and masked materials in ESSL 1.0 shaders
- filagui: Add support for feature level 0
- matc: Add support for post-process materials in feature level 0
- engine: Add `Material::getFeatureLevel()`
- engine: Add missing `Material::getReflectionMode()` method in Java
- engine: Support basic usage of post-processing materials on feature level 0
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.45.0'
implementation 'com.google.android.filament:filament-android:1.47.0'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.45.0'
pod 'Filament', '~> 1.47.0'
```
### Snapshots
+24
View File
@@ -7,6 +7,30 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.48.0
- matc: New option `-1` to disable generation of ESSL 1.0 code in Feature Level 0 materials
- matc: Support optimizations for ESSL 1.0 code [⚠️ **Recompile materials**]
## v1.47.0
- engine: Support up to 4 side-by-side stereoscopic eyes, configurable at Engine creation time. See
`Engine::Config::stereoscopicEyeCount`. [⚠️ **Recompile Materials**]
## v1.46.0
- engine: Allow instantiating Engine at a given feature level via `Engine::Builder::featureLevel`
- matc: Enable `GL_OES_standard_derivatives` extension in ESSL 1.0 shaders
- matc: Fix code generation of double sided and masked materials in ESSL 1.0 shaders
- filagui: Add support for feature level 0
- matc: Add support for post-process materials in feature level 0
- engine: Add `Material::getFeatureLevel()`
- engine: Add missing `Material::getReflectionMode()` method in Java
- engine: Support basic usage of post-processing materials on feature level 0
- engine: Fix critical GLES 2.0 bugs
- engine: Add `FILAMENT_ENABLE_FEATURE_LEVEL_0` build-time option optionally allow building Filament
without FL0 support.
## v1.45.1
- engine: Added parameter for configuring JobSystem thread count
@@ -384,6 +384,13 @@ Java_com_google_android_filament_Engine_nFlushAndWait(JNIEnv*, jclass,
engine->flushAndWait();
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_Engine_nFlush(JNIEnv*, jclass,
jlong nativeEngine) {
Engine* engine = (Engine*) nativeEngine;
engine->flush();
}
// Managers...
extern "C" JNIEXPORT jlong JNICALL
@@ -428,6 +435,13 @@ Java_com_google_android_filament_Engine_nIsAutomaticInstancingEnabled(JNIEnv*, j
return (jboolean)engine->isAutomaticInstancingEnabled();
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_Engine_nGetMaxStereoscopicEyes(JNIEnv*, jclass, jlong nativeEngine) {
Engine* engine = (Engine*) nativeEngine;
return (jlong) engine->getMaxStereoscopicEyes();
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_Engine_nGetSupportedFeatureLevel(JNIEnv *, jclass,
jlong nativeEngine) {
@@ -470,7 +484,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderConfig(JNIEnv*,
jclass, jlong nativeBuilder, jlong commandBufferSizeMB, jlong perRenderPassArenaSizeMB,
jlong driverHandleArenaSizeMB, jlong minCommandBufferSizeMB, jlong perFrameCommandsSizeMB,
jlong jobSystemThreadCount) {
jlong jobSystemThreadCount, jlong stereoscopicEyeCount) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
Engine::Config config = {
.commandBufferSizeMB = (uint32_t) commandBufferSizeMB,
@@ -479,6 +493,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
.minCommandBufferSizeMB = (uint32_t) minCommandBufferSizeMB,
.perFrameCommandsSizeMB = (uint32_t) perFrameCommandsSizeMB,
.jobSystemThreadCount = (uint32_t) jobSystemThreadCount,
.stereoscopicEyeCount = (uint8_t) stereoscopicEyeCount,
};
builder->config(&config);
}
@@ -19,6 +19,7 @@
#include <filament/Material.h>
#include "common/NioUtils.h"
#include "common/CallbackUtils.h"
using namespace filament;
@@ -271,3 +272,17 @@ Java_com_google_android_filament_Material_nHasParameter(JNIEnv* env, jclass,
env->ReleaseStringUTFChars(name_, name);
return (jboolean) hasParameter;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_Material_nCompile(JNIEnv *env, jclass clazz,
jlong nativeMaterial, jint priority, jint variants, jobject handler, jobject runnable) {
Material* material = (Material*) nativeMaterial;
JniCallback* jniCallback = JniCallback::make(env, handler, runnable);
material->compile(
(Material::CompilerPriorityQueue) priority,
(UserVariantFilterBit) variants,
jniCallback->getHandler(), [jniCallback](Material*){
JniCallback::postToJavaAndDestroy(jniCallback);
});
}
@@ -98,3 +98,22 @@ Java_com_google_android_filament_Scene_nHasEntity(JNIEnv *env, jclass type, jlon
Entity entity = Entity::import(entityId);
return (jboolean) scene->hasEntity(entity);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Scene_nGetEntities(JNIEnv *env, jclass ,
jlong nativeScene, jintArray outArray, jint length) {
Scene const* const scene = (Scene*) nativeScene;
if (length < scene->getEntityCount()) {
// should not happen because we already checked on the java side
return JNI_FALSE;
}
jint *out = (jint *) env->GetIntArrayElements(outArray, nullptr);
scene->forEach([out, length, i = 0](Entity entity)mutable {
if (i < length) { // this is just paranoia here
out[i++] = (jint) entity.getId();
}
});
env->ReleaseIntArrayElements(outArray, (jint*) out, JNI_ABORT);
return JNI_TRUE;
}
@@ -480,6 +480,17 @@ Java_com_google_android_filament_View_nIsStencilBufferEnabled(JNIEnv *, jclass,
return view->isStencilBufferEnabled();
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nSetStereoscopicOptions(JNIEnv *, jclass, jlong nativeView,
jboolean enabled) {
View* view = (View*) nativeView;
View::StereoscopicOptions options {
.enabled = (bool) enabled
};
view->setStereoscopicOptions(options);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_View_nSetGuardBandOptions(JNIEnv *, jclass,
@@ -111,6 +111,8 @@ public class Engine {
private long mNativeObject;
private Config mConfig;
@NonNull private final TransformManager mTransformManager;
@NonNull private final LightManager mLightManager;
@NonNull private final RenderableManager mRenderableManager;
@@ -150,8 +152,10 @@ public class Engine {
FEATURE_LEVEL_0,
/** OpenGL ES 3.0 features (default) */
FEATURE_LEVEL_1,
/** OpenGL ES 3.1 features + 16 textures units + cubemap arrays */
FEATURE_LEVEL_2,
/** OpenGL ES 3.1 features + 31 textures units + cubemap arrays */
FEATURE_LEVEL_2
FEATURE_LEVEL_3,
};
/**
@@ -161,6 +165,7 @@ public class Engine {
@SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
private final BuilderFinalizer mFinalizer;
private final long mNativeBuilder;
private Config mConfig;
public Builder() {
mNativeBuilder = nCreateBuilder();
@@ -202,10 +207,11 @@ public class Engine {
* @return A reference to this Builder for chaining calls.
*/
public Builder config(Config config) {
mConfig = config;
nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB,
config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB,
config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB,
config.jobSystemThreadCount);
config.jobSystemThreadCount, config.stereoscopicEyeCount);
return this;
}
@@ -233,7 +239,7 @@ public class Engine {
public Engine build() {
long nativeEngine = nBuilderBuild(mNativeBuilder);
if (nativeEngine == 0) throw new IllegalStateException("Couldn't create Engine");
return new Engine(nativeEngine);
return new Engine(nativeEngine, mConfig);
}
private static class BuilderFinalizer {
@@ -341,14 +347,24 @@ public class Engine {
* the number of threads to use.
*/
public long jobSystemThreadCount = 0;
/**
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
* between 1 and Engine#getMaxStereoscopicEyes() (inclusive).
*
* @see View#setStereoscopicOptions
* @see Engine#getMaxStereoscopicEyes
*/
public long stereoscopicEyeCount = 2;
}
private Engine(long nativeEngine) {
private Engine(long nativeEngine, Config config) {
mNativeObject = nativeEngine;
mTransformManager = new TransformManager(nGetTransformManager(nativeEngine));
mLightManager = new LightManager(nGetLightManager(nativeEngine));
mRenderableManager = new RenderableManager(nGetRenderableManager(nativeEngine));
mEntityManager = new EntityManager(nGetEntityManager(nativeEngine));
mConfig = config;
}
/**
@@ -541,6 +557,37 @@ public class Engine {
return nIsAutomaticInstancingEnabled(getNativeObject());
}
/**
* Retrieves the configuration settings of this {@link Engine}.
*
* This method returns the configuration object that was supplied to the Engine's {@link
* Builder#config} method during the creation of this Engine. If the {@link Builder::config}
* method was not explicitly called (or called with null), this method returns the default
* configuration settings.
*
* @return a {@link Config} object with this Engine's configuration
* @see Builder#config
*/
@NonNull
public Config getConfig() {
if (mConfig == null) {
mConfig = new Config();
}
return mConfig;
}
/**
* Returns the maximum number of stereoscopic eyes supported by Filament. The actual number of
* eyes rendered is set at Engine creation time with the {@link
* Engine#Config#stereoscopicEyeCount} setting.
*
* @return the max number of stereoscopic eyes supported
* @see Engine#Config#stereoscopicEyeCount
*/
public long getMaxStereoscopicEyes() {
return nGetMaxStereoscopicEyes(getNativeObject());
}
// SwapChain
@@ -1077,6 +1124,18 @@ public class Engine {
nFlushAndWait(getNativeObject());
}
/**
* Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) but does not wait
* for commands to be either executed or the hardware finished.
*
* <p>This is typically used after creating a lot of objects to start draining the command
* queue which has a limited size.</p>
*/
public void flush() {
nFlush(getNativeObject());
}
@UsedByReflection("TextureHelper.java")
public long getNativeObject() {
if (mNativeObject == 0) {
@@ -1149,6 +1208,7 @@ public class Engine {
private static native boolean nIsValidSwapChain(long nativeEngine, long nativeSwapChain);
private static native void nDestroyEntity(long nativeEngine, int entity);
private static native void nFlushAndWait(long nativeEngine);
private static native void nFlush(long nativeEngine);
private static native long nGetTransformManager(long nativeEngine);
private static native long nGetLightManager(long nativeEngine);
private static native long nGetRenderableManager(long nativeEngine);
@@ -1156,6 +1216,7 @@ public class Engine {
private static native long nGetEntityManager(long nativeEngine);
private static native void nSetAutomaticInstancingEnabled(long nativeEngine, boolean enable);
private static native boolean nIsAutomaticInstancingEnabled(long nativeEngine);
private static native long nGetMaxStereoscopicEyes(long nativeEngine);
private static native int nGetSupportedFeatureLevel(long nativeEngine);
private static native int nSetActiveFeatureLevel(long nativeEngine, int ordinal);
private static native int nGetActiveFeatureLevel(long nativeEngine);
@@ -1165,7 +1226,8 @@ public class Engine {
private static native void nSetBuilderBackend(long nativeBuilder, long backend);
private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB,
long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB,
long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount);
long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount,
long stereoscopicEyeCount);
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
private static native long nBuilderBuild(long nativeBuilder);
@@ -18,6 +18,7 @@ package com.google.android.filament;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Size;
import com.google.android.filament.proguard.UsedByNative;
@@ -238,6 +239,31 @@ public class Material {
FRONT_AND_BACK
}
public enum CompilerPriorityQueue {
HIGH,
LOW
}
public static class UserVariantFilterBit {
/** Directional lighting */
public static int DIRECTIONAL_LIGHTING = 0x01;
/** Dynamic lighting */
public static int DYNAMIC_LIGHTING = 0x02;
/** Shadow receiver */
public static int SHADOW_RECEIVER = 0x04;
/** Skinning */
public static int SKINNING = 0x08;
/** Fog */
public static int FOG = 0x10;
/** Variance shadow maps */
public static int VSM = 0x20;
/** Screen-space reflections */
public static int SSR = 0x40;
/** Instanced stereo rendering */
public static int STE = 0x80;
public static int ALL = 0xFF;
}
@UsedByNative("Material.cpp")
public static class Parameter {
private static final Type[] sTypeValues = Type.values();
@@ -352,6 +378,55 @@ public class Material {
}
}
/**
* Asynchronously ensures that a subset of this Material's variants are compiled. After issuing
* several compile() calls in a row, it is recommended to call {@link Engine#flush}
* such that the backend can start the compilation work as soon as possible.
* The provided callback is guaranteed to be called on the main thread after all specified
* variants of the material are compiled. This can take hundreds of milliseconds.
*<p>
* If all the material's variants are already compiled, the callback will be scheduled as
* soon as possible, but this might take a few dozen millisecond, corresponding to how
* many previous frames are enqueued in the backend. This also varies by backend. Therefore,
* it is recommended to only call this method once per material shortly after creation.
*</p>
*<p>
* If the same variant is scheduled for compilation multiple times, the first scheduling
* takes precedence; later scheduling are ignored.
*</p>
*<p>
* caveat: A consequence is that if a variant is scheduled on the low priority queue and later
* scheduled again on the high priority queue, the later scheduling is ignored.
* Therefore, the second callback could be called before the variant is compiled.
* However, the first callback, if specified, will trigger as expected.
*</p>
*<p>
* The callback is guaranteed to be called. If the engine is destroyed while some material
* variants are still compiling or in the queue, these will be discarded and the corresponding
* callback will be called. In that case however the Material pointer passed to the callback
* is guaranteed to be invalid (either because it's been destroyed by the user already, or,
* because it's been cleaned-up by the Engine).
*</p>
*<p>
* {@link UserVariantFilterBit#ALL} should be used with caution. Only variants that an application
* needs should be included in the variants argument. For example, the STE variant is only used
* for stereoscopic rendering. If an application is not planning to render in stereo, this bit
* should be turned off to avoid unnecessary material compilations.
*</p>
* @param priority Which priority queue to use, LOW or HIGH.
* @param variants Variants to include to the compile command.
* @param handler An {@link java.util.concurrent.Executor Executor}. On Android this can also be a {@link android.os.Handler Handler}.
* @param callback callback called on the main thread when the compilation is done on
* by backend.
*/
public void compile(@NonNull CompilerPriorityQueue priority,
int variants,
@Nullable Object handler,
@Nullable Runnable callback) {
nCompile(getNativeObject(), priority.ordinal(), variants, handler, callback);
}
/**
* Creates a new instance of this material. Material instances should be freed using
* {@link Engine#destroyMaterialInstance(MaterialInstance)}.
@@ -953,6 +1028,7 @@ public class Material {
private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name);
private static native long nGetDefaultInstance(long nativeMaterial);
private static native void nCompile(long nativeMaterial, int priority, int variants, Object handler, Runnable runnable);
private static native String nGetName(long nativeMaterial);
private static native int nGetShading(long nativeMaterial);
private static native int nGetInterpolation(long nativeMaterial);
@@ -16,6 +16,7 @@
package com.google.android.filament;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
@@ -190,6 +191,52 @@ public class Scene {
return mNativeObject;
}
/**
* Returns the list of all entities in the Scene. If outArray is provided and large enough,
* it is used to store the list and returned, otherwise a new array is allocated and returned.
* @param outArray an array to store the list of entities in the scene.
* @return outArray if it was used or a newly allocated array.
* @see #getEntityCount
*/
public int[] getEntities(@Nullable int[] outArray) {
int c = getEntityCount();
if (outArray == null || outArray.length < c) {
outArray = new int[c];
}
boolean success = nGetEntities(getNativeObject(), outArray, outArray.length);
if (!success) {
throw new IllegalStateException("Error retriving Scene's entities");
}
return outArray;
}
/**
* Returns the list of all entities in the Scene in a newly allocated array.
* @return an array containing the list of all entities in the scene.
* @see #getEntityCount
*/
public int[] getEntities() {
return getEntities(null);
}
public interface EntityProcessor {
void process(@Entity int entity);
}
/**
* Invokes user functor on each entity in the scene.
*
* It is not allowed to add or remove an entity from the scene within the functor.
*
* @param entityProcessor User provided functor called for each entity in the scene
*/
public void forEach(@NonNull EntityProcessor entityProcessor) {
int[] entities = getEntities(null);
for (int entity : entities) {
entityProcessor.process(entity);
}
}
void clearNativeObject() {
mNativeObject = 0;
}
@@ -204,4 +251,5 @@ public class Scene {
private static native int nGetRenderableCount(long nativeScene);
private static native int nGetLightCount(long nativeScene);
private static native boolean nHasEntity(long nativeScene, int entity);
private static native boolean nGetEntities(long nativeScene, int[] outArray, int length);
}
@@ -75,6 +75,7 @@ public class View {
private AmbientOcclusionOptions mAmbientOcclusionOptions;
private BloomOptions mBloomOptions;
private FogOptions mFogOptions;
private StereoscopicOptions mStereoscopicOptions;
private RenderTarget mRenderTarget;
private BlendMode mBlendMode;
private DepthOfFieldOptions mDepthOfFieldOptions;
@@ -1055,6 +1056,51 @@ public class View {
return nIsStencilBufferEnabled(getNativeObject());
}
/**
* Sets the stereoscopic rendering options for this view.
*
* <p>
* Currently, only one type of stereoscopic rendering is supported: side-by-side.
* Side-by-side stereo rendering splits the viewport into two halves: a left and right half.
* Eye 0 will render to the left half, while Eye 1 will render into the right half.
* </p>
*
* <p>
* Currently, the following features are not supported with stereoscopic rendering:
* - post-processing
* - shadowing
* - punctual lights
* </p>
*
* <p>
* Stereo rendering depends on device and platform support. To check if stereo rendering is
* supported, use {@link Engine#isStereoSupported()}. If stereo rendering is not supported, then
* the stereoscopic options have no effect.
* </p>
*
* @param options The stereoscopic options to use on this view
* @see #getStereoscopicOptions
*/
public void setStereoscopicOptions(@NonNull StereoscopicOptions options) {
mStereoscopicOptions = options;
nSetStereoscopicOptions(getNativeObject(), options.enabled);
}
/**
* Gets the stereoscopic options.
*
* @return options Stereoscopic options currently set.
* @see #setStereoscopicOptions
*/
@NonNull
public StereoscopicOptions getStereoscoopicOptions() {
if (mStereoscopicOptions == null) {
mStereoscopicOptions = new StereoscopicOptions();
}
return mStereoscopicOptions;
}
/**
* A class containing the result of a picking query
*/
@@ -1220,6 +1266,7 @@ public class View {
private static native void nSetBloomOptions(long nativeView, long dirtNativeObject, float dirtStrength, float strength, int resolution, int levels, int blendMode, boolean threshold, boolean enabled, float highlight,
boolean lensFlare, boolean starburst, float chromaticAberration, int ghostCount, float ghostSpacing, float ghostThreshold, float haloThickness, float haloRadius, float haloThreshold);
private static native void nSetFogOptions(long nativeView, float distance, float maximumOpacity, float height, float heightFalloff, float cutOffDistance, float v, float v1, float v2, float density, float inScatteringStart, float inScatteringSize, boolean fogColorFromIbl, long skyColorNativeObject, boolean enabled);
private static native void nSetStereoscopicOptions(long nativeView, boolean enabled);
private static native void nSetBlendMode(long nativeView, int blendMode);
private static native void nSetDepthOfFieldOptions(long nativeView, float cocScale, float maxApertureDiameter, boolean enabled, int filter,
boolean nativeResolution, int foregroundRingCount, int backgroundRingCount, int fastGatherRingCount, int maxForegroundCOC, int maxBackgroundCOC);
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.45.0
VERSION_NAME=1.47.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -26,6 +26,7 @@ import android.widget.TextView
import android.widget.Toast
import com.google.android.filament.Fence
import com.google.android.filament.IndirectLight
import com.google.android.filament.Material
import com.google.android.filament.Skybox
import com.google.android.filament.View
import com.google.android.filament.View.OnPickCallback
@@ -392,6 +393,36 @@ class MainActivity : Activity() {
Log.i(TAG, "The Filament backend took $total ms to load the model geometry.")
modelViewer.engine.destroyFence(it)
loadStartFence = null
val materials = mutableSetOf<Material>()
val rcm = modelViewer.engine.renderableManager
modelViewer.scene.forEach {
val entity = it
if (rcm.hasComponent(entity)) {
val ri = rcm.getInstance(entity)
val c = rcm.getPrimitiveCount(ri)
for (i in 0 until c) {
val mi = rcm.getMaterialInstanceAt(ri, i)
val ma = mi.material
materials.add(ma)
}
}
}
materials.forEach {
it.compile(
Material.CompilerPriorityQueue.HIGH,
Material.UserVariantFilterBit.DIRECTIONAL_LIGHTING or
Material.UserVariantFilterBit.DYNAMIC_LIGHTING or
Material.UserVariantFilterBit.SHADOW_RECEIVER,
null, null)
it.compile(
Material.CompilerPriorityQueue.LOW,
Material.UserVariantFilterBit.FOG or
Material.UserVariantFilterBit.SKINNING or
Material.UserVariantFilterBit.SSR or
Material.UserVariantFilterBit.VSM,
null, null)
}
}
}
@@ -20,6 +20,8 @@ import android.animation.ValueAnimator
import android.app.Activity
import android.opengl.Matrix
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.Choreographer
import android.view.Surface
import android.view.SurfaceView
@@ -157,6 +159,14 @@ class MainActivity : Activity() {
private fun loadMaterial() {
readUncompressedAsset("materials/baked_color.filamat").let {
material = Material.Builder().payload(it, it.remaining()).build(engine)
material.compile(
Material.CompilerPriorityQueue.HIGH,
Material.UserVariantFilterBit.ALL,
Handler(Looper.getMainLooper())) {
android.util.Log.i("hellotriangle",
"Material " + material.name + " compiled.")
}
engine.flush()
}
}
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+28 -3
View File
@@ -218,7 +218,6 @@ set(MATERIAL_SRCS
src/materials/colorGrading/customResolveAsSubpass.mat
src/materials/debugShadowCascades.mat
src/materials/defaultMaterial.mat
src/materials/defaultMaterial0.mat
src/materials/dof/dof.mat
src/materials/dof/dofCoc.mat
src/materials/dof/dofDownsample.mat
@@ -229,7 +228,6 @@ set(MATERIAL_SRCS
src/materials/dof/dofMipmap.mat
src/materials/dof/dofMedian.mat
src/materials/flare/flare.mat
src/materials/blitLow.mat
src/materials/bloom/bloomDownsample.mat
src/materials/bloom/bloomDownsample2x.mat
src/materials/bloom/bloomDownsample9.mat
@@ -238,13 +236,18 @@ set(MATERIAL_SRCS
src/materials/ssao/bilateralBlurBentNormals.mat
src/materials/ssao/mipmapDepth.mat
src/materials/skybox.mat
src/materials/skybox0.mat
src/materials/ssao/sao.mat
src/materials/ssao/saoBentNormals.mat
src/materials/separableGaussianBlur.mat
src/materials/antiAliasing/fxaa.mat
src/materials/antiAliasing/taa.mat
src/materials/vsmMipmap.mat
src/materials/blitLow.mat
)
set(MATERIAL_FL0_SRCS
src/materials/defaultMaterial0.mat
src/materials/skybox0.mat
)
# Embed the binary resource blob for materials.
@@ -271,6 +274,11 @@ if (NOT DFG_LUT_SIZE)
endif()
message(STATUS "DFG LUT size set to ${DFG_LUT_SIZE}x${DFG_LUT_SIZE}")
# Whether to include FL0 materials.
if (FILAMENT_ENABLE_FEATURE_LEVEL_0)
add_definitions(-DFILAMENT_ENABLE_FEATURE_LEVEL_0)
endif()
# ==================================================================================================
# Definitions
# ==================================================================================================
@@ -312,6 +320,23 @@ foreach (mat_src ${MATERIAL_SRCS})
list(APPEND MATERIAL_BINS ${output_path})
endforeach()
if (FILAMENT_ENABLE_FEATURE_LEVEL_0 AND FILAMENT_SUPPORTS_OPENGL)
foreach (mat_src ${MATERIAL_FL0_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 -a opengl -p ${MATC_TARGET} ${MATC_OPT_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 ()
endif ()
# Additional dependencies on included files for materials
add_custom_command(
+28 -16
View File
@@ -397,8 +397,8 @@ install(DIRECTORY ${PUBLIC_HDR_DIR}/backend DESTINATION include)
# ==================================================================================================
option(INSTALL_BACKEND_TEST "Install the backend test library so it can be consumed on iOS" OFF)
if (APPLE)
add_library(backend_test STATIC
if (APPLE OR LINUX)
set(BACKEND_TEST_SRC
test/BackendTest.cpp
test/ShaderGenerator.cpp
test/TrianglePrimitive.cpp
@@ -410,19 +410,26 @@ if (APPLE)
test/test_BufferUpdates.cpp
test/test_MRT.cpp
test/test_LoadImage.cpp
test/test_RenderExternalImage.cpp
test/test_StencilBuffer.cpp
test/test_Scissor.cpp
test/test_MipLevels.cpp
)
target_link_libraries(backend_test PRIVATE
)
set(BACKEND_TEST_LIBS
backend
getopt
gtest
imageio
filamat
SPIRV
spirv-cross-glsl)
endif()
if (APPLE)
# TODO: we should expand this test to Linux and other platforms.
list(APPEND BACKEND_TEST_SRC
test/test_RenderExternalImage.cpp)
add_library(backend_test STATIC ${BACKEND_TEST_SRC})
target_link_libraries(backend_test PRIVATE ${BACKEND_TEST_LIBS})
set(BACKEND_TEST_DEPS
OGLCompiler
@@ -436,12 +443,11 @@ if (APPLE)
glslang
spirv-cross-core
spirv-cross-glsl
spirv-cross-msl
)
spirv-cross-msl)
if (NOT IOS)
target_link_libraries(backend_test PRIVATE image imageio)
list(APPEND BACKEND_TEST_DEPS image imageio)
list(APPEND BACKEND_TEST_DEPS image)
endif()
set(BACKEND_TEST_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libbackendtest_combined.a")
@@ -455,15 +461,21 @@ if (APPLE)
endif()
set_target_properties(backend_test PROPERTIES FOLDER Tests)
if (APPLE AND NOT IOS)
add_executable(backend_test_mac test/mac_runner.mm)
target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore")
# Because each test case is a separate file, the -force_load flag is necessary to prevent the
# linker from removing "unused" symbols.
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
endif()
endif()
if (APPLE AND NOT IOS)
add_executable(backend_test_mac test/mac_runner.mm)
target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore")
# Because each test case is a separate file, the -force_load flag is necessary to prevent the
# linker from removing "unused" symbols.
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
if (LINUX)
add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC})
target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS})
set_target_properties(backend_test_linux PROPERTIES FOLDER Tests)
endif()
# ==================================================================================================
@@ -113,7 +113,7 @@ public:
/**
* Helper to create a BufferDescriptor that uses a KNOWN method pointer w/ object passed
* by pointer as the callback. e.g.:
* auto bd = BufferDescriptor::make(buffer, size, &Foo::method, foo);
* auto bd = BufferDescriptor::make<Foo, &Foo::method>(buffer, size, foo);
*
* @param buffer Memory address of the CPU buffer to reference
* @param size Size of the CPU buffer in bytes
@@ -121,12 +121,12 @@ public:
* @return a new BufferDescriptor
*/
template<typename T, void(T::*method)(void const*, size_t)>
static BufferDescriptor make(
void const* buffer, size_t size, T* data, CallbackHandler* handler = nullptr) noexcept {
static BufferDescriptor make(void const* buffer, size_t size, T* data,
CallbackHandler* handler = nullptr) noexcept {
return {
buffer, size,
handler, [](void* b, size_t s, void* u) {
(*static_cast<T**>(u)->*method)(b, s);
(static_cast<T*>(u)->*method)(b, s);
}, data
};
}
@@ -145,14 +145,14 @@ public:
* @return a new BufferDescriptor
*/
template<typename T>
static BufferDescriptor make(
void const* buffer, size_t size, T&& functor, CallbackHandler* handler = nullptr) noexcept {
static BufferDescriptor make(void const* buffer, size_t size, T&& functor,
CallbackHandler* handler = nullptr) noexcept {
return {
buffer, size,
handler, [](void* b, size_t s, void* u) {
T& that = *static_cast<T*>(u);
that(b, s);
delete &that;
T* const that = static_cast<T*>(u);
that->operator()(b, s);
delete that;
},
new T(std::forward<T>(functor))
};
@@ -201,7 +201,7 @@ public:
return mUser;
}
//! CPU mempry-buffer virtual address
//! CPU memory-buffer virtual address
void* buffer = nullptr;
//! CPU memory-buffer size in bytes
@@ -141,7 +141,7 @@ public:
CallbackHandler* handler = nullptr) noexcept {
return { buffer, size, format, type, alignment, left, top, stride,
handler, [](void* b, size_t s, void* u) {
(*static_cast<T**>(u)->*method)(b, s); }, data };
(static_cast<T*>(u)->*method)(b, s); }, data };
}
template<typename T, void(T::*method)(void const*, size_t)>
@@ -149,7 +149,7 @@ public:
PixelDataFormat format, PixelDataType type, T* data,
CallbackHandler* handler = nullptr) noexcept {
return { buffer, size, format, type, handler, [](void* b, size_t s, void* u) {
(*static_cast<T**>(u)->*method)(b, s); }, data };
(static_cast<T*>(u)->*method)(b, s); }, data };
}
template<typename T, void(T::*method)(void const*, size_t)>
@@ -157,7 +157,7 @@ public:
backend::CompressedPixelDataType format, uint32_t imageSize, T* data,
CallbackHandler* handler = nullptr) noexcept {
return { buffer, size, format, imageSize, handler, [](void* b, size_t s, void* u) {
(*static_cast<T**>(u)->*method)(b, s); }, data
(static_cast<T*>(u)->*method)(b, s); }, data
};
}
@@ -168,9 +168,9 @@ public:
CallbackHandler* handler = nullptr) noexcept {
return { buffer, size, format, type, alignment, left, top, stride,
handler, [](void* b, size_t s, void* u) {
T& that = *static_cast<T*>(u);
that(b, s);
delete &that;
T* const that = static_cast<T*>(u);
that->operator()(b, s);
delete that;
}, new T(std::forward<T>(functor))
};
}
@@ -181,9 +181,9 @@ public:
CallbackHandler* handler = nullptr) noexcept {
return { buffer, size, format, type,
handler, [](void* b, size_t s, void* u) {
T& that = *static_cast<T*>(u);
that(b, s);
delete &that;
T* const that = static_cast<T*>(u);
that->operator()(b, s);
delete that;
}, new T(std::forward<T>(functor))
};
}
@@ -194,9 +194,9 @@ public:
CallbackHandler* handler = nullptr) noexcept {
return { buffer, size, format, imageSize,
handler, [](void* b, size_t s, void* u) {
T& that = *static_cast<T*>(u);
that(b, s);
delete &that;
T* const that = static_cast<T*>(u);
that->operator()(b, s);
delete that;
}, new T(std::forward<T>(functor))
};
}
@@ -170,9 +170,6 @@ MetalShaderCompiler::program_token_t MetalShaderCompiler::createProgram(
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
mCompilerThreadPool.queue(priorityQueue, token,
[this, name, device = mDevice, program = std::move(program), token]() {
int sleepTime = atoi(name.c_str());
sleep(sleepTime);
MetalFunctionBundle compiledProgram = compileProgram(program, device);
token->set(compiledProgram);
@@ -284,6 +284,7 @@ void OpenGLContext::setDefaultState() noexcept {
if (ext.EXT_clip_cull_distance) {
glEnable(GL_CLIP_DISTANCE0);
glEnable(GL_CLIP_DISTANCE1);
}
}
+19 -6
View File
@@ -172,7 +172,7 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
mShaderCompilerService(*this),
mHandleAllocator("Handles", driverConfig.handleArenaSize),
mSamplerMap(32) {
std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr);
// set a reasonable default value for our stream array
@@ -268,9 +268,9 @@ void OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
if (UTILS_UNLIKELY(mContext.isES2())) {
for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) {
auto [buffer, age] = mUniformBindings[i];
auto [id, buffer, age] = mUniformBindings[i];
if (buffer) {
p->updateUniforms(i, buffer, age);
p->updateUniforms(i, id, buffer, age);
}
}
// Set the output colorspace for this program (linear or rec709). This in only relevant
@@ -479,6 +479,7 @@ void OpenGLDriver::createBufferObjectR(Handle<HwBufferObject> boh,
GLBufferObject* bo = construct<GLBufferObject>(boh, byteCount, bindingType, usage);
if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
bo->gl.id = ++mLastAssignedEmulatedUboId;
bo->gl.buffer = malloc(byteCount);
memset(bo->gl.buffer, 0, byteCount);
} else {
@@ -595,8 +596,9 @@ void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t,
}
} else {
glTexImage2D(t->gl.target, level, GLint(t->gl.internalFormat),
GLsizei(width), GLsizei(height), 0,
format, type, nullptr);
std::max(GLsizei(1), GLsizei(width >> level)),
std::max(GLsizei(1), GLsizei(height >> level)),
0, format, type, nullptr);
}
}
}
@@ -1967,6 +1969,13 @@ void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain>
GLSwapChain* scRead = handle_cast<GLSwapChain*>(schRead);
mPlatform.makeCurrent(scDraw->swapChain, scRead->swapChain);
mCurrentDrawSwapChain = scDraw;
// From the GL spec for glViewport and glScissor:
// When a GL context is first attached to a window, width and height are set to the
// dimensions of that window.
// So basically, our viewport/scissor can be reset to "something" here.
mContext.state.window.viewport = {};
mContext.state.window.scissor = {};
}
// ------------------------------------------------------------------------------------------------
@@ -2901,7 +2910,11 @@ void OpenGLDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t ind
assert_invariant(offset + size <= ub->byteCount);
if (UTILS_UNLIKELY(ub->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
mUniformBindings[index] = { static_cast<uint8_t const*>(ub->gl.buffer) + offset, ub->age };
mUniformBindings[index] = {
ub->gl.id,
static_cast<uint8_t const*>(ub->gl.buffer) + offset,
ub->age,
};
} else {
GLenum const target = GLUtils::getBufferBindingType(bindingType);
+4 -5
View File
@@ -83,11 +83,9 @@ public:
}
struct {
GLuint id;
union {
struct {
GLuint id;
GLenum binding;
};
GLenum binding;
void* buffer;
};
} gl;
@@ -363,7 +361,8 @@ private:
void setScissor(Viewport const& scissor) noexcept;
// ES2 only. Uniform buffer emulation binding points
std::array<std::pair<void const*, uint16_t>, Program::UNIFORM_BINDING_COUNT> mUniformBindings = {};
GLuint mLastAssignedEmulatedUboId = 0;
std::array<std::tuple<GLuint, void const*, uint16_t>, Program::UNIFORM_BINDING_COUNT> mUniformBindings = {};
// sampler buffer binding points (nullptr if not used)
std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT> mSamplerBindings = {}; // 4 pointers
@@ -229,15 +229,16 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept {
CHECK_GL_ERROR(utils::slog.e)
}
void OpenGLProgram::updateUniforms(uint32_t index, void const* buffer, uint16_t age) noexcept {
void OpenGLProgram::updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept {
assert_invariant(mUniformsRecords);
assert_invariant(buffer);
// only update the uniforms if the UBO has changed since last time we updated
UniformsRecord const& records = mUniformsRecords[index];
if (records.age == age) {
if (records.id == id && records.age == age) {
return;
}
records.id = id;
records.age = age;
assert_invariant(records.uniforms.size() == records.locations.size());
+2 -1
View File
@@ -72,7 +72,7 @@ public:
} gl; // 12 bytes
// For ES2 only
void updateUniforms(uint32_t index, void const* buffer, uint16_t age) noexcept;
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) noexcept;
void setRec709ColorSpace(bool rec709) const noexcept;
private:
@@ -98,6 +98,7 @@ private:
struct UniformsRecord {
Program::UniformInfo uniforms;
LocationInfo locations;
mutable GLuint id = 0;
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
};
UniformsRecord const* mUniformsRecords = nullptr;
+2
View File
@@ -190,8 +190,10 @@ using namespace glext;
#if defined(GL_EXT_clip_cull_distance)
# define GL_CLIP_DISTANCE0 GL_CLIP_DISTANCE0_EXT
# define GL_CLIP_DISTANCE1 GL_CLIP_DISTANCE1_EXT
#else
# define GL_CLIP_DISTANCE0 0x3000
# define GL_CLIP_DISTANCE1 0x3001
#endif
#if defined(GL_KHR_debug)
+9 -17
View File
@@ -268,13 +268,18 @@ bool VulkanCommands::flush() {
VK_NULL_HANDLE,
VK_NULL_HANDLE,
};
uint32_t waitSemaphoreCount = 0;
if (mSubmissionSignal) {
signals[waitSemaphoreCount++] = mSubmissionSignal;
}
if (mInjectedSignal) {
signals[waitSemaphoreCount++] = mInjectedSignal;
}
VkCommandBuffer const cmdbuffer = currentbuf->buffer();
VkSubmitInfo submitInfo{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = 0,
.pWaitSemaphores = signals,
.waitSemaphoreCount = waitSemaphoreCount,
.pWaitSemaphores = waitSemaphoreCount > 0 ? signals : nullptr,
.pWaitDstStageMask = waitDestStageMasks,
.commandBufferCount = 1,
.pCommandBuffers = &cmdbuffer,
@@ -282,19 +287,6 @@ bool VulkanCommands::flush() {
.pSignalSemaphores = &renderingFinished,
};
if (mSubmissionSignal) {
signals[submitInfo.waitSemaphoreCount++] = mSubmissionSignal;
}
if (mInjectedSignal) {
signals[submitInfo.waitSemaphoreCount++] = mInjectedSignal;
}
// To address a validation warning.
if (submitInfo.waitSemaphoreCount == 0) {
submitInfo.pWaitSemaphores = VK_NULL_HANDLE;
}
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
slog.i << "Submitting cmdbuffer=" << cmdbuffer
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
@@ -99,11 +99,16 @@ void VulkanSwapChain::present() {
mColors[mCurrentSwapIndex]->transitionLayout(cmdbuf, subresources, VulkanLayout::PRESENT);
}
mCommands->flush();
VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal();
VkResult const result = mPlatform->present(swapChain, mCurrentSwapIndex, finishedDrawing);
ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR
|| result == VK_ERROR_OUT_OF_DATE_KHR,
"Cannot present in swapchain.");
// We only present if it is not headless. No-op for headless (but note that we still need the
// flush() in the above line).
if (!mHeadless) {
VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal();
VkResult const result = mPlatform->present(swapChain, mCurrentSwapIndex, finishedDrawing);
ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR ||
result == VK_ERROR_OUT_OF_DATE_KHR,
"Cannot present in swapchain.");
}
// We presented the last acquired buffer.
mAcquired = false;
+68 -4
View File
@@ -18,11 +18,16 @@
#include <private/backend/PlatformFactory.h>
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
#include <backend/platforms/VulkanPlatform.h>
#endif
#include "BackendTest.h"
#include <utils/Hash.h>
#include <fstream>
#include <string>
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
@@ -33,6 +38,25 @@ static constexpr size_t CONFIG_COMMAND_BUFFERS_SIZE = 3 * CONFIG_MIN_COMMAND
using namespace filament;
using namespace filament::backend;
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
namespace filament {
class InternalVulkanPlatform : public VulkanPlatform {
public:
InternalVulkanPlatform() {
mCustomization = {
.gpu = { .index = 0 },
};
}
virtual VulkanPlatform::Customization getCustomization() const noexcept override {
return mCustomization;
}
private:
VulkanPlatform::Customization mCustomization;
};
}
#endif
#ifndef IOS
#include <imageio/ImageEncoder.h>
#include <image/ColorTransform.h>
@@ -40,6 +64,11 @@ using namespace filament::backend;
using namespace image;
#endif
namespace {
std::string const SHADER_VULKAN_TAG = "// select(vulkan) ";
std::string const SHADER_METAL_TAG = "// select(metal) ";
}
namespace test {
Backend BackendTest::sBackend = Backend::NOOP;
@@ -67,7 +96,14 @@ BackendTest::~BackendTest() {
void BackendTest::initializeDriver() {
auto backend = static_cast<filament::backend::Backend>(sBackend);
Platform* platform = PlatformFactory::create(&backend);
Platform* platform = nullptr;
if (backend == filament::backend::Backend::VULKAN) {
platform = new InternalVulkanPlatform();
} else {
platform = PlatformFactory::create(&backend);
}
assert_invariant(static_cast<uint8_t>(backend) == static_cast<uint8_t>(sBackend));
Platform::DriverConfig const driverConfig;
driver = platform->createDriver(nullptr, driverConfig);
@@ -86,13 +122,16 @@ void BackendTest::executeCommands() {
}
void BackendTest::flushAndWait() {
auto& api = getDriverApi();
api.finish();
getDriverApi().finish();
executeCommands();
getDriver().purge();
}
Handle<HwSwapChain> BackendTest::createSwapChain() {
const NativeView& view = getNativeView();
if (!view.ptr) {
return getDriverApi().createSwapChainHeadless(view.width, view.height, 0);
}
return getDriverApi().createSwapChain(view.ptr, 0);
}
@@ -190,6 +229,32 @@ void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, si
getDriverApi().readPixels(rt, 0, 0, width, height, std::move(pbd));
}
std::string BackendTest::transformShaderText(std::string const& shader) const {
std::string replacement;
switch (sBackend) {
case test::Backend::VULKAN:
replacement = SHADER_VULKAN_TAG;
break;
case test::Backend::METAL:
replacement = SHADER_METAL_TAG;
break;
default:
break;
}
if (replacement.size() == 0) {
return shader;
}
std::string replaced = shader;
while (true) {
size_t pos = replaced.find(replacement);
if (pos == std::string::npos) {
break;
}
replaced.replace(pos, replacement.length(), "");
}
return replaced;
}
class Environment : public ::testing::Environment {
public:
virtual void SetUp() override {
@@ -212,4 +277,3 @@ int runTests() {
}
} // namespace test
+2
View File
@@ -66,6 +66,8 @@ protected:
filament::backend::DriverApi& getDriverApi() { return *commandStream; }
filament::backend::Driver& getDriver() { return *driver; }
std::string transformShaderText(std::string const& shader) const;
private:
filament::backend::Driver* driver = nullptr;
+3
View File
@@ -22,6 +22,9 @@
namespace test {
constexpr uint32_t const WINDOW_WIDTH = 512;
constexpr uint32_t const WINDOW_HEIGHT = 512;
// To avoid a dependency on filabridge, the Backend enum is replicated here.
enum class Backend : uint8_t {
OPENGL = 1,
+56
View File
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 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 "BackendTest.h"
#include "PlatformRunner.h"
#include <array>
#include <iostream>
namespace test {
test::NativeView getNativeView() {
return {
.ptr = nullptr,
.width = WINDOW_WIDTH,
.height = WINDOW_HEIGHT,
};
}
}// namespace test
namespace {
std::array<test::Backend, 2> const VALID_BACKENDS{
test::Backend::OPENGL,
test::Backend::VULKAN,
};
}// namespace
int main(int argc, char* argv[]) {
auto backend = test::parseArgumentsForBackend(argc, argv);
if (!std::any_of(VALID_BACKENDS.begin(), VALID_BACKENDS.end(),
[backend](test::Backend validBackend) { return backend == validBackend; })) {
std::cerr << "Specified an invalid backend. Only GL and Vulkan are available" << std::endl;
return 1;
}
test::initTests(backend, false, argc, argv);
return test::runTests();
}
+1 -1
View File
@@ -58,7 +58,7 @@ test::NativeView getNativeView() {
}
- (NSView*)createView {
NSRect frame = NSMakeRect(0, 0, 512, 512);
NSRect frame = NSMakeRect(0, 0, test::WINDOW_WIDTH, test::WINDOW_HEIGHT);
NSWindow* window = [[NSWindow alloc] initWithContentRect:frame
styleMask:NSWindowStyleMaskBorderless
backing:NSBackingStoreBuffered
+40 -112
View File
@@ -193,60 +193,6 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
dapi.update3DImage(texture, level, 0, 0, 0, width, height, 6, std::move(pb));
}
TEST_F(BackendTest, CubemapMinify) {
auto& api = getDriverApi();
Handle<HwTexture> texture = api.createTexture(
SamplerType::SAMPLER_CUBEMAP, 4, TextureFormat::RGBA8, 1, 256, 256, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT);
createFaces(api, texture, 256, 256, 0, float3(0.5, 0, 0));
const int srcLevel = 0;
const int dstLevel = 1;
const TextureCubemapFace srcFace = TextureCubemapFace::NEGATIVE_Y;
const TextureCubemapFace dstFace = TextureCubemapFace::NEGATIVE_Y;
const TargetBufferInfo srcInfo = { texture, srcLevel, +srcFace };
const TargetBufferInfo dstInfo = { texture, dstLevel, +dstFace };
Handle<HwRenderTarget> srcRenderTarget;
srcRenderTarget = api.createRenderTarget( TargetBufferFlags::COLOR,
256 >> srcLevel, 256 >> srcLevel, 1, { srcInfo }, {}, {});
Handle<HwRenderTarget> dstRenderTarget;
dstRenderTarget = api.createRenderTarget( TargetBufferFlags::COLOR,
256 >> dstLevel, 256 >> dstLevel, 1, { dstInfo }, {}, {});
api.blit(TargetBufferFlags::COLOR0, dstRenderTarget,
{0, 0, 256 >> dstLevel, 256 >> dstLevel}, srcRenderTarget,
{0, 0, 256 >> srcLevel, 256 >> srcLevel}, SamplerMagFilter::LINEAR);
// Push through an empty frame. Note that this test does not do
// makeCurrent / commit and is therefore similar to renderStandaloneView.
api.beginFrame(0, 0);
api.endFrame(0);
// Grab a screenshot.
ScreenshotParams params { 256 >> dstLevel, 256 >> dstLevel, "CubemapMinify.png" };
api.beginFrame(0, 0);
dumpScreenshot(api, dstRenderTarget, &params);
api.endFrame(0);
// Wait for the ReadPixels result to come back.
api.finish();
executeCommands();
getDriver().purge();
// Cleanup.
api.destroyTexture(texture);
api.destroyRenderTarget(srcRenderTarget);
api.destroyRenderTarget(dstRenderTarget);
executeCommands();
}
TEST_F(BackendTest, ColorMagnify) {
auto& api = getDriverApi();
@@ -263,7 +209,7 @@ TEST_F(BackendTest, ColorMagnify) {
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
Handle<HwTexture> srcTexture = api.createTexture(
Handle<HwTexture> const srcTexture = api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT);
const bool flipY = sBackend == Backend::OPENGL;
@@ -271,7 +217,7 @@ TEST_F(BackendTest, ColorMagnify) {
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
// Create a destination texture.
Handle<HwTexture> dstTexture = api.createTexture(
Handle<HwTexture> const dstTexture = api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT);
@@ -338,7 +284,7 @@ TEST_F(BackendTest, ColorMinify) {
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
Handle<HwTexture> srcTexture = api.createTexture(
Handle<HwTexture> const srcTexture = api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT);
const bool flipY = sBackend == Backend::OPENGL;
@@ -346,7 +292,7 @@ TEST_F(BackendTest, ColorMinify) {
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
// Create a destination texture.
Handle<HwTexture> dstTexture = api.createTexture(
Handle<HwTexture> const dstTexture = api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT);
@@ -362,29 +308,20 @@ TEST_F(BackendTest, ColorMinify) {
// Do a "minify" blit from level 1 of the source RT to the level 0 of the destination RT.
const int srcLevel = 1;
api.blit(TargetBufferFlags::COLOR0, dstRenderTargets[0],
{0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTargets[srcLevel],
{0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel}, SamplerMagFilter::LINEAR);
// Push through an empty frame to allow the texture to upload and the blit to execute.
api.beginFrame(0, 0);
api.commit(swapChain);
api.endFrame(0);
api.blit(TargetBufferFlags::COLOR0,
dstRenderTargets[0], {0, 0, kDstTexWidth, kDstTexHeight},
srcRenderTargets[srcLevel], {0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel},
SamplerMagFilter::LINEAR);
// Grab a screenshot.
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMinify.png" };
api.beginFrame(0, 0);
dumpScreenshot(api, dstRenderTargets[0], &params);
api.commit(swapChain);
api.endFrame(0);
// Wait for the ReadPixels result to come back.
api.finish();
executeCommands();
getDriver().purge();
flushAndWait();
// Check if the image matches perfectly to our golden run.
const uint32_t expected = 0x7739bef5;
const uint32_t expected = 0xf3d9c53f;
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
EXPECT_TRUE(params.pixelHashResult == expected);
@@ -417,8 +354,6 @@ TEST_F(BackendTest, DepthMinify) {
{
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
prog.uniformBlockBindings({{"params", 1}});
program = api.createProgram(std::move(prog));
}
@@ -550,85 +485,82 @@ TEST_F(BackendTest, ColorResolve) {
constexpr auto kColorTexFormat = TextureFormat::RGBA8;
constexpr int kSampleCount = 4;
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = api.createSwapChainHeadless(256, 256, 0);
api.makeCurrent(swapChain, swapChain);
// Create a program.
ProgramHandle program;
{
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
prog.uniformBlockBindings({{"params", 1}});
program = api.createProgram(std::move(prog));
}
// Create a VertexBuffer, IndexBuffer, and RenderPrimitive.
TrianglePrimitive* triangle = new TrianglePrimitive(api);
TrianglePrimitive const triangle(api);
// Create 4-sample texture.
Handle<HwTexture> srcColorTexture = api.createTexture(
Handle<HwTexture> const srcColorTexture = api.createTexture(
SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::COLOR_ATTACHMENT);
// Create 1-sample texture.
Handle<HwTexture> dstColorTexture = api.createTexture(
Handle<HwTexture> const dstColorTexture = api.createTexture(
SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT);
// Create a 4-sample render target with the 4-sample texture.
Handle<HwRenderTarget> srcRenderTarget = api.createRenderTarget(
TargetBufferFlags::COLOR, kSrcTexWidth, kSrcTexHeight, kSampleCount,{{ srcColorTexture }}, {}, {});
Handle<HwRenderTarget> const srcRenderTarget = api.createRenderTarget(
TargetBufferFlags::COLOR, kSrcTexWidth, kSrcTexHeight, kSampleCount,
{{ srcColorTexture }}, {}, {});
// Create a 1-sample render target with the 1-sample texture.
Handle<HwRenderTarget> dstRenderTarget = api.createRenderTarget(
TargetBufferFlags::COLOR, kDstTexWidth, kDstTexHeight, 1, {{ dstColorTexture }}, {}, {});
Handle<HwRenderTarget> const dstRenderTarget = api.createRenderTarget(
TargetBufferFlags::COLOR, kDstTexWidth, kDstTexHeight, 1,
{{ dstColorTexture }}, {}, {});
// Prep for rendering.
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::COLOR;
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
params.clearColor = float4(1, 1, 0, 1);
params.viewport.width = kSrcTexWidth;
params.viewport.height = kSrcTexHeight;
PipelineState state = {};
state.rasterState.colorWrite = true;
state.rasterState.culling = RasterState::CullingMode::NONE;
state.program = program;
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.rasterState.culling = CullingMode::NONE;
auto ubuffer = api.createBufferObject(sizeof(MaterialParams),
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0);
api.bindUniformBuffer(0, ubuffer);
// Draw red triangle into srcRenderTarget.
uploadUniforms(api, ubuffer, {
.color = float4(1, 0, 0, 1),
.scale = float4(1, 1, 0.5, 0),
});
api.beginRenderPass(srcRenderTarget, params);
api.draw(state, triangle->getRenderPrimitive(), 1);
api.endRenderPass();
// FIXME: on Metal this triangle is not drawn. Can't understand why.
api.beginFrame(0, 0);
api.beginRenderPass(srcRenderTarget, params);
api.bindUniformBuffer(0, ubuffer);
api.draw(state, triangle.getRenderPrimitive(), 1);
api.endRenderPass();
api.endFrame(0);
// Resolve the MSAA render target into the single-sample render target.
api.blit(TargetBufferFlags::COLOR, dstRenderTarget,
{0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTarget,
{0, 0, kSrcTexWidth, kSrcTexHeight}, SamplerMagFilter::LINEAR);
api.blit(TargetBufferFlags::COLOR,
dstRenderTarget, {0, 0, kDstTexWidth, kDstTexHeight},
srcRenderTarget, {0, 0, kSrcTexWidth, kSrcTexHeight},
SamplerMagFilter::NEAREST);
// Grab a screenshot.
ScreenshotParams sparams { kDstTexWidth, kDstTexHeight, "ColorResolve.png" };
api.beginFrame(0, 0);
ScreenshotParams sparams{ kDstTexWidth, kDstTexHeight, "ColorResolve.png" };
dumpScreenshot(api, dstRenderTarget, &sparams);
api.commit(swapChain);
api.endFrame(0);
// Wait for the ReadPixels result to come back.
api.finish();
executeCommands();
getDriver().purge();
flushAndWait();
// Check if the image matches perfectly to our golden run.
const uint32_t expected = 0xebfac2ef;
@@ -640,11 +572,9 @@ TEST_F(BackendTest, ColorResolve) {
api.destroyProgram(program);
api.destroyTexture(srcColorTexture);
api.destroyTexture(dstColorTexture);
api.destroySwapChain(swapChain);
api.destroyRenderTarget(srcRenderTarget);
api.destroyRenderTarget(dstRenderTarget);
delete triangle;
executeCommands();
flushAndWait();
}
TEST_F(BackendTest, DepthResolve) {
@@ -667,8 +597,6 @@ TEST_F(BackendTest, DepthResolve) {
{
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
prog.uniformBlockBindings({{"params", 1}});
program = api.createProgram(std::move(prog));
}
+122 -42
View File
@@ -33,6 +33,8 @@
using namespace image;
#endif
#define DUMP_INTERMEDIATE_SCREENSHOTS 1
////////////////////////////////////////////////////////////////////////////////////////////////////
// Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -42,6 +44,7 @@ layout(location = 0) in vec4 mesh_position;
void main() {
// Hack: move and scale triangle so that it covers entire viewport.
gl_Position = vec4((mesh_position.xy + 0.5) * 5.0, 0.0, 1.0);
// select(vulkan) gl_Position.y = - gl_Position.y;
})";
static std::string fullscreenFs = R"(#version 450 core
@@ -50,7 +53,7 @@ layout(location = 0) out vec4 fragColor;
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
// This parameter is ignored for other backends.
layout(location = 0, set = 1) uniform sampler2D tex;
layout(binding = 0, set = 1) uniform sampler2D test_tex;
uniform Params {
highp float fbWidth;
@@ -60,8 +63,19 @@ uniform Params {
} params;
void main() {
vec2 fbsize = vec2(params.fbWidth, params.fbHeight);
vec2 uv = (gl_FragCoord.xy + 0.5) / fbsize;
fragColor = textureLod(tex, uv, params.sourceLevel);
float offset = 0;
// select(vulkan) offset = -offset;
vec2 uv = (gl_FragCoord.xy + offset) / fbsize;
// select(vulkan) uv.y = 1.0 - uv.y;
vec4 oo = vec4(0, 0, 0, 0);
if (uv.x > .5) {
oo += vec4(0.5, 0.0, 0.0, 1.0);
}
if (uv.y > .5) {
oo += vec4(0.0, 0.5, 0.0, 1.0);
}
// fragColor = oo;
fragColor = textureLod(test_tex, uv, params.sourceLevel);
})";
static uint32_t sPixelHashResult = 0;
@@ -99,22 +113,58 @@ static void uploadUniforms(DriverApi& dapi, Handle<HwBufferObject> ubh, Material
dapi.updateBufferObject(ubh, std::move(bd), 0);
}
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt) {
const size_t size = kTexWidth * kTexHeight * 4;
static void readScreenshot(DriverApi& dapi, Handle<HwRenderTarget> renderTargets[kNumLevels],
uint32_t target, std::function<void(void*)> output) {
Handle<HwRenderTarget> rt = renderTargets[target];
size_t const readWidth = kTexWidth >> target;
size_t const readHeight = kTexHeight >> target;
size_t const size = readWidth * readHeight * 4;
void* buffer = calloc(1, size);
auto cb = [](void* buffer, size_t size, void* user) {
int w = kTexWidth, h = kTexHeight;
const uint32_t* texels = (uint32_t*) buffer;
sPixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
#ifndef IOS
LinearImage image(w, h, 4);
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
std::ofstream pngstrm("feedback.png", std::ios::binary | std::ios::trunc);
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", "feedback.png");
#endif
struct ReadPixelUserData {
std::function<void(void*)> onRead;
};
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
dapi.readPixels(rt, 0, 0, kTexWidth, kTexHeight, std::move(pb));
ReadPixelUserData* userdata = new ReadPixelUserData { output };
auto cb = [](void* buffer, size_t size, void* user) {
ReadPixelUserData* data = (ReadPixelUserData*) user;
data->onRead(buffer);
free(buffer);
delete data;
};
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb,
userdata);
dapi.readPixels(rt, 0, 0, readWidth, readHeight, std::move(pb));
}
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> renderTargets[kNumLevels],
uint32_t target, uint32_t numFrame, bool downscale, test::Backend backend,
std::string fname="") {
Handle<HwRenderTarget> rt = renderTargets[target];
size_t const readWidth = kTexWidth >> target;
size_t const readHeight = kTexHeight >> target;
size_t const size = readWidth * readHeight * 4;
if (fname.empty()) {
std::string backendStr = "gl";
if (backend == test::Backend::METAL) {
backendStr = "mtl";
} else if (backend == test::Backend::VULKAN) {
backendStr = "vk";
}
std::string const down = downscale ? "down" : "up";
fname = "feedback_" + backendStr + "_" + std::to_string(numFrame) + "_" + down + "_" +
std::to_string(target) + ".png";
}
utils::slog.e <<"writing: " << fname << utils::io::endl;
readScreenshot(dapi, renderTargets, target,
[w = readWidth, h = readHeight, size, fname](void* buffer) {
uint32_t const* texels = (uint32_t*) buffer;
sPixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
#ifndef IOS
LinearImage image(w, h, 4);
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
std::ofstream pngstrm(fname.c_str(), std::ios::binary | std::ios::trunc);
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", fname.c_str());
#endif
});
}
// TODO: This test needs work to get Metal and OpenGL to agree on results.
@@ -130,27 +180,31 @@ TEST_F(BackendTest, FeedbackLoops) {
auto swapChain = createSwapChain();
api.makeCurrent(swapChain, swapChain);
bool const backendIsVulkan = sBackend == test::Backend::VULKAN;
// Create a program.
ProgramHandle program;
{
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
.name("backend_test_sib")
SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
ShaderGenerator shaderGen(fullscreenVs, fullscreenFs, sBackend, sIsMobilePlatform, &sib);
ShaderGenerator shaderGen(transformShaderText(fullscreenVs),
transformShaderText(fullscreenFs), sBackend, sIsMobilePlatform, &sib);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
Program::Sampler psamplers[] = {utils::CString("test_tex"), 0};
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers,
sizeof(psamplers) / sizeof(psamplers[0]));
prog.uniformBlockBindings({{"params", 1}});
program = api.createProgram(std::move(prog));
}
TrianglePrimitive triangle(getDriverApi());
TrianglePrimitive const triangle(getDriverApi());
// Create a texture.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
Handle<HwTexture> texture = api.createTexture(
Handle<HwTexture> const texture = api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage);
// Create a RenderTarget for each miplevel.
@@ -166,10 +220,25 @@ TEST_F(BackendTest, FeedbackLoops) {
const size_t size = kTexHeight * kTexWidth * 4;
uint8_t* buffer = (uint8_t*) malloc(size);
for (int r = 0, i = 0; r < kTexHeight; r++) {
int const adjustedR = backendIsVulkan ? r : kTexHeight - r - 1;
for (int c = 0; c < kTexWidth; c++, i += 4) {
auto hval = 0x1f * c / (kTexWidth - 1);
if (hval > 0x05) {
hval = 0x1f;
} else {
hval = 0x00;
}
auto vval = 0x1f * adjustedR / (kTexHeight - 1);
if (vval > 0x05) {
vval = 0x1f;
} else {
vval = 0x00;
}
buffer[i + 0] = 0x10;
buffer[i + 1] = 0x1f * r / (kTexHeight - 1);
buffer[i + 1] = 0x1f * adjustedR / (kTexHeight - 1);
// buffer[i + 1] = vval;
buffer[i + 2] = 0x1f * c / (kTexWidth - 1);
// buffer[i + 2] = hval;
buffer[i + 3] = 0xf0;
}
}
@@ -178,7 +247,6 @@ TEST_F(BackendTest, FeedbackLoops) {
api.update3DImage(texture, 0, 0, 0, 0, kTexWidth, kTexHeight, 1, std::move(pb));
for (int frame = 0; frame < kNumFrames; frame++) {
// Prep for rendering.
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::NONE;
@@ -190,8 +258,9 @@ TEST_F(BackendTest, FeedbackLoops) {
state.program = program;
SamplerGroup samplers(1);
SamplerParams sparams = {};
sparams.filterMag = SamplerMagFilter::LINEAR;
sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
sparams.filterMag = SamplerMagFilter::NEAREST;
// sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
sparams.filterMin = SamplerMinFilter::NEAREST;
samplers.setSampler(0, { texture, sparams });
auto sgroup =
api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test"));
@@ -206,19 +275,24 @@ TEST_F(BackendTest, FeedbackLoops) {
// Downsample passes
params.flags.discardStart = TargetBufferFlags::ALL;
state.rasterState.disableBlending();
for (int targetLevel = 1; targetLevel < kNumLevels; targetLevel++) {
const uint32_t sourceLevel = targetLevel - 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
uploadUniforms(getDriverApi(), ubuffer, {
.fbWidth = float(params.viewport.width),
.fbHeight = float(params.viewport.height),
.sourceLevel = float(sourceLevel),
});
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 1);
api.endRenderPass();
for (int targetLevel = 0; targetLevel < kNumLevels; targetLevel++) {
if (targetLevel > 0) {
const uint32_t sourceLevel = targetLevel - 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
uploadUniforms(getDriverApi(), ubuffer, {
.fbWidth = float(params.viewport.width),
.fbHeight = float(params.viewport.height),
.sourceLevel = float(sourceLevel),
});
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 1);
api.endRenderPass();
}
#if DUMP_INTERMEDIATE_SCREENSHOTS
dumpScreenshot(api, renderTargets, targetLevel, frame, true, sBackend);
#endif
}
// Upsample passes
@@ -238,16 +312,22 @@ TEST_F(BackendTest, FeedbackLoops) {
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 1);
api.endRenderPass();
#if DUMP_INTERMEDIATE_SCREENSHOTS
dumpScreenshot(api, renderTargets, targetLevel, frame, false, sBackend);
#endif
}
getDriverApi().setMinMaxLevels(texture, 0, 0x7f);
getDriverApi().destroySamplerGroup(sgroup);
getDriverApi().destroyBufferObject(ubuffer);
// Read back the render target corresponding to the base level.
//
// NOTE: Calling glReadPixels on any miplevel other than the base level
// seems to be un-reliable on some GPU's.
if (frame == kNumFrames - 1) {
dumpScreenshot(api, renderTargets[0]);
dumpScreenshot(api, renderTargets, 0, frame, false, sBackend, "Feedback.png");
}
api.flush();
+86 -67
View File
@@ -174,6 +174,41 @@ static const char* getSamplerTypeName(TextureFormat textureFormat) {
default:
return "sampler2D";
}
}
static SamplerFormat getSamplerFormat(TextureFormat textureFormat) {
switch (textureFormat) {
case TextureFormat::R8UI:
case TextureFormat::R16UI:
case TextureFormat::RG8UI:
case TextureFormat::RGB8UI:
case TextureFormat::R32UI:
case TextureFormat::RG16UI:
case TextureFormat::RGBA8UI:
case TextureFormat::RGB16UI:
case TextureFormat::RG32UI:
case TextureFormat::RGBA16UI:
case TextureFormat::RGB32UI:
case TextureFormat::RGBA32UI:
return SamplerFormat::UINT;
case TextureFormat::R8I:
case TextureFormat::R16I:
case TextureFormat::RG8I:
case TextureFormat::RGB8I:
case TextureFormat::R32I:
case TextureFormat::RG16I:
case TextureFormat::RGBA8I:
case TextureFormat::RGB16I:
case TextureFormat::RG32I:
case TextureFormat::RGBA16I:
case TextureFormat::RGB32I:
case TextureFormat::RGBA32I:
return SamplerFormat::INT;
default:
return SamplerFormat::FLOAT;
}
}
TEST_F(BackendTest, UpdateImage2D) {
@@ -231,9 +266,10 @@ TEST_F(BackendTest, UpdateImage2D) {
// Test integer format uploads.
// TODO: These cases fail on OpenGL and Vulkan.
testCases.emplace_back("RGB_INTEGER, UBYTE -> RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
testCases.emplace_back("RGB_INTEGER, USHORT -> RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
testCases.emplace_back("RGB_INTEGER, INT -> RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
// TODO: These cases now also fail on Metal, but at some point previously worked.
testCases.emplace_back("RGB_INTEGER, UBYTE -> RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
testCases.emplace_back("RGB_INTEGER, USHORT -> RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
testCases.emplace_back("RGB_INTEGER, INT -> RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
// Test uploads with buffer padding.
// TODO: Vulkan crashes with "Assertion failed: (offset + size <= allocationSize)"
@@ -261,48 +297,45 @@ TEST_F(BackendTest, UpdateImage2D) {
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
// Create a program.
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
.add( {{"tex", SamplerType::SAMPLER_2D, getSamplerFormat(t.textureFormat), Precision::HIGH }} )
.build();
ProgramHandle program;
std::string fragment = stringReplace("{samplerType}",
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(t.textureFormat), fragmentTemplate);
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
program = api.createProgram(std::move(prog));
ProgramHandle const program = api.createProgram(std::move(prog));
// Create a Texture.
auto usage = TextureUsage::SAMPLEABLE;
Handle<HwTexture> texture = api.createTexture(SamplerType::SAMPLER_2D, 1,
auto usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
Handle<HwTexture> const texture = api.createTexture(SamplerType::SAMPLER_2D, 1,
t.textureFormat, 1, 512, 512, 1u, usage);
// Upload some pixel data.
if (t.uploadSubregions) {
const auto& pf = t.pixelFormat;
const auto& pt = t.pixelType;
PixelBufferDescriptor subregion1 = checkerboardPixelBuffer(pf, pt, 256, t.bufferPadding);
PixelBufferDescriptor subregion2 = checkerboardPixelBuffer(pf, pt, 256, t.bufferPadding);
PixelBufferDescriptor subregion3 = checkerboardPixelBuffer(pf, pt, 256, t.bufferPadding);
PixelBufferDescriptor subregion4 = checkerboardPixelBuffer(pf, pt, 256, t.bufferPadding);
api.update3DImage(texture, 0, 0, 0, 0, 256, 256, 1, std::move(subregion1));
api.update3DImage(texture, 0, 256, 0, 0, 256, 256, 1, std::move(subregion2));
api.update3DImage(texture, 0, 0, 256, 0, 256, 256, 1, std::move(subregion3));
api.update3DImage(texture, 0, 256, 256, 0, 256, 256, 1, std::move(subregion4));
api.update3DImage(texture, 0, 0, 0, 0, 256, 256, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding));
api.update3DImage(texture, 0, 256, 0, 0, 256, 256, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding));
api.update3DImage(texture, 0, 0, 256, 0, 256, 256, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding));
api.update3DImage(texture, 0, 256, 256, 0, 256, 256, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding));
} else {
PixelBufferDescriptor descriptor
= checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding);
api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 1, std::move(descriptor));
api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding));
}
SamplerGroup samplers(1);
SamplerParams sparams = {};
sparams.filterMag = SamplerMagFilter::LINEAR;
sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
samplers.setSampler(0, { texture, sparams });
samplers.setSampler(0, { texture, {
.filterMag = SamplerMagFilter::NEAREST,
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST } });
auto sgroup = api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test"));
api.updateSamplerGroup(sgroup, samplers.toBufferDescriptor(api));
@@ -312,7 +345,6 @@ TEST_F(BackendTest, UpdateImage2D) {
readPixelsAndAssertHash(t.name, 512, 512, defaultRenderTarget, expectedHash);
api.flush();
api.commit(swapChain);
api.endFrame(0);
@@ -320,25 +352,21 @@ TEST_F(BackendTest, UpdateImage2D) {
api.destroySwapChain(swapChain);
api.destroyRenderTarget(defaultRenderTarget);
api.destroyTexture(texture);
// This ensures all driver commands have finished before exiting the test.
api.finish();
}
api.finish();
api.stopCapture();
executeCommands();
getDriver().purge();
flushAndWait();
}
TEST_F(BackendTest, UpdateImageSRGB) {
auto& api = getDriverApi();
api.startCapture();
PixelDataFormat pixelFormat = PixelDataFormat::RGB;
PixelDataType pixelType = PixelDataType::UBYTE;
TextureFormat textureFormat = TextureFormat::SRGB8_A8;
PixelDataFormat const pixelFormat = PixelDataFormat::RGBA;
PixelDataType const pixelType = PixelDataType::UBYTE;
TextureFormat const textureFormat = TextureFormat::SRGB8_A8;
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
@@ -346,22 +374,22 @@ TEST_F(BackendTest, UpdateImageSRGB) {
auto defaultRenderTarget = api.createDefaultRenderTarget(0);
// Create a program.
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
SamplerInterfaceBlock const sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
std::string fragment = stringReplace("{samplerType}",
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(textureFormat), fragmentTemplate);
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
ProgramHandle program = api.createProgram(std::move(prog));
ProgramHandle const program = api.createProgram(std::move(prog));
// Create a texture.
Handle<HwTexture> texture = api.createTexture(SamplerType::SAMPLER_2D, 1,
textureFormat, 1, 512, 512, 1, TextureUsage::SAMPLEABLE);
Handle<HwTexture> const texture = api.createTexture(SamplerType::SAMPLER_2D, 1,
textureFormat, 1, 512, 512, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE);
// Create image data.
size_t components; int bpp;
@@ -401,7 +429,7 @@ TEST_F(BackendTest, UpdateImageSRGB) {
renderTriangle(defaultRenderTarget, swapChain, program);
static const uint32_t expectedHash = 519370995;
static const uint32_t expectedHash = 359858623;
readPixelsAndAssertHash("UpdateImageSRGB", 512, 512, defaultRenderTarget, expectedHash);
api.flush();
@@ -415,12 +443,9 @@ TEST_F(BackendTest, UpdateImageSRGB) {
// This ensures all driver commands have finished before exiting the test.
api.finish();
api.stopCapture();
executeCommands();
getDriver().purge();
flushAndWait();
}
TEST_F(BackendTest, UpdateImageMipLevel) {
@@ -440,26 +465,26 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_3D, SamplerFormat::FLOAT, Precision::HIGH }} )
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
std::string fragment = stringReplace("{samplerType}",
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(textureFormat), fragmentUpdateImageMip);
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
ProgramHandle program = api.createProgram(std::move(prog));
ProgramHandle const program = api.createProgram(std::move(prog));
// Create a texture with 3 mip levels.
// Base level: 1024
// Level 1: 512 <-- upload data and sample from this level
// Level 2: 256
Handle<HwTexture> texture = api.createTexture(SamplerType::SAMPLER_2D, 3,
textureFormat, 1, 1024, 1024, 1, TextureUsage::SAMPLEABLE);
textureFormat, 1, 1024, 1024, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE);
// Create image data.
PixelBufferDescriptor descriptor = checkerboardPixelBuffer(pixelFormat, pixelType, 512);
api.update3DImage(texture, 1, 0, 0, 0, 512, 512, 1, std::move(descriptor));
api.update3DImage(texture, /* level*/ 1, 0, 0, 0, 512, 512, 1, std::move(descriptor));
api.beginFrame(0, 0);
@@ -490,12 +515,9 @@ TEST_F(BackendTest, UpdateImageMipLevel) {
// This ensures all driver commands have finished before exiting the test.
api.finish();
api.stopCapture();
executeCommands();
getDriver().purge();
flushAndWait();
}
TEST_F(BackendTest, UpdateImage3D) {
@@ -506,7 +528,7 @@ TEST_F(BackendTest, UpdateImage3D) {
PixelDataType pixelType = PixelDataType::FLOAT;
TextureFormat textureFormat = TextureFormat::RGBA16F;
SamplerType samplerType = SamplerType::SAMPLER_2D_ARRAY;
TextureUsage usage = TextureUsage::SAMPLEABLE;
TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
// Create a platform-specific SwapChain and make it current.
auto swapChain = createSwapChain();
@@ -517,15 +539,15 @@ TEST_F(BackendTest, UpdateImage3D) {
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_3D, SamplerFormat::FLOAT, Precision::HIGH }} )
.add( {{"tex", SamplerType::SAMPLER_2D_ARRAY, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
std::string fragment = stringReplace("{samplerType}",
getSamplerTypeName(samplerType), fragmentUpdateImage3DTemplate);
ShaderGenerator shaderGen(vertex, fragment, sBackend, sIsMobilePlatform, &sib);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("tex"), 0 };
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
ProgramHandle program = api.createProgram(std::move(prog));
ProgramHandle const program = api.createProgram(std::move(prog));
// Create a texture.
Handle<HwTexture> texture = api.createTexture(samplerType, 1,
@@ -577,12 +599,9 @@ TEST_F(BackendTest, UpdateImage3D) {
// This ensures all driver commands have finished before exiting the test.
api.finish();
api.stopCapture();
executeCommands();
getDriver().purge();
flushAndWait();
}
} // namespace test
+7 -11
View File
@@ -23,6 +23,7 @@
#include <utils/Hash.h>
#include <fstream>
#include <algorithm>
using namespace filament;
using namespace filament::backend;
@@ -107,7 +108,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
// The size of the actual render target, taking mip level into account;
size_t getRenderTargetSize () const {
return renderTargetBaseSize >> mipLevel;
return std::max(size_t(1), renderTargetBaseSize >> mipLevel);
}
// The rect that read pixels will read from.
@@ -169,7 +170,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
// The normative read pixels test case. Render a white triangle over a blue background and read
// the full viewport into a pixel buffer.
TestCase t;
TestCase const t0;
// Check that a subregion of the render target can be read into a pixel buffer.
TestCase t2;
@@ -233,7 +234,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
t8.testName = "readPixels_swapchain";
t8.useDefaultRT = true;
TestCase testCases[] = { t, t2, t3, t4, t5, t6, t7, t8 };
TestCase const testCases[] = { t0, t2, t3, t4, t5, t6, t7, t8 };
// Create programs.
Handle<HwProgram> programFloat, programUint;
@@ -264,7 +265,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
// Create a Texture and RenderTarget to render into.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
Handle<HwTexture> texture = getDriverApi().createTexture(SamplerType::SAMPLER_2D,
Handle<HwTexture> const texture = getDriverApi().createTexture(SamplerType::SAMPLER_2D,
t.mipLevels, t.textureFormat, 1, renderTargetBaseSize, renderTargetBaseSize, 1,
usage);
@@ -282,7 +283,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
{});
}
TrianglePrimitive triangle(getDriverApi());
TrianglePrimitive const triangle(getDriverApi());
RenderPassParams params = {};
fullViewport(params);
@@ -356,7 +357,6 @@ TEST_F(ReadPixelsTest, ReadPixels) {
getDriverApi().beginRenderPass(renderTarget, params);
getDriverApi().endRenderPass();
getDriverApi().flush();
getDriverApi().commit(swapChain);
getDriverApi().endFrame(0);
@@ -369,11 +369,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
getDriverApi().destroyProgram(programUint);
// This ensures all driver commands have finished before exiting the test.
getDriverApi().finish();
executeCommands();
getDriver().purge();
flushAndWait();
}
TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
@@ -45,10 +45,10 @@ std::string fragment (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
layout(location = 0) in vec2 uv;
layout(location = 0, set = 1) uniform sampler2D tex;
layout(location = 0, set = 1) uniform sampler2D test_tex;
void main() {
fragColor = texture(tex, uv);
fragColor = texture(test_tex, uv);
}
)");
@@ -66,7 +66,7 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
auto swapChain = createSwapChain();
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
.name("backend_test_sib")
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
@@ -74,7 +74,7 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
// Create a program that samples a texture.
Program p = shaderGen.getProgram(getDriverApi());
Program::Sampler sampler { utils::CString("tex"), 0 };
Program::Sampler sampler { utils::CString("test_tex"), 0 };
p.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, &sampler, 1);
backend::Handle<HwProgram> program = getDriverApi().createProgram(std::move(p));
@@ -146,7 +146,7 @@ TEST_F(BackendTest, RenderExternalImage) {
auto swapChain = createSwapChain();
SamplerInterfaceBlock sib = filament::SamplerInterfaceBlock::Builder()
.name("backend_test_sib")
.name("Test")
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
@@ -154,7 +154,7 @@ TEST_F(BackendTest, RenderExternalImage) {
// Create a program that samples a texture.
Program p = shaderGen.getProgram(getDriverApi());
Program::Sampler sampler { utils::CString("tex"), 0 };
Program::Sampler sampler { utils::CString("test_tex"), 0 };
p.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, &sampler, 1);
auto program = getDriverApi().createProgram(std::move(p));
+10 -7
View File
@@ -286,17 +286,20 @@ public:
/** Sets a custom projection matrix for each eye.
*
* The projectionForCulling, near, and far parameters establish a "culling frustum" which must
* encompass anything either eye can see.
* encompass anything any eye can see. All projection matrices must be set simultaneously. The
* number of stereoscopic eyes is controlled by the stereoscopicEyeCount setting inside of
* Engine::Config.
*
* @param projection an array of projection matrices, only the first
* CONFIG_STEREOSCOPIC_EYES (2) are read
* @param projection an array of projection matrices, only the first config.stereoscopicEyeCount
* are read
* @param count size of the projection matrix array to set, must be
* >= CONFIG_STEREOSCOPIC_EYES (2)
* >= config.stereoscopicEyeCount
* @param projectionForCulling custom projection matrix for culling, must encompass both eyes
* @param near distance in world units from the camera to the culling near plane. \p near > 0.
* @param far distance in world units from the camera to the culling far plane. \p far > \p
* near.
* @see setCustomProjection
* @see Engine::Config::stereoscopicEyeCount
*/
void setCustomEyeProjection(math::mat4 const* projection, size_t count,
math::mat4 const& projectionForCulling, double near, double far);
@@ -357,8 +360,8 @@ public:
* The projection matrix used for rendering always has its far plane set to infinity. This
* is why it may differ from the matrix set through setProjection() or setLensProjection().
*
* @param eyeId the index of the eye to return the projection matrix for, must be <
* CONFIG_STEREOSCOPIC_EYES (2)
* @param eyeId the index of the eye to return the projection matrix for, must be
* < config.stereoscopicEyeCount
* @return The projection matrix used for rendering
*
* @see setProjection, setLensProjection, setCustomProjection, getCullingProjectionMatrix,
@@ -416,7 +419,7 @@ public:
* This method is not intended to be called every frame. Instead, to update the position of the
* head, use Camera::setModelMatrix.
*
* @param eyeId the index of the eye to set, must be < CONFIG_STEREOSCOPIC_EYES (2)
* @param eyeId the index of the eye to set, must be < config.stereoscopicEyeCount
* @param model the model matrix for an individual eye
*/
void setEyeModelMatrix(uint8_t eyeId, math::mat4 const& model);
+32
View File
@@ -291,6 +291,15 @@ public:
* Currently only respected by the Metal backend.
*/
size_t textureUseAfterFreePoolSize = 0;
/*
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
* between 1 and Engine::getMaxStereoscopicEyes() (inclusive).
*
* @see View::setStereoscopicOptions
* @see Engine::getMaxStereoscopicEyes
*/
uint8_t stereoscopicEyeCount = 2;
};
@@ -554,6 +563,29 @@ public:
*/
bool isStereoSupported() const noexcept;
/**
* Retrieves the configuration settings of this Engine.
*
* This method returns the configuration object that was supplied to the Engine's
* Builder::config method during the creation of this Engine. If the Builder::config method was
* not explicitly called (or called with nullptr), this method returns the default configuration
* settings.
*
* @return a Config object with this Engine's configuration
* @see Builder::config
*/
const Config& getConfig() const noexcept;
/**
* Returns the maximum number of stereoscopic eyes supported by Filament. The actual number of
* eyes rendered is set at Engine creation time with the Engine::Config::stereoscopicEyeCount
* setting.
*
* @return the max number of stereoscopic eyes supported
* @see Engine::Config::stereoscopicEyeCount
*/
static size_t getMaxStereoscopicEyes() noexcept;
/**
* @return EntityManager used by filament
*/
+15 -14
View File
@@ -691,11 +691,12 @@ public:
* - punctual lights
*
* Stereo rendering depends on device and platform support. To check if stereo rendering is
* supported, use Engine::isStereoSupported().
* supported, use Engine::isStereoSupported(). If stereo rendering is not supported, then the
* stereoscopic options have no effect.
*
* @param options The stereoscopic options to use on this view
*/
void setStereoscopicOptions(StereoscopicOptions const& options);
void setStereoscopicOptions(StereoscopicOptions const& options) noexcept;
/**
* Returns the stereoscopic options associated with this View.
@@ -763,10 +764,10 @@ public:
* @param handler Handler to dispatch the callback or nullptr for the default handler.
*/
template<typename T, void(T::*method)(PickingQueryResult const&)>
void pick(uint32_t x, uint32_t y, T* instance, backend::CallbackHandler* handler = nullptr) noexcept {
void pick(uint32_t x, uint32_t y, T* instance,
backend::CallbackHandler* handler = nullptr) noexcept {
PickingQuery& query = pick(x, y, [](PickingQueryResult const& result, PickingQuery* pq) {
void* user = pq->storage;
(*static_cast<T**>(user)->*method)(result);
(static_cast<T*>(pq->storage[0])->*method)(result);
}, handler);
query.storage[0] = instance;
}
@@ -783,11 +784,11 @@ public:
* @param handler Handler to dispatch the callback or nullptr for the default handler.
*/
template<typename T, void(T::*method)(PickingQueryResult const&)>
void pick(uint32_t x, uint32_t y, T instance, backend::CallbackHandler* handler = nullptr) noexcept {
void pick(uint32_t x, uint32_t y, T instance,
backend::CallbackHandler* handler = nullptr) noexcept {
static_assert(sizeof(instance) <= sizeof(PickingQuery::storage), "user data too large");
PickingQuery& query = pick(x, y, [](PickingQueryResult const& result, PickingQuery* pq) {
void* user = pq->storage;
T* that = static_cast<T*>(user);
T* const that = static_cast<T*>(reinterpret_cast<void*>(pq->storage));
(that->*method)(result);
that->~T();
}, handler);
@@ -804,15 +805,15 @@ public:
* @param handler Handler to dispatch the callback or nullptr for the default handler.
*/
template<typename T>
void pick(uint32_t x, uint32_t y, T functor, backend::CallbackHandler* handler = nullptr) noexcept {
void pick(uint32_t x, uint32_t y, T functor,
backend::CallbackHandler* handler = nullptr) noexcept {
static_assert(sizeof(functor) <= sizeof(PickingQuery::storage), "functor too large");
PickingQuery& query = pick(x, y, handler,
(PickingQueryResultCallback)[](PickingQueryResult const& result, PickingQuery* pq) {
void* user = pq->storage;
T& that = *static_cast<T*>(user);
that(result);
that.~T();
});
T* const that = static_cast<T*>(reinterpret_cast<void*>(pq->storage));
that->operator()(result);
that->~T();
});
new(query.storage) T(std::move(functor));
}
+8
View File
@@ -327,10 +327,18 @@ size_t Engine::getMaxAutomaticInstances() const noexcept {
return downcast(this)->getMaxAutomaticInstances();
}
const Engine::Config& Engine::getConfig() const noexcept {
return downcast(this)->getConfig();
}
bool Engine::isStereoSupported() const noexcept {
return downcast(this)->isStereoSupported();
}
size_t Engine::getMaxStereoscopicEyes() noexcept {
return FEngine::getMaxStereoscopicEyes();
}
#if defined(__EMSCRIPTEN__)
void Engine::resetBackendState() noexcept {
downcast(this)->resetBackendState();
+2 -1
View File
@@ -82,7 +82,8 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
mat4f const& headFromWorld = camera.view;
for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) {
Engine::Config const& config = engine.getConfig();
for (int i = 0; i < config.stereoscopicEyeCount; i++) {
mat4f const& eyeFromHead = camera.eyeFromView[i]; // identity for monoscopic rendering
mat4f const& clipFromEye = camera.eyeProjection[i];
// clipFromEye * eyeFromHead * headFromWorld
+2 -1
View File
@@ -2714,7 +2714,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
backend::SamplerMagFilter filter) noexcept {
if (UTILS_LIKELY(!translucent && dsrOptions.quality == QualityLevel::LOW)) {
if (UTILS_LIKELY(!translucent && dsrOptions.quality == QualityLevel::LOW &&
mEngine.getDriverApi().getFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1)) {
return opaqueBlit(fg, input, vp, outDesc, filter);
}
+14 -9
View File
@@ -116,14 +116,17 @@ void RenderPass::appendCommands(FEngine& engine, CommandTypeFlags const commandT
commandCount += 1; // for the sentinel
Command* const curr = append(commandCount);
auto stereoscopicEyeCount =
renderFlags & IS_STEREOSCOPIC ? engine.getConfig().stereoscopicEyeCount : 1;
const float3 cameraPosition(mCameraPosition);
const float3 cameraForwardVector(mCameraForwardVector);
auto work = [commandTypeFlags, curr, &soa, variant, renderFlags, visibilityMask, cameraPosition,
cameraForwardVector]
cameraForwardVector, stereoscopicEyeCount]
(uint32_t startIndex, uint32_t indexCount) {
RenderPass::generateCommands(commandTypeFlags, curr,
soa, { startIndex, startIndex + indexCount }, variant, renderFlags, visibilityMask,
cameraPosition, cameraForwardVector);
cameraPosition, cameraForwardVector, stereoscopicEyeCount);
};
if (vr.size() <= JOBS_PARALLEL_FOR_COMMANDS_COUNT) {
@@ -374,8 +377,8 @@ UTILS_NOINLINE
void RenderPass::generateCommands(uint32_t commandTypeFlags, Command* const commands,
FScene::RenderableSoa const& soa, Range<uint32_t> range,
Variant variant, RenderFlags renderFlags,
FScene::VisibleMaskType visibilityMask,
float3 cameraPosition, float3 cameraForward) noexcept {
FScene::VisibleMaskType visibilityMask, float3 cameraPosition, float3 cameraForward,
uint8_t instancedStereoEyeCount) noexcept {
SYSTRACE_CALL();
@@ -406,11 +409,13 @@ void RenderPass::generateCommands(uint32_t commandTypeFlags, Command* const comm
switch (commandTypeFlags & (CommandTypeFlags::COLOR | CommandTypeFlags::DEPTH)) {
case CommandTypeFlags::COLOR:
curr = generateCommandsImpl<CommandTypeFlags::COLOR>(commandTypeFlags, curr,
soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward);
soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward,
instancedStereoEyeCount);
break;
case CommandTypeFlags::DEPTH:
curr = generateCommandsImpl<CommandTypeFlags::DEPTH>(commandTypeFlags, curr,
soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward);
soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward,
instancedStereoEyeCount);
break;
default:
// we should never end-up here
@@ -433,7 +438,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags,
Command* UTILS_RESTRICT curr,
FScene::RenderableSoa const& UTILS_RESTRICT soa, Range<uint32_t> range,
Variant const variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask,
float3 cameraPosition, float3 cameraForward) noexcept {
float3 cameraPosition, float3 cameraForward, uint8_t instancedStereoEyeCount) noexcept {
// generateCommands() writes both the draw and depth commands simultaneously such that
// we go throw the list of renderables just once.
@@ -529,7 +534,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags,
// eye count.
if (UTILS_UNLIKELY(hasInstancedStereo)) {
cmdColor.primitive.instanceCount =
(soaInstanceInfo[i].count * CONFIG_STEREOSCOPIC_EYES) |
(soaInstanceInfo[i].count * instancedStereoEyeCount) |
PrimitiveInfo::USER_INSTANCE_MASK;
}
@@ -555,7 +560,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags,
if (UTILS_UNLIKELY(hasInstancedStereo)) {
cmdColor.primitive.instanceCount =
(soaInstanceInfo[i].count * CONFIG_STEREOSCOPIC_EYES) |
(soaInstanceInfo[i].count * instancedStereoEyeCount) |
PrimitiveInfo::USER_INSTANCE_MASK;
}
}
+4 -2
View File
@@ -408,13 +408,15 @@ private:
FScene::RenderableSoa const& soa, utils::Range<uint32_t> range,
Variant variant, RenderFlags renderFlags,
FScene::VisibleMaskType visibilityMask,
math::float3 cameraPosition, math::float3 cameraForward) noexcept;
math::float3 cameraPosition, math::float3 cameraForward,
uint8_t instancedStereoEyeCount) noexcept;
template<uint32_t commandTypeFlags>
static inline Command* generateCommandsImpl(uint32_t extraFlags, Command* curr,
FScene::RenderableSoa const& soa, utils::Range<uint32_t> range,
Variant variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask,
math::float3 cameraPosition, math::float3 cameraForward) noexcept;
math::float3 cameraPosition, math::float3 cameraForward,
uint8_t instancedStereoEyeCount) noexcept;
static void setupColorCommand(Command& cmdDraw, Variant variant,
FMaterialInstance const* mi, bool inverseFrontFaces) noexcept;
+4 -1
View File
@@ -108,11 +108,13 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
ShadowMapInfo const& shadowMapInfo,
SceneInfo const& sceneInfo) noexcept {
// reset the visible shadow status
mHasVisibleShadows = false;
FLightManager const& lcm = engine.getLightManager();
FLightManager::Instance const li = lightData.elementAt<FScene::LIGHT_INSTANCE>(index);
FLightManager::ShadowParams const params = lcm.getShadowParams(li);
// We can't use LISPSM in stable mode
const auto direction = lightData.elementAt<FScene::SHADOW_DIRECTION>(index);
auto [Mv, znear, zfar, lsClippedShadowVolume, vertexCount, visibleShadows] =
@@ -137,6 +139,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
*/
mat4f W, Wp, L;
// We can't use LISPSM in stable mode
const bool useLispsm = params.options.lispsm && !params.options.stable;
if (useLispsm) {
// Orient the shadow map in the direction of the view vector by constructing a
+1 -1
View File
@@ -522,7 +522,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
// entire camera frustum, as if we only had a single cascade.
ShadowMap& shadowMap = cascadedShadowMaps[0];
const auto direction = options.transform * lightData.elementAt<FScene::DIRECTION>(0);
const auto direction = lightData.elementAt<FScene::SHADOW_DIRECTION>(0);
// We compute the directional light's model matrix using the origin's as the light position.
// The choice of the light's origin initially doesn't matter for a directional light.
+1 -1
View File
@@ -283,7 +283,7 @@ bool View::isStencilBufferEnabled() const noexcept {
return downcast(this)->isStencilBufferEnabled();
}
void View::setStereoscopicOptions(const StereoscopicOptions& options) {
void View::setStereoscopicOptions(const StereoscopicOptions& options) noexcept {
return downcast(this)->setStereoscopicOptions(options);
}
+23 -9
View File
@@ -96,11 +96,11 @@ void UTILS_NOINLINE FCamera::setCustomProjection(mat4 const& p,
void UTILS_NOINLINE FCamera::setCustomEyeProjection(math::mat4 const* projection, size_t count,
math::mat4 const& projectionForCulling, double near, double far) {
ASSERT_PRECONDITION(count >= CONFIG_STEREOSCOPIC_EYES,
const Engine::Config& config = mEngine.getConfig();
ASSERT_PRECONDITION(count >= config.stereoscopicEyeCount,
"All eye projections must be supplied together, count must be >= "
"CONFIG_STEREOSCOPIC_EYES(%d)",
CONFIG_STEREOSCOPIC_EYES);
for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) {
"config.stereoscopicEyeCount (%d)", config.stereoscopicEyeCount);
for (int i = 0; i < config.stereoscopicEyeCount; i++) {
mEyeProjection[i] = projection[i];
}
mProjectionForCulling = projectionForCulling;
@@ -165,7 +165,8 @@ void UTILS_NOINLINE FCamera::setProjection(Camera::Projection projection,
}
math::mat4 FCamera::getProjectionMatrix(uint8_t eye) const noexcept {
assert_invariant(eye < CONFIG_STEREOSCOPIC_EYES);
UTILS_UNUSED_IN_RELEASE const Engine::Config& config = mEngine.getConfig();
assert_invariant(eye < config.stereoscopicEyeCount);
// This is where we transform the user clip-space (GL convention) to our virtual clip-space
// (inverted DX convention)
// Note that this math ends up setting the projection matrix' p33 to 0, which is where we're
@@ -190,6 +191,13 @@ math::mat4 FCamera::getCullingProjectionMatrix() const noexcept {
return m * mProjectionForCulling;
}
const math::mat4& FCamera::getUserProjectionMatrix(uint8_t eyeId) const {
const Engine::Config& config = mEngine.getConfig();
ASSERT_PRECONDITION(eyeId < config.stereoscopicEyeCount,
"eyeId must be < config.stereoscopicEyeCount (%d)", config.stereoscopicEyeCount);
return mEyeProjection[eyeId];
}
void UTILS_NOINLINE FCamera::setModelMatrix(const mat4f& modelMatrix) noexcept {
FTransformManager& transformManager = mEngine.getTransformManager();
transformManager.setTransform(transformManager.getInstance(mEntity), modelMatrix);
@@ -201,8 +209,9 @@ void UTILS_NOINLINE FCamera::setModelMatrix(const mat4& modelMatrix) noexcept {
}
void UTILS_NOINLINE FCamera::setEyeModelMatrix(uint8_t eyeId, math::mat4 const& model) {
ASSERT_PRECONDITION(eyeId < CONFIG_STEREOSCOPIC_EYES,
"eyeId must be < CONFIG_STEREOSCOPIC_EYES(%d)", CONFIG_STEREOSCOPIC_EYES);
const Engine::Config& config = mEngine.getConfig();
ASSERT_PRECONDITION(eyeId < config.stereoscopicEyeCount,
"eyeId must be < config.stereoscopicEyeCount (%d)", config.stereoscopicEyeCount);
mEyeFromView[eyeId] = inverse(model);
}
@@ -249,10 +258,15 @@ double FCamera::computeEffectiveFov(double fovInDegrees, double focusDistance) n
return fov * math::d::RAD_TO_DEG;
}
uint8_t FCamera::getStereoscopicEyeCount() const noexcept {
const Engine::Config& config = mEngine.getConfig();
return config.stereoscopicEyeCount;
}
// ------------------------------------------------------------------------------------------------
CameraInfo::CameraInfo(FCamera const& camera) noexcept {
for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) {
for (int i = 0; i < camera.getStereoscopicEyeCount(); i++) {
eyeProjection[i] = mat4f{ camera.getProjectionMatrix(i) };
}
cullingProjection = mat4f{ camera.getCullingProjectionMatrix() };
@@ -268,7 +282,7 @@ CameraInfo::CameraInfo(FCamera const& camera) noexcept {
CameraInfo::CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform) noexcept {
const mat4 modelMatrix{ inWorldTransform * camera.getModelMatrix() };
for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) {
for (int i = 0; i < camera.getStereoscopicEyeCount(); i++) {
eyeProjection[i] = mat4f{ camera.getProjectionMatrix(i) };
eyeFromView[i] = mat4f{ camera.getEyeFromViewMatrix(i) };
}
+14 -16
View File
@@ -85,11 +85,7 @@ public:
math::mat4 getEyeFromViewMatrix(uint8_t eye) const noexcept { return mEyeFromView[eye]; }
// viewing projection matrix set by the user
const math::mat4& getUserProjectionMatrix(uint8_t eyeId) const {
ASSERT_PRECONDITION(eyeId < CONFIG_STEREOSCOPIC_EYES,
"eyeId must be < CONFIG_STEREOSCOPIC_EYES(%d)", CONFIG_STEREOSCOPIC_EYES);
return mEyeProjection[eyeId];
}
const math::mat4& getUserProjectionMatrix(uint8_t eyeId) const;
// culling projection matrix set by the user
math::mat4 getUserCullingProjectionMatrix() const noexcept { return mProjectionForCulling; }
@@ -189,6 +185,8 @@ public:
static double computeEffectiveFov(double fovInDegrees, double focusDistance) noexcept;
uint8_t getStereoscopicEyeCount() const noexcept;
utils::Entity getEntity() const noexcept {
return mEntity;
}
@@ -204,10 +202,10 @@ private:
utils::Entity mEntity;
// For monoscopic cameras, mEyeProjection[0] == mEyeProjection[1].
math::mat4 mEyeProjection[CONFIG_STEREOSCOPIC_EYES]; // projection matrix per eye (infinite far)
math::mat4 mProjectionForCulling; // projection matrix (with far plane)
math::mat4 mEyeFromView[CONFIG_STEREOSCOPIC_EYES]; // transforms from the main view (head)
// space to each eye's unique view space
math::mat4 mEyeProjection[CONFIG_MAX_STEREOSCOPIC_EYES]; // projection matrix per eye (infinite far)
math::mat4 mProjectionForCulling; // projection matrix (with far plane)
math::mat4 mEyeFromView[CONFIG_MAX_STEREOSCOPIC_EYES]; // transforms from the main view (head)
// space to each eye's unique view space
math::double2 mScalingCS = {1.0}; // additional scaling applied to projection
math::double2 mShiftCS = {0.0}; // additional translation applied to projection
@@ -232,15 +230,15 @@ struct CameraInfo {
math::mat4f projection;
// for stereo rendering, one matrix per eye
math::mat4f eyeProjection[CONFIG_STEREOSCOPIC_EYES] = {};
math::mat4f eyeProjection[CONFIG_MAX_STEREOSCOPIC_EYES] = {};
};
math::mat4f cullingProjection; // projection matrix for culling
math::mat4f model; // camera model matrix
math::mat4f view; // camera view matrix (inverse(model))
math::mat4f eyeFromView[CONFIG_STEREOSCOPIC_EYES]; // eye view matrix (only for stereoscopic)
math::mat4 worldTransform; // world transform (already applied
// to model and view)
math::mat4f cullingProjection; // projection matrix for culling
math::mat4f model; // camera model matrix
math::mat4f view; // camera view matrix (inverse(model))
math::mat4f eyeFromView[CONFIG_MAX_STEREOSCOPIC_EYES]; // eye view matrix (only for stereoscopic)
math::mat4 worldTransform; // world transform (already applied
// to model and view)
math::float4 clipTransform{1, 1, 0, 0}; // clip-space transform, only for VERTEX_DOMAIN_DEVICE
float zn{}; // distance (positive) to the near plane
float zf{}; // distance (positive) to the far plane
+11 -1
View File
@@ -244,6 +244,10 @@ void FEngine::init() {
mActiveFeatureLevel = std::min(mActiveFeatureLevel, driverApi.getFeatureLevel());
#ifndef FILAMENT_ENABLE_FEATURE_LEVEL_0
assert_invariant(mActiveFeatureLevel > FeatureLevel::FEATURE_LEVEL_0);
#endif
slog.i << "Backend feature level: " << int(driverApi.getFeatureLevel()) << io::endl;
slog.i << "FEngine feature level: " << int(mActiveFeatureLevel) << io::endl;
@@ -328,12 +332,15 @@ void FEngine::init() {
driverApi.update3DImage(mDummyZeroTexture, 0, 0, 0, 0, 1, 1, 1,
{ zeroes, 4, Texture::Format::RGBA, Texture::Type::UBYTE });
#ifdef FILAMENT_ENABLE_FEATURE_LEVEL_0
if (UTILS_UNLIKELY(mActiveFeatureLevel == FeatureLevel::FEATURE_LEVEL_0)) {
FMaterial::DefaultMaterialBuilder defaultMaterialBuilder;
defaultMaterialBuilder.package(
MATERIALS_DEFAULTMATERIAL0_DATA, MATERIALS_DEFAULTMATERIAL0_SIZE);
mDefaultMaterial = downcast(defaultMaterialBuilder.build(*const_cast<FEngine*>(this)));
} else {
} else
#endif
{
mDefaultColorGrading = downcast(ColorGrading::Builder().build(*this));
FMaterial::DefaultMaterialBuilder defaultMaterialBuilder;
@@ -1266,6 +1273,9 @@ Engine::Config Engine::BuilderDetails::validateConfig(const Config* const pConfi
// This value gets validated during driver creation, so pass it through
config.driverHandleArenaSizeMB = config.driverHandleArenaSizeMB;
config.stereoscopicEyeCount =
std::clamp(config.stereoscopicEyeCount, uint8_t(1), CONFIG_MAX_STEREOSCOPIC_EYES);
return config;
}
+4
View File
@@ -184,6 +184,10 @@ public:
bool isStereoSupported() const noexcept { return getDriver().isStereoSupported(); }
static size_t getMaxStereoscopicEyes() noexcept {
return CONFIG_MAX_STEREOSCOPIC_EYES;
}
PostProcessManager const& getPostProcessManager() const noexcept {
return mPostProcessManager;
}
+3
View File
@@ -260,6 +260,9 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS,
(bool)powerVrShaderWorkarounds });
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT,
(int)engine.getConfig().stereoscopicEyeCount });
if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) {
// The actual value of this spec-constant is set in the OpenGLDriver backend.
mSpecializationConstants.push_back({
+6 -8
View File
@@ -267,16 +267,16 @@ void FScene::prepare(utils::JobSystem& js,
// in the code below, we only transform directions, so the translation of the
// world transform is irrelevant, and we don't need to use getWorldTransformAccurate()
mat3 const worldDirectionTransform =
mat3::getTransformForNormals(tcm.getWorldTransformAccurate(ti).upperLeft());
FLightManager::ShadowParams const params = lcm.getShadowParams(li);
float3 const localDirection = lcm.getLocalDirection(li);
float3 const shadowLocalDirection = params.options.transform * localDirection;
mat3 const worldDirectionTransform = tcm.getWorldTransformAccurate(ti).upperLeft();
mat3 const shaderWorldTransform = worldTransform.upperLeft() * worldDirectionTransform;
float3 const localDirection = worldDirectionTransform * lcm.getLocalDirection(li);
double3 const shadowLocalDirection = params.options.transform * localDirection;
// using mat3::getTransformForNormals handles non-uniform scaling
// note: in the common case of the rigid-body transform, getTransformForNormals() returns
// identity.
mat3 const worlTransformNormals = mat3::getTransformForNormals(shaderWorldTransform);
mat3 const worlTransformNormals = mat3::getTransformForNormals(worldTransform.upperLeft());
double3 const d = worlTransformNormals * localDirection;
double3 const s = worlTransformNormals * shadowLocalDirection;
@@ -290,10 +290,8 @@ void FScene::prepare(utils::JobSystem& js,
// is pointing down, which is a common case for lights. See ShadowMap.cpp.
return transpose(mat3::lookTo(direction, double3{ 1, 0, 0 }));
};
double3 const worldDirection =
mat3::getTransformForNormals(worldDirectionTransform) * shadowLocalDirection;
double3 const worldOrigin = transpose(worldTransform.upperLeft()) * worldTransform[3].xyz;
mat3 const Mv = getMv(worldDirection);
mat3 const Mv = getMv(shadowLocalDirection);
double2 const lsReferencePoint = (Mv * worldOrigin).xy;
constexpr float inf = std::numeric_limits<float>::infinity();
+4 -1
View File
@@ -118,9 +118,12 @@ FSkybox::FSkybox(FEngine& engine, const Builder& builder) noexcept
FMaterial const* FSkybox::createMaterial(FEngine& engine) {
Material::Builder builder;
#ifdef FILAMENT_ENABLE_FEATURE_LEVEL_0
if (UTILS_UNLIKELY(engine.getActiveFeatureLevel() == Engine::FeatureLevel::FEATURE_LEVEL_0)) {
builder.package(MATERIALS_SKYBOX0_DATA, MATERIALS_SKYBOX0_SIZE);
} else {
} else
#endif
{
builder.package(MATERIALS_SKYBOX_DATA, MATERIALS_SKYBOX_SIZE);
}
auto material = builder.build(engine);
+1 -3
View File
@@ -1126,9 +1126,7 @@ View::PickingQuery& FView::pick(uint32_t x, uint32_t y, backend::CallbackHandler
return *pQuery;
}
void FView::setStereoscopicOptions(const StereoscopicOptions& options) {
ASSERT_PRECONDITION(!options.enabled || mIsStereoSupported,
"Stereo rendering is not supported.");
void FView::setStereoscopicOptions(const StereoscopicOptions& options) noexcept {
mStereoscopicOptions = options;
}
+4 -2
View File
@@ -168,7 +168,9 @@ public:
bool hasDPCF() const noexcept { return mShadowType == ShadowType::DPCF; }
bool hasPCSS() const noexcept { return mShadowType == ShadowType::PCSS; }
bool hasPicking() const noexcept { return mActivePickingQueriesList != nullptr; }
bool hasInstancedStereo() const noexcept { return mStereoscopicOptions.enabled; }
bool hasInstancedStereo() const noexcept {
return mIsStereoSupported && mStereoscopicOptions.enabled;
}
FrameGraphId<FrameGraphTexture> renderShadowMaps(FEngine& engine, FrameGraph& fg,
CameraInfo const& cameraInfo, math::float4 const& userTime,
@@ -192,7 +194,7 @@ public:
bool isStencilBufferEnabled() const noexcept { return mStencilBufferEnabled; }
void setStereoscopicOptions(StereoscopicOptions const& options);
void setStereoscopicOptions(StereoscopicOptions const& options) noexcept;
FCamera const* getDirectionalLightCamera() const noexcept {
return mShadowMapManager.getDirectionalLightCamera();
+1 -1
View File
@@ -527,7 +527,7 @@ private:
template<typename Data, typename Setup, typename Execute>
FrameGraphPass<Data>& FrameGraph::addPass(char const* name, Setup setup, Execute&& execute) {
static_assert(sizeof(Execute) < 1024, "Execute() lambda is capturing too much data.");
static_assert(sizeof(Execute) < 2048, "Execute() lambda is capturing too much data.");
// create the FrameGraph pass
auto* const pass = mArena.make<FrameGraphPassConcrete<Data, Execute>>(std::forward<Execute>(execute));
+6 -6
View File
@@ -82,22 +82,22 @@ uint32_t RenderPassNode::declareRenderTarget(FrameGraph& fg, FrameGraph::Builder
RenderPassData data;
data.name = name;
data.descriptor = descriptor;
FrameGraphRenderPass::Attachments const& attachments = data.descriptor.attachments;
// retrieve the ResourceNode of the attachments coming to us -- this will be used later
// to compute the discard flags.
DependencyGraph const& dependencyGraph = fg.getGraph();
auto incomingEdges = dependencyGraph.getIncomingEdges(this);
auto outgoingEdges = dependencyGraph.getOutgoingEdges(this);
for (size_t i = 0; i < RenderPassData::ATTACHMENT_COUNT; i++) {
if (descriptor.attachments.array[i]) {
data.attachmentInfo[i] = attachments.array[i];
FrameGraphId<FrameGraphTexture> const& handle =
data.descriptor.attachments.array[i];
if (handle) {
data.attachmentInfo[i] = handle;
// TODO: this is not very efficient
auto incomingPos = std::find_if(incomingEdges.begin(), incomingEdges.end(),
[&dependencyGraph, handle = descriptor.attachments.array[i]]
[&dependencyGraph, handle]
(DependencyGraph::Edge const* edge) {
ResourceNode const* node = static_cast<ResourceNode const*>(
dependencyGraph.getNode(edge->from));
@@ -111,7 +111,7 @@ uint32_t RenderPassNode::declareRenderTarget(FrameGraph& fg, FrameGraph::Builder
}
// this could be either outgoing or incoming (if there are no outgoing)
data.outgoing[i] = fg.getActiveResourceNode(descriptor.attachments.array[i]);
data.outgoing[i] = fg.getActiveResourceNode(handle);
if (data.outgoing[i] == data.incoming[i]) {
data.outgoing[i] = nullptr;
}
+1 -1
View File
@@ -35,7 +35,7 @@ vertex {
fragment {
void postProcess(inout PostProcessInputs postProcess) {
#if __VERSION__ == 100
#if FILAMENT_EFFECTIVE_VERSION == 100
postProcess.color = texture2D(materialParams_color, variable_vertex.xy);
#else
postProcess.color = textureLod(materialParams_color, variable_vertex.xy, 0.0);
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.45.0"
spec.version = "1.47.0"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.45.0/filament-v1.45.0-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.47.0/filament-v1.47.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
@@ -28,7 +28,7 @@
namespace filament {
// update this when a new version of filament wouldn't work with older materials
static constexpr size_t MATERIAL_VERSION = 45;
static constexpr size_t MATERIAL_VERSION = 47;
/**
* Supported shading models
@@ -66,6 +66,7 @@ enum class ReservedSpecializationConstants : uint8_t {
CONFIG_POWER_VR_SHADER_WORKAROUNDS = 5,
CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP = 6,
CONFIG_DEBUG_FROXEL_VISUALIZATION = 7,
CONFIG_STEREO_EYE_COUNT = 8,
};
// This value is limited by UBO size, ES3.0 only guarantees 16 KiB.
@@ -75,7 +76,8 @@ constexpr size_t CONFIG_MAX_LIGHT_INDEX = CONFIG_MAX_LIGHT_COUNT - 1;
// The number of specialization constants that Filament reserves for its own use. These are always
// the first constants (from 0 to CONFIG_MAX_RESERVED_SPEC_CONSTANTS - 1).
constexpr size_t CONFIG_MAX_RESERVED_SPEC_CONSTANTS = 8;
// Updating this value necessitates a material version bump.
constexpr size_t CONFIG_MAX_RESERVED_SPEC_CONSTANTS = 16;
// The maximum number of shadowmaps.
// There is currently a maximum limit of 128 shadowmaps.
@@ -120,8 +122,10 @@ constexpr size_t CONFIG_MAX_BONE_COUNT = 256;
// Furthermore, this is constrained by CONFIG_MINSPEC_UBO_SIZE (16 bytes per morph target).
constexpr size_t CONFIG_MAX_MORPH_TARGET_COUNT = 256;
// The number of eyes in stereoscopic mode.
constexpr uint8_t CONFIG_STEREOSCOPIC_EYES = 2;
// The max number of eyes supported in stereoscopic mode.
// The number of eyes actually rendered is set at Engine creation time, see
// Engine::Config::stereoscopicEyeCount.
constexpr uint8_t CONFIG_MAX_STEREOSCOPIC_EYES = 4;
} // namespace filament
@@ -76,7 +76,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
math::mat4f worldFromViewMatrix; // clip view -> world : model matrix
math::mat4f clipFromViewMatrix; // clip <- view world : projection matrix
math::mat4f viewFromClipMatrix; // clip -> view world : inverse projection matrix
math::mat4f clipFromWorldMatrix[CONFIG_STEREOSCOPIC_EYES]; // clip <- view <- world
math::mat4f clipFromWorldMatrix[CONFIG_MAX_STEREOSCOPIC_EYES]; // clip <- view <- world
math::mat4f worldFromClipMatrix; // clip -> view -> world
math::mat4f userWorldFromWorldMatrix; // userWorld <- world
math::float4 clipTransform; // [sx, sy, tx, ty] only used by VERTEX_DOMAIN_DEVICE
@@ -203,7 +203,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
float es2Reserved2;
// bring PerViewUib to 2 KiB
math::float4 reserved[48];
math::float4 reserved[40];
};
// 2 KiB == 128 float4s
+1
View File
@@ -39,6 +39,7 @@ 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}
@@ -135,6 +135,7 @@ protected:
Optimization mOptimization = Optimization::PERFORMANCE;
bool mPrintShaders = false;
bool mGenerateDebugInfo = false;
bool mIncludeEssl1 = true;
utils::bitset32 mShaderModels;
struct CodeGenParams {
ShaderModel shaderModel;
@@ -271,6 +272,9 @@ public:
MaterialBuilder& noSamplerValidation(bool enabled) noexcept;
//! Enable generation of ESSL 1.0 code in FL0 materials.
MaterialBuilder& includeEssl1(bool enabled) noexcept;
//! Set the name of this material.
MaterialBuilder& name(const char* name) noexcept;
+17 -3
View File
@@ -23,6 +23,7 @@
#include <spirv_glsl.hpp>
#include <spirv_msl.hpp>
#include "backend/DriverEnums.h"
#include "sca/builtinResource.h"
#include "sca/GLSLTools.h"
@@ -32,6 +33,7 @@
#include "MetalArgumentBuffer.h"
#include "SpirvFixup.h"
#include "utils/ostream.h"
#include <filament/MaterialEnums.h>
@@ -395,7 +397,9 @@ bool GLSLPostProcessor::process(const std::string& inputShader, Config const& co
break;
case MaterialBuilder::Optimization::SIZE:
case MaterialBuilder::Optimization::PERFORMANCE:
fullOptimization(tShader, config, internalConfig);
if (!fullOptimization(tShader, config, internalConfig)) {
return false;
}
break;
}
@@ -478,7 +482,7 @@ void GLSLPostProcessor::preprocessOptimization(glslang::TShader& tShader,
}
}
void GLSLPostProcessor::fullOptimization(const TShader& tShader,
bool GLSLPostProcessor::fullOptimization(const TShader& tShader,
GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const {
SpirvBlob spirv;
@@ -546,19 +550,29 @@ void GLSLPostProcessor::fullOptimization(const TShader& tShader,
}
}
#ifdef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
*internalConfig.glslOutput = glslCompiler.compile();
#else
try {
*internalConfig.glslOutput = glslCompiler.compile();
} catch (spirv_cross::CompilerError e) {
slog.e << "ERROR: " << e.what() << io::endl;
return false;
}
#endif
// spirv-cross automatically redeclares gl_ClipDistance if it's used. Some drivers don't
// like this, so we simply remove it.
// According to EXT_clip_cull_distance, gl_ClipDistance can be
// "implicitly sized by indexing it only with integral constant expressions".
std::string& str = *internalConfig.glslOutput;
const std::string clipDistanceDefinition = "out float gl_ClipDistance[1];";
const std::string clipDistanceDefinition = "out float gl_ClipDistance[2];";
size_t const found = str.find(clipDistanceDefinition);
if (found != std::string::npos) {
str.replace(found, clipDistanceDefinition.length(), "");
}
}
return true;
}
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
+1 -1
View File
@@ -93,7 +93,7 @@ private:
ShaderMinifier minifier;
};
void fullOptimization(const glslang::TShader& tShader,
bool fullOptimization(const glslang::TShader& tShader,
GLSLPostProcessor::Config const& config, InternalConfig& internalConfig) const;
void preprocessOptimization(glslang::TShader& tShader,
+12 -7
View File
@@ -139,13 +139,13 @@ void MaterialBuilderBase::prepare(bool vulkanSemantics,
glTargetLanguage,
effectiveFeatureLevel,
});
if (featureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0
&& shaderModel == ShaderModel::MOBILE) {
// ESSL1 code may never be compiled to SPIR-V.
if (mIncludeEssl1
&& featureLevel == filament::backend::FeatureLevel::FEATURE_LEVEL_0
&& shaderModel == ShaderModel::MOBILE) {
mCodeGenPermutations.push_back({
shaderModel,
TargetApi::OPENGL,
TargetLanguage::GLSL,
glTargetLanguage,
filament::backend::FeatureLevel::FEATURE_LEVEL_0
});
}
@@ -1421,15 +1421,15 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo&
uniforms.push_back({
"objectUniforms.data[0].morphTargetCount",
offsetof(PerRenderableUib, data[0].morphTargetCount), 1,
UniformType::UINT });
UniformType::INT });
uniforms.push_back({
"objectUniforms.data[0].flagsChannels",
offsetof(PerRenderableUib, data[0].flagsChannels), 1,
UniformType::UINT });
UniformType::INT });
uniforms.push_back({
"objectUniforms.data[0].objectId",
offsetof(PerRenderableUib, data[0].objectId), 1,
UniformType::UINT });
UniformType::INT });
uniforms.push_back({
"objectUniforms.data[0].userData",
offsetof(PerRenderableUib, data[0].userData), 1,
@@ -1565,4 +1565,9 @@ MaterialBuilder& MaterialBuilder::noSamplerValidation(bool enabled) noexcept {
return *this;
}
MaterialBuilder& MaterialBuilder::includeEssl1(bool enabled) noexcept {
mIncludeEssl1 = enabled;
return *this;
}
} // namespace filamat
+45 -8
View File
@@ -151,6 +151,36 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade
out << "#define FILAMENT_HAS_FEATURE_INSTANCING\n";
}
// During compilation and optimization, __VERSION__ reflects the shader language version of the
// intermediate code, not the version of the final code. spirv-cross automatically adapts
// certain language features (e.g. fragment output) but leaves others untouched (e.g. sampler
// functions, bit shift operations). Client code may have to make decisions based on this
// information, so define a FILAMENT_EFFECTIVE_VERSION constant.
const char *effective_version;
if (mTargetLanguage == TargetLanguage::GLSL) {
effective_version = "__VERSION__";
} else {
switch (mShaderModel) {
case ShaderModel::MOBILE:
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
effective_version = "300";
} else {
effective_version = "100";
}
break;
case ShaderModel::DESKTOP:
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
effective_version = "450";
} else {
effective_version = "410";
}
break;
default:
assert(false);
}
}
generateDefine(out, "FILAMENT_EFFECTIVE_VERSION", effective_version);
if (stage == ShaderStage::VERTEX) {
CodeGenerator::generateDefine(out, "FLIP_UV_ATTRIBUTE", material.flipUV);
CodeGenerator::generateDefine(out, "LEGACY_MORPHING", material.useLegacyMorphing);
@@ -253,10 +283,13 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade
generateSpecializationConstant(out, "CONFIG_POWER_VR_SHADER_WORKAROUNDS",
+ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS, false);
// CONFIG_STEREOSCOPIC_EYES is used to size arrays and on Adreno GPUs + vulkan, this has to
generateSpecializationConstant(out, "CONFIG_STEREO_EYE_COUNT",
+ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT, 2);
// CONFIG_MAX_STEREOSCOPIC_EYES is used to size arrays and on Adreno GPUs + vulkan, this has to
// be explicitly, statically defined (as in #define). Otherwise (using const int for
// example), we'd run into a GPU crash.
out << "#define CONFIG_STEREOSCOPIC_EYES " << (int) CONFIG_STEREOSCOPIC_EYES << "\n";
out << "#define CONFIG_MAX_STEREOSCOPIC_EYES " << (int) CONFIG_MAX_STEREOSCOPIC_EYES << "\n";
if (mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) {
// On ES2 since we don't have post-processing, we need to emulate EGL_GL_COLORSPACE_KHR,
@@ -268,8 +301,9 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade
out << '\n';
out << SHADERS_COMMON_DEFINES_GLSL_DATA;
if (mFeatureLevel > FeatureLevel::FEATURE_LEVEL_0 &&
material.featureLevel == FeatureLevel::FEATURE_LEVEL_0) {
if (material.featureLevel == FeatureLevel::FEATURE_LEVEL_0 &&
(mFeatureLevel > FeatureLevel::FEATURE_LEVEL_0
|| mTargetLanguage == TargetLanguage::SPIRV)) {
// Insert compatibility definitions for ESSL 1.0 functions which were removed in ESSL 3.0.
// This is the minimum required value according to the OpenGL ES Shading Language Version
@@ -457,11 +491,14 @@ io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type,
const char* materialTypeString = getOutputTypeName(materialOutputType);
const char* typeString = getOutputTypeName(outputType);
bool generate_essl3_code = mTargetLanguage == TargetLanguage::SPIRV
|| mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1;
out << "\n#define FRAG_OUTPUT" << index << " " << name.c_str();
if (mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) {
out << "\n#define FRAG_OUTPUT_AT" << index << " gl_FragColor";
} else {
if (generate_essl3_code) {
out << "\n#define FRAG_OUTPUT_AT" << index << " output_" << name.c_str();
} else {
out << "\n#define FRAG_OUTPUT_AT" << index << " gl_FragColor";
}
out << "\n#define FRAG_OUTPUT_MATERIAL_TYPE" << index << " " << materialTypeString;
out << "\n#define FRAG_OUTPUT_PRECISION" << index << " " << precisionString;
@@ -469,7 +506,7 @@ io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type,
out << "\n#define FRAG_OUTPUT_SWIZZLE" << index << " " << swizzleString;
out << "\n";
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
if (generate_essl3_code) {
out << "\nlayout(location=" << index << ") out " << precisionString << " "
<< typeString << " output_" << name.c_str() << ";\n";
}
+1 -1
View File
@@ -39,7 +39,7 @@ BufferInterfaceBlock const& UibGenerator::getPerViewUib() noexcept {
{ "worldFromViewMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 },
{ "clipFromViewMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 },
{ "viewFromClipMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 },
{ "clipFromWorldMatrix", CONFIG_STEREOSCOPIC_EYES,
{ "clipFromWorldMatrix", CONFIG_MAX_STEREOSCOPIC_EYES,
Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 },
{ "worldFromClipMatrix", 0, Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 },
{ "userWorldFromWorldMatrix",0,Type::MAT4, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 },
@@ -34,6 +34,7 @@ struct Config {
filament::camutils::Mode cameraMode = filament::camutils::Mode::ORBIT;
bool resizeable = true;
bool headless = false;
int stereoscopicEyeCount = 2;
// Provided to indicate GPU preference for vulkan
std::string vulkanGPUHint;
@@ -85,6 +85,8 @@ public:
PostRenderCallback postRender = PostRenderCallback(),
size_t width = 1024, size_t height = 640);
void reconfigureCameras() { mReconfigureCameras = true; }
filament::Material const* getDefaultMaterial() const noexcept { return mDefaultMaterial; }
filament::Material const* getTransparentMaterial() const noexcept { return mTransparentMaterial; }
IBL* getIBL() const noexcept { return mIBL.get(); }
@@ -189,6 +191,7 @@ private:
void fixupMouseCoordinatesForHdpi(ssize_t& x, ssize_t& y) const;
FilamentApp* const mFilamentApp = nullptr;
Config mConfig;
const bool mIsHeadless;
SDL_Window* mWindow = nullptr;
@@ -251,6 +254,7 @@ private:
float mCameraFocalLength = 28.0f;
float mCameraNear = 0.1f;
float mCameraFar = 100.0f;
bool mReconfigureCameras = false;
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
filament::backend::VulkanPlatform* mVulkanPlatform = nullptr;
+24 -3
View File
@@ -448,6 +448,11 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
preRender(mEngine, window->mViews[0]->getView(), mScene, renderer);
}
if (mReconfigureCameras) {
window->configureCamerasForWindow();
mReconfigureCameras = false;
}
if (renderer->beginFrame(window->getSwapChain())) {
for (filament::View* offscreenView : mOffscreenViews) {
renderer->render(offscreenView);
@@ -568,7 +573,7 @@ void FilamentApp::initSDL() {
FilamentApp::Window::Window(FilamentApp* filamentApp,
const Config& config, std::string title, size_t w, size_t h)
: mFilamentApp(filamentApp), mIsHeadless(config.headless) {
: mFilamentApp(filamentApp), mConfig(config), mIsHeadless(config.headless) {
const int x = SDL_WINDOWPOS_CENTERED;
const int y = SDL_WINDOWPOS_CENTERED;
uint32_t windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI;
@@ -596,6 +601,9 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
}
#endif
Engine::Config engineConfig = {};
engineConfig.stereoscopicEyeCount = config.stereoscopicEyeCount;
if (backend == Engine::Backend::VULKAN) {
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
mFilamentApp->mVulkanPlatform =
@@ -604,12 +612,14 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
.backend(backend)
.platform(mFilamentApp->mVulkanPlatform)
.featureLevel(config.featureLevel)
.config(&engineConfig)
.build();
#endif
}
return Engine::Builder()
.backend(backend)
.featureLevel(config.featureLevel)
.config(&engineConfig)
.build();
};
@@ -875,12 +885,23 @@ void FilamentApp::Window::configureCamerasForWindow() {
double near = mFilamentApp->mCameraNear;
double far = mFilamentApp->mCameraFar;
mMainCamera->setLensProjection(mFilamentApp->mCameraFocalLength, 1.0, near, far);
if (mMainView->getView()->getStereoscopicOptions().enabled) {
mat4 projections[4];
projections[0] = Camera::projection(mFilamentApp->mCameraFocalLength, 1.0, near, far);
projections[1] = projections[0];
// simulate foveated rendering
projections[2] = Camera::projection(mFilamentApp->mCameraFocalLength * 2.0, 1.0, near, far);
projections[3] = projections[2];
mMainCamera->setCustomEyeProjection(projections, 4, projections[0], near, far);
} else {
mMainCamera->setLensProjection(mFilamentApp->mCameraFocalLength, 1.0, near, far);
}
mDebugCamera->setProjection(45.0, double(width) / height, 0.0625, 4096, Camera::Fov::VERTICAL);
auto aspectRatio = double(mainWidth) / height;
if (mMainView->getView()->getStereoscopicOptions().enabled) {
aspectRatio = double(mainWidth) / 2.0 / height;
const int ec = mConfig.stereoscopicEyeCount;
aspectRatio = double(mainWidth) / ec / height;
}
mMainCamera->setScaling({1.0 / aspectRatio, 1.0});
+6 -5
View File
@@ -176,6 +176,11 @@ if (WEBGL_PTHREADS)
target_compile_definitions(gltfio_core PUBLIC -DFILAMENT_WASM_THREADS)
endif()
set(GLTFIO_WARNINGS -Wall -Werror)
if (NOT MSVC)
target_compile_options(gltfio_core PRIVATE ${GLTFIO_WARNINGS})
endif()
if (NOT WEBGL AND NOT ANDROID AND NOT IOS)
# ==================================================================================================
@@ -185,10 +190,6 @@ if (NOT WEBGL AND NOT ANDROID AND NOT IOS)
target_link_libraries(${TARGET} PUBLIC filamat gltfio_core)
target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
set_target_properties(${TARGET} PROPERTIES FOLDER Libs)
# ==================================================================================================
# Compiler flags
# ==================================================================================================
if (NOT MSVC)
target_compile_options(${TARGET} PRIVATE -Wno-deprecated-register)
endif()
@@ -229,7 +230,7 @@ if (TNT_DEV AND NOT WEBGL AND NOT ANDROID AND NOT IOS)
target_link_libraries(${TEST_TARGET} PRIVATE ${TARGET} gtest uberarchive)
if (NOT MSVC)
target_compile_options(${TEST_TARGET} PRIVATE -Wno-deprecated-register)
target_compile_options(${TEST_TARGET} PRIVATE ${GLTFIO_WARNINGS})
endif()
set_target_properties(${TEST_TARGET} PROPERTIES FOLDER Tests)
endif()
+4 -2
View File
@@ -69,9 +69,12 @@ class UTILS_PUBLIC ResourceLoader {
public:
using BufferDescriptor = filament::backend::BufferDescriptor;
ResourceLoader(const ResourceConfiguration& config);
explicit ResourceLoader(const ResourceConfiguration& config);
~ResourceLoader();
void setConfiguration(const ResourceConfiguration& config);
/**
* Feeds the binary content of an external resource into the loader's URI cache.
*
@@ -154,7 +157,6 @@ public:
private:
bool loadResources(FFilamentAsset* asset, bool async);
void normalizeSkinningWeights(FFilamentAsset* asset) const;
AssetPool* mPool;
struct Impl;
Impl* pImpl;
};
-15
View File
@@ -118,21 +118,6 @@ Material* ArchiveCache::getMaterial(const ArchiveRequirements& reqs) {
mMaterials[i] = Material::Builder()
.package(spec.package, spec.packageByteCount)
.build(mEngine);
// Don't attempt to precompile shaders on WebGL.
// Chrome already suffers from slow shader compilation:
// https://github.com/google/filament/issues/6615
// Precompiling shaders exacerbates the problem.
#if !defined(__EMSCRIPTEN__)
// First compile high priority variants
mMaterials[i]->compile(Material::CompilerPriorityQueue::HIGH,
UserVariantFilterBit::DIRECTIONAL_LIGHTING |
UserVariantFilterBit::DYNAMIC_LIGHTING |
UserVariantFilterBit::SHADOW_RECEIVER);
// and then, everything else at low priority
mMaterials[i]->compile(Material::CompilerPriorityQueue::LOW);
#endif
}
return mMaterials[i];
+2 -2
View File
@@ -42,6 +42,7 @@
#include <math/vec3.h>
#include <math/vec4.h>
#include <utils/compiler.h>
#include <utils/EntityManager.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Log.h>
@@ -768,7 +769,6 @@ void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity, const
void FAssetLoader::createMaterialVariants(const cgltf_mesh* mesh, Entity entity,
FFilamentAsset* fAsset, FFilamentInstance* instance) {
const cgltf_data* srcAsset = fAsset->mSourceAsset->hierarchy;
UvMap uvmap {};
for (cgltf_size prim = 0, n = mesh->primitives_count; prim < n; ++prim) {
const cgltf_primitive& srcPrim = mesh->primitives[prim];
@@ -1076,7 +1076,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, const char* na
.build(mEngine);
outPrim->targets = targets;
fAsset->mMorphTargetBuffers.push_back(targets);
const cgltf_accessor* previous = nullptr;
UTILS_UNUSED_IN_RELEASE cgltf_accessor const* previous = nullptr;
for (int tindex = 0; tindex < targetsCount; ++tindex) {
const cgltf_morph_target& inTarget = inPrim.targets[tindex];
for (cgltf_size aindex = 0; aindex < inTarget.attributes_count; ++aindex) {
+2 -1
View File
@@ -20,6 +20,7 @@
#include <draco/compression/decode.h>
#endif
#include <utils/compiler.h>
#include <utils/Log.h>
#if GLTFIO_DRACO_SUPPORTED
@@ -65,7 +66,7 @@ DracoMesh::~DracoMesh() {
}
// Gets the number of components in the given cgltf vector type, or -1 for matrices.
static int getNumComponents(cgltf_type ctype) {
UTILS_UNUSED_IN_RELEASE static int getNumComponents(cgltf_type ctype) {
return ((int) ctype) <= 4 ? ((int) ctype) : -1;
}
+1 -1
View File
@@ -71,7 +71,7 @@ namespace utils {
namespace filament::gltfio {
class Wireframe;
struct Wireframe;
// Encapsulates VertexBuffer::setBufferAt() or IndexBuffer::setBuffer().
struct BufferSlot {
+1 -1
View File
@@ -116,7 +116,7 @@ Texture* Ktx2Provider::pushTexture(const uint8_t* data, size_t byteCount,
}
JobSystem* js = &mEngine->getJobSystem();
item->job = jobs::createJob(*js, mDecoderRootJob, [this, item] {
item->job = jobs::createJob(*js, mDecoderRootJob, [item] {
using Result = ktxreader::Ktx2Reader::Result;
const bool success = Result::SUCCESS == item->async->doTranscoding();
item->transcoderState.store(success ? TranscoderState::SUCCESS : TranscoderState::ERROR);
+10 -4
View File
@@ -32,6 +32,7 @@
#include <geometry/Transcoder.h>
#include <utils/compiler.h>
#include <utils/JobSystem.h>
#include <utils/Log.h>
#include <utils/Systrace.h>
@@ -74,15 +75,15 @@ enum class CacheResult {
};
struct ResourceLoader::Impl {
Impl(const ResourceConfiguration& config) :
explicit Impl(const ResourceConfiguration& config) :
mEngine(config.engine),
mNormalizeSkinningWeights(config.normalizeSkinningWeights),
mGltfPath(config.gltfPath ? config.gltfPath : ""),
mUriDataCache(std::make_shared<UriDataCache>()) {}
Engine* const mEngine;
const bool mNormalizeSkinningWeights;
const std::string mGltfPath;
bool mNormalizeSkinningWeights;
std::string mGltfPath;
// User-provided resource data with URI string keys, populated with addResourceData().
// This is used on platforms without traditional file systems, such as Android, iOS, and WebGL.
@@ -243,7 +244,7 @@ static void decodeMeshoptCompression(cgltf_data* data) {
void* destination = malloc(compression->count * compression->stride);
assert_invariant(destination);
int error = 0;
UTILS_UNUSED_IN_RELEASE int error = 0;
switch (compression->mode) {
case cgltf_meshopt_compression_mode_invalid:
break;
@@ -320,6 +321,11 @@ ResourceLoader::~ResourceLoader() {
delete pImpl;
}
void ResourceLoader::setConfiguration(const ResourceConfiguration& config) {
pImpl->mNormalizeSkinningWeights = config.normalizeSkinningWeights;
pImpl->mGltfPath = config.gltfPath;
}
void ResourceLoader::addResourceData(const char* uri, BufferDescriptor&& buffer) {
pImpl->addResourceData(uri, std::move(buffer));
}
+1 -1
View File
@@ -126,7 +126,7 @@ Texture* StbProvider::pushTexture(const uint8_t* data, size_t byteCount,
}
JobSystem* js = &mEngine->getJobSystem();
info->decoderJob = jobs::createJob(*js, mDecoderRootJob, [this, info] {
info->decoderJob = jobs::createJob(*js, mDecoderRootJob, [info] {
auto& source = info->sourceBuffer;
int width, height, comp;
-4
View File
@@ -43,9 +43,6 @@ using namespace backend;
using namespace gltfio;
using namespace utils;
constexpr uint32_t WIDTH = 64;
constexpr uint32_t HEIGHT = 64;
char const* ANIMATED_MORPH_CUBE_GLB = "AnimatedMorphCube.glb";
static std::ifstream::pos_type getFileSize(const char* filename) {
@@ -178,7 +175,6 @@ do { \
TEST_F(glTFIOTest, AnimatedMorphCubeTransforms) {
FilamentAsset const& morphCubeAsset = *mData[ANIMATED_MORPH_CUBE_GLB]->getAsset();
auto const& transformManager = mEngine->getTransformManager();
auto const& renderableManager = mEngine->getRenderableManager();
Entity const* renderables = morphCubeAsset.getRenderableEntities();
EXPECT_EQ(morphCubeAsset.getRenderableEntityCount(), 1u);
+1 -1
View File
@@ -60,7 +60,7 @@ private:
// This variable is to implement a *hanging* effect for /api/status. The call to /api/status
// will always block until statusMaterialId is updated again. The client is expected to keep
// calling /api/status (a constant "pull" to simulate a push).
std::atomic_uint64_t mCurrentStatus = 0;
std::atomic<uint64_t> mCurrentStatus = 0;
};
} // filament::matdbg
+16
View File
@@ -127,3 +127,19 @@ async function statusLoop(isConnected, onStatus) {
setTimeout(() => statusLoop(isConnected, onStatus), STATUS_LOOP_TIMEOUT)
}
}
// Use browser User-agent to guess the current backend. This is mainly for matinfo which does
// not have a running backend.
function guessBackend() {
const AGENTS_TO_BACKEND = [
['Mac OS', 'metal'],
['Windows', 'opengl'],
['Linux', 'vulkan'],
];
const result = AGENTS_TO_BACKEND.filter((agent_backend) => {
return window.navigator.userAgent.search(agent_backend[0]);
}).map((agent_backend) => agent_backend[1]);
return result.length > 0 ? result[0] : null;
}
+237 -65
View File
@@ -25,6 +25,8 @@ const LANGUAGE_CHOICES = {
'metal': ['msl'],
};
const BACKENDS = Object.keys(LANGUAGE_CHOICES);
const MATERIAL_INFO_KEY_TO_STRING = {
'model': 'shading model',
'vertex_domain': 'vertex domain',
@@ -54,19 +56,6 @@ const BACKGROUND_COLOR = '#5362e5';
const HOVER_BACKGROUND_COLOR = '#b3c2ff';
const CODE_VIEWER_BOTTOM_ROW_HEIGHT = 60;
const REGULAR_FONT_SIZE = 12;
const MENU_HR = `
display: block;
height: 1px;
border: 0px;
border-top: 1px solid ${UNSELECTED_COLOR};
padding: 0;
width: 100%;
margin: 3px 0 8px 0;
`;
const MENU_SECTION_TITLE = `
font-size: 16px;
color: ${UNSELECTED_COLOR};
`;
// Set up the Monaco editor. See also CodeViewer
const kMonacoBaseUrl = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/';
@@ -85,6 +74,14 @@ window.MonacoEnvironment = {
}
};
const _validDict = (obj) => {
return obj && Object.keys(obj).length > 0;
}
const _isMatInfoMode = (database) => {
return Object.keys(database).length == 1;
}
class Button extends LitElement {
static get styles() {
return css`
@@ -222,11 +219,19 @@ class CodeViewer extends LitElement {
scrollBeyondLastLine: false,
readOnly: false,
minimap: { enabled: false },
automaticLayout: true
automaticLayout: true,
// Workaround see https://github.com/microsoft/monaco-editor/issues/3217
fontLigatures: '',
});
const KeyMod = monaco.KeyMod, KeyCode = monaco.KeyCode;
this.editor.onDidChangeModelContent(this._onEdit.bind(this));
this.editor.addCommand(KeyMod.CtrlCmd | KeyCode.KEY_S, this._rebuild.bind(this));
// It might be that the code is available before the editor has been created.
if (this.code && this.code.length > 0) {
this.editor.setValue(this.code);
}
});
}
@@ -254,13 +259,17 @@ class CodeViewer extends LitElement {
updated(props) {
if (props.has('code') && this.code.length > 0) {
this.editor.setValue(this.code);
// Note that the prop might have been updated before the editor is available.
if (this.editor) {
this.editor.setValue(this.code);
}
}
if ((props.has('expectedWidth') || props.has('expectedHeight')) &&
(this.expectedWidth > 0 && (this.expectedHeight - CODE_VIEWER_BOTTOM_ROW_HEIGHT) > 0)) {
this._editorDiv.style.width = Math.floor(this.expectedWidth) + 'px';
this._editorDiv.style.height =
(Math.floor(this.expectedHeight) - CODE_VIEWER_BOTTOM_ROW_HEIGHT) + 'px';
const actualWidth = Math.floor(this.expectedWidth);
const actualHeight = (Math.floor(this.expectedHeight) - CODE_VIEWER_BOTTOM_ROW_HEIGHT);
this._editorDiv.style.width = actualWidth + 'px';
this._editorDiv.style.height = actualHeight + 'px';
}
}
@@ -311,12 +320,12 @@ class CodeViewer extends LitElement {
}
customElements.define("code-viewer", CodeViewer);
class MaterialInfo extends LitElement {
class MenuSection extends LitElement {
static get properties() {
return {
info: {type: Object, state: true},
showing: {type: Boolean, state: true},
}
title: {type: String, attribute: 'title'},
};
}
static get styles() {
@@ -324,18 +333,23 @@ class MaterialInfo extends LitElement {
:host {
font-size: ${unsafeCSS(REGULAR_FONT_SIZE)}px;
color: ${unsafeCSS(UNSELECTED_COLOR)};
margin-bottom: 20px;
}
.section-title {
${unsafeCSS(MENU_SECTION_TITLE)}
font-size: 16px;
color: ${unsafeCSS(UNSELECTED_COLOR)};
cursor: pointer;
}
hr {
${unsafeCSS(MENU_HR)}
.container {
margin-bottom: 20px;
}
.hide {
display: none;
flex-direction: column;
hr {
display: block;
height: 1px;
border: 0px;
border-top: 1px solid ${unsafeCSS(UNSELECTED_COLOR)};
padding: 0;
width: 100%;
margin: 3px 0 8px 0;
}
.expander {
display: flex;
@@ -346,9 +360,48 @@ class MaterialInfo extends LitElement {
`;
}
_showClick() {
this.showing = !this.showing;
}
constructor() {
super();
this.showing = true;
}
render() {
const expandedIcon = this.showing ? '-' : '+';
const slot = (() => html`<slot></slot>`)();
return html`
<div class="container">
<div class="section-title expander" @click="${this._showClick}">
<span>${this.title}</span> <span>${expandedIcon}</span>
</div>
<hr />
${this.showing ? slot : []}
</div>
`;
}
}
customElements.define('menu-section', MenuSection);
class MaterialInfo extends LitElement {
static get properties() {
return {
info: {type: Object, state: true},
};
}
static get styles() {
return css`
:host {
font-size: ${unsafeCSS(REGULAR_FONT_SIZE)}px;
}
`;
}
constructor() {
super();
this.info = null;
}
@@ -356,12 +409,7 @@ class MaterialInfo extends LitElement {
return this.info && Object.keys(this.info).length > 0;
}
_showClick() {
this.showing = !this.showing;
}
render() {
const expandedIcon = this._hasInfo() ? (this.showing ? '-' : '+') : '';
let infoDivs = [];
if (this._hasInfo()) {
if (this.info.shading && this.info.shading.material_domain === 'surface') {
@@ -387,23 +435,123 @@ class MaterialInfo extends LitElement {
);
}
}
let divClass = 'container';
if (infoDivs.length == 0) {
divClass += ' hide';
const shouldHide = infoDivs.length == 0;
if (infoDivs.length > 0) {
return html`
<menu-section title="Material Details">
${infoDivs}
</menu-section>
`;
}
return html`
<div class="${divClass}">
<div class="section-title expander" @click="${this._showClick}">
<span>Material Details</span> <span>${expandedIcon}</span>
</div>
<hr />
${this.showing ? infoDivs : []}
</div>
`;
return html``;
}
}
customElements.define('material-info', MaterialInfo);
class AdvancedOptions extends LitElement {
static get properties() {
return {
currentBackend: {type: String, attribute: 'current-backend'},
availableBackends: {type: Array, state: true},
};
}
static get styles() {
return css`
:host {
font-size: ${unsafeCSS(REGULAR_FONT_SIZE)}px;
}
.option {
border: 1px solid ${unsafeCSS(UNSELECTED_COLOR)};
border-radius: 5px;
padding: 4px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
label {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-right: 5px;
}
label input {
margin: 0 4px 0 0;
}
form {
display: flex;
}
.option-heading {
margin-bottom: 5px;
}
`;
}
get _backendOptionForm() {
return this.renderRoot.querySelector('#backend-option-form');
}
updated(props) {
if (props.has('currentBackend') || props.has('availableBackends')) {
// Clear the radio button selections. The correct option will be selected
// in _backendOption().
if (this._backendOptionForm) {
this._backendOptionForm.reset();
}
}
}
_backendOption() {
if (this.availableBackends.length == 0) {
return null;
}
const onChange = (ev) => {
const backend = ev.currentTarget.getAttribute('name');
this.dispatchEvent(
new CustomEvent(
'option-backend',
{detail: backend, bubbles: true, composed: true}));
}
const div = this.availableBackends.map((backend) => {
const selected = backend == this.currentBackend;
return html`
<label>
<input type="radio" name="${backend}"
?checked=${selected} @change=${onChange}/>
${backend}
</label>
`;
});
return html`
<div class="option">
<div class="option-heading">Current Backend</div>
<form action="" id="backend-option-form">
${div}
</form>
</div>
`;
}
constructor() {
super();
this.availableBackends = [];
}
render() {
return html`
<menu-section title="Advanced Options">
${this._backendOption() ?? nothing}
</menu-section>
`;
}
}
customElements.define('advanced-options', AdvancedOptions);
class MaterialSidePanel extends LitElement {
// Setting the style in render() has poor performance implications. We use it simply to avoid
// having another container descending from the root to host the background color.
@@ -424,9 +572,6 @@ class MaterialSidePanel extends LitElement {
margin: 0 0 10px 0;
font-size: 20px;
}
.material-section {
${MENU_SECTION_TITLE}
}
.materials {
display: flex;
flex-direction: column;
@@ -458,9 +603,6 @@ class MaterialSidePanel extends LitElement {
flex-direction: row;
display: flex;
}
hr {
${MENU_HR}
}
`;
}
@@ -484,6 +626,10 @@ class MaterialSidePanel extends LitElement {
return this.renderRoot.querySelector('#material-info');
}
get _advancedOptions() {
return this.renderRoot.querySelector('#advanced-options');
}
constructor() {
super();
this.connected = false;
@@ -528,9 +674,9 @@ class MaterialSidePanel extends LitElement {
});
}
if (props.has('currentMaterial')) {
if (this.currentBackend && this.database && this.activeShaders && this.currentMaterial) {
if (this.currentBackend && this.database && this.currentMaterial) {
const material = this.database[this.currentMaterial];
const activeVariants = this.activeShaders[this.currentMaterial].variants;
const activeVariants = _validDict(this.activeShaders) ? this.activeShaders[this.currentMaterial].variants : [];
const materialShaders = material[this.currentBackend];
let variants = [];
for (const [index, shader] of materialShaders.entries()) {
@@ -546,6 +692,11 @@ class MaterialSidePanel extends LitElement {
if (this.currentMaterial && this.database) {
const material = this.database[this.currentMaterial];
this._materialInfo.info = material;
// The matinfo usecase
if (_isMatInfoMode(this.database)) {
this._advancedOptions.availableBackends = BACKENDS.filter((backend) => !!material[backend]);
}
}
}
}
@@ -650,23 +801,29 @@ class MaterialSidePanel extends LitElement {
${shaderDiv ?? nothing}
`;
});
return html`
<div class="materials">
<div class="material-section">
${title}
</div>
<hr />
${mats}
</div>
`;
if (mats.length > 0) {
return html`<menu-section title="${title}">${mats}</menu-section>`;
}
return null;
};
let advancedOptions = null;
// Currently we only have one advanced option and it's only for when we're in matinfo
if (_isMatInfoMode(this.database)) {
advancedOptions =
(() => html`
<advanced-options id="advanced-options"
current-backend=${this.currentBackend}></advanced-options>
`)();
}
return html`
<style>${this.dynamicStyle()}</style>
<div class="container">
<div class="title">matdbg</div>
${sections("Surface", "surface")}
${sections("Post-processing", "postpro")}
${sections("Surface", "surface") ?? nothing}
${sections("Post-processing", "postpro") ?? nothing}
<material-info id="material-info"></material-info>
${advancedOptions ?? nothing}
</div>
`;
}
@@ -794,6 +951,12 @@ class MatdbgViewer extends LitElement {
}
);
this.addEventListener('option-backend',
(ev) => {
this.currentBackend = ev.detail;
}
);
addEventListener('resize', this._onResize.bind(this));
}
@@ -875,7 +1038,7 @@ class MatdbgViewer extends LitElement {
material.active = material.active || shader.active;
}
}
if (this.activeShaders) {
if (_validDict(this.activeShaders)) {
let backends = {};
for (let matid in this.activeShaders) {
const backend = this.activeShaders[matid].backend;
@@ -889,6 +1052,9 @@ class MatdbgViewer extends LitElement {
if (backendList.length > 0) {
this.currentBackend = backendList[0];
}
} else if (!this.currentBackend) {
// Make a guess on the backend if one wasn't from activeShaders.
this.currentBackend = guessBackend();
}
this._sidepanel.database = this.database;
@@ -901,6 +1067,12 @@ class MatdbgViewer extends LitElement {
this.database[matInfo.matid] = matInfo;
this.database = this.database;
}
// In the `matinfo -w` usecase, we assume the current material to be the only
// material available in the database.
if (_isMatInfoMode(this.database)) {
this.currentMaterial = Object.keys(this.database)[0];
}
})();
}
}
+9 -9
View File
@@ -58,7 +58,7 @@ public:
/* compound assignment products by a scalar
*/
template<typename U>
template<typename U, typename = std::enable_if_t<std::is_arithmetic_v<U>>>
constexpr QUATERNION<T>& operator*=(U v) {
QUATERNION<T>& lhs = static_cast<QUATERNION<T>&>(*this);
for (size_t i = 0; i < QUATERNION<T>::size(); i++) {
@@ -67,7 +67,7 @@ public:
return lhs;
}
template<typename U>
template<typename U, typename = std::enable_if_t<std::is_arithmetic_v<U>>>
constexpr QUATERNION<T>& operator/=(U v) {
QUATERNION<T>& lhs = static_cast<QUATERNION<T>&>(*this);
for (size_t i = 0; i < QUATERNION<T>::size(); i++) {
@@ -125,25 +125,25 @@ public:
* q.w*r.z + q.x*r.y - q.y*r.x + q.z*r.w);
*
*/
template<typename U>
template<typename U, typename = std::enable_if_t<std::is_arithmetic_v<U>>>
friend inline constexpr
QUATERNION<arithmetic_result_t<T, U>> MATH_PURE operator*(QUATERNION<T> q, U scalar) {
// don't pass q by reference because we need a copy anyway
return q *= scalar;
return QUATERNION<arithmetic_result_t<T, U>>(q *= scalar);
}
template<typename U>
template<typename U, typename = std::enable_if_t<std::is_arithmetic_v<U>>>
friend inline constexpr
QUATERNION<arithmetic_result_t<T, U>> MATH_PURE operator*(T scalar, QUATERNION<U> q) {
QUATERNION<arithmetic_result_t<T, U>> MATH_PURE operator*(U scalar, QUATERNION<T> q) {
// don't pass q by reference because we need a copy anyway
return q *= scalar;
return QUATERNION<arithmetic_result_t<T, U>>(q *= scalar);
}
template<typename U>
template<typename U, typename = std::enable_if_t<std::is_arithmetic_v<U>>>
friend inline constexpr
QUATERNION<arithmetic_result_t<T, U>> MATH_PURE operator/(QUATERNION<T> q, U scalar) {
// don't pass q by reference because we need a copy anyway
return q /= scalar;
return QUATERNION<arithmetic_result_t<T, U>>(q /= scalar);
}
};
+101
View File
@@ -17,6 +17,7 @@
#include <math.h>
#include <random>
#include <functional>
#include <type_traits>
#include <gtest/gtest.h>
@@ -324,3 +325,103 @@ TEST_F(QuatTest, NaN) {
EXPECT_NEAR(qs[2], 0.5, 0.1);
EXPECT_NEAR(qs[3], 0.5, 0.1);
}
TEST_F(QuatTest, Conversions) {
quat qd;
quatf qf;
float3 vf;
double3 vd;
double d = 0.0;
float f = 0.0f;
static_assert(std::is_same<details::arithmetic_result_t<float, float>, float>::value);
static_assert(std::is_same<details::arithmetic_result_t<float, double>, double>::value);
static_assert(std::is_same<details::arithmetic_result_t<double, float>, double>::value);
static_assert(std::is_same<details::arithmetic_result_t<double, double>, double>::value);
{
auto r1 = qd * d;
auto r2 = qd * f;
auto r3 = qf * d;
auto r4 = qf * f;
static_assert(std::is_same<decltype(r1), quat>::value);
static_assert(std::is_same<decltype(r2), quat>::value);
static_assert(std::is_same<decltype(r3), quat>::value);
static_assert(std::is_same<decltype(r4), quatf>::value);
}
{
auto r1 = qd / d;
auto r2 = qd / f;
auto r3 = qf / d;
auto r4 = qf / f;
static_assert(std::is_same<decltype(r1), quat>::value);
static_assert(std::is_same<decltype(r2), quat>::value);
static_assert(std::is_same<decltype(r3), quat>::value);
static_assert(std::is_same<decltype(r4), quatf>::value);
}
{
auto r1 = d * qd;
auto r2 = f * qd;
auto r3 = d * qf;
auto r4 = f * qf;
static_assert(std::is_same<decltype(r1), quat>::value);
static_assert(std::is_same<decltype(r2), quat>::value);
static_assert(std::is_same<decltype(r3), quat>::value);
static_assert(std::is_same<decltype(r4), quatf>::value);
}
{
auto r1 = qd * vd;
auto r2 = qf * vd;
auto r3 = qd * vf;
auto r4 = qf * vf;
static_assert(std::is_same<decltype(r1), double3>::value);
static_assert(std::is_same<decltype(r2), double3>::value);
static_assert(std::is_same<decltype(r3), double3>::value);
static_assert(std::is_same<decltype(r4), float3>::value);
}
{
auto r1 = qd * qd;
auto r2 = qf * qd;
auto r3 = qd * qf;
auto r4 = qf * qf;
static_assert(std::is_same<decltype(r1), quat>::value);
static_assert(std::is_same<decltype(r2), quat>::value);
static_assert(std::is_same<decltype(r3), quat>::value);
static_assert(std::is_same<decltype(r4), quatf>::value);
}
{
auto r1 = dot(qd, qd);
auto r2 = dot(qf, qd);
auto r3 = dot(qd, qf);
auto r4 = dot(qf, qf);
static_assert(std::is_same<decltype(r1), double>::value);
static_assert(std::is_same<decltype(r2), double>::value);
static_assert(std::is_same<decltype(r3), double>::value);
static_assert(std::is_same<decltype(r4), float>::value);
}
{
auto r1 = cross(qd, qd);
auto r2 = cross(qf, qd);
auto r3 = cross(qd, qf);
auto r4 = cross(qf, qf);
static_assert(std::is_same<decltype(r1), quat>::value);
static_assert(std::is_same<decltype(r2), quat>::value);
static_assert(std::is_same<decltype(r3), quat>::value);
static_assert(std::is_same<decltype(r4), quatf>::value);
}
}
template <typename L, typename R, typename = void>
struct has_divide_assign : std::false_type {};
template <typename L, typename R>
struct has_divide_assign<L, R,
decltype(std::declval<L&>() /= std::declval<R>(), void())> : std::true_type {};
// Static assertions to validate the availability of the /= operator for specific type
// combinations. The first static_assert checks that the quat does not have a /= operator with Foo.
// This ensures that quat does not provide an inappropriate overload that could be erroneously
// selected.
struct Foo {};
static_assert(!has_divide_assign<quat, Foo>::value);
static_assert(has_divide_assign<quat, float>::value);
+10 -9
View File
@@ -169,8 +169,9 @@ public:
// the caller must ensure the object will outlive the Job
template<typename T, void(T::*method)(JobSystem&, Job*)>
Job* createJob(Job* parent, T* data) noexcept {
Job* job = create(parent, [](void* user, JobSystem& js, Job* job) {
(*static_cast<T**>(user)->*method)(js, job);
Job* job = create(parent, +[](void* storage, JobSystem& js, Job* job) {
T* const that = static_cast<T*>(reinterpret_cast<void**>(storage)[0]);
(that->*method)(js, job);
});
if (job) {
job->storage[0] = data;
@@ -182,8 +183,8 @@ public:
template<typename T, void(T::*method)(JobSystem&, Job*)>
Job* createJob(Job* parent, T data) noexcept {
static_assert(sizeof(data) <= sizeof(Job::storage), "user data too large");
Job* job = create(parent, [](void* user, JobSystem& js, Job* job) {
T* that = static_cast<T*>(user);
Job* job = create(parent, [](void* storage, JobSystem& js, Job* job) {
T* const that = static_cast<T*>(storage);
(that->*method)(js, job);
that->~T();
});
@@ -197,10 +198,10 @@ public:
template<typename T>
Job* createJob(Job* parent, T functor) noexcept {
static_assert(sizeof(functor) <= sizeof(Job::storage), "functor too large");
Job* job = create(parent, [](void* user, JobSystem& js, Job* job){
T& that = *static_cast<T*>(user);
that(js, job);
that.~T();
Job* job = create(parent, [](void* storage, JobSystem& js, Job* job){
T* const that = static_cast<T*>(storage);
that->operator()(js, job);
that->~T();
});
if (job) {
new(job->storage) T(std::move(functor));
@@ -252,7 +253,7 @@ public:
void signal() noexcept;
/*
* Add job to this thread's execution queue and and keep a reference to it.
* Add job to this thread's execution queue and keep a reference to it.
* Current thread must be owned by JobSystem's thread pool. See adopt().
*
* This job MUST BE waited on with wait(), or released with release().
+2
View File
@@ -18,6 +18,8 @@
#include <utils/Panic.h>
#include <cstdlib>
namespace utils {
// we use a non-inlined, not marked as "no return" function for aborting so that we can set

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