Merge branch 'rc/1.46.0' into release
This commit is contained in:
@@ -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))))
|
||||
@@ -16,3 +16,5 @@ settings.json
|
||||
test*.png
|
||||
test*.json
|
||||
results
|
||||
/compile_commands.json
|
||||
/.cache
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -7,3 +7,4 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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);
|
||||
|
||||
+49
-40
@@ -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,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.
|
||||
|
||||
|
||||
+3
-8
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -95,6 +95,7 @@ public:
|
||||
|
||||
// screen-space reflection and/or refraction (SSR)
|
||||
void prepareSSR(TextureHandle ssr,
|
||||
bool disableSSR,
|
||||
float refractionLodOffset,
|
||||
ScreenSpaceReflectionsOptions const& ssrOptions) noexcept;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -859,6 +859,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
|
||||
PostProcessManager::generateMipmapSSR(ppm, fg,
|
||||
reflections, ssrConfig.reflection, false, ssrConfig);
|
||||
}
|
||||
config.screenSpaceReflectionHistoryNotReady = !reflections;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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 = {
|
||||
|
||||
+18
-10
@@ -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; }
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace utils {
|
||||
|
||||
namespace filament::gltfio {
|
||||
|
||||
class Wireframe;
|
||||
struct Wireframe;
|
||||
|
||||
// Encapsulates VertexBuffer::setBufferAt() or IndexBuffer::setBuffer().
|
||||
struct BufferSlot {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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> </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>
|
||||
|
||||
@@ -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 += ` <a class='status_button ${glsl}'>[GLSL]</a>`;
|
||||
break;
|
||||
case "metal":
|
||||
statusString += ` <a class='status_button ${msl}'>[MSL]</a>`;
|
||||
break;
|
||||
case "vulkan":
|
||||
statusString += ` <a class='status_button ${glsl}'>[GLSL]</a>`;
|
||||
statusString += ` <a class='status_button ${spirv}'>[SPIRV]</a>`;
|
||||
break;
|
||||
}
|
||||
if (shader.modified && gCurrentLanguage !== "spirv") {
|
||||
statusString += " <a class='status_button rebuild'>[rebuild]</a>";
|
||||
}
|
||||
if (!shader.active) {
|
||||
statusString += " <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();
|
||||
@@ -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
Reference in New Issue
Block a user