Merge branch 'rc/1.46.0' into release

This commit is contained in:
Powei Feng
2023-11-14 21:34:54 -08:00
111 changed files with 1513 additions and 1464 deletions
+7
View File
@@ -0,0 +1,7 @@
;;; Directory Local Variables -*- no-byte-compile: t -*-
;;; For more information see (info "(emacs) Directory Variables")
((c++-mode . ((c-file-style . "filament")
(apheleia-inhibit . t)))
(c-mode . ((c-file-style . "filament")
(apheleia-inhibit . t))))
+2
View File
@@ -16,3 +16,5 @@ settings.json
test*.png
test*.json
results
/compile_commands.json
/.cache
+2
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."
)
+1
View File
@@ -7,3 +7,4 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.45.1'
implementation 'com.google.android.filament:filament-android:1.46.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.1'
pod 'Filament', '~> 1.46.0'
```
### Snapshots
+12
View File
@@ -128,3 +128,15 @@ Navigate to [Filament's release
workflow](https://github.com/google/filament/actions/workflows/release.yml). Hit the _Run workflow_
dropdown. Modify _Platform to build_ and _Release tag to build_, then hit _Run workflow_. This will
initiate a new release run.
## 11. Kick off the npm and CocoaPods release jobs
Navigate to [Filament's npm deploy
workflow](https://github.com/google/filament/actions/workflows/npm-deploy.yml).
Hit the _Run workflow_ dropdown. Modify _Release tag to deploy_ to the tag corresponding to this
release (for example, v1.42.2).
Navigate to [Filament's CocoaPods deploy
workflow](https://github.com/google/filament/actions/workflows/cocopods-deploy.yml).
Hit the _Run workflow_ dropdown. Modify _Release tag to deploy_ to the tag corresponding to this
release (for example, v1.42.2).
+15
View File
@@ -7,6 +7,20 @@ 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.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
@@ -15,6 +29,7 @@ Instead, if you are authoring a PR for the main branch, add your release note to
- engine: New tone mapper: `AgXTonemapper`.
- matinfo: Add support for viewing ESSL 1.0 shaders
- engine: Add `Renderer::getClearOptions()` [b/243846268]
- engine: Fix stable shadows (again) when an IBL rotation is used
## v1.45.0
@@ -483,6 +483,12 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
builder->config(&config);
}
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderFeatureLevel(
JNIEnv*, jclass, jlong nativeBuilder, jint ordinal) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
builder->featureLevel((Engine::FeatureLevel)ordinal);
}
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderSharedContext(
JNIEnv*, jclass, jlong nativeBuilder, jlong sharedContext) {
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
@@ -105,6 +105,22 @@ Java_com_google_android_filament_Material_nGetRefractionType(JNIEnv*, jclass,
return (jint) material->getRefractionType();
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_google_android_filament_Material_nGetReflectionMode(JNIEnv*, jclass,
jlong nativeMaterial) {
Material* material = (Material*) nativeMaterial;
return (jint) material->getReflectionMode();
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_google_android_filament_Material_nGetFeatureLevel(JNIEnv*, jclass,
jlong nativeMaterial) {
Material* material = (Material*) nativeMaterial;
return (jint) material->getFeatureLevel();
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_google_android_filament_Material_nGetVertexDomain(JNIEnv*, jclass,
@@ -209,6 +209,17 @@ public class Engine {
return this;
}
/**
* Sets the initial featureLevel for the Engine.
*
* @param featureLevel The feature level at which initialize Filament.
* @return A reference to this Builder for chaining calls.
*/
public Builder featureLevel(FeatureLevel featureLevel) {
nSetBuilderFeatureLevel(mNativeBuilder, featureLevel.ordinal());
return this;
}
/**
* Creates an instance of Engine
*
@@ -468,17 +479,23 @@ public class Engine {
}
/**
* Activate all features of a given feature level. By default FeatureLevel::FEATURE_LEVEL_1 is
* active. The selected feature level must not be higher than the value returned by
* getActiveFeatureLevel() and it's not possible lower the active feature level.
* Activate all features of a given feature level. If an explicit feature level is not specified
* at Engine initialization time via {@link Builder#featureLevel}, the default feature level is
* {@link FeatureLevel#FEATURE_LEVEL_0} on devices not compatible with GLES 3.0; otherwise, the
* default is {@link FeatureLevel::FEATURE_LEVEL_1}. The selected feature level must not be
* higher than the value returned by {@link #getActiveFeatureLevel} and it's not possible lower
* the active feature level. Additionally, it is not possible to modify the feature level at all
* if the Engine was initialized at {@link FeatureLevel#FEATURE_LEVEL_0}.
*
* @param featureLevel the feature level to activate. If featureLevel is lower than
* getActiveFeatureLevel(), the current (higher) feature level is kept.
* If featureLevel is higher than getSupportedFeatureLevel(), an exception
* is thrown, or the program is terminated if exceptions are disabled.
* @param featureLevel the feature level to activate. If featureLevel is lower than {@link
* #getActiveFeatureLevel}, the current (higher) feature level is kept. If
* featureLevel is higher than {@link #getSupportedFeatureLevel}, or if the
* engine was initialized at feature level 0, an exception is thrown, or the
* program is terminated if exceptions are disabled.
*
* @return the active feature level.
*
* @see Builder#featureLevel
* @see #getSupportedFeatureLevel
* @see #getActiveFeatureLevel
*/
@@ -1149,6 +1166,7 @@ public class Engine {
private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB,
long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB,
long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount);
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);
}
@@ -21,6 +21,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Size;
import com.google.android.filament.proguard.UsedByNative;
import com.google.android.filament.Engine.FeatureLevel;
import java.nio.Buffer;
import java.util.ArrayList;
@@ -46,6 +47,8 @@ public class Material {
static final BlendingMode[] sBlendingModeValues = BlendingMode.values();
static final RefractionMode[] sRefractionModeValues = RefractionMode.values();
static final RefractionType[] sRefractionTypeValues = RefractionType.values();
static final ReflectionMode[] sReflectionModeValues = ReflectionMode.values();
static final FeatureLevel[] sFeatureLevelValues = FeatureLevel.values();
static final VertexDomain[] sVertexDomainValues = VertexDomain.values();
static final CullingMode[] sCullingModeValues = CullingMode.values();
static final VertexBuffer.VertexAttribute[] sVertexAttributeValues =
@@ -181,6 +184,18 @@ public class Material {
THIN
}
/**
* Supported reflection modes
*
* @see
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/lighting:reflections">
* Lighting: reflections</a>
*/
public enum ReflectionMode {
DEFAULT,
SCREEN_SPACE
}
/**
* Supported types of vertex domains
*
@@ -437,6 +452,28 @@ public class Material {
return EnumCache.sRefractionTypeValues[nGetRefractionType(getNativeObject())];
}
/**
* Returns the reflection mode of this material.
*
* @see
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/lighting:reflections">
* Lighting: reflections</a>
*/
public ReflectionMode getReflectionMode() {
return EnumCache.sReflectionModeValues[nGetReflectionMode(getNativeObject())];
}
/**
* Returns the minimum required feature level for this material.
*
* @see
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/general:featurelevel">
* General: featureLevel</a>
*/
public FeatureLevel getFeatureLevel() {
return EnumCache.sFeatureLevelValues[nGetFeatureLevel(getNativeObject())];
}
/**
* Returns the vertex domain of this material.
*
@@ -932,6 +969,8 @@ public class Material {
private static native float nGetSpecularAntiAliasingThreshold(long nativeMaterial);
private static native int nGetRefractionMode(long nativeMaterial);
private static native int nGetRefractionType(long nativeMaterial);
private static native int nGetReflectionMode(long nativeMaterial);
private static native int nGetFeatureLevel(long nativeMaterial);
private static native int nGetParameterCount(long nativeMaterial);
@@ -227,8 +227,10 @@ public class UiHelper {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
}
// the call above won't cause TextureView.onSurfaceTextureSizeChanged()
mRenderCallback.onResized(width, height);
if (mRenderCallback != null) {
// the call above won't cause TextureView.onSurfaceTextureSizeChanged()
mRenderCallback.onResized(width, height);
}
}
@Override
@@ -298,7 +300,6 @@ public class UiHelper {
/**
* Checks whether we are ready to render into the attached surface.
*
* Using OpenGL ES when this returns true, will result in drawing commands being lost,
* HOWEVER, GLES state will be preserved. This is useful to initialize the engine.
*
@@ -343,7 +344,6 @@ public class UiHelper {
/**
* Controls whether the render target (SurfaceView or TextureView) is opaque or not.
* The render target is considered opaque by default.
*
* Must be called before calling {@link #attachTo(SurfaceView)}, {@link #attachTo(TextureView)},
* or {@link #attachTo(SurfaceHolder)}.
*
@@ -366,10 +366,8 @@ public class UiHelper {
* positioned above other surfaces but below the activity's surface. This property
* only has an effect when used in combination with {@link #setOpaque(boolean) setOpaque(false)}
* and does not affect TextureView targets.
*
* Must be called before calling {@link #attachTo(SurfaceView)}
* or {@link #attachTo(TextureView)}.
*
* Has no effect when using {@link #attachTo(SurfaceHolder)}.
*
* @param overlay Indicates whether the render target should be rendered below the activity's
@@ -390,7 +388,6 @@ public class UiHelper {
/**
* Associate UiHelper with a SurfaceView.
*
* As soon as SurfaceView is ready (i.e. has a Surface), we'll create the
* EGL resources needed, and call user callbacks if needed.
*/
@@ -412,21 +409,23 @@ public class UiHelper {
final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
public void surfaceCreated(@NonNull SurfaceHolder holder) {
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
createSwapChain(holder.getSurface());
}
@Override
public void surfaceChanged(
SurfaceHolder holder, int format, int width, int height) {
@NonNull SurfaceHolder holder, int format, int width, int height) {
// Note: this is always called at least once after surfaceCreated()
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
mRenderCallback.onResized(width, height);
if (mRenderCallback != null) {
mRenderCallback.onResized(width, height);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
destroySwapChain();
}
@@ -450,7 +449,6 @@ public class UiHelper {
/**
* Associate UiHelper with a TextureView.
*
* As soon as TextureView is ready (i.e. has a buffer), we'll create the
* EGL resources needed, and call user callbacks if needed.
*/
@@ -463,7 +461,7 @@ public class UiHelper {
TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(
SurfaceTexture surfaceTexture, int width, int height) {
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
@@ -478,40 +476,44 @@ public class UiHelper {
createSwapChain(surface);
// Call this the first time because onSurfaceTextureSizeChanged()
// isn't called at initialization time
mRenderCallback.onResized(width, height);
if (mRenderCallback != null) {
// Call this the first time because onSurfaceTextureSizeChanged()
// isn't called at initialization time
mRenderCallback.onResized(width, height);
}
}
@Override
public void onSurfaceTextureSizeChanged(
SurfaceTexture surfaceTexture, int width, int height) {
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()");
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
mRenderCallback.onResized(mDesiredWidth, mDesiredHeight);
} else {
mRenderCallback.onResized(width, height);
if (mRenderCallback != null) {
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
mRenderCallback.onResized(mDesiredWidth, mDesiredHeight);
} else {
mRenderCallback.onResized(width, height);
}
// We must recreate the SwapChain to guarantee that it sees the new size.
// More precisely, for an EGL client, the EGLSurface must be recreated. For
// a Vulkan client, the SwapChain must be recreated. Calling
// onNativeWindowChanged() will accomplish that.
// This requirement comes from SurfaceTexture.setDefaultBufferSize()
// documentation.
TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface;
mRenderCallback.onNativeWindowChanged(textureViewHandler.getSurface());
}
// We must recreate the SwapChain to guarantee that it sees the new size.
// More precisely, for an EGL client, the EGLSurface must be recreated. For
// a Vulkan client, the SwapChain must be recreated. Calling
// onNativeWindowChanged() will accomplish that.
// This requirement comes from SurfaceTexture.setDefaultBufferSize()
// documentation.
TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface;
mRenderCallback.onNativeWindowChanged(textureViewHandler.getSurface());
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()");
destroySwapChain();
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { }
};
view.setSurfaceTextureListener(listener);
@@ -519,14 +521,15 @@ public class UiHelper {
// in case the View's SurfaceTexture already existed
if (view.isAvailable()) {
SurfaceTexture surfaceTexture = view.getSurfaceTexture();
listener.onSurfaceTextureAvailable(surfaceTexture, mDesiredWidth, mDesiredHeight);
if (surfaceTexture != null) {
listener.onSurfaceTextureAvailable(surfaceTexture, mDesiredWidth, mDesiredHeight);
}
}
}
}
/**
* Associate UiHelper with a SurfaceHolder.
*
* As soon as a Surface is created, we'll create the
* EGL resources needed, and call user callbacks if needed.
*/
@@ -539,20 +542,22 @@ public class UiHelper {
final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
createSwapChain(holder.getSurface());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
// Note: this is always called at least once after surfaceCreated()
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
mRenderCallback.onResized(width, height);
if (mRenderCallback != null) {
mRenderCallback.onResized(width, height);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
destroySwapChain();
}
@@ -587,7 +592,9 @@ public class UiHelper {
}
private void createSwapChain(@NonNull Surface surface) {
mRenderCallback.onNativeWindowChanged(surface);
if (mRenderCallback != null) {
mRenderCallback.onNativeWindowChanged(surface);
}
mHasSwapChain = true;
}
@@ -595,7 +602,9 @@ public class UiHelper {
if (mRenderSurface != null) {
mRenderSurface.detach();
}
mRenderCallback.onDetachedFromSurface();
if (mRenderCallback != null) {
mRenderCallback.onDetachedFromSurface();
}
mHasSwapChain = false;
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.45.1
VERSION_NAME=1.46.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -110,7 +110,7 @@ class MainActivity : Activity() {
}
private fun setupFilament() {
engine = Engine.create()
engine = Engine.Builder().featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0).build()
renderer = engine.createRenderer()
scene = engine.createScene()
view = engine.createView()
@@ -120,13 +120,8 @@ class MainActivity : Activity() {
private fun setupView() {
scene.skybox = Skybox.Builder().color(0.035f, 0.035f, 0.035f, 1.0f).build(engine)
if (engine.activeFeatureLevel == Engine.FeatureLevel.FEATURE_LEVEL_0) {
// post-processing is not supported at feature level 0
view.isPostProcessingEnabled = false
} else {
// NOTE: Try to disable post-processing (tone-mapping, etc.) to see the difference
// view.isPostProcessingEnabled = false
}
// post-processing is not supported at feature level 0
view.isPostProcessingEnabled = false
// Tell the view which camera we want to use
view.camera = camera
+9
View File
@@ -191,6 +191,7 @@ function build_clean {
rm -Rf android/filamat-android/build android/filamat-android/.externalNativeBuild android/filamat-android/.cxx
rm -Rf android/gltfio-android/build android/gltfio-android/.externalNativeBuild android/gltfio-android/.cxx
rm -Rf android/filament-utils-android/build android/filament-utils-android/.externalNativeBuild android/filament-utils-android/.cxx
rm -f compile_commands.json
}
function build_clean_aggressive {
@@ -232,6 +233,8 @@ function build_desktop_target {
${ASAN_UBSAN_OPTION} \
${architectures} \
../..
ln -sf "out/cmake-${lc_target}/compile_commands.json" \
../../compile_commands.json
fi
${BUILD_COMMAND} ${build_targets}
@@ -287,6 +290,8 @@ function build_webgl_with_target {
-DCMAKE_INSTALL_PREFIX="../webgl-${lc_target}/filament" \
-DWEBGL=1 \
../..
ln -sf "out/cmake-webgl-${lc_target}/compile_commands.json" \
../../compile_commands.json
${BUILD_COMMAND} ${BUILD_TARGETS}
)
fi
@@ -359,6 +364,8 @@ function build_android_target {
${MATOPT_OPTION} \
${VULKAN_ANDROID_OPTION} \
../..
ln -sf "out/cmake-android-${lc_target}-${arch}/compile_commands.json" \
../../compile_commands.json
fi
# We must always install Android libraries to build the AAR
@@ -591,6 +598,8 @@ function build_ios_target {
${MATDBG_OPTION} \
${MATOPT_OPTION} \
../..
ln -sf "out/cmake-ios-${lc_target}-${arch}/compile_commands.json" \
../../compile_commands.json
fi
${BUILD_COMMAND}
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.
+39 -4
View File
@@ -228,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
@@ -245,11 +244,18 @@ set(MATERIAL_SRCS
src/materials/vsmMipmap.mat
)
set(MATERIAL_ES2_SRCS
set(MATERIAL_FL0_SRCS
src/materials/defaultMaterial0.mat
src/materials/skybox0.mat
)
# TODO(exv): Replace the below duplicated materials with a command-line option
# to matc to force disable including ESSL 1.0 code in FL0 materials.
set(MATERIAL_SWITCH_FL0_SRCS_HACK
src/materials/blitLow.mat
)
# Embed the binary resource blob for materials.
get_resgen_vars(${RESOURCE_DIR} materials)
list(APPEND PRIVATE_HDRS ${RESGEN_HEADER})
@@ -274,6 +280,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
# ==================================================================================================
@@ -315,8 +326,32 @@ foreach (mat_src ${MATERIAL_SRCS})
list(APPEND MATERIAL_BINS ${output_path})
endforeach()
if (IS_MOBILE_TARGET AND FILAMENT_SUPPORTS_OPENGL)
foreach (mat_src ${MATERIAL_ES2_SRCS})
# HACK: Pick between the normal and FL0-exclusionary variants of the materials
# based on the value of FILAMENT_ENABLE_FEATURE_LEVEL_0.
foreach (mat_src ${MATERIAL_SWITCH_FL0_SRCS_HACK})
get_filename_component(localname "${mat_src}" NAME_WE)
get_filename_component(fullname "${mat_src}" ABSOLUTE)
set(output_path "${MATERIAL_DIR}/${localname}.filamat")
if (NOT FILAMENT_ENABLE_FATURE_LEVEL_0)
# Pick the non-FL0 variant instead.
string(REGEX REPLACE "\\.mat$" "1.mat" fullname "${fullname}")
string(REGEX REPLACE "\\.mat$" "1.mat" mat_src "${mat_src}")
endif()
add_custom_command(
OUTPUT ${output_path}
COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname}
MAIN_DEPENDENCY ${fullname}
DEPENDS matc
COMMENT "Compiling material ${mat_src} to ${output_path}"
)
list(APPEND MATERIAL_BINS ${output_path})
endforeach()
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")
@@ -154,6 +154,19 @@ enum class ShaderLanguage {
MSL = 3,
};
static constexpr const char* shaderLanguageToString(ShaderLanguage shaderLanguage) {
switch (shaderLanguage) {
case ShaderLanguage::ESSL1:
return "ESSL 1.0";
case ShaderLanguage::ESSL3:
return "ESSL 3.0";
case ShaderLanguage::SPIRV:
return "SPIR-V";
case ShaderLanguage::MSL:
return "MSL";
}
}
/**
* Bitmask for selecting render buffers
*/
+12 -9
View File
@@ -373,16 +373,19 @@ constexpr inline GLenum getCullingMode(CullingMode mode) noexcept {
constexpr inline std::pair<GLenum, GLenum> textureFormatToFormatAndType(
TextureFormat format) noexcept {
switch (format) {
case TextureFormat::RGB8: return { GL_RGB, GL_UNSIGNED_BYTE };
case TextureFormat::RGBA8: return { GL_RGBA, GL_UNSIGNED_BYTE };
case TextureFormat::RGB565: return { GL_RGB, GL_UNSIGNED_SHORT_5_6_5 };
case TextureFormat::RGB5_A1: return { GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 };
case TextureFormat::RGBA4: return { GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 };
case TextureFormat::DEPTH16: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT };
case TextureFormat::DEPTH24: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_INT };
case TextureFormat::R8: return { 0x1909 /*GL_LUMINANCE*/, GL_UNSIGNED_BYTE };
case TextureFormat::RGB8: return { GL_RGB, GL_UNSIGNED_BYTE };
case TextureFormat::SRGB8: return { GL_RGB, GL_UNSIGNED_BYTE };
case TextureFormat::RGBA8: return { GL_RGBA, GL_UNSIGNED_BYTE };
case TextureFormat::SRGB8_A8: return { GL_RGBA, GL_UNSIGNED_BYTE };
case TextureFormat::RGB565: return { GL_RGB, GL_UNSIGNED_SHORT_5_6_5 };
case TextureFormat::RGB5_A1: return { GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 };
case TextureFormat::RGBA4: return { GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 };
case TextureFormat::DEPTH16: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT };
case TextureFormat::DEPTH24: return { GL_DEPTH_COMPONENT, GL_UNSIGNED_INT };
case TextureFormat::DEPTH24_STENCIL8:
return { GL_DEPTH24_STENCIL8, GL_UNSIGNED_INT_24_8 };
default: return { GL_NONE, GL_NONE };
return { GL_DEPTH24_STENCIL8, GL_UNSIGNED_INT_24_8 };
default: return { GL_NONE, GL_NONE };
}
}
+22 -11
View File
@@ -147,17 +147,28 @@ OpenGLContext::OpenGLContext() noexcept {
#ifndef NDEBUG
// this is useful for development
slog.v
<< "GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT" << gets.max_anisotropy << '\n'
<< "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS" << gets.max_combined_texture_image_units << '\n'
<< "GL_MAX_DRAW_BUFFERS" << gets.max_draw_buffers << '\n'
<< "GL_MAX_RENDERBUFFER_SIZE" << gets.max_renderbuffer_size << '\n'
<< "GL_MAX_SAMPLES" << gets.max_samples << '\n'
<< "GL_MAX_TEXTURE_IMAGE_UNITS" << gets.max_texture_image_units << '\n'
<< "GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS" << gets.max_transform_feedback_separate_attribs << '\n'
<< "GL_MAX_UNIFORM_BLOCK_SIZE" << gets.max_uniform_block_size << '\n'
<< "GL_MAX_UNIFORM_BUFFER_BINDINGS" << gets.max_uniform_buffer_bindings << '\n'
<< "GL_NUM_PROGRAM_BINARY_FORMATS" << gets.num_program_binary_formats << '\n'
<< "GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT" << gets.uniform_buffer_offset_alignment << '\n'
<< "GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = "
<< gets.max_anisotropy << '\n'
<< "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = "
<< gets.max_combined_texture_image_units << '\n'
<< "GL_MAX_DRAW_BUFFERS = "
<< gets.max_draw_buffers << '\n'
<< "GL_MAX_RENDERBUFFER_SIZE = "
<< gets.max_renderbuffer_size << '\n'
<< "GL_MAX_SAMPLES = "
<< gets.max_samples << '\n'
<< "GL_MAX_TEXTURE_IMAGE_UNITS = "
<< gets.max_texture_image_units << '\n'
<< "GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = "
<< gets.max_transform_feedback_separate_attribs << '\n'
<< "GL_MAX_UNIFORM_BLOCK_SIZE = "
<< gets.max_uniform_block_size << '\n'
<< "GL_MAX_UNIFORM_BUFFER_BINDINGS = "
<< gets.max_uniform_buffer_bindings << '\n'
<< "GL_NUM_PROGRAM_BINARY_FORMATS = "
<< gets.num_program_binary_formats << '\n'
<< "GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = "
<< gets.uniform_buffer_offset_alignment << '\n'
;
flush(slog.v);
#endif
+37 -10
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);
}
}
}
@@ -1290,7 +1292,9 @@ void OpenGLDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
checkDimensions(rt->gl.color[i], color[i].level);
}
}
glDrawBuffers((GLsizei)maxDrawBuffers, bufs);
if (UTILS_LIKELY(!getContext().isES2())) {
glDrawBuffers((GLsizei)maxDrawBuffers, bufs);
}
CHECK_GL_ERROR(utils::slog.e)
}
#endif
@@ -1451,6 +1455,11 @@ void OpenGLDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
DEBUG_MARKER()
if (sbh) {
GLSamplerGroup* sb = handle_cast<GLSamplerGroup*>(sbh);
for (auto& binding : mSamplerBindings) {
if (binding == sb) {
binding = nullptr;
}
}
destruct(sbh, sb);
}
}
@@ -1459,9 +1468,9 @@ void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
DEBUG_MARKER()
if (th) {
auto& gl = mContext;
GLTexture* t = handle_cast<GLTexture*>(th);
if (UTILS_LIKELY(!t->gl.imported)) {
auto& gl = mContext;
if (UTILS_LIKELY(t->usage & TextureUsage::SAMPLEABLE)) {
gl.unbindTexture(t->gl.target, t->gl.id);
if (UTILS_UNLIKELY(t->hwStream)) {
@@ -1479,6 +1488,8 @@ void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
if (t->gl.sidecarRenderBufferMS) {
glDeleteRenderbuffers(1, &t->gl.sidecarRenderBufferMS);
}
} else {
gl.unbindTexture(t->gl.target, t->gl.id);
}
destruct(th, t);
}
@@ -1775,6 +1786,10 @@ bool OpenGLDriver::isRenderTargetFormatSupported(TextureFormat format) {
// support more formats, but it requires querying GL_INTERNALFORMAT_SUPPORTED which is not
// available in OpenGL ES.
auto& gl = mContext;
if (UTILS_UNLIKELY(gl.isES2())) {
auto [es2format, type] = textureFormatToFormatAndType(format);
return es2format != GL_NONE && type != GL_NONE;
}
switch (format) {
// Core formats.
case TextureFormat::R8:
@@ -2269,8 +2284,16 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
return;
}
GLenum const glFormat = getFormat(p.format);
GLenum const glType = getType(p.type);
GLenum glFormat;
GLenum glType;
if (mContext.isES2()) {
auto formatAndType = textureFormatToFormatAndType(t->format);
glFormat = formatAndType.first;
glType = formatAndType.second;
} else {
glFormat = getFormat(p.format);
glType = getType(p.type);
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (!gl.isES2()) {
@@ -2880,7 +2903,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
@@ -212,6 +212,7 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept {
assert_invariant(binding < Program::SAMPLER_BINDING_COUNT);
auto const * const sb = samplerBindings[binding];
assert_invariant(sb);
if (!sb) continue; // should never happen, this would be a user error.
for (uint8_t j = 0, m = sb->textureUnitEntries.size(); j < m; ++j, ++tmu) { // "<=" on purpose here
const GLTexture* const t = sb->textureUnitEntries[j].texture;
if (t) { // program may not use all samplers of sampler group
@@ -228,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;
@@ -168,7 +168,7 @@ error:
}
bool PlatformWGL::isExtraContextSupported() const noexcept {
return true;
return false;
}
void PlatformWGL::createContext(bool shared) {
+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;
@@ -272,6 +272,8 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
return;
}
assert_invariant(hostData->size > 0 && "Data is empty");
// Otherwise, use vkCmdCopyBufferToImage.
void* mapped = nullptr;
VulkanStage const* stage = mStagePool.acquireStage(hostData->size);
+24 -14
View File
@@ -172,10 +172,11 @@ public:
using Platform = backend::Platform;
using Backend = backend::Backend;
using DriverConfig = backend::Platform::DriverConfig;
using FeatureLevel = backend::FeatureLevel;
/**
* Config is used to define the memory footprint used by the engine, such as the
* command buffer size. Config can be used to customize engine requirements based
* command buffer size. Config can be used to customize engine requirements based
* on the applications needs.
*
* .perRenderPassArenaSizeMB (default: 3 MiB)
@@ -351,6 +352,12 @@ public:
*/
Builder& sharedContext(void* sharedContext) noexcept;
/**
* @param featureLevel The feature level at which initialize Filament.
* @return A reference to this Builder for chaining calls.
*/
Builder& featureLevel(FeatureLevel featureLevel) noexcept;
#if UTILS_HAS_THREADING
/**
* Creates the filament Engine asynchronously.
@@ -482,9 +489,6 @@ public:
*/
static void destroy(Engine* engine);
using FeatureLevel = backend::FeatureLevel;
/**
* Query the feature level supported by the selected backend.
*
@@ -496,17 +500,23 @@ public:
FeatureLevel getSupportedFeatureLevel() const noexcept;
/**
* Activate all features of a given feature level. By default FeatureLevel::FEATURE_LEVEL_1 is
* active. The selected feature level must not be higher than the value returned by
* getActiveFeatureLevel() and it's not possible lower the active feature level.
* Activate all features of a given feature level. If an explicit feature level is not specified
* at Engine initialization time via Builder::featureLevel, the default feature level is
* FeatureLevel::FEATURE_LEVEL_0 on devices not compatible with GLES 3.0; otherwise, the default
* is FeatureLevel::FEATURE_LEVEL_1. The selected feature level must not be higher than the
* value returned by getActiveFeatureLevel() and it's not possible lower the active feature
* level. Additionally, it is not possible to modify the feature level at all if the Engine was
* initialized at FeatureLevel::FEATURE_LEVEL_0.
*
* @param featureLevel the feature level to activate. If featureLevel is lower than
* getActiveFeatureLevel(), the current (higher) feature level is kept.
* If featureLevel is higher than getSupportedFeatureLevel(), an exception
* is thrown, or the program is terminated if exceptions are disabled.
* getActiveFeatureLevel(), the current (higher) feature level is kept. If
* featureLevel is higher than getSupportedFeatureLevel(), or if the engine
* was initialized at feature level 0, an exception is thrown, or the
* program is terminated if exceptions are disabled.
*
* @return the active feature level.
*
* @see Builder::featureLevel
* @see getSupportedFeatureLevel
* @see getActiveFeatureLevel
*/
@@ -827,14 +837,14 @@ public:
#if defined(__EMSCRIPTEN__)
/**
* WebGL only: Tells the driver to reset any internal state tracking if necessary.
*
* This is only useful when integrating an external renderer into Filament on platforms
*
* This is only useful when integrating an external renderer into Filament on platforms
* like WebGL, where share contexts do not exist. Filament keeps track of the GL
* state it has set (like which texture is bound), and does not re-set that state if
* it does not think it needs to. However, if an external renderer has set different
* state in the mean time, Filament will use that new state unknowingly.
*
* If you are in this situation, call this function - ideally only once per frame,
*
* If you are in this situation, call this function - ideally only once per frame,
* immediately after calling Engine::execute().
*/
void resetBackendState() noexcept;
+3
View File
@@ -294,6 +294,9 @@ public:
//! Returns the reflection mode used by this material.
ReflectionMode getReflectionMode() const noexcept;
//! Returns the minimum required feature level for this material.
backend::FeatureLevel getFeatureLevel() const noexcept;
/**
* Returns the number of parameters declared by this material.
* The returned value can be 0.
+3 -3
View File
@@ -104,12 +104,12 @@ Froxelizer::Froxelizer(FEngine& engine)
static_assert(std::is_same_v<RecordBufferType, uint8_t>,
"Record Buffer must use bytes");
if (UTILS_UNLIKELY(engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)) {
DriverApi& driverApi = engine.getDriverApi();
if (UTILS_UNLIKELY(driverApi.getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)) {
return;
}
DriverApi& driverApi = engine.getDriverApi();
mFroxelBufferEntryCount = std::min(
FROXEL_BUFFER_MAX_ENTRY_COUNT,
engine.getDriverApi().getMaxUniformBufferSize() / 16u);
+4
View File
@@ -120,6 +120,10 @@ ReflectionMode Material::getReflectionMode() const noexcept {
return downcast(this)->getReflectionMode();
}
FeatureLevel Material::getFeatureLevel() const noexcept {
return downcast(this)->getFeatureLevel();
}
bool Material::hasParameter(const char* name) const noexcept {
return downcast(this)->hasParameter(name);
}
+1 -1
View File
@@ -60,7 +60,7 @@ void PerShadowMapUniforms::prepareCamera(Transaction const& transaction,
s.viewFromClipMatrix = viewFromClip; // 1/projection
s.clipFromWorldMatrix[0] = clipFromWorld; // projection * view
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin));
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldTransform));
s.clipTransform = camera.clipTransform;
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
+4 -3
View File
@@ -75,7 +75,7 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n
s.clipFromViewMatrix = clipFromView; // projection
s.viewFromClipMatrix = viewFromClip; // 1/projection
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin));
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldTransform));
s.clipTransform = camera.clipTransform;
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
@@ -151,7 +151,7 @@ void PerViewUniforms::prepareFog(FEngine& engine, const CameraInfo& cameraInfo,
// why we store the cofactor matrix.
mat4f const viewFromWorld = cameraInfo.view;
mat4 const worldFromUserWorld = cameraInfo.worldOrigin;
mat4 const worldFromUserWorld = cameraInfo.worldTransform;
mat4 const worldFromFog = worldFromUserWorld * userWorldFromFog;
mat4 const viewFromFog = viewFromWorld * worldFromFog;
@@ -245,6 +245,7 @@ void PerViewUniforms::prepareMaterialGlobals(
}
void PerViewUniforms::prepareSSR(Handle<HwTexture> ssr,
bool disableSSR,
float refractionLodOffset,
ScreenSpaceReflectionsOptions const& ssrOptions) noexcept {
@@ -255,7 +256,7 @@ void PerViewUniforms::prepareSSR(Handle<HwTexture> ssr,
auto& s = mUniforms.edit();
s.refractionLodOffset = refractionLodOffset;
s.ssrDistance = ssrOptions.enabled ? ssrOptions.maxDistance : 0.0f;
s.ssrDistance = (ssrOptions.enabled && !disableSSR) ? ssrOptions.maxDistance : 0.0f;
}
void PerViewUniforms::prepareHistorySSR(Handle<HwTexture> ssr,
+1
View File
@@ -95,6 +95,7 @@ public:
// screen-space reflection and/or refraction (SSR)
void prepareSSR(TextureHandle ssr,
bool disableSSR,
float refractionLodOffset,
ScreenSpaceReflectionsOptions const& ssrOptions) noexcept;
+15 -4
View File
@@ -207,10 +207,13 @@ PostProcessManager::PostProcessMaterial& PostProcessManager::getPostProcessMater
#define MATERIAL(n) MATERIALS_ ## n ## _DATA, MATERIALS_ ## n ## _SIZE
static const PostProcessManager::MaterialInfo sMaterialListFeatureLevel0[] = {
{ "blitLow", MATERIAL(BLITLOW) },
};
static const PostProcessManager::MaterialInfo sMaterialList[] = {
{ "bilateralBlur", MATERIAL(BILATERALBLUR) },
{ "bilateralBlurBentNormals", MATERIAL(BILATERALBLURBENTNORMALS) },
{ "blitLow", MATERIAL(BLITLOW) },
{ "bloomDownsample", MATERIAL(BLOOMDOWNSAMPLE) },
{ "bloomDownsample2x", MATERIAL(BLOOMDOWNSAMPLE2X) },
{ "bloomDownsample9", MATERIAL(BLOOMDOWNSAMPLE9) },
@@ -273,10 +276,17 @@ void PostProcessManager::init() noexcept {
driver.isWorkaroundNeeded(Workaround::ALLOW_READ_ONLY_ANCILLARY_FEEDBACK_LOOP);
#pragma nounroll
for (auto const& info : sMaterialList) {
for (auto const& info : sMaterialListFeatureLevel0) {
registerPostProcessMaterial(info.name, info);
}
if (mEngine.getActiveFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1) {
#pragma nounroll
for (auto const& info : sMaterialList) {
registerPostProcessMaterial(info.name, info);
}
}
mStarburstTexture = driver.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::R8, 1, 256, 1, 1, TextureUsage::DEFAULT);
@@ -380,7 +390,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
// generate depth pass at the requested resolution
auto& structurePass = fg.addPass<StructurePassData>("Structure Pass",
[&](FrameGraph::Builder& builder, auto& data) {
bool const isES2 = mEngine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0;
bool const isES2 = mEngine.getDriverApi().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0;
data.depth = builder.createTexture("Structure Buffer", {
.width = width, .height = height,
.levels = uint8_t(levelCount),
@@ -2704,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);
}
+3 -3
View File
@@ -100,7 +100,7 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
"Depth/Stencil Buffer" : "Depth Buffer";
bool const isES2 =
engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0;
engine.getDriverApi().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0;
TextureFormat const stencilFormat = isES2 ?
TextureFormat::DEPTH24_STENCIL8 : TextureFormat::DEPTH32F_STENCIL8;
@@ -193,8 +193,8 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
TextureHandle const ssr = data.ssr ?
resources.getTexture(data.ssr) : engine.getOneTextureArray();
view.prepareSSR(ssr, config.ssrLodOffset,
view.getScreenSpaceReflectionsOptions());
view.prepareSSR(ssr, config.screenSpaceReflectionHistoryNotReady,
config.ssrLodOffset, view.getScreenSpaceReflectionsOptions());
// Note: here we can't use data.color's descriptor for the viewport because
// the actual viewport might be offset when the target is the swapchain.
+2
View File
@@ -67,6 +67,8 @@ public:
bool hasScreenSpaceReflectionsOrRefractions;
// Use a depth format with a stencil component.
bool enabledStencilBuffer;
// whether the screenspace reflections history buffer is initialized
bool screenSpaceReflectionHistoryNotReady;
};
static FrameGraphId<FrameGraphTexture> colorPass(
+77 -113
View File
@@ -77,20 +77,13 @@ void ShadowMap::initialize(size_t lightIndex, ShadowType shadowType,
mFace = face;
}
mat4f ShadowMap::getDirectionalLightViewMatrix(float3 direction, float3 position) noexcept {
auto z_axis = direction;
auto norm_up = float3{ 0, 1, 0 };
if (UTILS_UNLIKELY(std::abs(dot(z_axis, norm_up)) > 0.999f)) {
// Fix up vector if we're degenerate (looking straight up, basically)
norm_up = { norm_up.z, norm_up.x, norm_up.y };
}
auto x_axis = normalize(cross(z_axis, norm_up));
auto y_axis = cross(x_axis, z_axis);
const mat4f Mm{
float4{ x_axis, 0 },
float4{ y_axis, 0 },
float4{ -z_axis, 0 },
float4{ position, 1 }};
math::mat4f ShadowMap::getDirectionalLightViewMatrix(math::float3 direction, math::float3 up,
math::float3 position) noexcept {
// 1. we use the x-axis as the "up" reference so that the math is stable when the light
// is pointing down, which is a common case for lights.
// 2. we do the math in double to avoid some precision issues when the light is almost
// straight (i.e. parallel to the x-axis)
mat4f const Mm = mat4f{ mat4::lookTo(direction, position, up) };
return FCamera::rigidTransformInverse(Mm);
}
@@ -105,7 +98,7 @@ math::mat4f ShadowMap::getPointLightViewMatrix(backend::TextureCubemapFace face,
case TextureCubemapFace::POSITIVE_Z: direction = { 0, 0, 1 }; break;
case TextureCubemapFace::NEGATIVE_Z: direction = { 0, 0, -1 }; break;
}
const mat4f Mv = getDirectionalLightViewMatrix(direction, position);
const mat4f Mv = getDirectionalLightViewMatrix(direction, { 0, 1, 0 }, position);
return Mv;
}
@@ -115,12 +108,14 @@ 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 = params.options.transform * lightData.elementAt<FScene::DIRECTION>(index);
const auto direction = lightData.elementAt<FScene::SHADOW_DIRECTION>(index);
auto [Mv, znear, zfar, lsClippedShadowVolume, vertexCount, visibleShadows] =
computeDirectionalShadowBounds(engine, direction, params, camera, sceneInfo);
@@ -144,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
@@ -166,11 +162,22 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
// This is the most important step to increase the quality of the shadow map.
//
// In LiPSM mode, we're using the warped space here.
const mat4f F = computeFocusMatrix(LMpMv, WLMp,
sceneInfo.wsShadowReceiversVolume,
float4 f = computeFocusParams(LMpMv, WLMp,
lsClippedShadowVolume, vertexCount,
camera, sceneInfo.csNearFar,
shadowMapInfo.shadowDimension, params.options.stable);
params.options.shadowFar, params.options.stable);
if (params.options.stable) {
const auto lsRef = lightData.elementAt<FScene::SHADOW_REF>(index);
snapLightFrustum(f.xy, f.zw, lsRef, shadowMapInfo.shadowDimension);
}
const mat4f F(mat4f::row_major_init {
f.x, 0.0f, 0.0f, f.z,
0.0f, f.y, 0.0f, f.w,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
});
/*
* Final shadow map transform
@@ -222,7 +229,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
mCamera->setCustomProjection(mat4(Mn * F * WLMp), znear, zfar);
// for the debug camera, we need to undo the world origin
mDebugCamera->setCustomProjection(mat4(S * b * camera.worldOrigin), znear, zfar);
mDebugCamera->setCustomProjection(mat4(S * b * camera.worldTransform), znear, zfar);
mHasVisibleShadows = true;
@@ -297,7 +304,7 @@ ShadowMap::ShaderParameters ShadowMap::updateSpot(FEngine& engine,
auto radius = lightData.elementAt<FScene::POSITION_RADIUS>(index).w;
auto li = lightData.elementAt<FScene::LIGHT_INSTANCE>(index);
const FLightManager::ShadowParams& params = lcm.getShadowParams(li);
const mat4f Mv = getDirectionalLightViewMatrix(direction, position);
const mat4f Mv = getDirectionalLightViewMatrix(direction, { 0, 1, 0 }, position);
// We only keep this for reference. updateSceneInfoSpot() is quite expensive on large scenes
// currently, and only needed to find a near/far. Instead, we just use a small near and the
@@ -401,7 +408,8 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
// 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.
// This will be adjusted later because of how we compute the depth metric for VSM.
mat4f const MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction);
mat4f const MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction,
normalize(camera.worldTransform[0].xyz));
Aabb lsLightFrustumBounds = computeLightFrustumBounds(
@@ -455,10 +463,6 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
std::max(lsLightFrustumBounds.min.z, sceneInfo.lsCastersNearFar[1]);
}
// Now that we know the znear (-lsLightFrustumBounds.max.z), adjust the light's position such
// that znear = 0, this is only needed for VSM, but doesn't hurt PCF.
const mat4f Mv = getDirectionalLightViewMatrix(direction, direction * -lsLightFrustumBounds.max.z);
// near / far planes are specified relative to the direction the eye is looking at
// i.e. the -z axis (see: ortho)
const float znear = 0.0f;
@@ -473,6 +477,11 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
v.z -= lsLightFrustumBounds.max.z;
}
// Now that we know the znear (-lsLightFrustumBounds.max.z), adjust the light's position such
// that znear = 0, this is only needed for VSM, but doesn't hurt PCF.
const mat4f Mv = getDirectionalLightViewMatrix(direction, normalize(camera.worldTransform[0].xyz),
direction * -lsLightFrustumBounds.max.z);
return { Mv, znear, zfar, lsClippedShadowVolume, vertexCount, true };
}
@@ -577,65 +586,36 @@ math::mat4f ShadowMap::computeLightRotation(math::float3 const& lsDirection) noe
return L;
}
math::mat4f ShadowMap::computeFocusMatrix(
const mat4f& LMpMv, const mat4f& WLMp,
Aabb const& wsShadowReceiversVolume,
math::float4 ShadowMap::computeFocusParams(
mat4f const& LMpMv,
mat4f const& WLMp,
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
filament::CameraInfo const& camera, float2 const& csNearFar,
uint16_t shadowDimension, bool stable) noexcept {
float shadowFar, bool stable) noexcept {
float2 s, o;
float4 wsViewVolumeBoundingSphere = {};
if (stable) {
// In stable mode, the light frustum size must be fixed, so we can choose either the
// whole view frustum, or the whole scene bounding volume. We simply pick whichever
// is smaller.
// in stable mode we simply take the shadow receivers volume
const float4 shadowReceiverVolumeBoundingSphere = computeBoundingSphere(
wsShadowReceiversVolume.getCorners().data(), 8);
// in stable mode we simply take the view volume bounding sphere, but we calculate it
// In stable mode, the light frustum size must be fixed, so we choose the
// whole view frustum.
// We simply take the view volume bounding sphere, but we calculate it
// in view space, so that it's perfectly stable.
mat4f const viewFromClip = inverse(camera.cullingProjection);
Corners const wsFrustumVertices = computeFrustumCorners(viewFromClip, csNearFar);
wsViewVolumeBoundingSphere = computeBoundingSphere(wsFrustumVertices.vertices, 8);
if (shadowReceiverVolumeBoundingSphere.w < wsViewVolumeBoundingSphere.w) {
// When using the shadowReceiver volume, we don't have to use its enclosing sphere
// because (we assume) the scene volume doesn't change. Seen from the light it only
// changes when the light moves or rotates, and it is acceptable in that case to have
// non "stable" shadows (the shadow will never be stable when the light moves).
//
// On the other hand, when using the view volume, we must use a sphere because otherwise
// its projection's bounds in light space change with the camera, leading to unstable
// shadows with camera movement.
auto getViewVolumeBoundingSphere = [&]() {
if (shadowFar > 0) {
float4 const wsViewVolumeBoundingSphere = { camera.getPosition(), shadowFar };
return wsViewVolumeBoundingSphere;
} else {
mat4f const viewFromClip = inverse(camera.cullingProjection);
Corners const wsFrustumVertices = computeFrustumCorners(viewFromClip, csNearFar);
float4 const wsViewVolumeBoundingSphere =
computeBoundingSphere(wsFrustumVertices.vertices, 8);
return wsViewVolumeBoundingSphere;
}
};
wsViewVolumeBoundingSphere.w = 0;
}
if (wsViewVolumeBoundingSphere.w > 0) {
s = 1.0f / wsViewVolumeBoundingSphere.w;
o = mat4f::project(LMpMv * camera.model, wsViewVolumeBoundingSphere.xyz).xy;
} else {
// TODO: another options is the sphere around the intersections of receiver & casters
// FIXME: this is not stable with the global rotation because wsShadowReceiversVolume
// is not stable with it.
Aabb const bounds = compute2DBounds(LMpMv,
wsShadowReceiversVolume.getCorners().data(),
wsShadowReceiversVolume.getCorners().size());
assert_invariant(bounds.min.x < bounds.max.x);
assert_invariant(bounds.min.y < bounds.max.y);
s = 2.0f / float2(bounds.max.xy - bounds.min.xy);
o = float2(bounds.max.xy + bounds.min.xy) * 0.5f;
// Quantize the scale in world-space units. This value can be very small because
// if it wasn't for floating-point imprecision, the scale would be a constant.
double2 const quantizer = 0.0625;
s = 1.0 / (ceil(1.0 / (s * quantizer)) * quantizer);
}
float4 const wsViewVolumeBoundingSphere = getViewVolumeBoundingSphere();
s = 1.0f / wsViewVolumeBoundingSphere.w;
o = mat4f::project(LMpMv * camera.model, wsViewVolumeBoundingSphere.xyz).xy;
o = -s * o;
} else {
Aabb const bounds = compute2DBounds(WLMp, lsShadowVolume.data(), vertexCount);
assert_invariant(bounds.min.x < bounds.max.x);
@@ -643,29 +623,11 @@ math::mat4f ShadowMap::computeFocusMatrix(
s = 2.0f / float2(bounds.max.xy - bounds.min.xy);
o = float2(bounds.max.xy + bounds.min.xy) * 0.5f;
// TODO: we could quantize `s` here to give some stability when lispsm is disabled,
// however, the quantization paramater should probably be user settable.
o = -s * o;
}
// adjust offset for scale
o = -s * o;
if (stable) {
snapLightFrustum(s, o, LMpMv, wsShadowReceiversVolume.center(), shadowDimension);
}
const mat4f F(mat4f::row_major_init {
s.x, 0.0f, 0.0f, o.x,
0.0f, s.y, 0.0f, o.y,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
});
return F;
return { s, o };
}
// Apply these remapping in double to maintain a high precision for the depth axis
ShadowMap::TextureCoordsMapping ShadowMap::getTextureCoordsMapping(ShadowMapInfo const& info,
backend::Viewport const& viewport) noexcept {
@@ -679,6 +641,8 @@ ShadowMap::TextureCoordsMapping ShadowMap::getTextureCoordsMapping(ShadowMapInfo
0.0f, 0.0f, 0.0f, 1.0f
}};
constexpr mat4f MtInverse = inverse(Mt);
// apply the viewport transform
const float2 o = float2{ viewport.left, viewport.bottom } / float(info.atlasDimension);
const float2 s = float2{ viewport.width, viewport.height } / float(info.atlasDimension);
@@ -700,7 +664,7 @@ ShadowMap::TextureCoordsMapping ShadowMap::getTextureCoordsMapping(ShadowMapInfo
}} : mat4f{};
// Compute shadow-map texture access and viewport transform
return { Mf * (Mv * Mt), inverse(Mt) * (Mv * Mt) };
return { Mf * (Mv * Mt), MtInverse * (Mv * Mt) };
}
mat4f ShadowMap::computeVsmLightSpaceMatrix(const mat4f& lightSpacePcf,
@@ -841,8 +805,8 @@ ShadowMap::Corners ShadowMap::computeFrustumCorners(
Aabb ShadowMap::computeLightFrustumBounds(mat4f const& lightView,
Aabb const& wsShadowReceiversVolume, Aabb const& wsShadowCastersVolume,
ShadowMap::SceneInfo const& sceneInfo, bool stable, bool focusShadowCasters,
bool farUsesShadowCasters) noexcept {
ShadowMap::SceneInfo const& sceneInfo,
bool stable, bool focusShadowCasters, bool farUsesShadowCasters) noexcept {
Aabb lsLightFrustumBounds{};
float const receiversFar = sceneInfo.lsReceiversNearFar[1];
@@ -873,13 +837,13 @@ Aabb ShadowMap::computeLightFrustumBounds(mat4f const& lightView,
}
void ShadowMap::snapLightFrustum(float2& s, float2& o,
mat4f const& Mv, double3 wsSnapCoords, int2 resolution) noexcept {
double2 lsRef, int2 resolution) noexcept {
auto proj = [](mat4 m, double3 v) -> double3 {
// for directional light p.w == 1, exactly
auto p = m * v;
assert_invariant(p.w == 1.0);
return p.xyz;
auto proj2 = [](mat4 m, double2 v) -> double2 {
double2 p;
p.x = dot(double2{ m[0].x, m[1].x }, v) + m[3].x;
p.y = dot(double2{ m[0].y, m[1].y }, v) + m[3].y;
return p;
};
auto fract = [](auto v) {
@@ -896,13 +860,13 @@ void ShadowMap::snapLightFrustum(float2& s, float2& o,
});
// The (resolution * 0.5) comes from Mv having a NDC in the range -1,1 (so a range of 2).
// focused light-space
mat4 const FMv{ F * Mv };
// Another (resolution * 0.5) is there to snap on even texels, which helps with debugging
// This offsets the texture coordinates, so it has a fixed offset w.r.t the world
double2 const lsOrigin = proj(FMv, wsSnapCoords).xy;
double2 const d = (fract(lsOrigin * resolution * 0.5) * 2.0) / resolution;
// F * Mv * ref
double2 const lsFocusedOrigin = proj2(F, lsRef);
double2 const d = fract(lsFocusedOrigin * (resolution * 0.25)) / (resolution * 0.25);
// adjust offset
o -= d;
+7 -8
View File
@@ -122,8 +122,8 @@ public:
uint8_t visibleLayers;
};
static math::mat4f getDirectionalLightViewMatrix(
math::float3 direction, math::float3 position = {}) noexcept;
static math::mat4f getDirectionalLightViewMatrix(math::float3 direction, math::float3 up,
math::float3 position = {}) noexcept;
static math::mat4f getPointLightViewMatrix(backend::TextureCubemapFace face,
math::float3 position) noexcept;
@@ -246,16 +246,15 @@ private:
static inline math::mat4f computeLightRotation(math::float3 const& lsDirection) noexcept;
static inline math::mat4f computeFocusMatrix(
const math::mat4f& LMpMv,
const math::mat4f& WLMp,
Aabb const& wsShadowReceiversVolume,
static inline math::float4 computeFocusParams(
math::mat4f const& LMpMv,
math::mat4f const& WLMp,
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
filament::CameraInfo const& camera, math::float2 const& csNearFar,
uint16_t shadowDimension, bool stable) noexcept;
float shadowFar, bool stable) noexcept;
static inline void snapLightFrustum(math::float2& s, math::float2& o,
math::mat4f const& Mv, math::double3 wsSnapCoords, math::int2 resolution) noexcept;
math::double2 lsRef, math::int2 resolution) noexcept;
static inline Aabb computeLightFrustumBounds(const math::mat4f& lightView,
Aabb const& wsShadowReceiversVolume, Aabb const& wsShadowCastersVolume,
+43 -43
View File
@@ -90,40 +90,39 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(FEngine& engine, FVie
}
void ShadowMapManager::reset() noexcept {
mCascadeShadowMaps.clear();
mSpotShadowMaps.clear();
mDirectionalShadowMapCount = 0;
mSpotShadowMapCount = 0;
}
void ShadowMapManager::setDirectionalShadowMap(size_t lightIndex,
LightManager::ShadowOptions const* options) noexcept {
assert_invariant(options->shadowCascades <= CONFIG_MAX_SHADOW_CASCADES);
// this updates getCascadedShadowMap()
mDirectionalShadowMapCount = options->shadowCascades;
utils::Slice<ShadowMap> cascadedShadowMap = getCascadedShadowMap();
for (size_t c = 0; c < options->shadowCascades; c++) {
const size_t i = c;
assert_invariant(i < CONFIG_MAX_SHADOW_CASCADES);
auto* pShadowMap = getShadowMap(i);
pShadowMap->initialize(lightIndex, ShadowType::DIRECTIONAL, i, 0, options);
mCascadeShadowMaps.push_back(pShadowMap);
ShadowMap& shadowMap = cascadedShadowMap[c];
shadowMap.initialize(lightIndex, ShadowType::DIRECTIONAL, c, 0, options);
}
}
void ShadowMapManager::addShadowMap(size_t lightIndex, bool spotlight,
LightManager::ShadowOptions const* options) noexcept {
if (spotlight) {
const size_t c = mSpotShadowMaps.size();
const size_t c = mSpotShadowMapCount++;
const size_t i = c + CONFIG_MAX_SHADOW_CASCADES;
assert_invariant(i < CONFIG_MAX_SHADOWMAPS);
auto* pShadowMap = getShadowMap(i);
pShadowMap->initialize(lightIndex, ShadowType::SPOT, i, 0, options);
mSpotShadowMaps.push_back(pShadowMap);
auto& shadowMap = getShadowMap(i);
shadowMap.initialize(lightIndex, ShadowType::SPOT, i, 0, options);
} else {
// point-light, generate 6 independent shadowmaps
for (size_t face = 0; face < 6; face++) {
const size_t c = mSpotShadowMaps.size();
const size_t c = mSpotShadowMapCount++;
const size_t i = c + CONFIG_MAX_SHADOW_CASCADES;
assert_invariant(i < CONFIG_MAX_SHADOWMAPS);
auto* pShadowMap = getShadowMap(i);
pShadowMap->initialize(lightIndex, ShadowType::POINT, i, face, options);
mSpotShadowMaps.push_back(pShadowMap);
auto& shadowMap = getShadowMap(i);
shadowMap.initialize(lightIndex, ShadowType::POINT, i, face, options);
}
}
}
@@ -179,11 +178,11 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
// Directional, cascaded shadow maps
auto const directionalShadowCastersRange = view.getVisibleDirectionalShadowCasters();
if (!directionalShadowCastersRange.empty()) {
for (auto* pShadowMap : mCascadeShadowMaps) {
for (auto& shadowMap : getCascadedShadowMap()) {
// for the directional light, we already know if it has visible shadows.
if (pShadowMap->hasVisibleShadows()) {
if (shadowMap.hasVisibleShadows()) {
passList.push_back({
{}, pShadowMap, directionalShadowCastersRange,
{}, &shadowMap, directionalShadowCastersRange,
VISIBLE_DIR_SHADOW_RENDERABLE });
}
}
@@ -192,10 +191,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
// Point lights and Spotlight shadow maps
auto const spotShadowCastersRange = view.getVisibleSpotShadowCasters();
if (!spotShadowCastersRange.empty()) {
for (auto* pShadowMap : mSpotShadowMaps) {
assert_invariant(!pShadowMap->isDirectionalShadow());
for (auto& shadowMap : getSpotShadowMaps()) {
assert_invariant(!shadowMap.isDirectionalShadow());
passList.push_back({
{}, pShadowMap, spotShadowCastersRange,
{}, &shadowMap, spotShadowCastersRange,
VISIBLE_DYN_SHADOW_RENDERABLE });
}
}
@@ -517,17 +516,19 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
};
bool hasVisibleShadows = false;
if (!mCascadeShadowMaps.empty()) {
utils::Slice<ShadowMap> cascadedShadowMaps = getCascadedShadowMap();
if (!cascadedShadowMaps.empty()) {
// Even if we have more than one cascade, we cull directional shadow casters against the
// entire camera frustum, as if we only had a single cascade.
ShadowMap& shadowMap = *mCascadeShadowMaps[0];
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.
// This will be adjusted later because of how we compute the depth metric for VSM.
const mat4f MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction);
const mat4f MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction,
normalize(cameraInfo.worldTransform[0].xyz));
// Compute scene-dependent values shared across all cascades
ShadowMap::updateSceneInfoDirectional(MvAtOrigin, *scene, sceneInfo);
@@ -557,7 +558,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
vsFar = std::max(vsFar, sceneInfo.vsNearFar.y);
}
const size_t cascadeCount = mCascadeShadowMaps.size();
const size_t cascadeCount = cascadedShadowMaps.size();
// We divide the camera frustum into N cascades. This gives us N + 1 split positions.
// The first split position is the near plane; the last split position is the far plane.
@@ -591,11 +592,9 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
// note: normalBias is set to zero for VSM
const float normalBias = shadowMapInfo.vsm ? 0.0f : 0.5f * lcm.getShadowNormalBias(0);
for (size_t i = 0, c = mCascadeShadowMaps.size(); i < c; i++) {
assert_invariant(mCascadeShadowMaps[i]);
for (size_t i = 0, c = cascadedShadowMaps.size(); i < c; i++) {
// Compute the frustum for the directional light.
ShadowMap& shadowMap = *mCascadeShadowMaps[i];
ShadowMap& shadowMap = cascadedShadowMaps[i];
assert_invariant(shadowMap.getLightIndex() == 0);
sceneInfo.csNearFar = { csSplitPosition[i], csSplitPosition[i + 1] };
@@ -642,7 +641,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
}
uint32_t cascades = 0;
cascades |= uint32_t(mCascadeShadowMaps.size());
cascades |= uint32_t(cascadedShadowMaps.size());
cascades |= cascadeHasVisibleShadows << 8u;
mShadowMappingUniforms.directionalShadows = directionalShadowsMask;
@@ -695,7 +694,7 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap,
const auto outerConeAngle = lcm.getSpotLightOuterCone(li);
// compute shadow map frustum for culling
const mat4f Mv = ShadowMap::getDirectionalLightViewMatrix(direction, position);
const mat4f Mv = ShadowMap::getDirectionalLightViewMatrix(direction, { 0, 1, 0 }, position);
const mat4f Mp = mat4f::perspective(outerConeAngle * f::RAD_TO_DEG * 2.0f, 1.0f, 0.01f, radius);
const mat4f MpMv = math::highPrecisionMultiply(Mp, Mv);
const Frustum frustum(MpMv);
@@ -851,16 +850,17 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine
lightData.data<FScene::SHADOW_INFO>());
ShadowTechnique shadowTechnique{};
if (!mSpotShadowMaps.empty()) {
utils::Slice<ShadowMap> const spotShadowMaps = getSpotShadowMaps();
if (!spotShadowMaps.empty()) {
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
for (auto const* pShadowMap : mSpotShadowMaps) {
const size_t lightIndex = pShadowMap->getLightIndex();
for (ShadowMap const& shadowMap : spotShadowMaps) {
const size_t lightIndex = shadowMap.getLightIndex();
// gather the per-light (not per shadow map) information. For point lights we will
// "see" 6 shadowmaps (one per face), we must use the first face one, the shader
// knows how to find the entry for other faces (they're guaranteed to be sequential).
if (pShadowMap->getFace() == 0) {
if (shadowMap.getFace() == 0) {
shadowInfo[lightIndex].castsShadows = true; // FIXME: is that set correctly?
shadowInfo[lightIndex].index = pShadowMap->getShadowIndex();
shadowInfo[lightIndex].index = shadowMap.getShadowIndex();
}
}
}
@@ -890,18 +890,18 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
uint8_t layer = 0;
uint32_t maxDimension = 0;
bool elvsm = false;
for (auto* pShadowMap : mCascadeShadowMaps) {
for (ShadowMap& shadowMap : getCascadedShadowMap()) {
// Shadow map size should be the same for all cascades.
auto const& options = pShadowMap->getShadowOptions();
auto const& options = shadowMap.getShadowOptions();
maxDimension = std::max(maxDimension, options->mapSize);
elvsm = elvsm || options->vsm.elvsm;
pShadowMap->setLayer(layer++);
shadowMap.setLayer(layer++);
}
for (auto& pShadowMap : mSpotShadowMaps) {
auto const& options = pShadowMap->getShadowOptions();
for (ShadowMap& shadowMap : getSpotShadowMaps()) {
auto const& options = shadowMap.getShadowOptions();
maxDimension = std::max(maxDimension, options->mapSize);
elvsm = elvsm || options->vsm.elvsm;
pShadowMap->setLayer(layer++);
shadowMap.setLayer(layer++);
}
const uint8_t layersNeeded = layer;
+30 -20
View File
@@ -31,7 +31,7 @@
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Slice.h>
#include <math/vec3.h>
@@ -89,15 +89,6 @@ public:
FrameGraphId<FrameGraphTexture> render(FEngine& engine, FrameGraph& fg, RenderPass const& pass,
FView& view, CameraInfo const& mainCameraInfo, math::float4 const& userTime) noexcept;
ShadowMap* getShadowMap(size_t index) noexcept {
assert_invariant(index < CONFIG_MAX_SHADOWMAPS);
return std::launder(reinterpret_cast<ShadowMap*>(&mShadowMapCache[index]));
}
ShadowMap const* getShadowMap(size_t index) const noexcept {
return const_cast<ShadowMapManager*>(this)->getShadowMap(index);
}
// valid after calling update() above
ShadowMappingUniforms getShadowMappingUniforms() const noexcept {
return mShadowMappingUniforms;
@@ -105,7 +96,12 @@ public:
auto& getShadowUniformsHandle() const { return mShadowUbh; }
bool hasSpotShadows() const { return !mSpotShadowMaps.empty(); }
bool hasSpotShadows() const { return !mSpotShadowMapCount; }
// for debugging only
FCamera const* getDirectionalLightCamera() const noexcept {
return &getShadowMap(0).getDebugCamera();
}
private:
ShadowMapManager::ShadowTechnique updateCascadeShadowMaps(FEngine& engine,
@@ -184,19 +180,33 @@ private:
ShadowMap::SceneInfo mSceneInfo;
utils::FixedCapacityVector<ShadowMap*> mCascadeShadowMaps{
utils::FixedCapacityVector<ShadowMap*>::with_capacity(
CONFIG_MAX_SHADOW_CASCADES) };
utils::FixedCapacityVector<ShadowMap*> mSpotShadowMaps{
utils::FixedCapacityVector<ShadowMap*>::with_capacity(
CONFIG_MAX_SHADOWMAPS - CONFIG_MAX_SHADOW_CASCADES) };
// Inline storage for all our ShadowMap objects, we can't easily use a std::array<> directly.
// Because ShadowMap doesn't have a default ctor, and we avoid out-of-line allocations.
// Each ShadowMap is currently 40 bytes (total of 2.5KB for 64 shadow maps)
using ShadowMapStorage = std::aligned_storage<sizeof(ShadowMap), alignof(ShadowMap)>::type;
std::array<ShadowMapStorage, CONFIG_MAX_SHADOWMAPS> mShadowMapCache;
using ShadowMapCacheContainer = std::array<ShadowMapStorage, CONFIG_MAX_SHADOWMAPS>;
ShadowMapCacheContainer mShadowMapCache;
uint32_t mDirectionalShadowMapCount = 0;
uint32_t mSpotShadowMapCount = 0;
ShadowMap& getShadowMap(size_t index) noexcept {
assert_invariant(index < CONFIG_MAX_SHADOWMAPS);
return *std::launder(reinterpret_cast<ShadowMap*>(&mShadowMapCache[index]));
}
ShadowMap const& getShadowMap(size_t index) const noexcept {
return const_cast<ShadowMapManager*>(this)->getShadowMap(index);
}
utils::Slice<ShadowMap> getCascadedShadowMap() noexcept {
ShadowMap* const p = &getShadowMap(0);
return { p, mDirectionalShadowMapCount };
}
utils::Slice<ShadowMap> getSpotShadowMaps() noexcept {
ShadowMap* const p = &getShadowMap(CONFIG_MAX_SHADOW_CASCADES);
return { p, mSpotShadowMapCount };
}
};
} // namespace filament
+40 -30
View File
@@ -214,12 +214,12 @@ RenderableManager::Builder& RenderableManager::Builder::enableSkinningBuffers(bo
RenderableManager::Builder& RenderableManager::Builder::boneIndicesAndWeights(size_t primitiveIndex,
math::float2 const* indicesAndWeights, size_t count, size_t bonesPerVertex) noexcept {
size_t vertexCount = count / bonesPerVertex;
size_t const vertexCount = count / bonesPerVertex;
utils::FixedCapacityVector<utils::FixedCapacityVector<filament::math::float2>> bonePairs(vertexCount);
for ( size_t iVertex = 0; iVertex < vertexCount; iVertex++) {
for (size_t iVertex = 0; iVertex < vertexCount; iVertex++) {
utils::FixedCapacityVector<float2> vertexData(bonesPerVertex);
std::copy_n(indicesAndWeights + iVertex * bonesPerVertex,
bonesPerVertex, vertexData.data());
bonesPerVertex, vertexData.data());
bonePairs[iVertex] = std::move(vertexData);
}
return boneIndicesAndWeights(primitiveIndex, bonePairs);
@@ -275,13 +275,13 @@ void RenderableManager::BuilderDetails::processBoneIndicesAndWights(Engine& engi
size_t maxPairsCount = 0; //size of texture, number of bone pairs
size_t maxPairsCountPerVertex = 0; //maximum of number of bone per vertex
for (auto iBonePair = mBonePairs.begin(); iBonePair != mBonePairs.end(); ++iBonePair){
auto primitiveIndex = iBonePair->first;
for (auto& bonePair: mBonePairs) {
auto primitiveIndex = bonePair.first;
auto entries = mEntries;
ASSERT_PRECONDITION(primitiveIndex < entries.size() && primitiveIndex >= 0,
"[primitive @ %u] primitiveindex is out of size (%u)", primitiveIndex, entries.size());
auto entry = mEntries[primitiveIndex];
auto bonePairsForPrimitive = iBonePair->second;
auto bonePairsForPrimitive = bonePair.second;
auto vertexCount = entry.vertices->getVertexCount();
ASSERT_PRECONDITION(bonePairsForPrimitive.size() == vertexCount,
"[primitive @ %u] bone indices and weights pairs count (%u) must be equal to vertex count (%u)",
@@ -292,7 +292,7 @@ void RenderableManager::BuilderDetails::processBoneIndicesAndWights(Engine& engi
"[entity=%u, primitive @ %u] for advanced skinning set VertexBuffer::Builder::advancedSkinning()",
entity.getId(), primitiveIndex);
for (size_t iVertex = 0; iVertex < vertexCount; iVertex++) {
size_t bonesPerVertex = bonePairsForPrimitive[iVertex].size();
size_t const bonesPerVertex = bonePairsForPrimitive[iVertex].size();
maxPairsCount += bonesPerVertex;
maxPairsCountPerVertex = std::max(bonesPerVertex, maxPairsCountPerVertex);
}
@@ -303,29 +303,28 @@ void RenderableManager::BuilderDetails::processBoneIndicesAndWights(Engine& engi
// final texture data, indices and weights
mBoneIndicesAndWeights = utils::FixedCapacityVector<float2>(maxPairsCount);
// temporary indices and weights for one vertex
std::unique_ptr<float2[]> tempPairs = std::make_unique<float2[]>
(maxPairsCountPerVertex);
for (auto iBonePair = mBonePairs.begin(); iBonePair != mBonePairs.end(); ++iBonePair) {
auto primitiveIndex = iBonePair->first;
auto bonePairsForPrimitive = iBonePair->second;
if (!bonePairsForPrimitive.size()) {
continue;
auto const tempPairs = std::make_unique<float2[]>(maxPairsCountPerVertex);
for (auto& bonePair: mBonePairs) {
auto primitiveIndex = bonePair.first;
auto bonePairsForPrimitive = bonePair.second;
if (bonePairsForPrimitive.empty()) {
continue;
}
size_t vertexCount = mEntries[primitiveIndex].vertices->getVertexCount();
std::unique_ptr<uint16_t[]> skinJoints = std::make_unique<uint16_t[]>
(4 * vertexCount); // temporary indices for one vertex
std::unique_ptr<float[]> skinWeights = std::make_unique<float[]>
(4 * vertexCount); // temporary weights for one vertex
size_t const vertexCount = mEntries[primitiveIndex].vertices->getVertexCount();
// temporary indices for one vertex
auto skinJoints = std::make_unique<uint16_t[]>(4 * vertexCount);
// temporary weights for one vertex
auto skinWeights = std::make_unique<float[]>(4 * vertexCount);
for (size_t iVertex = 0; iVertex < vertexCount; iVertex++) {
size_t tempPairCount = 0;
float boneWeightsSum = 0;
double boneWeightsSum = 0;
for (size_t k = 0; k < bonePairsForPrimitive[iVertex].size(); k++) {
auto boneWeight = bonePairsForPrimitive[iVertex][k][1];
auto boneIndex = bonePairsForPrimitive[iVertex][k][0];
ASSERT_PRECONDITION(boneWeight >= 0,
"[entity=%u, primitive @ %u] bone weight (%f) of vertex=%u is negative ",
entity.getId(), primitiveIndex, boneWeight, iVertex);
if (boneWeight) {
if (boneWeight > 0.0f) {
ASSERT_PRECONDITION(boneIndex >= 0,
"[entity=%u, primitive @ %u] bone index (%i) of vertex=%u is negative ",
entity.getId(), primitiveIndex, (int) boneIndex, iVertex);
@@ -342,26 +341,37 @@ void RenderableManager::BuilderDetails::processBoneIndicesAndWights(Engine& engi
ASSERT_PRECONDITION(boneWeightsSum > 0,
"[entity=%u, primitive @ %u] sum of bone weights of vertex=%u is %f, it should be positive.",
entity.getId(), primitiveIndex, iVertex, boneWeightsSum);
if (abs(boneWeightsSum - 1.f) > std::numeric_limits<float>::epsilon()) {
// see https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#skinned-mesh-attributes
double const epsilon = 2e-7 * double(tempPairCount);
if (abs(boneWeightsSum - 1.0) <= epsilon) {
boneWeightsSum = 1.0;
}
#ifndef NDEBUG
else {
utils::slog.w << "Warning of skinning: [entity=%" << entity.getId()
<< ", primitive @ %" << primitiveIndex
<< "] sum of bone weights of vertex=" << iVertex << " is " << boneWeightsSum
<< ", it should be one. Weights will be normalized." << utils::io::endl;
}
#endif
// prepare data for vertex attributes
auto offset = iVertex * 4;
// set attributes, indices and weights, for <= 4 pairs
for (size_t j = 0, c = std::min((int) tempPairCount, 4); j < c; j++) {
skinJoints[j + offset] = tempPairs[j][0];
skinWeights[j + offset] = tempPairs[j][1] / boneWeightsSum;
for (size_t j = 0, c = std::min((int)tempPairCount, 4); j < c; j++) {
skinJoints[j + offset] = uint16_t(tempPairs[j][0]);
skinWeights[j + offset] = tempPairs[j][1] / float(boneWeightsSum);
}
// prepare data for texture
if (tempPairCount > 4) { // set attributes, indices and weights, for > 4 pairs
skinWeights[3 + offset] = -(float) (pairsCount + 1); // negative offset to texture 0..-1, 1..-2
skinJoints[3 + offset] = (uint16_t) tempPairCount; // number pairs per vertex in texture
// number pairs per vertex in texture
skinJoints[3 + offset] = (uint16_t)tempPairCount;
// negative offset to texture 0..-1, 1..-2
skinWeights[3 + offset] = -float(pairsCount + 1);
for (size_t j = 3; j < tempPairCount; j++) {
mBoneIndicesAndWeights[pairsCount][0] = tempPairs[j][0];
mBoneIndicesAndWeights[pairsCount][1] = tempPairs[j][1] / boneWeightsSum;
mBoneIndicesAndWeights[pairsCount][1] = tempPairs[j][1] / float(boneWeightsSum);
pairsCount++;
}
}
@@ -386,7 +396,7 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng
mImpl->mInstanceCount,
CONFIG_MAX_INSTANCES);
if (mImpl->mInstanceBuffer) {
size_t bufferInstanceCount = mImpl->mInstanceBuffer->mInstanceCount;
size_t const bufferInstanceCount = mImpl->mInstanceBuffer->mInstanceCount;
ASSERT_PRECONDITION(mImpl->mInstanceCount <= bufferInstanceCount,
"instance count (%zu) must be less than or equal to the InstanceBuffer's instance "
"count "
@@ -402,7 +412,7 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng
auto& entry = mImpl->mEntries[i];
// entry.materialInstance must be set to something even if indices/vertices are null
FMaterial const* material = nullptr;
FMaterial const* material;
if (!entry.materialInstance) {
material = downcast(engine.getDefaultMaterial());
entry.materialInstance = material->getDefaultInstance();
+3 -3
View File
@@ -266,8 +266,8 @@ CameraInfo::CameraInfo(FCamera const& camera) noexcept {
d = std::max(zn, camera.getFocusDistance());
}
CameraInfo::CameraInfo(FCamera const& camera, const math::mat4& worldOriginCamera) noexcept {
const mat4 modelMatrix{ worldOriginCamera * camera.getModelMatrix() };
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++) {
eyeProjection[i] = mat4f{ camera.getProjectionMatrix(i) };
eyeFromView[i] = mat4f{ camera.getEyeFromViewMatrix(i) };
@@ -275,7 +275,7 @@ CameraInfo::CameraInfo(FCamera const& camera, const math::mat4& worldOriginCamer
cullingProjection = mat4f{ camera.getCullingProjectionMatrix() };
model = mat4f{ modelMatrix };
view = mat4f{ inverse(modelMatrix) };
worldOrigin = worldOriginCamera;
worldTransform = inWorldTransform;
zn = (float)camera.getNear();
zf = (float)camera.getCullingFar();
ev100 = Exposure::ev100(camera);
+3 -3
View File
@@ -223,7 +223,7 @@ private:
struct CameraInfo {
CameraInfo() noexcept {}
explicit CameraInfo(FCamera const& camera) noexcept;
CameraInfo(FCamera const& camera, const math::mat4& worldOriginCamera) noexcept;
CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform) noexcept;
union {
// projection matrix for drawing (infinite zfar)
@@ -239,7 +239,7 @@ struct CameraInfo {
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 worldOrigin; // world origin transform (already applied
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
@@ -250,7 +250,7 @@ struct CameraInfo {
float d{}; // focus distance [m]
math::float3 const& getPosition() const noexcept { return model[3].xyz; }
math::float3 getForwardVector() const noexcept { return normalize(-model[2].xyz); }
math::mat4 getUserViewMatrix() const noexcept { return view * worldOrigin; }
math::mat4 getUserViewMatrix() const noexcept { return view * worldTransform; }
};
FILAMENT_DOWNCAST(Camera)
+16 -2
View File
@@ -67,6 +67,7 @@ struct Engine::BuilderDetails {
Backend mBackend = Backend::DEFAULT;
Platform* mPlatform = nullptr;
Engine::Config mConfig;
FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1;
void* mSharedContext = nullptr;
static Config validateConfig(const Config* pConfig) noexcept;
};
@@ -185,6 +186,7 @@ static const uint16_t sFullScreenTriangleIndices[3] = { 0, 1, 2 };
FEngine::FEngine(Engine::Builder const& builder) :
mBackend(builder->mBackend),
mActiveFeatureLevel(builder->mFeatureLevel),
mPlatform(builder->mPlatform),
mSharedGLContext(builder->mSharedContext),
mPostProcessManager(*this),
@@ -242,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;
@@ -326,7 +332,7 @@ void FEngine::init() {
driverApi.update3DImage(mDummyZeroTexture, 0, 0, 0, 0, 1, 1, 1,
{ zeroes, 4, Texture::Format::RGBA, Texture::Type::UBYTE });
#ifdef FILAMENT_TARGET_MOBILE
#ifdef FILAMENT_ENABLE_FEATURE_LEVEL_0
if (UTILS_UNLIKELY(mActiveFeatureLevel == FeatureLevel::FEATURE_LEVEL_0)) {
FMaterial::DefaultMaterialBuilder defaultMaterialBuilder;
defaultMaterialBuilder.package(
@@ -359,11 +365,12 @@ void FEngine::init() {
driverApi.update3DImage(mDummyZeroTextureArray, 0, 0, 0, 0, 1, 1, 1,
{ zeroes, 4, Texture::Format::RGBA, Texture::Type::UBYTE });
mPostProcessManager.init();
mLightManager.init(*this);
mDFG.init(*this);
}
mPostProcessManager.init();
mDebugRegistry.registerProperty("d.shadowmap.debug_directional_shadowmap",
&debug.shadowmap.debug_directional_shadowmap, [this]() {
mMaterials.forEach([](FMaterial* material) {
@@ -1171,6 +1178,8 @@ Engine::FeatureLevel FEngine::getSupportedFeatureLevel() const noexcept {
Engine::FeatureLevel FEngine::setActiveFeatureLevel(FeatureLevel featureLevel) {
ASSERT_PRECONDITION(featureLevel <= getSupportedFeatureLevel(),
"Feature level %u not supported", (unsigned)featureLevel);
ASSERT_PRECONDITION(mActiveFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1,
"Cannot adjust feature level beyond 0 at runtime");
return (mActiveFeatureLevel = std::max(mActiveFeatureLevel, featureLevel));
}
@@ -1204,6 +1213,11 @@ Engine::Builder& Engine::Builder::config(Engine::Config const* config) noexcept
return *this;
}
Engine::Builder& Engine::Builder::featureLevel(FeatureLevel featureLevel) noexcept {
mImpl->mFeatureLevel = featureLevel;
return *this;
}
Engine::Builder& Engine::Builder::sharedContext(void* sharedContext) noexcept {
mImpl->mSharedContext = sharedContext;
return *this;
+1 -1
View File
@@ -231,7 +231,7 @@ public:
default:
return backend::ShaderLanguage::ESSL3;
case Backend::OPENGL:
return mActiveFeatureLevel == FeatureLevel::FEATURE_LEVEL_0
return getDriver().getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0
? backend::ShaderLanguage::ESSL1 : backend::ShaderLanguage::ESSL3;
case Backend::VULKAN:
return backend::ShaderLanguage::SPIRV;
+6 -5
View File
@@ -57,7 +57,9 @@ static MaterialParser* createParser(Backend backend, ShaderLanguage language,
}
ASSERT_PRECONDITION(materialResult != MaterialParser::ParseResult::ERROR_MISSING_BACKEND,
"the material was not built for the %s backend\n", backendToString(backend));
"the material was not built for the %s backend and %s shader language\n",
backendToString(backend),
shaderLanguageToString(language));
ASSERT_PRECONDITION(materialResult == MaterialParser::ParseResult::SUCCESS,
"could not parse the material package");
@@ -184,7 +186,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
success = parser->getUIB(&mUniformInterfaceBlock);
assert_invariant(success);
if (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) {
if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) {
success = parser->getBindingUniformInfo(&mBindingUniformInfo);
assert_invariant(success);
@@ -258,7 +260,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS,
(bool)powerVrShaderWorkarounds });
if (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0) {
if (UTILS_UNLIKELY(engine.getShaderLanguage() == ShaderLanguage::ESSL1)) {
// The actual value of this spec-constant is set in the OpenGLDriver backend.
mSpecializationConstants.push_back({
+ReservedSpecializationConstants::CONFIG_SRGB_SWAPCHAIN_EMULATION,
@@ -640,7 +642,6 @@ Program FMaterial::getProgramWithVariants(
FEngine const& engine = mEngine;
const ShaderModel sm = engine.getShaderModel();
const bool isNoop = engine.getBackend() == Backend::NOOP;
const FeatureLevel engineFeatureLevel = engine.getActiveFeatureLevel();
/*
* Vertex shader
*/
@@ -694,7 +695,7 @@ Program FMaterial::getProgramWithVariants(
}
}
if (engineFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) {
if (UTILS_UNLIKELY(mEngine.getShaderLanguage() == ShaderLanguage::ESSL1)) {
assert_invariant(!mBindingUniformInfo.empty());
for (auto const& [index, uniforms] : mBindingUniformInfo) {
program.uniforms(uint32_t(index), uniforms);
+1
View File
@@ -859,6 +859,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
PostProcessManager::generateMipmapSSR(ppm, fg,
reflections, ssrConfig.reflection, false, ssrConfig);
}
config.screenSpaceReflectionHistoryNotReady = !reflections;
}
// --------------------------------------------------------------------------------------------
+47 -17
View File
@@ -33,6 +33,8 @@
#include <utils/Range.h>
#include <utils/Systrace.h>
#include <math/quat.h>
#include <algorithm>
using namespace filament::backend;
@@ -52,7 +54,7 @@ FScene::~FScene() noexcept = default;
void FScene::prepare(utils::JobSystem& js,
LinearAllocatorArena& allocator,
const mat4& worldOriginTransform,
mat4 const& worldTransform,
bool shadowReceiversAreCasters) noexcept {
// TODO: can we skip this in most cases? Since we rely on indices staying the same,
// we could only skip, if nothing changed in the RCM.
@@ -168,7 +170,7 @@ void FScene::prepare(utils::JobSystem& js,
* Fill the SoA with the JobSystem
*/
auto renderableWork = [first = renderableInstances.data(), &rcm, &tcm, &worldOriginTransform,
auto renderableWork = [first = renderableInstances.data(), &rcm, &tcm, &worldTransform,
&sceneData, shadowReceiversAreCasters](auto* p, auto c) {
SYSTRACE_NAME("renderableWork");
@@ -176,12 +178,12 @@ void FScene::prepare(utils::JobSystem& js,
auto [ri, ti] = p[i];
// this is where we go from double to float for our transforms
const mat4f worldTransform{
worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
const bool reversedWindingOrder = det(worldTransform.upperLeft()) < 0;
const mat4f shaderWorldTransform{
worldTransform * tcm.getWorldTransformAccurate(ti) };
const bool reversedWindingOrder = det(shaderWorldTransform.upperLeft()) < 0;
// compute the world AABB so we can perform culling
const Box worldAABB = rigidTransform(rcm.getAABB(ri), worldTransform);
const Box worldAABB = rigidTransform(rcm.getAABB(ri), shaderWorldTransform);
auto visibility = rcm.getVisibility(ri);
visibility.reversedWindingOrder = reversedWindingOrder;
@@ -199,7 +201,7 @@ void FScene::prepare(utils::JobSystem& js,
assert_invariant(index < sceneData.size());
sceneData.elementAt<RENDERABLE_INSTANCE>(index) = ri;
sceneData.elementAt<WORLD_TRANSFORM>(index) = worldTransform;
sceneData.elementAt<WORLD_TRANSFORM>(index) = shaderWorldTransform;
sceneData.elementAt<VISIBILITY_STATE>(index) = visibility;
sceneData.elementAt<SKINNING_BUFFER>(index) = rcm.getSkinningBufferInfo(ri);
sceneData.elementAt<MORPHING_BUFFER>(index) = rcm.getMorphingBufferInfo(ri);
@@ -216,19 +218,20 @@ void FScene::prepare(utils::JobSystem& js,
}
};
auto lightWork = [first = lightInstances.data(), &lcm, &tcm, &worldOriginTransform,
auto lightWork = [first = lightInstances.data(), &lcm, &tcm, &worldTransform,
&lightData](auto* p, auto c) {
SYSTRACE_NAME("lightWork");
for (size_t i = 0; i < c; i++) {
auto [li, ti] = p[i];
// this is where we go from double to float for our transforms
const mat4f worldTransform{ worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
const float4 position = worldTransform * float4{ lcm.getLocalPosition(li), 1 };
mat4f const shaderWorldTransform{
worldTransform * tcm.getWorldTransformAccurate(ti) };
float4 const position = shaderWorldTransform * float4{ lcm.getLocalPosition(li), 1 };
float3 d = 0;
if (!lcm.isPointLight(li) || lcm.isIESLight(li)) {
d = lcm.getLocalDirection(li);
// using mat3f::getTransformForNormals handles non-uniform scaling
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
d = normalize(mat3f::getTransformForNormals(shaderWorldTransform.upperLeft()) * d);
}
size_t const index = DIRECTIONAL_LIGHTS_COUNT + std::distance(first, p) + i;
assert_invariant(index < lightData.size());
@@ -261,14 +264,41 @@ void FScene::prepare(utils::JobSystem& js,
*/
if (auto [li, ti] = directionalLightInstances ; li) {
const mat4f worldTransform{
worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
// using mat3f::getTransformForNormals handles non-uniform scaling
float3 d = lcm.getLocalDirection(li);
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
// 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 = 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(worldTransform.upperLeft());
double3 const d = worlTransformNormals * localDirection;
double3 const s = worlTransformNormals * shadowLocalDirection;
// We compute the reference point for snapping shadowmaps without applying the
// rotation of `worldOriginTransform` on both sides, so that we don't have any instability
// due to the limited precision of the "light space" matrix (even at double precision).
// getMv() Returns the world-to-lightspace transformation. See ShadowMap.cpp.
auto getMv = [](double3 direction) -> mat3 {
// We use the x-axis as the "up" reference so that the math is stable when the light
// is pointing down, which is a common case for lights. See ShadowMap.cpp.
return transpose(mat3::lookTo(direction, double3{ 1, 0, 0 }));
};
double3 const worldOrigin = transpose(worldTransform.upperLeft()) * worldTransform[3].xyz;
mat3 const Mv = getMv(shadowLocalDirection);
double2 const lsReferencePoint = (Mv * worldOrigin).xy;
constexpr float inf = std::numeric_limits<float>::infinity();
lightData.elementAt<POSITION_RADIUS>(0) = float4{ 0, 0, 0, inf };
lightData.elementAt<DIRECTION>(0) = d;
lightData.elementAt<DIRECTION>(0) = normalize(d);
lightData.elementAt<SHADOW_DIRECTION>(0) = normalize(s);
lightData.elementAt<SHADOW_REF>(0) = lsReferencePoint;
lightData.elementAt<LIGHT_INSTANCE>(0) = li;
} else {
lightData.elementAt<LIGHT_INSTANCE>(0) = 0;
+5 -1
View File
@@ -71,7 +71,7 @@ public:
void terminate(FEngine& engine);
void prepare(utils::JobSystem& js, LinearAllocatorArena& allocator,
math::mat4 const& worldOriginTransform, bool shadowReceiversAreCasters) noexcept;
math::mat4 const& worldTransform, bool shadowReceiversAreCasters) noexcept;
void prepareVisibleRenderables(utils::Range<uint32_t> visibleRenderables) noexcept;
@@ -162,6 +162,8 @@ public:
enum {
POSITION_RADIUS,
DIRECTION,
SHADOW_DIRECTION,
SHADOW_REF,
LIGHT_INSTANCE,
VISIBILITY,
SCREEN_SPACE_Z_RANGE,
@@ -171,6 +173,8 @@ public:
using LightSoa = utils::StructureOfArrays<
math::float4,
math::float3,
math::float3,
math::double2,
FLightManager::Instance,
Culler::result_type,
math::float2,
+1 -1
View File
@@ -118,7 +118,7 @@ FSkybox::FSkybox(FEngine& engine, const Builder& builder) noexcept
FMaterial const* FSkybox::createMaterial(FEngine& engine) {
Material::Builder builder;
#ifdef FILAMENT_TARGET_MOBILE
#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
+1 -1
View File
@@ -104,7 +104,7 @@ public:
// Returns the max number of levels for a texture of given dimensions
static inline uint8_t maxLevelCount(uint32_t width, uint32_t height) noexcept {
uint32_t maxDimension = std::max(width, height);
uint32_t const maxDimension = std::max(width, height);
return maxLevelCount(maxDimension);
}
+16 -11
View File
@@ -397,8 +397,8 @@ CameraInfo FView::computeCameraInfo(FEngine& engine) const noexcept {
* The "world origin" is also used to keep the origin close to the camera position to
* improve fp precision in the shader for large scenes.
*/
mat4 translation;
mat4 rotation;
double3 translation;
mat3 rotation;
/*
* Calculate all camera parameters needed to render this View for this frame.
@@ -409,16 +409,18 @@ CameraInfo FView::computeCameraInfo(FEngine& engine) const noexcept {
// view-space, which improves floating point precision in the shader by staying around
// zero, where fp precision is highest. This also ensures that when the camera is placed
// very far from the origin, objects are still rendered and lit properly.
translation = mat4::translation( -camera->getPosition() );
translation = -camera->getPosition();
}
FIndirectLight const* const ibl = scene->getIndirectLight();
if (ibl) {
// the IBL transformation must be a rigid transform
rotation = mat4{ transpose(scene->getIndirectLight()->getRotation()) };
rotation = mat3{ transpose(scene->getIndirectLight()->getRotation()) };
// it is important to orthogonalize the matrix when converting it to doubles, because
// as float, it only has about a 1e-8 precision on the size of the basis vectors
rotation = orthogonalize(rotation);
}
return { *camera, rotation * translation };
return { *camera, mat4{ rotation } * mat4::translation(translation) };
}
void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
@@ -446,7 +448,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
// intent of the code, which is that we should only depend on CameraInfo here.
// This is an extremely uncommon case.
const mat4 projection = mCullingCamera->getCullingProjectionMatrix();
const mat4 view = inverse(cameraInfo.worldOrigin * mCullingCamera->getModelMatrix());
const mat4 view = inverse(cameraInfo.worldTransform * mCullingCamera->getModelMatrix());
return Frustum{ mat4f{ projection * view }};
}
};
@@ -459,7 +461,9 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
* Gather all information needed to render this scene. Apply the world origin to all
* objects in the scene.
*/
scene->prepare(js, arena.getAllocator(), cameraInfo.worldOrigin, hasVSM());
scene->prepare(js, arena.getAllocator(),
cameraInfo.worldTransform,
hasVSM());
/*
* Light culling: runs in parallel with Renderable culling (below)
@@ -658,7 +662,6 @@ void FView::bindPerViewUniformsAndSamplers(FEngine::DriverApi& driver) const noe
mPerViewUniforms.bind(driver);
if (UTILS_UNLIKELY(driver.getFeatureLevel() == backend::FeatureLevel::FEATURE_LEVEL_0)) {
// FIXME: should be okay to use driver (instead of engine) for FEATURE_LEVEL_0 checks
return;
}
@@ -743,9 +746,11 @@ void FView::prepareSSAO(Handle<HwTexture> ssao) const noexcept {
mPerViewUniforms.prepareSSAO(ssao, mAmbientOcclusionOptions);
}
void FView::prepareSSR(Handle<HwTexture> ssr, float refractionLodOffset,
void FView::prepareSSR(Handle<HwTexture> ssr,
bool disableSSR,
float refractionLodOffset,
ScreenSpaceReflectionsOptions const& ssrOptions) const noexcept {
mPerViewUniforms.prepareSSR(ssr, refractionLodOffset, ssrOptions);
mPerViewUniforms.prepareSSR(ssr, disableSSR, refractionLodOffset, ssrOptions);
}
void FView::prepareStructure(Handle<HwTexture> structure) const noexcept {
+3 -2
View File
@@ -145,7 +145,8 @@ public:
void prepareLighting(FEngine& engine, ArenaScope& arena, CameraInfo const& cameraInfo) noexcept;
void prepareSSAO(backend::Handle<backend::HwTexture> ssao) const noexcept;
void prepareSSR(backend::Handle<backend::HwTexture> ssr, float refractionLodOffset,
void prepareSSR(backend::Handle<backend::HwTexture> ssr, bool disableSSR,
float refractionLodOffset,
ScreenSpaceReflectionsOptions const& ssrOptions) const noexcept;
void prepareStructure(backend::Handle<backend::HwTexture> structure) const noexcept;
void prepareShadow(backend::Handle<backend::HwTexture> structure) const noexcept;
@@ -194,7 +195,7 @@ public:
void setStereoscopicOptions(StereoscopicOptions const& options);
FCamera const* getDirectionalLightCamera() const noexcept {
return &mShadowMapManager.getShadowMap(0)->getDebugCamera();
return mShadowMapManager.getDirectionalLightCamera();
}
void setRenderTarget(FRenderTarget* renderTarget) noexcept {
+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;
}
+6 -2
View File
@@ -22,7 +22,8 @@ material {
],
depthWrite : false,
depthCulling : false,
domain: postprocess
domain: postprocess,
featureLevel : 0
}
vertex {
@@ -34,7 +35,10 @@ vertex {
fragment {
void postProcess(inout PostProcessInputs postProcess) {
#if __VERSION__ == 100
postProcess.color = texture2D(materialParams_color, variable_vertex.xy);
#else
postProcess.color = textureLod(materialParams_color, variable_vertex.xy, 0.0);
#endif
}
}
+39
View File
@@ -0,0 +1,39 @@
material {
name : blitLow,
parameters : [
{
type : sampler2d,
name : color,
precision: medium
},
{
type : float4,
name : resolution,
precision: high
},
{
type : float4,
name : viewport,
precision: high
}
],
variables : [
vertex
],
depthWrite : false,
depthCulling : false,
domain: postprocess,
}
vertex {
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = materialParams.viewport.xy + postProcess.normalizedUV * materialParams.viewport.zw;
postProcess.vertex.xy = uvToRenderTargetUV(postProcess.vertex.xy);
}
}
fragment {
void postProcess(inout PostProcessInputs postProcess) {
postProcess.color = textureLod(materialParams_color, variable_vertex.xy, 0.0);
}
}
+2 -2
View File
@@ -702,8 +702,8 @@ TEST(FilamentTest, FroxelData) {
LightManager::Instance instance = engine->getLightManager().getInstance(e);
FScene::LightSoa lights;
lights.push_back({}, {}, {}, {}, {}, {}); // first one is always skipped
lights.push_back(float4{ 0, 0, -5, 1 }, {}, instance, 1, {}, {});
lights.push_back({}, {}, {}, {}, {}, {}, {}, {}); // first one is always skipped
lights.push_back(float4{ 0, 0, -5, 1 }, {}, {}, {}, instance, 1, {}, {});
{
froxelData.froxelizeLights(*engine, {}, lights);
+79
View File
@@ -0,0 +1,79 @@
;;; c-filament-style.el --- Filament C++ style -*- lexical-binding: t -*-
;; Copyright (C) 2023 Google LLC
;; Author: Eliza Velasquez
;; Version: 0.1.0
;; Created: 2023-10-31
;; Package-Requires: ((emacs "24.1"))
;; Keywords: c
;; URL: https://github.com/google/filament
;; SPDX-License-Identifier: Apache-2
;;; Commentary:
;; Defines a basic Filament style for C++ code in Emacs.
;;; Code:
(require 'cc-mode)
(defvar c-syntactic-context)
(defun c-filament-style-lineup-brace-list-intro (langelem)
"Indent first line of braced lists in the Filament style.
This properly indents doubled-up arglists + lists, e.g. ({. It
also properly indents enums at 1x and other lists at 2x.
LANGELEM is the cons of the syntactic symbol and the anchor
position (or nil if there is none)."
(save-excursion
(let (case-fold-search)
(goto-char (c-langelem-pos langelem))
(if (looking-at "enum\\b")
c-basic-offset
(if (assq 'arglist-cont-nonempty c-syntactic-context)
(- c-basic-offset)
(* 2 c-basic-offset))))))
(defun c-filament-style-lineup-brace-list-entry (_langelem)
"Indent following lines in braced lists in the Filament style.
This properly indents doubled-up arglists + lists, e.g. ({."
(if (assq 'arglist-cont-nonempty c-syntactic-context)
(- (* c-basic-offset 2))
0))
(defun c-filament-style-lineup-arglist (langelem)
"Indent following lines in braced lists in the Filament style.
This properly indents arglists nested in if statements. LANGELEM
is the cons of the syntactic symbol and the anchor position (or
nil if there is none)."
(save-excursion
(let (case-fold-search)
(goto-char (c-langelem-pos langelem))
(if (and (cdr c-syntactic-context)
(looking-at "if\\b"))
c-basic-offset
(* 2 c-basic-offset)))))
(c-add-style "filament"
'((c-basic-offset . 4)
(c-offsets-alist
(innamespace . 0)
(inextern-lang . 0)
(arglist-intro . c-filament-style-lineup-arglist)
(arglist-cont . 0)
(arglist-cont-nonempty . c-filament-style-lineup-arglist)
(arglist-close . c-filament-style-lineup-arglist)
(statement-cont . ++)
(case-label . +)
(brace-list-intro . c-filament-style-lineup-brace-list-intro)
(brace-list-entry . c-filament-style-lineup-brace-list-entry)
(brace-list-close . c-filament-style-lineup-brace-list-entry)
(label . [0]))))
(provide 'c-filament-style)
;;; c-filament-style.el ends here
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.45.1"
spec.version = "1.46.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.1/filament-v1.45.1-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.46.0/filament-v1.46.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_5" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_5" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@@ -20,24 +20,24 @@
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="B3q-vU-ULy">
<rect key="frame" x="68" y="80" width="279" height="636"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<mutableString key="text">This is a native UITextView. The triangle is rendered by Filament.
<string key="text">This is a native UITextView. The triangle is rendered by Filament.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras tincidunt neque a metus laoreet imperdiet. In cursus arcu quis turpis viverra convallis. Sed eu laoreet metus. Nunc id est lacus. Sed hendrerit elementum velit a finibus. Mauris vitae scelerisque erat. Proin ultricies quam nec justo vehicula, sed porta nulla facilisis.
Phasellus orci lectus, ullamcorper efficitur urna non, sodales venenatis sem. Suspendisse auctor, enim non dapibus malesuada, est urna sollicitudin sapien, in sodales mi nulla quis odio. Suspendisse viverra vel orci vitae vestibulum. Fusce tristique ligula nisi, bibendum gravida est volutpat sed. Vivamus ut justo porttitor, feugiat lorem eget, rutrum orci. Donec nec nibh vitae ex consectetur pulvinar. Sed dignissim, metus a tincidunt finibus, justo massa auctor nunc, sit amet cursus ligula sapien ut enim. Quisque ut congue libero, in ultricies tortor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean ac justo in tortor commodo efficitur et ut leo.
</mutableString>
</string>
<color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2KD-1D-oAh" customClass="FilamentView">
<view opaque="NO" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2KD-1D-oAh" customClass="FilamentView">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="cdP-0z-J5l"/>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
@@ -45,4 +45,12 @@ Phasellus orci lectus, ullamcorper efficitur urna non, sodales venenatis sem. Su
<point key="canvasLocation" x="140.57971014492756" y="133.25892857142856"/>
</scene>
</scenes>
<resources>
<systemColor name="labelColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
@@ -89,6 +89,7 @@ static constexpr uint8_t BAKED_COLOR_PACKAGE[] = {
#elif FILAMENT_APP_USE_METAL
[self initializeMetalLayer];
#endif
self.opaque = NO;
[self initializeFilament];
self.contentScaleFactor = UIScreen.mainScreen.nativeScale;
}
@@ -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 = 46;
/**
* Supported shading models
@@ -160,6 +160,8 @@ public:
bool isEmpty() const noexcept { return mFieldInfoList.empty(); }
bool isEmptyForFeatureLevel(backend::FeatureLevel featureLevel) const noexcept;
Alignment getAlignment() const noexcept { return mAlignment; }
Target getTarget() const noexcept { return mTarget; }
+8 -1
View File
@@ -171,6 +171,14 @@ BufferInterfaceBlock::FieldInfo const* BufferInterfaceBlock::getFieldInfo(
return &mFieldInfoList[pos->second];
}
bool BufferInterfaceBlock::isEmptyForFeatureLevel(
backend::FeatureLevel featureLevel) const noexcept {
return std::all_of(mFieldInfoList.begin(), mFieldInfoList.end(),
[featureLevel](auto const &info) {
return featureLevel < info.minFeatureLevel;
});
}
uint8_t UTILS_NOINLINE BufferInterfaceBlock::baseAlignmentForType(BufferInterfaceBlock::Type type) noexcept {
switch (type) {
case Type::BOOL:
@@ -230,4 +238,3 @@ uint8_t UTILS_NOINLINE BufferInterfaceBlock::strideForType(BufferInterfaceBlock:
}
} // namespace filament
+9
View File
@@ -35,10 +35,19 @@ set(MATERIAL_SRCS
file(MAKE_DIRECTORY ${MATERIAL_DIR})
# HACK: Pick between the normal and FL0-exclusionary variants of the materials
# based on the value of FILAMENT_ENABLE_FEATURE_LEVEL_0.
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")
if (NOT FILAMENT_ENABLE_FATURE_LEVEL_0)
# Pick the non-FL0 variant instead.
string(REGEX REPLACE "\\.mat$" "1.mat" fullname "${fullname}")
string(REGEX REPLACE "\\.mat$" "1.mat" mat_src "${mat_src}")
endif()
add_custom_command(
OUTPUT ${output_path}
COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname}
+3 -2
View File
@@ -13,7 +13,8 @@ material {
shadingModel : unlit,
culling : none,
depthCulling: false,
blending : transparent
blending : transparent,
featureLevel : 0
}
fragment {
@@ -21,7 +22,7 @@ fragment {
prepareMaterial(material);
vec2 uv = getUV0();
uv.y = 1.0 - uv.y;
vec4 albedo = texture(materialParams_albedo, uv);
vec4 albedo = texture2D(materialParams_albedo, uv);
material.baseColor = getColor() * albedo;
material.baseColor.rgb *= material.baseColor.a;
}
+28
View File
@@ -0,0 +1,28 @@
material {
name : uiBlit,
parameters : [
{
type : sampler2d,
name : albedo
}
],
requires : [
uv0,
color
],
shadingModel : unlit,
culling : none,
depthCulling: false,
blending : transparent,
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
vec2 uv = getUV0();
uv.y = 1.0 - uv.y;
vec4 albedo = texture(materialParams_albedo, uv);
material.baseColor = getColor() * albedo;
material.baseColor.rgb *= material.baseColor.a;
}
}
+8 -2
View File
@@ -587,11 +587,11 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
}
if (mBlendingMode == BlendingMode::MASKED) {
ibb.add({{ "_maskThreshold", 0, UniformType::FLOAT }});
ibb.add({{ "_maskThreshold", 0, UniformType::FLOAT, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }});
}
if (mDoubleSidedCapability) {
ibb.add({{ "_doubleSided", 0, UniformType::BOOL }});
ibb.add({{ "_doubleSided", 0, UniformType::BOOL, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }});
}
mRequiredAttributes.set(VertexAttribute::POSITION);
@@ -1098,6 +1098,12 @@ error:
return Package::invalidPackage();
}
// Force post process materials to be unlit. This prevents imposing a lot of extraneous
// data, code, and expectations for materials which do not need them.
if (mMaterialDomain == MaterialDomain::POST_PROCESS) {
mShading = Shading::UNLIT;
}
// Add a default color output.
if (mMaterialDomain == MaterialDomain::POST_PROCESS && mOutputs.empty()) {
output(VariableQualifier::OUT,
+29 -8
View File
@@ -63,9 +63,6 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade
out << "#extension GL_OES_EGL_image_external : require\n\n";
}
}
if (material.has3dSamplers && mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) {
out << "#extension GL_OES_texture_3D : require\n\n";
}
if (v.hasInstancedStereo() && stage == ShaderStage::VERTEX) {
// If we're not processing the shader through glslang (in the case of unoptimized
// OpenGL shaders), then we need to add the #extension string ourselves.
@@ -89,6 +86,10 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade
break;
}
if (mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) {
out << "#extension GL_OES_standard_derivatives : require\n\n";
}
// This allows our includer system to use the #line directive to denote the source file for
// #included code. This way, glslang reports errors more accurately.
out << "#extension GL_GOOGLE_cpp_style_line_directive : enable\n\n";
@@ -163,11 +164,13 @@ utils::io::sstream& CodeGenerator::generateProlog(utils::io::sstream& out, Shade
mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
if (stage == ShaderStage::VERTEX) {
generateDefine(out, "VARYING", "out");
generateDefine(out, "ATTRIBUTE", "in");
} else if (stage == ShaderStage::FRAGMENT) {
generateDefine(out, "VARYING", "in");
}
} else {
generateDefine(out, "VARYING", "varying");
generateDefine(out, "ATTRIBUTE", "attribute");
}
auto getShadingDefine = [](Shading shading) -> const char* {
@@ -421,6 +424,13 @@ io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type,
return out;
}
// Feature level 0 only supports one output.
if (index > 0 && mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) {
slog.w << "Discarding an output in the generated ESSL 1.0 shader: index = " << index
<< ", name = " << name.c_str() << io::endl;
return out;
}
// TODO: add and support additional variable qualifiers
(void) qualifier;
assert(qualifier == MaterialBuilder::VariableQualifier::OUT);
@@ -434,7 +444,9 @@ io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type,
// formats behind the scenes. It's an error to output fewer components than the attachment
// needs, so we always output a float4 instead of a float3. It's never an error to output extra
// components.
if (mTargetApi == TargetApi::METAL) {
//
// Meanwhile, ESSL 1.0 must always write to gl_FragColor, a vec4.
if (mTargetApi == TargetApi::METAL || mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) {
if (outputType == MaterialBuilder::OutputType::FLOAT3) {
outputType = MaterialBuilder::OutputType::FLOAT4;
swizzleString = ".rgb";
@@ -446,13 +458,21 @@ io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type,
const char* typeString = getOutputTypeName(outputType);
out << "\n#define FRAG_OUTPUT" << index << " " << name.c_str();
out << "\n#define FRAG_OUTPUT_AT" << index << " output_" << name.c_str();
if (mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) {
out << "\n#define FRAG_OUTPUT_AT" << index << " gl_FragColor";
} else {
out << "\n#define FRAG_OUTPUT_AT" << index << " output_" << name.c_str();
}
out << "\n#define FRAG_OUTPUT_MATERIAL_TYPE" << index << " " << materialTypeString;
out << "\n#define FRAG_OUTPUT_PRECISION" << index << " " << precisionString;
out << "\n#define FRAG_OUTPUT_TYPE" << index << " " << typeString;
out << "\n#define FRAG_OUTPUT_SWIZZLE" << index << " " << swizzleString;
out << "\nlayout(location=" << index << ") out " << precisionString << " "
out << "\n";
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
out << "\nlayout(location=" << index << ") out " << precisionString << " "
<< typeString << " output_" << name.c_str() << ";\n";
}
return out;
}
@@ -554,11 +574,12 @@ io::sstream& CodeGenerator::generateUboAsPlainUniforms(io::sstream& out, ShaderS
io::sstream& CodeGenerator::generateBufferInterfaceBlock(io::sstream& out, ShaderStage stage,
uint32_t binding, const BufferInterfaceBlock& uib) const {
auto const& infos = uib.getFieldInfoList();
if (infos.empty()) {
if (uib.isEmptyForFeatureLevel(mFeatureLevel)) {
return out;
}
auto const& infos = uib.getFieldInfoList();
if (mTargetLanguage == TargetLanguage::GLSL &&
mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0) {
// we need to generate a structure instead
-18
View File
@@ -872,24 +872,6 @@ TEST_F(MaterialCompiler, FeatureLevel0Sampler2D) {
EXPECT_TRUE(result.isValid());
}
TEST_F(MaterialCompiler, FeatureLevel0Sampler3D) {
std::string shaderCode(R"(
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = texture3D(materialParams_sampler, vec3(0.0, 0.0, 0.0));
}
)");
filamat::MaterialBuilder builder;
builder.parameter("sampler", SamplerType::SAMPLER_3D);
builder.featureLevel(FeatureLevel::FEATURE_LEVEL_0);
builder.shading(filament::Shading::UNLIT);
builder.material(shaderCode.c_str());
builder.printShaders(true);
filamat::Package result = builder.build(*jobSystem);
EXPECT_TRUE(result.isValid());
}
TEST_F(MaterialCompiler, FeatureLevel0Ess3CallFails) {
std::string shaderCode(R"(
void material(inout MaterialInputs material) {
@@ -9,7 +9,8 @@ material {
blending : transparent,
culling : none,
depthCulling : false,
shadingModel : unlit
shadingModel : unlit,
featureLevel : 0
}
fragment {
+7 -5
View File
@@ -603,10 +603,14 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
return Engine::Builder()
.backend(backend)
.platform(mFilamentApp->mVulkanPlatform)
.featureLevel(config.featureLevel)
.build();
#endif
}
return Engine::Builder().backend(backend).build();
return Engine::Builder()
.backend(backend)
.featureLevel(config.featureLevel)
.build();
};
if (config.headless) {
@@ -647,10 +651,8 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
#endif
// Select the feature level to use
config.featureLevel = std::min(config.featureLevel,
mFilamentApp->mEngine->getSupportedFeatureLevel());
mFilamentApp->mEngine->setActiveFeatureLevel(config.featureLevel);
// Write back the active feature level.
config.featureLevel = mFilamentApp->mEngine->getActiveFeatureLevel();
mSwapChain = mFilamentApp->mEngine->createSwapChain(
nativeSwapChain, filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
+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;
};
+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);
@@ -217,7 +217,7 @@ Texture* IBLPrefilterContext::EquirectangularToCubemap::operator()(
"equirect must be a 2D texture.");
UTILS_UNUSED_IN_RELEASE
const uint8_t maxLevelCount = uint8_t(std::log2(equirect->getWidth()) + 0.5f) + 1u;
const uint8_t maxLevelCount = std::max(1, std::ilogbf(float(equirect->getWidth())) + 1);
ASSERT_PRECONDITION(equirect->getLevels() == maxLevelCount,
"equirect must have %u mipmap levels allocated.", +maxLevelCount);
+2 -2
View File
@@ -41,8 +41,8 @@ set(SRCS
set(RESOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR})
set(RESOURCE_BINS
${CMAKE_CURRENT_SOURCE_DIR}/web/style.css
${CMAKE_CURRENT_SOURCE_DIR}/web/script.js
${CMAKE_CURRENT_SOURCE_DIR}/web/api.js
${CMAKE_CURRENT_SOURCE_DIR}/web/app.js
${CMAKE_CURRENT_SOURCE_DIR}/web/index.html
)
+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
+42 -57
View File
@@ -50,13 +50,28 @@ using utils::FixedCapacityVector;
// serves files directly from the source code tree.
#define SERVE_FROM_SOURCE_TREE 0
// When set to 1, we will serve an experimental frontend, which will potentially replace the current
// frontend when ready.
#define EXPERIMENTAL_WEB_FRAMEWORK 0
#if SERVE_FROM_SOURCE_TREE
namespace {
std::string const BASE_URL = "libs/matdbg/web";
} // anonymous
#else
#if !SERVE_FROM_SOURCE_TREE
#include "matdbg_resources.h"
#endif
#include <unordered_map>
namespace {
struct Asset {
std::string_view mime;
std::string_view data;
};
std::unordered_map<std::string_view, Asset> ASSET_MAP;
} // anonymous
#endif // SERVE_FROM_SOURCE_TREE
namespace filament::matdbg {
@@ -74,14 +89,6 @@ std::string_view const DebugServer::kErrorHeader =
"HTTP/1.1 404 Not Found\r\nContent-Type: %s\r\n"
"Connection: close\r\n\r\n";
#if EXPERIMENTAL_WEB_FRAMEWORK
namespace {
std::string const BASE_URL = "libs/matdbg/web/experiment";
} // anonymous
class FileRequestHandler : public CivetHandler {
public:
FileRequestHandler(DebugServer* server) : mServer(server) {}
@@ -92,66 +99,44 @@ public:
if (uri == "/") {
uri = "/index.html";
}
#if SERVE_FROM_SOURCE_TREE
if (uri == "/index.html" || uri == "/app.js" || uri == "/api.js") {
mg_send_file(conn, (BASE_URL + uri).c_str());
return true;
}
slog.e << "DebugServer: bad request at line " << __LINE__ << ": " << uri << io::endl;
return false;
}
private:
DebugServer* mServer;
};
#else
class FileRequestHandler : public CivetHandler {
public:
FileRequestHandler(DebugServer* server) : mServer(server) {}
bool handleGet(CivetServer *server, struct mg_connection *conn) {
auto const& kSuccessHeader = DebugServer::kSuccessHeader;
const struct mg_request_info* request = mg_get_request_info(conn);
std::string uri(request->request_uri);
if (uri == "/" || uri == "/index.html") {
#if SERVE_FROM_SOURCE_TREE
mg_send_file(conn, "libs/matdbg/web/index.html");
#else
mg_printf(conn, kSuccessHeader.data(), "text/html");
mg_write(conn, mServer->mHtml.c_str(), mServer->mHtml.size());
#endif
return true;
}
if (uri == "/style.css") {
#if SERVE_FROM_SOURCE_TREE
mg_send_file(conn, "libs/matdbg/web/style.css");
#else
mg_printf(conn, kSuccessHeader.data(), "text/css");
mg_write(conn, mServer->mCss.c_str(), mServer->mCss.size());
#endif
return true;
}
if (uri == "/script.js") {
#if SERVE_FROM_SOURCE_TREE
mg_send_file(conn, "libs/matdbg/web/script.js");
#else
mg_printf(conn, kSuccessHeader.data(), "text/javascript");
mg_write(conn, mServer->mJavascript.c_str(), mServer->mJavascript.size());
#endif
auto const& asset_itr = ASSET_MAP.find(uri);
if (asset_itr != ASSET_MAP.end()) {
auto const& mime = asset_itr->second.mime;
auto const& data = asset_itr->second.data;
mg_printf(conn, kSuccessHeader.data(), mime.data());
mg_write(conn, data.data(), data.size());
return true;
}
#endif
slog.e << "DebugServer: bad request at line " << __LINE__ << ": " << uri << io::endl;
return false;
}
private:
DebugServer* mServer;
};
#endif
DebugServer::DebugServer(Backend backend, int port) : mBackend(backend) {
#if !SERVE_FROM_SOURCE_TREE
mHtml = CString((const char*) MATDBG_RESOURCES_INDEX_DATA, MATDBG_RESOURCES_INDEX_SIZE - 1);
mJavascript = CString((const char*) MATDBG_RESOURCES_SCRIPT_DATA, MATDBG_RESOURCES_SCRIPT_SIZE - 1);
mCss = CString((const char*) MATDBG_RESOURCES_STYLE_DATA, MATDBG_RESOURCES_STYLE_SIZE - 1);
ASSET_MAP["/index.html"] = {
.mime = "text/html",
.data = {(char const*) MATDBG_RESOURCES_INDEX_DATA},
};
ASSET_MAP["/app.js"] = {
.mime = "text/javascript",
.data = {(char const*) MATDBG_RESOURCES_APP_DATA},
};
ASSET_MAP["/api.js"] = {
.mime = "text/javascript",
.data = {(char const*) MATDBG_RESOURCES_API_DATA},
};
#endif
// By default the server spawns 50 threads so we override this to 10. According to the civetweb
@@ -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;
}
@@ -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) > 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);
}
});
}
@@ -242,6 +247,10 @@ class CodeViewer extends LitElement {
}
_rebuild() {
if (!this.active || !this.modified) {
console.log('Called rebuild while variant is inactive or unmodified');
return;
}
this.dispatchEvent(new CustomEvent(
'rebuild-shader',
{detail: this.editor.getValue(), bubbles: true, composed: true}
@@ -250,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';
}
}
@@ -307,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() {
@@ -320,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;
@@ -342,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;
}
@@ -352,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') {
@@ -383,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.
@@ -420,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;
@@ -454,9 +603,6 @@ class MaterialSidePanel extends LitElement {
flex-direction: row;
display: flex;
}
hr {
${MENU_HR}
}
`;
}
@@ -480,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;
@@ -524,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()) {
@@ -542,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]);
}
}
}
}
@@ -646,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>
`;
}
@@ -790,6 +951,12 @@ class MatdbgViewer extends LitElement {
}
);
this.addEventListener('option-backend',
(ev) => {
this.currentBackend = ev.detail;
}
);
addEventListener('resize', this._onResize.bind(this));
}
@@ -871,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;
@@ -885,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;
@@ -897,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];
}
})();
}
}
-23
View File
@@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Filament Debugger</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
<link href="https://google.github.io/filament/favicon.png" rel="icon" type="image/x-icon" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">
<style>
html, body {
height: 100%;
margin: 0;
font-family: "Open Sans";
}
</style>
<script src="api.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/vs/loader.js"></script>
</head>
<body>
<script src="app.js" type="module"></script>
<matdbg-viewer></matdbg-viewer>
</body>
</html>
+18 -91
View File
@@ -1,96 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<title>Filament Debugger</title>
<meta charset="utf-8">
<link rel="icon" type="image/png" href="https://google.github.io/filament/favicon.png" />
<link href="https://fonts.googleapis.com/css?family=Lexend Deca" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body class="vbox viewport">
<header>matdbg</header>
<section class="main hbox space-between">
<nav class="vbox">
<div id="material-list" class="scrollable squishy">
</div>
<div id="material-detail" class="scrollable stretchy">
</div>
</nav>
<article id="shader-source">
</article>
</section>
<footer>&nbsp;</footer>
<template id="material-list-template">
<div style="white-space: nowrap">
{{#item}}
{{#is_label}}
<br/><b>{{label}}</b>
{{/is_label}}
{{#is_material}}
<a class="material {{classes}}" data-matid="{{matid}}">{{name}}</a>
{{/is_material}}
<br/>
{{/item}}
</div>
</template>
<template id="material-detail-template">
<div style="white-space: pre">
name = {{ name }}
version = {{ version }}
{{#required_attributes.length}}
<b>Required attributes</b>
{{#required_attributes}}
{{.}}
{{/required_attributes}}
{{/required_attributes.length}}
<b>OpenGL shaders</b>
{{#opengl}}
<a class="shader {{classes}}" data-glindex="{{index}}">{{index}} {{shaderModel}} {{pipelineStage}} {{variantString}}</a>
{{/opengl}}
<b>Vulkan shaders</b>
{{#vulkan}}
<a class="shader {{classes}}" data-vkindex="{{index}}">{{index}} {{shaderModel}} {{pipelineStage}} {{variantString}}</a>
{{/vulkan}}
<b>Metal shaders</b>
{{#metal}}
<a class="shader {{classes}}" data-metalindex="{{index}}">{{index}} {{shaderModel}} {{pipelineStage}} {{variantString}}</a>
{{/metal}}
<b>Material details</b>
{{#shading}}
shading model = {{model}}
vertex domain = {{vertex_domain}}
interpolation = {{interpolation}}
shadow multiply = {{shadow_multiply}}
specular antialiasing = {{specular_antialiasing}}
variance = {{variance}}
threshold = {{threshold}}
clear coat IOR change = {{clear_coat_IOR_change}}
{{/shading}}
{{#raster}}
blending = {{blending}}
mask threshold = {{mask_threshold}}
color write = {{color_write}}
depth write = {{depth_write}}
depth test = {{depth_test}}
double sided = {{double_sided}}
culling = {{culling}}
transparency = {{transparency}}
{{/raster}}
</div>
</template>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/3.0.1/mustache.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.17.1/min/vs/loader.js"></script>
<script src="script.js"></script>
</body>
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
<link href="https://google.github.io/filament/favicon.png" rel="icon" type="image/x-icon" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">
<style>
html, body {
height: 100%;
margin: 0;
font-family: "Open Sans";
}
</style>
<script src="api.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/vs/loader.js"></script>
</head>
<body>
<script src="app.js" type="module"></script>
<matdbg-viewer></matdbg-viewer>
</body>
</html>
-516
View File
@@ -1,516 +0,0 @@
/*
* Copyright (C) 2019 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.
*/
const kMonacoBaseUrl = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/';
const kUntitledPlaceholder = "untitled";
const materialList = document.getElementById("material-list");
const materialDetail = document.getElementById("material-detail");
const header = document.querySelector("header");
const footer = document.querySelector("footer");
const shaderSource = document.getElementById("shader-source");
const matDetailTemplate = document.getElementById("material-detail-template");
const matListTemplate = document.getElementById("material-list-template");
const STATUS_LOOP_TIMEOUT = 3000;
const gMaterialDatabase = {};
let gEditor = null;
let gCurrentMaterial = "00000000";
let gCurrentLanguage = "glsl";
let gCurrentShader = { matid: "00000000", glindex: 0 };
let gEditorIsLoading = false;
require.config({ paths: { "vs": `${kMonacoBaseUrl}vs` }});
window.MonacoEnvironment = {
getWorkerUrl: function() {
return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
self.MonacoEnvironment = {
baseUrl: '${kMonacoBaseUrl}'
};
importScripts('${kMonacoBaseUrl}vs/base/worker/workerMain.js');`
)}`;
}
};
function getShaderAPI(selection) {
if (!selection) {
selection = gCurrentShader;
}
if ("glindex" in selection) return "opengl";
if ("vkindex" in selection) return "vulkan";
if ("metalindex" in selection) return "metal";
return "error";
}
function rebuildMaterial() {
let api = 0, index = -1;
const shader = getShaderRecord(gCurrentShader);
const shaderApi = getShaderAPI();
switch (shaderApi) {
case "opengl": api = 1; index = gCurrentShader.glindex; break;
case "vulkan": api = 2; index = gCurrentShader.vkindex; break;
case "metal": api = 3; index = gCurrentShader.metalindex; break;
}
if (shaderApi === "vulkan") {
if (gCurrentLanguage === "glsl") {
delete shader["spirv"];
} else if (gCurrentLanguage === "spirv") {
delete shader["glsl"];
}
}
const editedText = shader[gCurrentLanguage];
const req = new XMLHttpRequest();
req.open('POST', '/api/edit');
req.send(`${gCurrentShader.matid} ${api} ${index} ${editedText}`);
}
document.querySelector("body").addEventListener("click", (evt) => {
const anchor = evt.target.closest("a");
if (!anchor) {
return;
}
// Handle selection of a material.
if (anchor.classList.contains("material")) {
selectMaterial(anchor.dataset.matid, true);
return;
}
// Handle selection of a shader.
if (anchor.classList.contains("shader")) {
selectShader(anchor.dataset);
return;
}
// Handle a rebuild.
if (anchor.classList.contains("rebuild")) {
rebuildMaterial();
return;
}
// Handle language selection.
for (const lang of "glsl spirv msl".split(" ")) {
if (anchor.classList.contains(lang)) {
gCurrentLanguage = lang;
selectShader(gCurrentShader);
return;
}
}
});
// Handle Ctrl+Arrow for fast keyboard navigation between shader variants and materials. Either the
// materialStep or shaderStep argument can be non-zero (not both) and they must be -1, 0, or +1.
// TODO: this function could be vastly simplified by changing the format of the shader selector.
function selectNextShader(materialStep, shaderStep) {
if (materialStep !== 0) {
const matids = getDisplayedMaterials().map(m => m.matid).filter(m => m);
const currentIndex = matids.indexOf(gCurrentMaterial);
const nextIndex = currentIndex + materialStep;
if (nextIndex >= 0 && nextIndex < matids.length) {
selectMaterial(matids[nextIndex], true);
}
return;
}
const material = gMaterialDatabase[gCurrentMaterial];
const variants = [];
let currentIndex = 0;
for (const [index, shader] of material.opengl.entries()) {
if (index === gCurrentShader.glindex) currentIndex = variants.length;
variants.push({ matid, glindex: index });
}
for (const [index, shader] of material.vulkan.entries()) {
if (index === gCurrentShader.vkindex) currentIndex = variants.length;
variants.push({ matid, vkindex: index });
}
for (const [index, shader] of material.metal.entries()) {
if (index === gCurrentShader.metalindex) currentIndex = variants.length;
variants.push({ matid, metalindex: index });
}
const nextIndex = currentIndex + shaderStep;
if (nextIndex >= 0 && nextIndex < variants.length) {
selectShader(variants[nextIndex]);
}
}
function fetchMaterial(matid) {
fetch(`api/material?matid=${matid}`).then(function(response) {
return response.json();
}).then(function(matInfo) {
if (matid in gMaterialDatabase) {
return;
}
matInfo.matid = matid;
gMaterialDatabase[matid] = matInfo;
renderMaterialList();
});
}
function queryActiveShaders() {
if (!isConnected()) {
return;
}
fetch("api/active").then(function(response) {
return response.json();
}).then(function(activeMaterials) {
// The only active materials are the ones with active variants.
for (matid in gMaterialDatabase) {
const material = gMaterialDatabase[matid];
material.active = false;
}
for (matid in activeMaterials) {
const material = gMaterialDatabase[matid];
const activeBackend = activeMaterials[matid][0];
const activeShaders = activeMaterials[matid].slice(1);
for (const shader of material[activeBackend]) {
shader.active = activeShaders.indexOf(shader.variant) > -1;
material.active = material.active || shader.active;
}
}
renderMaterialList();
renderMaterialDetail();
})
.catch(error => {
// This can occur if the JSON is invalid.
console.error(error);
});
}
function isConnected() {
return footer.innerText == 'connected';
}
function onConnected() {
footer.innerText = 'connected';
fetch("api/matids").then(function(response) {
return response.json();
}).then(function(matInfo) {
for (matid of matInfo) {
if (!(matid in gMaterialDatabase)) {
fetchMaterial(matid);
}
}
});
}
function onDisconnected() {
footer.innerText = 'not connected';
for (matid in gMaterialDatabase) {
const material = gMaterialDatabase[matid];
material.active = false;
for (const shader of material.opengl) shader.active = false;
for (const shader of material.vulkan) shader.active = false;
for (const shader of material.metal) shader.active = false;
}
renderMaterialList();
renderMaterialDetail();
}
function statusLoop() {
// This is a hanging get except for when transition from disconnected to connected, which
// should return immediately.
fetch("api/status" + (isConnected() ? '' : '?firstTime'))
.then(async (response) => {
const matid = await response.text();
// A first-time request returned successfully
if (matid === '0') {
onConnected();
} else if (matid != '1') {
fetchMaterial(matid);
} // else matid == '1' and it's a no-op, we just loop again.
statusLoop();
})
.catch(err => {
onDisconnected();
setTimeout(statusLoop, STATUS_LOOP_TIMEOUT)
});
}
function fetchMaterials() {
fetch("api/materials").then(function(response) {
return response.json();
}).then(function(matJson) {
for (const matInfo of matJson) {
if (matInfo.matid in gMaterialDatabase) {
continue;
}
gMaterialDatabase[matInfo.matid] = matInfo;
}
selectMaterial(matJson[0].matid, true);
});
}
function fetchShader(selection, matinfo, onDone) {
let query, target, index;
switch (getShaderAPI(selection)) {
case "opengl":
index = parseInt(selection.glindex);
query = `type=${gCurrentLanguage}&glindex=${index}`;
target = matinfo.opengl[index];
break;
case "vulkan":
index = parseInt(selection.vkindex);
query = `type=${gCurrentLanguage}&vkindex=${index}`;
target = matinfo.vulkan[index];
break;
case "metal":
index = parseInt(selection.metalindex);
query = `type=${gCurrentLanguage}&metalindex=${index}`;
target = matinfo.metal[index];
break;
}
fetch(`api/shader?matid=${matinfo.matid}&${query}`).then(function(response) {
return response.text();
}).then(function(shaderText) {
target[gCurrentLanguage] = shaderText;
onDone();
});
}
function getDisplayedMaterials() {
const items = [];
// Names need not be unique, so we display a numeric suffix for non-unique names.
// To achieve stable ordering of anonymous materials, we first sort by matid.
const labels = new Set();
const matids = Object.keys(gMaterialDatabase).sort();
const duplicatedLabels = {};
for (const matid of matids) {
const name = gMaterialDatabase[matid].name || kUntitledPlaceholder;
if (labels.has(name)) {
duplicatedLabels[name] = 0;
} else {
labels.add(name);
}
}
// Build a list of objects to pass into the template string.
for (const matid of matids) {
const item = Object.assign({}, gMaterialDatabase[matid]);
item.classes = matid === gCurrentMaterial ? "current " : "";
if (!item.active) {
item.classes += "inactive "
}
item.domain = item.shading.material_domain === "surface" ? "surface" : "postpro";
item.is_material = true;
const name = item.name || kUntitledPlaceholder;
if (name in duplicatedLabels) {
const index = duplicatedLabels[name];
item.name = `${name} (${index})`;
duplicatedLabels[name] = index + 1;
} else {
item.name = name;
}
items.push(item);
}
// The template takes a flat list of items, so here we insert items for section headers using
// blank names, which causes them to sort to the top of their respective sections.
const sectionLabel = {"is_label": true, "name": ""};
items.push(Object.assign({"label": "Surface materials", "domain": "surface"}, sectionLabel));
items.push(Object.assign({"label": "PostProcess materials", "domain": "postpro"}, sectionLabel));
// Next, sort all materials and section headers.
items.sort((a, b) => {
if (a.domain > b.domain) return -1;
if (a.domain < b.domain) return +1;
if (a.name < b.name) return -1;
if (a.name > b.name) return +1;
return 0;
});
return items;
}
function renderMaterialList() {
const items = getDisplayedMaterials();
materialList.innerHTML = Mustache.render(matListTemplate.innerHTML, { "item": items } );
}
function updateClassList(array, indexProperty, selectedIndex) {
for (let item of array) {
const current = parseInt(item[indexProperty]) === selectedIndex;
item.classes = current ? "current " : "";
if (!item.active) {
item.classes += "inactive "
}
}
}
function renderMaterialDetail() {
const mat = gMaterialDatabase[gCurrentMaterial];
const ok = mat.matid === gCurrentShader.matid;
updateClassList(mat.opengl, "index", ok ? parseInt(gCurrentShader.glindex) : -1);
updateClassList(mat.vulkan, "index", ok ? parseInt(gCurrentShader.vkindex) : -1);
updateClassList(mat.metal, "index", ok ? parseInt(gCurrentShader.metalindex) : -1);
const item = Object.assign({}, mat);
if (item.shading.material_domain !== "surface") {
delete item.shading;
}
materialDetail.innerHTML = Mustache.render(matDetailTemplate.innerHTML, item);
}
function getShaderRecord(selection) {
const mat = gMaterialDatabase[gCurrentMaterial];
if (selection.glindex >= 0) return mat.opengl[parseInt(selection.glindex)];
if (selection.vkindex >= 0) return mat.vulkan[parseInt(selection.vkindex)];
if (selection.metalindex >= 0) return mat.metal[parseInt(selection.metalindex)];
return null;
}
function renderShaderStatus() {
const shader = getShaderRecord(gCurrentShader);
let statusString = "";
if (shader) {
const glsl = "glsl " + (gCurrentLanguage === "glsl" ? "active" : "");
const msl = "msl " + (gCurrentLanguage === "msl" ? "active" : "");
const spirv = "spirv " + (gCurrentLanguage === "spirv" ? "active" : "");
switch (getShaderAPI()) {
case "opengl":
statusString += ` &nbsp; <a class='status_button ${glsl}'>[GLSL]</a>`;
break;
case "metal":
statusString += ` &nbsp; <a class='status_button ${msl}'>[MSL]</a>`;
break;
case "vulkan":
statusString += ` &nbsp; <a class='status_button ${glsl}'>[GLSL]</a>`;
statusString += ` &nbsp; <a class='status_button ${spirv}'>[SPIRV]</a>`;
break;
}
if (shader.modified && gCurrentLanguage !== "spirv") {
statusString += " &nbsp; <a class='status_button rebuild'>[rebuild]</a>";
}
if (!shader.active) {
statusString += " &nbsp; <span class='warning'> selected variant is inactive </span>";
}
}
header.innerHTML = "matdbg" + statusString;
}
function selectShader(selection) {
const shader = getShaderRecord(selection);
if (!shader) {
console.error("Shader not yet available.")
return;
}
// Change the current language selection if necessary.
switch (getShaderAPI(selection)) {
case "opengl":
if (gCurrentLanguage !== "glsl") {
gCurrentLanguage = "glsl";
}
break;
case "vulkan":
if (gCurrentLanguage !== "spirv" && gCurrentLanguage !== "glsl") {
gCurrentLanguage = "spirv";
}
break;
case "metal":
if (gCurrentLanguage !== "msl") {
gCurrentLanguage = "msl";
}
break;
}
const showShaderSource = () => {
gCurrentShader = selection;
gCurrentShader.matid = gCurrentMaterial;
renderMaterialDetail();
gEditorIsLoading = true;
gEditor.setValue(shader[gCurrentLanguage]);
gEditorIsLoading = false;
shaderSource.style.visibility = "visible";
renderShaderStatus();
};
if (!shader[gCurrentLanguage]) {
const matInfo = gMaterialDatabase[gCurrentMaterial];
fetchShader(selection, matInfo, showShaderSource);
} else {
showShaderSource();
}
}
function onEdit(changes) {
if (gEditorIsLoading) {
return;
}
const shader = getShaderRecord(gCurrentShader);
if (!shader) {
return;
}
if (!shader.modified) {
shader.modified = true;
renderShaderStatus();
}
shader[gCurrentLanguage] = gEditor.getValue();
}
function selectMaterial(matid, selectFirstShader) {
gCurrentMaterial = matid;
renderMaterialList();
renderMaterialDetail();
if (selectFirstShader) {
const mat = gMaterialDatabase[gCurrentMaterial];
const selection = { matid };
if (mat.opengl.length > 0) selection.glindex = 0;
else if (mat.vulkan.length > 0) selection.vkindex = 0;
else if (mat.metal.length > 0) selection.metalindex = 0;
selectShader(selection);
}
}
function init() {
require(["vs/editor/editor.main"], function () {
const KeyMod = monaco.KeyMod, KeyCode = monaco.KeyCode;
gEditor = monaco.editor.create(shaderSource, {
value: "",
language: "cpp",
scrollBeyondLastLine: false,
readOnly: false,
minimap: { enabled: false }
});
gEditor.onDidChangeModelContent((e) => { onEdit(e.changes); });
gEditor.addCommand(KeyMod.CtrlCmd | KeyCode.KEY_S, () => rebuildMaterial());
gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.UpArrow, () => selectNextShader(-1, 0));
gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.DownArrow, () => selectNextShader(+1, 0));
gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.LeftArrow, () => selectNextShader(0, -1));
gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.RightArrow, () => selectNextShader(0, +1));
fetchMaterials();
});
Mustache.parse(matDetailTemplate.innerHTML);
Mustache.parse(matListTemplate.innerHTML);
statusLoop();
// Poll for active shaders once every second.
// Take care not to poll more frequently than the frame rate. Active variants are determined
// by the list of variants that were fetched between this query and the previous query.
setInterval(queryActiveShaders, 1000);
}
init();
-110
View File
@@ -1,110 +0,0 @@
html, body, .viewport {
width: 100%;
height: 100%;
margin: 0;
}
body {
font-family: 'Lexend Deca', sans-serif;
overflow: hidden;
}
a, a:visited {
text-decoration: none;
color: #567;
cursor: pointer;
}
a:hover, a.current {
font-weight: bold;
color: #07f;
}
a.status_button {
color: #e4e682;
}
a.status_button.active {
color: #fff;
}
span.warning {
color: black;
background: orange;
font-size: small;
}
a.inactive {
color: #aaa;
}
a.inactive.current {
color: #5af;
}
pre {
font-family: Menlo, Monaco, "Courier New", monospace;
}
.vbox {
display: flex;
flex-direction: column;
}
.scrollable {
position: relative;
overflow-y: scroll;
}
.scrollable > * {
position: absolute;
}
.squishy {
flex-grow: 1;
}
.stretchy {
flex-grow: 1;
}
.hbox {
display: flex;
flex-direction: row;
}
.space-between {
justify-content: space-between;
}
header, footer {
height: 26px;
background: cornflowerblue;
padding: 5px 0 0 5px;
}
.main {
flex: 1;
}
article {
flex: 5;
border-top: solid 2px;
border-bottom: solid 2px;
visibility: hidden;
}
nav {
border: solid 2px;
font-size: 12px;
flex: 1;
}
nav > *:first-child {
border-bottom: solid 2px;
}
nav > * {
padding-left: 5px;
padding-right: 5px;
}

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