Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0a3b10681 |
@@ -7,4 +7,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- engine: add `MaterialInstance::setConstant()` and `MaterialInstance::getConstant()` methods. These allow for per-material instance specialization constant overrides.
|
||||
- matc: better material compression using a Multi-Base Variable-Length Interleaved Token Stream [⚠️ **New Material Version**]
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.71.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.70.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -50,7 +50,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.71.1'
|
||||
pod 'Filament', '~> 1.70.1'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,13 +7,6 @@ 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.71.1
|
||||
|
||||
|
||||
## v1.71.0
|
||||
|
||||
- matc: better material compression using a Multi-Base Variable-Length Interleaved Token Stream [⚠️ **New Material Version**]
|
||||
|
||||
## v1.70.2
|
||||
|
||||
|
||||
|
||||
@@ -22,12 +22,6 @@
|
||||
|
||||
#include <utils/Entity.h>
|
||||
#include <utils/EntityManager.h>
|
||||
#include <utils/tribool.h>
|
||||
|
||||
#include <filament/Material.h>
|
||||
#include <filament/View.h>
|
||||
|
||||
#include "common/CallbackUtils.h"
|
||||
|
||||
using namespace filament;
|
||||
using namespace utils;
|
||||
@@ -435,24 +429,6 @@ Java_com_google_android_filament_Engine_nIsPaused(JNIEnv*, jclass,
|
||||
return (jboolean)engine->isPaused();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Engine_nCompile(JNIEnv* env, jclass,
|
||||
jlong nativeEngine, jint priority, jlong nativeMaterial, jlong nativeView,
|
||||
jint shadowReceiver, jint skinning, jobject handler, jobject runnable) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
Material* material = (Material*) nativeMaterial;
|
||||
View* view = (View*) nativeView;
|
||||
JniCallback* jniCallback = JniCallback::make(env, handler, runnable);
|
||||
engine->compile(
|
||||
(backend::CompilerPriorityQueue) priority,
|
||||
material, view,
|
||||
(utils::tribool::value_t) shadowReceiver,
|
||||
(utils::tribool::value_t) skinning,
|
||||
jniCallback->getHandler(), [jniCallback](Material*){
|
||||
JniCallback::postToJavaAndDestroy(jniCallback);
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Engine_nSetPaused(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jboolean paused) {
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
#include <filament/Texture.h>
|
||||
#include <filament/TextureSampler.h>
|
||||
|
||||
#include "common/CallbackUtils.h"
|
||||
|
||||
#include <math/mat3.h>
|
||||
#include <math/mat4.h>
|
||||
#include <math/vec2.h>
|
||||
@@ -248,69 +246,6 @@ Java_com_google_android_filament_MaterialInstance_nSetFloatParameterArray(JNIEnv
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nGetConstantBool(JNIEnv *env, jclass,
|
||||
jlong nativeMaterialInstance, jstring name_) {
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
jboolean result = instance->getConstant<bool>(name);
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
return result;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jfloat JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nGetConstantFloat(JNIEnv *env, jclass,
|
||||
jlong nativeMaterialInstance, jstring name_) {
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
jfloat result = instance->getConstant<float>(name);
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
return result;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nGetConstantInt(JNIEnv *env, jclass,
|
||||
jlong nativeMaterialInstance, jstring name_) {
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
jint result = instance->getConstant<int32_t>(name);
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
return result;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nSetConstantBool(JNIEnv *env, jclass,
|
||||
jlong nativeMaterialInstance, jstring name_, jboolean x) {
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
instance->setConstant<bool>(name, x);
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nSetConstantFloat(JNIEnv *env, jclass,
|
||||
jlong nativeMaterialInstance, jstring name_, jfloat x) {
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
instance->setConstant<float>(name, x);
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nSetConstantInt(JNIEnv *env, jclass,
|
||||
jlong nativeMaterialInstance, jstring name_, jint x) {
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
instance->setConstant<int32_t>(name, x);
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
// defined in TextureSampler.cpp
|
||||
namespace filament::JniUtils {
|
||||
TextureSampler from_long(jlong params) noexcept;
|
||||
@@ -645,17 +580,3 @@ Java_com_google_android_filament_MaterialInstance_nGetTransparencyMode(JNIEnv*,
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
return (jint) instance->getTransparencyMode();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nCompile(JNIEnv *env, jclass clazz,
|
||||
jlong nativeMaterialInstance, jint priority, jint variants, jobject handler, jobject runnable) {
|
||||
MaterialInstance* materialInstance = (MaterialInstance*) nativeMaterialInstance;
|
||||
JniCallback* jniCallback = JniCallback::make(env, handler, runnable);
|
||||
materialInstance->compile(
|
||||
(MaterialInstance::CompilerPriorityQueue) priority,
|
||||
(UserVariantFilterBit) variants,
|
||||
jniCallback->getHandler(), [jniCallback](MaterialInstance*){
|
||||
JniCallback::postToJavaAndDestroy(jniCallback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -205,15 +205,6 @@ public class Engine {
|
||||
REALTIME,
|
||||
};
|
||||
|
||||
/**
|
||||
* Three-state feature state.
|
||||
*/
|
||||
public enum FeatureState {
|
||||
FALSE,
|
||||
TRUE,
|
||||
INDETERMINATE
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs <code>Engine</code> objects using a builder pattern.
|
||||
*/
|
||||
@@ -627,35 +618,6 @@ public class Engine {
|
||||
return mNativeObject != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously ensures that the variants of the specified Material required to render it
|
||||
* in the provided View are compiled. This determines the necessary permutations of
|
||||
* feature flags based on the supplied View, and compiles the corresponding shader variants.
|
||||
*
|
||||
* See {@link Material#compile(Material.CompilerPriorityQueue, int, Object, Runnable)} for
|
||||
* important details about the compilation process, callback scheduling, priorities, and flushing the engine.
|
||||
*
|
||||
* @param priority Which priority queue to use, LOW or HIGH.
|
||||
* @param material The Material whose variants will be compiled.
|
||||
* @param view The View in which the material will be rendered.
|
||||
* @param shadowReceiver Indicates whether to compile variants where the material receives shadows.
|
||||
* Use FeatureState.INDETERMINATE to compile both permutations.
|
||||
* @param skinning Indicates whether to compile variants with skinning.
|
||||
* Use FeatureState.INDETERMINATE to compile both permutations.
|
||||
* @param handler An {@link java.util.concurrent.Executor Executor}. On Android this can also be a {@link android.os.Handler Handler}.
|
||||
* @param callback callback called on the main thread when the compilation is done
|
||||
* by backend.
|
||||
*/
|
||||
public void compile(@NonNull Material.CompilerPriorityQueue priority,
|
||||
@NonNull Material material,
|
||||
@NonNull View view,
|
||||
@NonNull FeatureState shadowReceiver,
|
||||
@NonNull FeatureState skinning,
|
||||
@Nullable Object handler,
|
||||
@Nullable Runnable callback) {
|
||||
nCompile(getNativeObject(), priority.ordinal(), material.getNativeObject(), view.getNativeObject(), shadowReceiver.ordinal(), skinning.ordinal(), handler, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the <code>Engine</code> instance and all associated resources.
|
||||
* <p>
|
||||
@@ -1569,7 +1531,6 @@ public class Engine {
|
||||
private static native void nDestroyEntity(long nativeEngine, int entity);
|
||||
private static native boolean nFlushAndWait(long nativeEngine, long timeout);
|
||||
private static native void nFlush(long nativeEngine);
|
||||
private static native void nCompile(long nativeEngine, int priority, long nativeMaterial, long nativeView, int shadowReceiver, int skinning, Object handler, Runnable callback);
|
||||
private static native boolean nIsPaused(long nativeEngine);
|
||||
private static native void nSetPaused(long nativeEngine, boolean paused);
|
||||
private static native void nUnprotected(long nativeEngine);
|
||||
|
||||
@@ -522,19 +522,11 @@ public class Material {
|
||||
* for stereoscopic rendering. If an application is not planning to render in stereo, this bit
|
||||
* should be turned off to avoid unnecessary material compilations.
|
||||
*</p>
|
||||
*<p>
|
||||
* Note that it is possible to override specialization constants on a per-MaterialInstance basis
|
||||
* (see {@link MaterialInstance#setConstant}). In that case, the programs compiled by a call to
|
||||
* Material::compile() may not be reusable by that MaterialInstance. It's better to call
|
||||
* MaterialInstance::compile() in cases where you intend to override specialization constants.
|
||||
*</p>
|
||||
* @param priority Which priority queue to use, LOW or HIGH.
|
||||
* @param variants Variants to include to the compile command.
|
||||
* @param handler An {@link java.util.concurrent.Executor Executor}. On Android this can also be a {@link android.os.Handler Handler}.
|
||||
* @param callback callback called on the main thread when the compilation is done on
|
||||
* by backend.
|
||||
*
|
||||
* @see MaterialInstance#compile
|
||||
*/
|
||||
public void compile(@NonNull CompilerPriorityQueue priority,
|
||||
int variants,
|
||||
|
||||
-98
@@ -18,7 +18,6 @@ package com.google.android.filament;
|
||||
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.Size;
|
||||
|
||||
import com.google.android.filament.proguard.UsedByNative;
|
||||
@@ -143,28 +142,6 @@ public class MaterialInstance {
|
||||
return mMaterial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously ensures that a subset of this MaterialInstance's variants are compiled.
|
||||
*
|
||||
* <p>This function behaves identically to {@link Material#compile}, but takes into account
|
||||
* the specific constants overridden by {@link #setConstant}.</p>
|
||||
*
|
||||
* @param priority Priority of the compile command.
|
||||
* @param variants Variants to include to the compile command.
|
||||
* @param handler An {@link java.util.concurrent.Executor Executor}. On Android this can also be a {@link android.os.Handler Handler}.
|
||||
* @param callback callback called on the main thread when the compilation is done on
|
||||
* by backend.
|
||||
*
|
||||
* @see Material#compile
|
||||
* @see #setConstant
|
||||
*/
|
||||
public void compile(@NonNull Material.CompilerPriorityQueue priority,
|
||||
int variants,
|
||||
@Nullable Object handler,
|
||||
@Nullable Runnable callback) {
|
||||
nCompile(getNativeObject(), priority.ordinal(), variants, handler, callback);
|
||||
}
|
||||
|
||||
/** @return the name associated with this instance */
|
||||
@NonNull
|
||||
public String getName() {
|
||||
@@ -425,69 +402,6 @@ public class MaterialInstance {
|
||||
nSetParameterFloat4(getNativeObject(), name, color[0], color[1], color[2], color[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides a specialization constant of this material instance.
|
||||
*
|
||||
* @param name The name of the constant as defined in the material.
|
||||
* @param value The value of the constant.
|
||||
* @see Material.Builder#constant
|
||||
*/
|
||||
public void setConstant(@NonNull String name, boolean value) {
|
||||
nSetConstantBool(getNativeObject(), name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides a specialization constant of this material instance.
|
||||
*
|
||||
* @param name The name of the constant as defined in the material.
|
||||
* @param value The value of the constant.
|
||||
* @see Material.Builder#constant
|
||||
*/
|
||||
public void setConstant(@NonNull String name, float value) {
|
||||
nSetConstantFloat(getNativeObject(), name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides a specialization constant of this material instance.
|
||||
*
|
||||
* @param name The name of the constant as defined in the material.
|
||||
* @param value The value of the constant.
|
||||
* @see Material.Builder#constant
|
||||
*/
|
||||
public void setConstant(@NonNull String name, int value) {
|
||||
nSetConstantInt(getNativeObject(), name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a specialization constant by name.
|
||||
*
|
||||
* @param name The name of the constant as defined in the material.
|
||||
* @return The value of the constant.
|
||||
*/
|
||||
public boolean getConstantBoolean(@NonNull String name) {
|
||||
return nGetConstantBool(getNativeObject(), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a specialization constant by name.
|
||||
*
|
||||
* @param name The name of the constant as defined in the material.
|
||||
* @return The value of the constant.
|
||||
*/
|
||||
public float getConstantFloat(@NonNull String name) {
|
||||
return nGetConstantFloat(getNativeObject(), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a specialization constant by name.
|
||||
*
|
||||
* @param name The name of the constant as defined in the material.
|
||||
* @return The value of the constant.
|
||||
*/
|
||||
public int getConstantInt(@NonNull String name) {
|
||||
return nGetConstantInt(getNativeObject(), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set-up a custom scissor rectangle; by default it is disabled.
|
||||
*
|
||||
@@ -1023,17 +937,6 @@ public class MaterialInstance {
|
||||
@NonNull String name, int element, @NonNull @Size(min = 1) float[] v,
|
||||
@IntRange(from = 0) int offset, @IntRange(from = 1) int count);
|
||||
|
||||
private static native boolean nGetConstantBool(long nativeMaterialInstance, @NonNull String name);
|
||||
private static native float nGetConstantFloat(long nativeMaterialInstance, @NonNull String name);
|
||||
private static native int nGetConstantInt(long nativeMaterialInstance, @NonNull String name);
|
||||
|
||||
private static native void nSetConstantBool(long nativeMaterialInstance,
|
||||
@NonNull String name, boolean x);
|
||||
private static native void nSetConstantFloat(long nativeMaterialInstance,
|
||||
@NonNull String name, float x);
|
||||
private static native void nSetConstantInt(long nativeMaterialInstance,
|
||||
@NonNull String name, int x);
|
||||
|
||||
private static native void nSetParameterTexture(long nativeMaterialInstance,
|
||||
@NonNull String name, long nativeTexture, long sampler);
|
||||
|
||||
@@ -1097,5 +1000,4 @@ public class MaterialInstance {
|
||||
private static native int nGetDepthFunc(long nativeMaterialInstance);
|
||||
private static native void nSetTransparencyMode(long nativeMaterialInstance, int mode);
|
||||
private static native int nGetTransparencyMode(long nativeMaterialInstance);
|
||||
private static native void nCompile(long nativeMaterialInstance, int priority, int variants, Object handler, Runnable callback);
|
||||
}
|
||||
|
||||
@@ -2115,10 +2115,9 @@ public class View {
|
||||
public int msaaSamples = 1;
|
||||
/**
|
||||
* Whether to use a 32-bits or 16-bits texture format for VSM shadow maps. 32-bits
|
||||
* precision is rarely needed, but it does reduce light leaks as well as "fading"
|
||||
* precision is rarely needed, but it does reduces light leaks as well as "fading"
|
||||
* of the shadows in some situations. Setting highPrecision to true for a single
|
||||
* shadow map will double the memory usage of all shadow maps.
|
||||
* This may not be supported on all mobile devices.
|
||||
*/
|
||||
public boolean highPrecision = false;
|
||||
/**
|
||||
|
||||
@@ -30,8 +30,8 @@ using namespace filament;
|
||||
namespace {
|
||||
|
||||
constexpr std::array<backend::Platform::DeviceInfoType, 3> VULKAN_INFO = {
|
||||
backend::Platform::DeviceInfoType::VULKAN_DRIVER_NAME,
|
||||
backend::Platform::DeviceInfoType::VULKAN_DEVICE_NAME,
|
||||
backend::Platform::DeviceInfoType::VULKAN_DRIVER_NAME,
|
||||
backend::Platform::DeviceInfoType::VULKAN_DRIVER_INFO,
|
||||
};
|
||||
|
||||
|
||||
+29
-36
@@ -143,7 +143,7 @@ class ModelViewer(
|
||||
light = EntityManager.get().create()
|
||||
|
||||
val (r, g, b) = Colors.cct(6_500.0f)
|
||||
LightManager.Builder(LightManager.Type.SUN)
|
||||
LightManager.Builder(LightManager.Type.DIRECTIONAL)
|
||||
.color(r, g, b)
|
||||
.intensity(100_000.0f)
|
||||
.direction(0.0f, -1.0f, 0.0f)
|
||||
@@ -390,45 +390,38 @@ class ModelViewer(
|
||||
view.addOnAttachStateChangeListener(object : android.view.View.OnAttachStateChangeListener {
|
||||
override fun onViewAttachedToWindow(v: android.view.View) {}
|
||||
override fun onViewDetachedFromWindow(v: android.view.View) {
|
||||
destroy()
|
||||
uiHelper.detach()
|
||||
|
||||
destroyModel()
|
||||
assetLoader.destroy()
|
||||
materialProvider.destroyMaterials()
|
||||
materialProvider.destroy()
|
||||
resourceLoader.destroy()
|
||||
|
||||
if (indirectLightCubemap != null) {
|
||||
engine.destroyTexture(indirectLightCubemap!!)
|
||||
indirectLightCubemap = null
|
||||
}
|
||||
|
||||
if (skyboxCubemap != null) {
|
||||
engine.destroyTexture(skyboxCubemap!!)
|
||||
skyboxCubemap = null
|
||||
}
|
||||
|
||||
engine.destroyEntity(light)
|
||||
engine.destroyRenderer(renderer)
|
||||
engine.destroyView(this@ModelViewer.view)
|
||||
engine.destroyScene(scene)
|
||||
engine.destroyCameraComponent(camera.entity)
|
||||
EntityManager.get().destroy(camera.entity)
|
||||
|
||||
EntityManager.get().destroy(light)
|
||||
|
||||
engine.destroy()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly destroys the ModelViewer and its underlying Filament engine and resources.
|
||||
*/
|
||||
fun destroy() {
|
||||
uiHelper.detach()
|
||||
|
||||
destroyModel()
|
||||
assetLoader.destroy()
|
||||
materialProvider.destroyMaterials()
|
||||
materialProvider.destroy()
|
||||
resourceLoader.destroy()
|
||||
|
||||
if (indirectLightCubemap != null) {
|
||||
engine.destroyTexture(indirectLightCubemap!!)
|
||||
indirectLightCubemap = null
|
||||
}
|
||||
|
||||
if (skyboxCubemap != null) {
|
||||
engine.destroyTexture(skyboxCubemap!!)
|
||||
skyboxCubemap = null
|
||||
}
|
||||
|
||||
engine.destroyEntity(light)
|
||||
engine.destroyRenderer(renderer)
|
||||
engine.destroyView(this@ModelViewer.view)
|
||||
engine.destroyScene(scene)
|
||||
engine.destroyCameraComponent(camera.entity)
|
||||
EntityManager.get().destroy(camera.entity)
|
||||
|
||||
EntityManager.get().destroy(light)
|
||||
|
||||
engine.destroy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a [MotionEvent] to enable one-finger orbit, two-finger pan, and pinch-to-zoom.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.71.1
|
||||
VERSION_NAME=1.70.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "Default Test",
|
||||
"backends": [
|
||||
"opengl",
|
||||
"vulkan"
|
||||
"opengl"
|
||||
],
|
||||
"models": {
|
||||
"DamagedHelmet": "helmet.glb"
|
||||
|
||||
+57
-118
@@ -59,23 +59,22 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
|
||||
private lateinit var surfaceView: SurfaceView
|
||||
private lateinit var choreographer: Choreographer
|
||||
private lateinit var modelViewer: ModelViewer
|
||||
private lateinit var statusTextView: TextView
|
||||
private lateinit var testResultsHeader: TextView
|
||||
private lateinit var testProgress: android.widget.ProgressBar
|
||||
private lateinit var resultsContainer: LinearLayout
|
||||
private lateinit var inputManager: ValidationInputManager
|
||||
private var currentInput: ValidationInputManager.ValidationInput? = null
|
||||
|
||||
private var currentAlphaDiffBitmap: Bitmap? = null
|
||||
private var globalEnhancementFactor: Float = 1.0f
|
||||
private var modelViewer: ModelViewer? = null
|
||||
|
||||
private data class TestImages(
|
||||
val testName: String,
|
||||
val golden: File?,
|
||||
val rendered: File?,
|
||||
val diff: File?,
|
||||
val alphaDiff: File?
|
||||
val golden: Bitmap?,
|
||||
val rendered: Bitmap?,
|
||||
val diff: Bitmap?,
|
||||
val alphaDiff: Bitmap?
|
||||
)
|
||||
|
||||
private val diffImageViews = mutableListOf<ImageView>()
|
||||
@@ -85,8 +84,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
private lateinit var loadButton: Button
|
||||
private lateinit var optionsButton: Button
|
||||
private lateinit var enhancementContainer: LinearLayout
|
||||
private lateinit var backendFilterContainer: LinearLayout
|
||||
private lateinit var backendRadioGroup: android.widget.RadioGroup
|
||||
private lateinit var enhancementLabel: TextView
|
||||
private lateinit var enhancementSlider: android.widget.SeekBar
|
||||
|
||||
@@ -97,7 +94,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
private val frameScheduler = object : Choreographer.FrameCallback {
|
||||
override fun doFrame(frameTimeNanos: Long) {
|
||||
choreographer.postFrameCallback(this)
|
||||
modelViewer?.render(frameTimeNanos)
|
||||
modelViewer.render(frameTimeNanos)
|
||||
validationRunner?.onFrame(frameTimeNanos)
|
||||
}
|
||||
}
|
||||
@@ -112,15 +109,12 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
|
||||
statusTextView = findViewById(R.id.status_text)
|
||||
testResultsHeader = findViewById(R.id.test_results_header)
|
||||
testProgress = findViewById(R.id.test_progress)
|
||||
resultsContainer = findViewById(R.id.results_container)
|
||||
|
||||
runButton = findViewById(R.id.run_button)
|
||||
loadButton = findViewById(R.id.load_button)
|
||||
optionsButton = findViewById(R.id.options_button)
|
||||
enhancementContainer = findViewById(R.id.enhancement_container)
|
||||
backendFilterContainer = findViewById(R.id.backend_filter_container)
|
||||
backendRadioGroup = findViewById(R.id.backend_radio_group)
|
||||
enhancementLabel = findViewById(R.id.enhancement_label)
|
||||
enhancementSlider = findViewById(R.id.enhancement_slider)
|
||||
|
||||
@@ -156,7 +150,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
popup.menu.add(0, 4, 0, "Test ADB Info")
|
||||
popup.menu.add(0, 5, 0, "Result ADB Info")
|
||||
popup.menu.add(0, 6, 0, "Toggle Enhancement Slider")
|
||||
popup.menu.add(0, 7, 0, "Toggle Backend Filter")
|
||||
|
||||
popup.setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
@@ -173,9 +166,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
6 -> {
|
||||
enhancementContainer.visibility = if (enhancementContainer.visibility == View.VISIBLE) View.GONE else View.VISIBLE
|
||||
}
|
||||
7 -> {
|
||||
backendFilterContainer.visibility = if (backendFilterContainer.visibility == View.VISIBLE) View.GONE else View.VISIBLE
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -185,8 +175,12 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
|
||||
choreographer = Choreographer.getInstance()
|
||||
modelViewer = ModelViewer(surfaceView=surfaceView, manipulator=null)
|
||||
inputManager = ValidationInputManager(this)
|
||||
|
||||
// Initialize IBL
|
||||
createIndirectLight()
|
||||
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
@@ -321,31 +315,29 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
|
||||
private fun createIndirectLight() {
|
||||
try {
|
||||
modelViewer?.let { mv ->
|
||||
val engine = mv.engine
|
||||
val scene = mv.scene
|
||||
val iblName = "default_env"
|
||||
val engine = modelViewer.engine
|
||||
val scene = modelViewer.scene
|
||||
val iblName = "default_env"
|
||||
|
||||
fun readAsset(path: String): ByteBuffer {
|
||||
val input = assets.open(path)
|
||||
val bytes = input.readBytes()
|
||||
return ByteBuffer.wrap(bytes)
|
||||
}
|
||||
|
||||
readAsset("envs/$iblName/${iblName}_ibl.ktx").let {
|
||||
val bundle = KTX1Loader.createIndirectLight(engine, it)
|
||||
scene.indirectLight = bundle.indirectLight
|
||||
mv.indirectLightCubemap = bundle.cubemap
|
||||
scene.indirectLight!!.intensity = 30_000.0f
|
||||
}
|
||||
|
||||
readAsset("envs/$iblName/${iblName}_skybox.ktx").let {
|
||||
val bundle = KTX1Loader.createSkybox(engine, it)
|
||||
scene.skybox = bundle.skybox
|
||||
mv.skyboxCubemap = bundle.cubemap
|
||||
}
|
||||
Log.i(TAG, "IBL loaded successfully")
|
||||
fun readAsset(path: String): ByteBuffer {
|
||||
val input = assets.open(path)
|
||||
val bytes = input.readBytes()
|
||||
return ByteBuffer.wrap(bytes)
|
||||
}
|
||||
|
||||
readAsset("envs/$iblName/${iblName}_ibl.ktx").let {
|
||||
val bundle = KTX1Loader.createIndirectLight(engine, it)
|
||||
scene.indirectLight = bundle.indirectLight
|
||||
modelViewer.indirectLightCubemap = bundle.cubemap
|
||||
scene.indirectLight!!.intensity = 30_000.0f
|
||||
}
|
||||
|
||||
readAsset("envs/$iblName/${iblName}_skybox.ktx").let {
|
||||
val bundle = KTX1Loader.createSkybox(engine, it)
|
||||
scene.skybox = bundle.skybox
|
||||
modelViewer.skyboxCubemap = bundle.cubemap
|
||||
}
|
||||
Log.i(TAG, "IBL loaded successfully")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to load IBL", e)
|
||||
statusTextView.text = "Warning: Failed to load IBL"
|
||||
@@ -377,8 +369,10 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
}
|
||||
|
||||
private fun createResultManager(outputDir: File): ValidationResultManager {
|
||||
val gpuDriverInfo = com.google.android.filament.utils.DeviceUtils.getGpuDriverInfo(modelViewer.engine)
|
||||
return ValidationResultManager(
|
||||
outputDir = outputDir,
|
||||
gpuDriverInfo = gpuDriverInfo,
|
||||
deviceName = android.os.Build.MODEL,
|
||||
deviceCodeName = android.os.Build.DEVICE,
|
||||
androidVersion = android.os.Build.VERSION.RELEASE,
|
||||
@@ -395,24 +389,10 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
Log.i(TAG, "Output dir: ${input.outputDir.absolutePath}")
|
||||
|
||||
testResultsHeader.text = "${input.config.name}"
|
||||
testProgress.visibility = View.VISIBLE
|
||||
testProgress.progress = 0
|
||||
|
||||
resultManager = createResultManager(input.outputDir)
|
||||
|
||||
val backendFilter = when (backendRadioGroup.checkedRadioButtonId) {
|
||||
R.id.radio_gles -> "gles"
|
||||
R.id.radio_vulkan -> "vulkan"
|
||||
else -> "both"
|
||||
}
|
||||
|
||||
// If we are starting another run, we need to clean up the previous runner's
|
||||
// resources before we can proceed.
|
||||
validationRunner?.let {
|
||||
it.cleanup()
|
||||
}
|
||||
|
||||
validationRunner = ValidationRunner(this, surfaceView, input.config, resultManager!!, backendFilter)
|
||||
validationRunner = ValidationRunner(this, modelViewer, input.config, resultManager!!)
|
||||
validationRunner?.callback = this
|
||||
validationRunner?.generateGoldens = input.generateGoldens
|
||||
validationRunner?.start()
|
||||
@@ -423,14 +403,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onModelViewerRecreated(modelViewer: ModelViewer?) {
|
||||
runOnUiThread {
|
||||
this.modelViewer = modelViewer
|
||||
// Re-apply IBL to the new engine/scene
|
||||
createIndirectLight()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
choreographer.postFrameCallback(frameScheduler)
|
||||
@@ -496,18 +468,12 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
val imagesRow = LinearLayout(this)
|
||||
imagesRow.orientation = LinearLayout.HORIZONTAL
|
||||
|
||||
val outDir = resultManager!!.getOutputDir()
|
||||
val renderedFile = File(outDir, "${result.testName}.png")
|
||||
val diffFile = File(outDir, "${result.testName}_diff.png")
|
||||
val alphaDiffFile = File(outDir, "${result.testName}_alpha_diff.png")
|
||||
val goldenFile = result.goldenPath?.let { File(it) }
|
||||
|
||||
val testImages = TestImages(
|
||||
testName = result.testName,
|
||||
golden = goldenFile?.takeIf { currentGoldenBitmap != null },
|
||||
rendered = renderedFile.takeIf { currentRenderedBitmap != null },
|
||||
diff = diffFile.takeIf { currentDiffBitmap != null },
|
||||
alphaDiff = alphaDiffFile.takeIf { currentAlphaDiffBitmap != null }
|
||||
golden = currentGoldenBitmap,
|
||||
rendered = currentRenderedBitmap,
|
||||
diff = currentDiffBitmap,
|
||||
alphaDiff = currentAlphaDiffBitmap
|
||||
)
|
||||
|
||||
fun addImage(label: String, bitmap: Bitmap?, isDiff: Boolean) {
|
||||
@@ -562,16 +528,8 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTestProgress(current: Int, total: Int) {
|
||||
runOnUiThread {
|
||||
testProgress.max = total
|
||||
testProgress.progress = current
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAllTestsFinished() {
|
||||
runOnUiThread {
|
||||
testProgress.visibility = View.GONE
|
||||
statusTextView.text = "All tests finished!"
|
||||
enhancementSlider.isEnabled = true
|
||||
Log.i(TAG, "All tests finished " + if (currentInput?.autoExport == true) "Exporting bundle" else "x")
|
||||
@@ -624,24 +582,20 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
}
|
||||
|
||||
override fun onImageResult(type: String, bitmap: Bitmap) {
|
||||
// Create a scaled-down thumbnail (e.g. 128x128) to save memory in the UI scroll view.
|
||||
// We use true for filter to smooth the scaling.
|
||||
val scaledBitmap = Bitmap.createScaledBitmap(bitmap, 128, 128, true)
|
||||
|
||||
runOnUiThread {
|
||||
// Update the "live" views
|
||||
when (type) {
|
||||
"Rendered" -> {
|
||||
currentRenderedBitmap = scaledBitmap
|
||||
currentRenderedBitmap = bitmap
|
||||
}
|
||||
"Golden" -> {
|
||||
currentGoldenBitmap = scaledBitmap
|
||||
currentGoldenBitmap = bitmap
|
||||
}
|
||||
"Diff" -> {
|
||||
currentDiffBitmap = scaledBitmap
|
||||
currentDiffBitmap = bitmap
|
||||
}
|
||||
"Alpha Diff" -> {
|
||||
currentAlphaDiffBitmap = scaledBitmap
|
||||
currentAlphaDiffBitmap = bitmap
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -679,23 +633,17 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
|
||||
titleView.text = images.testName
|
||||
|
||||
val availableFiles = mutableListOf<Pair<String, File>>()
|
||||
images.rendered?.takeIf { it.exists() }?.let { availableFiles.add(Pair("Rendered", it)) }
|
||||
images.golden?.takeIf { it.exists() }?.let { availableFiles.add(Pair("Golden", it)) }
|
||||
images.diff?.takeIf { it.exists() }?.let { availableFiles.add(Pair("Diff", it)) }
|
||||
images.alphaDiff?.takeIf { it.exists() }?.let { availableFiles.add(Pair("Alpha Diff", it)) }
|
||||
val availableImages = mutableListOf<Pair<String, Bitmap>>()
|
||||
images.rendered?.let { availableImages.add(Pair("Rendered", it)) }
|
||||
images.golden?.let { availableImages.add(Pair("Golden", it)) }
|
||||
images.diff?.let { availableImages.add(Pair("Diff", it)) }
|
||||
images.alphaDiff?.let { availableImages.add(Pair("Alpha Diff", it)) }
|
||||
|
||||
if (availableFiles.isEmpty()) return
|
||||
if (availableImages.isEmpty()) return
|
||||
|
||||
var currentIndex = availableFiles.indexOfFirst { it.first == initialLabel }
|
||||
var currentIndex = availableImages.indexOfFirst { it.first == initialLabel }
|
||||
if (currentIndex == -1) currentIndex = 0
|
||||
|
||||
var currentDialogBitmap: Bitmap? = null
|
||||
dialog.setOnDismissListener {
|
||||
currentDialogBitmap?.recycle()
|
||||
currentDialogBitmap = null
|
||||
}
|
||||
|
||||
var currentDialogEnhancement = globalEnhancementFactor
|
||||
|
||||
val matrix = android.graphics.Matrix()
|
||||
@@ -738,13 +686,9 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
}
|
||||
|
||||
fun updateView() {
|
||||
val (label, file) = availableFiles[currentIndex]
|
||||
val (label, bitmap) = availableImages[currentIndex]
|
||||
typeView.text = label
|
||||
|
||||
currentDialogBitmap?.recycle()
|
||||
currentDialogBitmap = android.graphics.BitmapFactory.decodeFile(file.absolutePath)
|
||||
|
||||
imageView.setImageBitmap(currentDialogBitmap)
|
||||
imageView.setImageBitmap(bitmap)
|
||||
(imageView.drawable as? android.graphics.drawable.BitmapDrawable)?.setAntiAlias(false)
|
||||
(imageView.drawable as? android.graphics.drawable.BitmapDrawable)?.setFilterBitmap(false)
|
||||
imageView.imageMatrix = matrix
|
||||
@@ -762,11 +706,8 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
val drawable = imageView.drawable ?: return
|
||||
val width = imageView.width.toFloat()
|
||||
val height = imageView.height.toFloat()
|
||||
if (width <= 0f || height <= 0f) return
|
||||
|
||||
val dw = drawable.intrinsicWidth.toFloat()
|
||||
val dh = drawable.intrinsicHeight.toFloat()
|
||||
if (dw <= 0f || dh <= 0f) return
|
||||
|
||||
val scaleX = width / dw
|
||||
val scaleY = height / dh
|
||||
@@ -781,20 +722,14 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
imageView.imageMatrix = matrix
|
||||
}
|
||||
|
||||
imageView.addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
|
||||
if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
|
||||
resetMatrix()
|
||||
}
|
||||
}
|
||||
|
||||
btnClose.setOnClickListener { dialog.dismiss() }
|
||||
btnReset.setOnClickListener { resetMatrix() }
|
||||
btnPrev.setOnClickListener {
|
||||
currentIndex = (currentIndex - 1 + availableFiles.size) % availableFiles.size
|
||||
currentIndex = (currentIndex - 1 + availableImages.size) % availableImages.size
|
||||
updateView()
|
||||
}
|
||||
btnNext.setOnClickListener {
|
||||
currentIndex = (currentIndex + 1) % availableFiles.size
|
||||
currentIndex = (currentIndex + 1) % availableImages.size
|
||||
updateView()
|
||||
}
|
||||
|
||||
@@ -813,6 +748,10 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
override fun onStopTrackingTouch(seekBar: android.widget.SeekBar?) {}
|
||||
})
|
||||
|
||||
imageView.post {
|
||||
resetMatrix()
|
||||
}
|
||||
|
||||
updateView()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
+4
-6
@@ -31,7 +31,7 @@ data class RenderTestConfig(
|
||||
data class TestConfig(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val backend: String,
|
||||
val backends: List<String>,
|
||||
val models: Set<String>,
|
||||
val rendering: JSONObject,
|
||||
val tolerance: JSONObject?
|
||||
@@ -102,7 +102,7 @@ class ConfigParser {
|
||||
val testsJson = json.getJSONArray("tests")
|
||||
val tests = mutableListOf<TestConfig>()
|
||||
for (i in 0 until testsJson.length()) {
|
||||
tests.addAll(parseTestConfig(testsJson.getJSONObject(i), models.keys, presets, backends))
|
||||
tests.add(parseTestConfig(testsJson.getJSONObject(i), models.keys, presets, backends))
|
||||
}
|
||||
|
||||
return RenderTestConfig(name, backends, models, tests)
|
||||
@@ -125,7 +125,7 @@ class ConfigParser {
|
||||
existingModels: Set<String>,
|
||||
presets: Map<String, PresetConfig>,
|
||||
defaultBackends: List<String>
|
||||
): List<TestConfig> {
|
||||
): TestConfig {
|
||||
val name = json.getString("name")
|
||||
val description = json.optString("description")
|
||||
val backends = json.optJSONArray("backends")?.toList<String>() ?: defaultBackends
|
||||
@@ -165,9 +165,7 @@ class ConfigParser {
|
||||
|
||||
val tolerance = json.optJSONObject("tolerance") ?: lastTolerance
|
||||
|
||||
return backends.map { backend ->
|
||||
TestConfig(name, description, backend, combinedModels, rendering, tolerance)
|
||||
}
|
||||
return TestConfig(name, description, backends, combinedModels, rendering, tolerance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-9
@@ -28,12 +28,12 @@ import org.json.JSONObject
|
||||
data class ValidationResult(
|
||||
val testName: String,
|
||||
val passed: Boolean,
|
||||
val diffMetric: Float = 0f,
|
||||
val goldenPath: String? = null
|
||||
val diffMetric: Float = 0f
|
||||
)
|
||||
|
||||
class ValidationResultManager(
|
||||
private val outputDir: File,
|
||||
private val gpuDriverInfo: String,
|
||||
private val deviceName: String,
|
||||
private val deviceCodeName: String,
|
||||
private val androidVersion: String,
|
||||
@@ -45,7 +45,6 @@ class ValidationResultManager(
|
||||
}
|
||||
|
||||
private val results = mutableListOf<ValidationResult>()
|
||||
private val gpuDriverInfos = JSONObject()
|
||||
|
||||
init {
|
||||
if (!outputDir.exists()) {
|
||||
@@ -53,10 +52,6 @@ class ValidationResultManager(
|
||||
}
|
||||
}
|
||||
|
||||
fun addGpuDriverInfo(backend: String, info: String) {
|
||||
gpuDriverInfos.put(backend, info)
|
||||
}
|
||||
|
||||
fun addResult(result: ValidationResult) {
|
||||
results.add(result)
|
||||
}
|
||||
@@ -240,7 +235,7 @@ class ValidationResultManager(
|
||||
|
||||
// Backends (optional override)
|
||||
val testBackends = JSONArray()
|
||||
testBackends.put(test.backend)
|
||||
test.backends.forEach { testBackends.put(it) }
|
||||
testJson.put("backends", testBackends)
|
||||
|
||||
testsArray.put(testJson)
|
||||
@@ -282,7 +277,7 @@ class ValidationResultManager(
|
||||
val rootObject = JSONObject()
|
||||
|
||||
val metadataObject = JSONObject()
|
||||
metadataObject.put("gpu_driver_info", gpuDriverInfos)
|
||||
metadataObject.put("gpu_driver_info", gpuDriverInfo ?: "")
|
||||
metadataObject.put("total_time_ms", totalTimeMs)
|
||||
metadataObject.put("device_name", deviceName ?: "")
|
||||
metadataObject.put("device_code_name", deviceCodeName ?: "")
|
||||
|
||||
+60
-120
@@ -29,10 +29,9 @@ import java.nio.ByteBuffer
|
||||
|
||||
class ValidationRunner(
|
||||
private val context: Context,
|
||||
private val surfaceView: android.view.SurfaceView,
|
||||
private val modelViewer: ModelViewer,
|
||||
private val config: RenderTestConfig,
|
||||
private val resultManager: ValidationResultManager,
|
||||
private val backendFilter: String = "both" // "gles", "vulkan", or "both"
|
||||
private val resultManager: ValidationResultManager
|
||||
) {
|
||||
|
||||
private var currentState = State.IDLE
|
||||
@@ -41,32 +40,9 @@ class ValidationRunner(
|
||||
private var currentEngine: AutomationEngine? = null
|
||||
private var currentTestConfig: TestConfig? = null
|
||||
private var currentModelName: String? = null
|
||||
private var currentBackend: String? = null
|
||||
|
||||
private var frameCounter = 0
|
||||
private var suiteStartTime: Long = 0
|
||||
private var modelViewer: ModelViewer? = null
|
||||
|
||||
private val sortedTests: List<Pair<TestConfig, String>> by lazy {
|
||||
val filtered = config.tests.filter {
|
||||
when (backendFilter) {
|
||||
"gles" -> it.backend == "opengl"
|
||||
"vulkan" -> it.backend == "vulkan"
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
val expanded = mutableListOf<Pair<TestConfig, String>>()
|
||||
filtered.forEach { test ->
|
||||
test.models.forEach { model ->
|
||||
expanded.add(test to model)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by backend then model. This tries to minimize the number of times we need to
|
||||
// recreate the Engine, and then the ModelViewer.
|
||||
expanded.sortedWith(compareBy({ it.first.backend }, { it.second }))
|
||||
}
|
||||
|
||||
enum class State {
|
||||
IDLE,
|
||||
@@ -76,79 +52,42 @@ class ValidationRunner(
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onTestProgress(current: Int, total: Int)
|
||||
fun onTestFinished(result: ValidationResult)
|
||||
fun onAllTestsFinished()
|
||||
fun onStatusChanged(status: String)
|
||||
fun onImageResult(type: String, bitmap: Bitmap)
|
||||
fun onModelViewerRecreated(modelViewer: ModelViewer?)
|
||||
}
|
||||
|
||||
var callback: Callback? = null
|
||||
var generateGoldens: Boolean = false
|
||||
|
||||
fun start() {
|
||||
if (sortedTests.isEmpty()) {
|
||||
if (config.tests.isEmpty()) {
|
||||
callback?.onAllTestsFinished()
|
||||
return
|
||||
}
|
||||
suiteStartTime = System.currentTimeMillis()
|
||||
currentBackend = null
|
||||
currentTestIndex = 0
|
||||
runTest(sortedTests[0])
|
||||
currentModelIndex = 0
|
||||
startTest(config.tests[0])
|
||||
}
|
||||
|
||||
private fun runTest(testPair: Pair<TestConfig, String>) {
|
||||
callback?.onTestProgress(currentTestIndex, sortedTests.size)
|
||||
val (test, modelName) = testPair
|
||||
private fun startTest(test: TestConfig) {
|
||||
currentTestConfig = test
|
||||
|
||||
if (currentBackend != test.backend) {
|
||||
currentBackend = test.backend
|
||||
callback?.onStatusChanged("Switching to backend: ${test.backend}")
|
||||
recreateEngine(test.backend)
|
||||
// After recreation, we need to reload the model
|
||||
currentModelName = null
|
||||
if (test.models.isEmpty()) {
|
||||
nextTest()
|
||||
return
|
||||
}
|
||||
|
||||
startModel(modelName)
|
||||
}
|
||||
|
||||
public fun cleanup() {
|
||||
// When the runner ends, we need to destroy the resources to ensure that they are free for
|
||||
// another run.
|
||||
modelViewer?.destroy()
|
||||
}
|
||||
|
||||
private fun recreateEngine(backendName: String) {
|
||||
val backend = when (backendName.lowercase()) {
|
||||
"vulkan" -> com.google.android.filament.Engine.Backend.VULKAN
|
||||
else -> com.google.android.filament.Engine.Backend.OPENGL
|
||||
}
|
||||
|
||||
// ModelViewer's detach listener will handle engine destruction when surface is detached,
|
||||
// but here we are still using the same surface. We need a way to swap the engine.
|
||||
// The easiest way is to recreate ModelViewer with a new Engine.
|
||||
modelViewer?.destroy()
|
||||
|
||||
val engine = com.google.android.filament.Engine.create(backend)
|
||||
|
||||
resultManager.addGpuDriverInfo(backendName, com.google.android.filament.utils.DeviceUtils.getGpuDriverInfo(engine))
|
||||
|
||||
// Setting the camera manipulator to null enables changing the camera outside of ModelViewer
|
||||
val newModelViewer = ModelViewer(surfaceView, engine, manipulator=null)
|
||||
|
||||
// Update the reference in MainActivity (via callback)
|
||||
callback?.onModelViewerRecreated(newModelViewer)
|
||||
modelViewer = newModelViewer
|
||||
currentModelIndex = 0
|
||||
startModel(test.models.elementAt(0))
|
||||
}
|
||||
|
||||
private fun startModel(modelName: String) {
|
||||
if (currentModelName == modelName) {
|
||||
Log.i("ValidationRunner", "Reusing model $modelName")
|
||||
callback?.onStatusChanged("Reusing $modelName for ${currentTestConfig?.name} (${currentTestConfig?.backend})")
|
||||
callback?.onStatusChanged("Reusing $modelName for ${currentTestConfig?.name}")
|
||||
|
||||
modelViewer?.resetToDefaultState()
|
||||
modelViewer.resetToDefaultState()
|
||||
|
||||
frameCounter = 0
|
||||
currentState = State.WARMUP
|
||||
@@ -159,10 +98,10 @@ class ValidationRunner(
|
||||
val modelPath = config.models[modelName]
|
||||
if (modelPath == null) {
|
||||
Log.e("ValidationRunner", "Model $modelName not found")
|
||||
nextTest()
|
||||
nextModel()
|
||||
return
|
||||
}
|
||||
callback?.onStatusChanged("Loading $modelName for ${currentTestConfig?.name} (${currentTestConfig?.backend})")
|
||||
callback?.onStatusChanged("Loading $modelName for ${currentTestConfig?.name}")
|
||||
|
||||
// Load model on main thread (required by ModelViewer)
|
||||
loadModel(modelPath)
|
||||
@@ -170,21 +109,21 @@ class ValidationRunner(
|
||||
|
||||
private fun loadModel(path: String) {
|
||||
// Assume called on Main Thread
|
||||
modelViewer?.destroyModel()
|
||||
modelViewer.destroyModel()
|
||||
try {
|
||||
Log.i("ValidationRunner", "Reading model file: $path")
|
||||
val bytes = File(path).readBytes()
|
||||
Log.i("ValidationRunner", "Loading GLB buffer... (${bytes.size} bytes)")
|
||||
val buffer = ByteBuffer.wrap(bytes)
|
||||
modelViewer?.loadModelGlb(buffer)
|
||||
modelViewer.loadModelGlb(buffer)
|
||||
Log.i("ValidationRunner", "Model loaded.")
|
||||
modelViewer?.transformToUnitCube()
|
||||
modelViewer.transformToUnitCube()
|
||||
currentState = State.WAITING_FOR_RESOURCES
|
||||
frameCounter = 0
|
||||
Log.i("ValidationRunner", "State set to WAITING_FOR_RESOURCES")
|
||||
} catch (e: Exception) {
|
||||
Log.e("ValidationRunner", "Failed to load $path", e)
|
||||
nextTest()
|
||||
nextModel()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,41 +131,37 @@ class ValidationRunner(
|
||||
when (currentState) {
|
||||
State.IDLE -> {}
|
||||
State.WAITING_FOR_RESOURCES -> {
|
||||
modelViewer?.let { mv ->
|
||||
val progress = mv.progress
|
||||
if (progress >= 1.0f) {
|
||||
Log.i("ValidationRunner", "Resources loaded. Starting warmup.")
|
||||
frameCounter = 0
|
||||
currentState = State.WARMUP
|
||||
}
|
||||
}
|
||||
val progress = modelViewer.progress
|
||||
if (progress >= 1.0f) {
|
||||
Log.i("ValidationRunner", "Resources loaded. Starting warmup.")
|
||||
frameCounter = 0
|
||||
currentState = State.WARMUP
|
||||
}
|
||||
}
|
||||
State.WARMUP -> {
|
||||
frameCounter++
|
||||
if (frameCounter > 1) { // 1 frames warmup
|
||||
if (frameCounter > 5) { // 5 frames warmup
|
||||
startAutomation()
|
||||
}
|
||||
}
|
||||
State.RUNNING_TEST -> {
|
||||
currentEngine?.let { engine ->
|
||||
modelViewer?.let { mv ->
|
||||
val content = AutomationEngine.ViewerContent()
|
||||
content.view = mv.view
|
||||
content.renderer = mv.renderer
|
||||
content.scene = mv.scene
|
||||
content.lightManager = mv.engine.lightManager
|
||||
val content = AutomationEngine.ViewerContent()
|
||||
content.view = modelViewer.view
|
||||
content.renderer = modelViewer.renderer
|
||||
content.scene = modelViewer.scene
|
||||
content.lightManager = modelViewer.engine.lightManager
|
||||
|
||||
// Tick
|
||||
val deltaTime = 1.0f / 60.0f
|
||||
engine.tick(mv.engine, content, deltaTime)
|
||||
// Tick
|
||||
val deltaTime = 1.0f / 60.0f
|
||||
engine.tick(modelViewer.engine, content, deltaTime)
|
||||
|
||||
frameCounter++
|
||||
if (engine.shouldClose()) {
|
||||
Log.i("ValidationRunner", "Finishing test (frames: $frameCounter)")
|
||||
// Test finished (for this spec)
|
||||
currentState = State.IDLE
|
||||
captureAndCompare()
|
||||
}
|
||||
frameCounter++
|
||||
if (engine.shouldClose()) {
|
||||
Log.i("ValidationRunner", "Finishing test (frames: $frameCounter)")
|
||||
// Test finished (for this spec)
|
||||
currentState = State.IDLE
|
||||
captureAndCompare()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,7 +178,7 @@ class ValidationRunner(
|
||||
currentEngine = AutomationEngine(fullSpec)
|
||||
val options = AutomationEngine.Options()
|
||||
options.sleepDuration = 0.0f // Minimal sleep, let frames drive it
|
||||
options.minFrameCount = 1 // Ensure some frames pass
|
||||
options.minFrameCount = 5 // Ensure some frames pass
|
||||
currentEngine?.setOptions(options)
|
||||
|
||||
// Use batch mode to ensure shouldClose() works reliably
|
||||
@@ -256,8 +191,8 @@ class ValidationRunner(
|
||||
|
||||
|
||||
private fun captureAndCompare() {
|
||||
callback?.onStatusChanged("Comparing ${currentTestConfig?.name} (${currentTestConfig?.backend})...")
|
||||
modelViewer?.debugGetNextFrameCallback { bitmap ->
|
||||
callback?.onStatusChanged("Comparing ${currentTestConfig?.name}...")
|
||||
modelViewer.debugGetNextFrameCallback { bitmap ->
|
||||
compareCapturedImage(bitmap)
|
||||
}
|
||||
}
|
||||
@@ -265,7 +200,7 @@ class ValidationRunner(
|
||||
private fun compareCapturedImage(bitmap: Bitmap) {
|
||||
val testName = currentTestConfig!!.name
|
||||
val modelName = currentModelName!!
|
||||
val backend = currentTestConfig?.backend ?: "opengl"
|
||||
val backend = currentTestConfig?.backends?.firstOrNull() ?: "opengl"
|
||||
val testFullName = "${testName}.${backend}.${modelName}"
|
||||
|
||||
// Golden path
|
||||
@@ -377,12 +312,9 @@ class ValidationRunner(
|
||||
alphaDiffImg.setPixels(alphaPixels, 0, width, 0, 0, width, height)
|
||||
callback?.onImageResult("Alpha Diff", alphaDiffImg)
|
||||
resultManager.saveImage("${testFullName}_alpha_diff", alphaDiffImg)
|
||||
alphaDiffImg.recycle()
|
||||
}
|
||||
diffImg.recycle()
|
||||
}
|
||||
}
|
||||
golden.recycle()
|
||||
} else {
|
||||
callback?.onStatusChanged("Failed to load golden")
|
||||
}
|
||||
@@ -395,27 +327,35 @@ class ValidationRunner(
|
||||
// Save output
|
||||
resultManager.saveImage(testFullName, flipped)
|
||||
|
||||
val result = ValidationResult(testFullName, passed, diffMetric, goldenFile?.absolutePath)
|
||||
val result = ValidationResult(testFullName, passed, diffMetric)
|
||||
resultManager.addResult(result)
|
||||
callback?.onTestFinished(result)
|
||||
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
||||
nextTest()
|
||||
nextModel()
|
||||
}
|
||||
|
||||
} catch (e: Throwable) {
|
||||
} catch (e: Exception) {
|
||||
Log.e("ValidationRunner", "Comparison failed", e)
|
||||
// Recycle even on error
|
||||
bitmap.recycle()
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).post { nextTest() }
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).post { nextModel() }
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun nextModel() {
|
||||
currentModelIndex++
|
||||
if (currentTestConfig != null && currentModelIndex < currentTestConfig!!.models.size) {
|
||||
startModel(currentTestConfig!!.models.elementAt(currentModelIndex))
|
||||
} else {
|
||||
nextTest()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun nextTest() {
|
||||
currentTestIndex++
|
||||
if (currentTestIndex < sortedTests.size) {
|
||||
runTest(sortedTests[currentTestIndex])
|
||||
if (currentTestIndex < config.tests.size) {
|
||||
startTest(config.tests[currentTestIndex])
|
||||
} else {
|
||||
currentState = State.IDLE
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingTop="20dp"
|
||||
@@ -83,53 +82,6 @@
|
||||
android:layout_marginStart="8dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Backend Filter (Hidden by default) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/backend_filter_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:visibility="gone"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Backend:"
|
||||
android:textSize="12sp"
|
||||
android:minWidth="120dp" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/backend_radio_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radio_both"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Both"
|
||||
android:textSize="12sp"
|
||||
android:checked="true" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radio_gles"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="GLES"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radio_vulkan"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Vulkan"
|
||||
android:textSize="12sp" />
|
||||
</RadioGroup>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Global Enhancement Controls (Hidden by default) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/enhancement_container"
|
||||
@@ -163,19 +115,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Test Results"
|
||||
android:gravity="center"
|
||||
android:textSize="14sp"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="12dp" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/test_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:gravity="center"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="4dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone" />
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
+10
-8
@@ -4,6 +4,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#FF202020"
|
||||
android:padding="8dp">
|
||||
|
||||
<!-- Header with title and close button -->
|
||||
@@ -19,14 +20,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Test Result"
|
||||
android:textAppearance="?attr/textAppearanceTitleLarge"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
<ImageButton
|
||||
android:id="@+id/btn_close"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
app:tint="?attr/colorOnSurface"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -37,8 +38,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:text="Rendered"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textColor="#BBBBBB"
|
||||
android:textSize="14sp"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
<!-- Image Area with Arrows -->
|
||||
@@ -77,7 +78,7 @@
|
||||
android:layout_height="64dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_media_previous"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
app:tint="#FFFFFF" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_reset"
|
||||
@@ -86,7 +87,7 @@
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_menu_revert"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
app:tint="#FFFFFF" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_next"
|
||||
@@ -94,7 +95,7 @@
|
||||
android:layout_height="64dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_media_next"
|
||||
app:tint="?attr/colorOnSurface" />
|
||||
app:tint="#FFFFFF" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Enhancement Controls (only for diff images) -->
|
||||
@@ -113,7 +114,8 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Enhance: 1.0x"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="12sp"
|
||||
android:minWidth="100dp" />
|
||||
|
||||
<SeekBar
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 83 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
+13
-12
@@ -204,9 +204,10 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -234,9 +235,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -409,9 +410,9 @@
|
||||
"integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -428,9 +429,9 @@
|
||||
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
||||
@@ -81,8 +81,6 @@ in table [standardProperties].
|
||||
**roughness** | Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections
|
||||
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
|
||||
**reflectance** | Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections
|
||||
**specularFactor** | Scales the amount of specular reflection for non-metallic surfaces
|
||||
**specularColorFactor** | Color of the specular reflection for non-metallic surfaces
|
||||
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
|
||||
**clearCoat** | Strength of the clear coat layer
|
||||
**clearCoatRoughness** | Perceived smoothness or roughness of the clear coat layer
|
||||
@@ -112,8 +110,6 @@ The type and range of each property is described in table [standardPropertiesTyp
|
||||
**metallic** | float | [0..1] | Should be 0 or 1
|
||||
**roughness** | float | [0..1] |
|
||||
**reflectance** | float | [0..1] | Prefer values > 0.35
|
||||
**specularFactor** | float | [0..1] | Optional, defaults to 1.0
|
||||
**specularColorFactor** | float3 | [0..1] | Linear RGB, defaults to 1.0
|
||||
**sheenColor** | float3 | [0..1] | Linear RGB
|
||||
**sheenRoughness** | float | [0..1] |
|
||||
**clearCoat** | float | [0..1] | Should be 0 or 1
|
||||
@@ -308,20 +304,6 @@ it easy to define from a texture.
|
||||
See section [Index of refraction] for more information about the `ior` property and refractive
|
||||
indices.
|
||||
|
||||
### Specular factor
|
||||
|
||||
The `specularFactor` property scales the amount of specular reflection for non-metallic surfaces.
|
||||
This property is often used to implement the `KHR_materials_specular` glTF extension. This value
|
||||
scales the Fresnel reflectance at normal incidence (F0), which is also computed from the
|
||||
`reflectance` property. It also sets the Fresnel reflectance at grazing angles (F90).
|
||||
|
||||
### Specular color factor
|
||||
|
||||
The `specularColorFactor` property defines the color of the specular reflection for non-metallic
|
||||
surfaces. This property is often used to implement the `KHR_materials_specular` glTF extension. This
|
||||
value scales the Fresnel reflectance at normal incidence (F0), which is also computed from the
|
||||
`reflectance` property.
|
||||
|
||||
### Sheen color
|
||||
|
||||
The sheen color controls the color appearance and strength of an optional sheen layer on top of the
|
||||
|
||||
+2
-2
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.70.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.70.1'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
@@ -195,7 +195,7 @@ dependencies {
|
||||
</div>
|
||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.70.2'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.70.1'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
|
||||
@@ -173,10 +173,10 @@
|
||||
<p>The root object contains the following categories:</p>
|
||||
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Key</th><th style="text-align: left">Type</th><th style="text-align: left">Description</th></tr></thead><tbody>
|
||||
<tr><td style="text-align: left"><code>view</code></td><td style="text-align: left">Object</td><td style="text-align: left">Post-processing and rendering quality settings.</td></tr>
|
||||
<tr><td style="text-align: left"><code>camera</code></td><td style="text-align: left">Object</td><td style="text-align: left">Explicit camera control (pose, projection, exposure).</td></tr>
|
||||
<tr><td style="text-align: left"><code>lighting</code></td><td style="text-align: left">Object</td><td style="text-align: left">Environment and dynamic light settings.</td></tr>
|
||||
<tr><td style="text-align: left"><code>camera</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Explicit camera control (pose, projection, exposure).</td></tr>
|
||||
<tr><td style="text-align: left"><code>lighting</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Environment and dynamic light settings.</td></tr>
|
||||
<tr><td style="text-align: left"><code>viewer</code></td><td style="text-align: left">Object</td><td style="text-align: left">Global viewer options (skybox, background, scaling).</td></tr>
|
||||
<tr><td style="text-align: left"><code>animation</code></td><td style="text-align: left">Object</td><td style="text-align: left">Animation playback control.</td></tr>
|
||||
<tr><td style="text-align: left"><code>animation</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Animation playback control.</td></tr>
|
||||
<tr><td style="text-align: left"><code>material</code></td><td style="text-align: left">Object</td><td style="text-align: left">Material overrides.</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
|
||||
+35
-55
@@ -198,25 +198,23 @@ counter-increment: h6;margin-right:10px}
|
||||
<a href="#materialmodels/litmodel/metals" class="level3"><span class="tocNumber">3.1.5 </span>Metals</a><br/>
|
||||
<a href="#materialmodels/litmodel/refraction" class="level3"><span class="tocNumber">3.1.6 </span>Refraction</a><br/>
|
||||
<a href="#materialmodels/litmodel/reflectance" class="level3"><span class="tocNumber">3.1.7 </span>Reflectance</a><br/>
|
||||
<a href="#materialmodels/litmodel/specularfactor" class="level3"><span class="tocNumber">3.1.8 </span>Specular factor</a><br/>
|
||||
<a href="#materialmodels/litmodel/specularcolorfactor" class="level3"><span class="tocNumber">3.1.9 </span>Specular color factor</a><br/>
|
||||
<a href="#materialmodels/litmodel/sheencolor" class="level3"><span class="tocNumber">3.1.10 </span>Sheen color</a><br/>
|
||||
<a href="#materialmodels/litmodel/sheenroughness" class="level3"><span class="tocNumber">3.1.11 </span>Sheen roughness</a><br/>
|
||||
<a href="#materialmodels/litmodel/clearcoat" class="level3"><span class="tocNumber">3.1.12 </span>Clear coat</a><br/>
|
||||
<a href="#materialmodels/litmodel/clearcoatroughness" class="level3"><span class="tocNumber">3.1.13 </span>Clear coat roughness</a><br/>
|
||||
<a href="#materialmodels/litmodel/anisotropy" class="level3"><span class="tocNumber">3.1.14 </span>Anisotropy</a><br/>
|
||||
<a href="#materialmodels/litmodel/anisotropydirection" class="level3"><span class="tocNumber">3.1.15 </span>Anisotropy direction</a><br/>
|
||||
<a href="#materialmodels/litmodel/ambientocclusion" class="level3"><span class="tocNumber">3.1.16 </span>Ambient occlusion</a><br/>
|
||||
<a href="#materialmodels/litmodel/normal" class="level3"><span class="tocNumber">3.1.17 </span>Normal</a><br/>
|
||||
<a href="#materialmodels/litmodel/bentnormal" class="level3"><span class="tocNumber">3.1.18 </span>Bent normal</a><br/>
|
||||
<a href="#materialmodels/litmodel/clearcoatnormal" class="level3"><span class="tocNumber">3.1.19 </span>Clear coat normal</a><br/>
|
||||
<a href="#materialmodels/litmodel/emissive" class="level3"><span class="tocNumber">3.1.20 </span>Emissive</a><br/>
|
||||
<a href="#materialmodels/litmodel/post-lightingcolor" class="level3"><span class="tocNumber">3.1.21 </span>Post-lighting color</a><br/>
|
||||
<a href="#materialmodels/litmodel/indexofrefraction" class="level3"><span class="tocNumber">3.1.22 </span>Index of refraction</a><br/>
|
||||
<a href="#materialmodels/litmodel/transmission" class="level3"><span class="tocNumber">3.1.23 </span>Transmission</a><br/>
|
||||
<a href="#materialmodels/litmodel/absorption" class="level3"><span class="tocNumber">3.1.24 </span>Absorption</a><br/>
|
||||
<a href="#materialmodels/litmodel/micro-thicknessandthickness" class="level3"><span class="tocNumber">3.1.25 </span>Micro-thickness and thickness</a><br/>
|
||||
<a href="#materialmodels/litmodel/dispersion" class="level3"><span class="tocNumber">3.1.26 </span>Dispersion</a><br/>
|
||||
<a href="#materialmodels/litmodel/sheencolor" class="level3"><span class="tocNumber">3.1.8 </span>Sheen color</a><br/>
|
||||
<a href="#materialmodels/litmodel/sheenroughness" class="level3"><span class="tocNumber">3.1.9 </span>Sheen roughness</a><br/>
|
||||
<a href="#materialmodels/litmodel/clearcoat" class="level3"><span class="tocNumber">3.1.10 </span>Clear coat</a><br/>
|
||||
<a href="#materialmodels/litmodel/clearcoatroughness" class="level3"><span class="tocNumber">3.1.11 </span>Clear coat roughness</a><br/>
|
||||
<a href="#materialmodels/litmodel/anisotropy" class="level3"><span class="tocNumber">3.1.12 </span>Anisotropy</a><br/>
|
||||
<a href="#materialmodels/litmodel/anisotropydirection" class="level3"><span class="tocNumber">3.1.13 </span>Anisotropy direction</a><br/>
|
||||
<a href="#materialmodels/litmodel/ambientocclusion" class="level3"><span class="tocNumber">3.1.14 </span>Ambient occlusion</a><br/>
|
||||
<a href="#materialmodels/litmodel/normal" class="level3"><span class="tocNumber">3.1.15 </span>Normal</a><br/>
|
||||
<a href="#materialmodels/litmodel/bentnormal" class="level3"><span class="tocNumber">3.1.16 </span>Bent normal</a><br/>
|
||||
<a href="#materialmodels/litmodel/clearcoatnormal" class="level3"><span class="tocNumber">3.1.17 </span>Clear coat normal</a><br/>
|
||||
<a href="#materialmodels/litmodel/emissive" class="level3"><span class="tocNumber">3.1.18 </span>Emissive</a><br/>
|
||||
<a href="#materialmodels/litmodel/post-lightingcolor" class="level3"><span class="tocNumber">3.1.19 </span>Post-lighting color</a><br/>
|
||||
<a href="#materialmodels/litmodel/indexofrefraction" class="level3"><span class="tocNumber">3.1.20 </span>Index of refraction</a><br/>
|
||||
<a href="#materialmodels/litmodel/transmission" class="level3"><span class="tocNumber">3.1.21 </span>Transmission</a><br/>
|
||||
<a href="#materialmodels/litmodel/absorption" class="level3"><span class="tocNumber">3.1.22 </span>Absorption</a><br/>
|
||||
<a href="#materialmodels/litmodel/micro-thicknessandthickness" class="level3"><span class="tocNumber">3.1.23 </span>Micro-thickness and thickness</a><br/>
|
||||
<a href="#materialmodels/litmodel/dispersion" class="level3"><span class="tocNumber">3.1.24 </span>Dispersion</a><br/>
|
||||
<a href="#materialmodels/subsurfacemodel" class="level2"><span class="tocNumber">3.2 </span>Subsurface model</a><br/>
|
||||
<a href="#materialmodels/subsurfacemodel/thickness" class="level3"><span class="tocNumber">3.2.1 </span>Thickness</a><br/>
|
||||
<a href="#materialmodels/subsurfacemodel/subsurfacecolor" class="level3"><span class="tocNumber">3.2.2 </span>Subsurface color</a><br/>
|
||||
@@ -379,8 +377,6 @@ in <a href="#table_standardproperties">table 1</a>.
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">roughness</strong> </td><td style="text-align:left"> Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">metallic</strong> </td><td style="text-align:left"> Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1) </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">reflectance</strong> </td><td style="text-align:left"> Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">specularFactor</strong> </td><td style="text-align:left"> Scales the amount of specular reflection for non-metallic surfaces </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">specularColorFactor</strong> </td><td style="text-align:left"> Color of the specular reflection for non-metallic surfaces </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">ambientOcclusion</strong> </td><td style="text-align:left"> Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0 </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoat</strong> </td><td style="text-align:left"> Strength of the clear coat layer </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoatRoughness</strong> </td><td style="text-align:left"> Perceived smoothness or roughness of the clear coat layer </td></tr>
|
||||
@@ -409,8 +405,6 @@ The type and range of each property is described in <a href="#table_standardprop
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">metallic</strong> </td><td style="text-align:center"> float </td><td style="text-align:center"> [0..1] </td><td style="text-align:left"> Should be 0 or 1 </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">roughness</strong> </td><td style="text-align:center"> float </td><td style="text-align:center"> [0..1] </td><td style="text-align:left"> </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">reflectance</strong> </td><td style="text-align:center"> float </td><td style="text-align:center"> [0..1] </td><td style="text-align:left"> Prefer values > 0.35 </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">specularFactor</strong> </td><td style="text-align:center"> float </td><td style="text-align:center"> [0..1] </td><td style="text-align:left"> Optional, defaults to 1.0 </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">specularColorFactor</strong> </td><td style="text-align:center"> float3 </td><td style="text-align:center"> [0..1] </td><td style="text-align:left"> Linear RGB, defaults to 1.0 </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">sheenColor</strong> </td><td style="text-align:center"> float3 </td><td style="text-align:center"> [0..1] </td><td style="text-align:left"> Linear RGB </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">sheenRoughness</strong> </td><td style="text-align:center"> float </td><td style="text-align:center"> [0..1] </td><td style="text-align:left"> </td></tr>
|
||||
<tr><td style="text-align:right"> <strong class="asterisk">clearCoat</strong> </td><td style="text-align:center"> float </td><td style="text-align:center"> [0..1] </td><td style="text-align:left"> Should be 0 or 1 </td></tr>
|
||||
@@ -598,24 +592,10 @@ impossible materials, however, this might be desirable for artistic reasons.
|
||||
The <code>reflectance</code> property is designed as a normalized property in the range 0..1 which makes
|
||||
it easy to define from a texture.
|
||||
</p><p>
|
||||
See section <a href="#toc3.1.22">3.1.22</a> for more information about the <code>ior</code> property and refractive
|
||||
See section <a href="#toc3.1.20">3.1.20</a> for more information about the <code>ior</code> property and refractive
|
||||
indices.
|
||||
</p>
|
||||
<a class="target" name="specularfactor"> </a><a class="target" name="materialmodels/litmodel/specularfactor"> </a><a class="target" name="toc3.1.8"> </a><h3 id="specular-factor"><a class="header" href="#specular-factor">Specular factor</a></h3>
|
||||
<p>
|
||||
<p>The <code>specularFactor</code> property scales the amount of specular reflection for non-metallic surfaces.
|
||||
This property is often used to implement the <code>KHR_materials_specular</code> glTF extension. This value
|
||||
scales the Fresnel reflectance at normal incidence (F0), which is also computed from the
|
||||
<code>reflectance</code> property. It also sets the Fresnel reflectance at grazing angles (F90).</p>
|
||||
</p>
|
||||
<a class="target" name="specularcolorfactor"> </a><a class="target" name="materialmodels/litmodel/specularcolorfactor"> </a><a class="target" name="toc3.1.9"> </a><h3 id="specular-color-factor"><a class="header" href="#specular-color-factor">Specular color factor</a></h3>
|
||||
<p>
|
||||
<p>The <code>specularColorFactor</code> property defines the color of the specular reflection for non-metallic
|
||||
surfaces. This property is often used to implement the <code>KHR_materials_specular</code> glTF extension. This
|
||||
value scales the Fresnel reflectance at normal incidence (F0), which is also computed from the
|
||||
<code>reflectance</code> property.</p>
|
||||
</p>
|
||||
<a class="target" name="sheencolor"> </a><a class="target" name="materialmodels/litmodel/sheencolor"> </a><a class="target" name="toc3.1.10"> </a><h3 id="sheen-color"><a class="header" href="#sheen-color">Sheen color</a></h3>
|
||||
<a class="target" name="sheencolor"> </a><a class="target" name="materialmodels/litmodel/sheencolor"> </a><a class="target" name="toc3.1.8"> </a><h3 id="sheen-color"><a class="header" href="#sheen-color">Sheen color</a></h3>
|
||||
<p>
|
||||
<p>The sheen color controls the color appearance and strength of an optional sheen layer on top of the
|
||||
base layer described by the properties above. The sheen layer always sits below the clear coat layer
|
||||
@@ -633,7 +613,7 @@ The effect of <code>sheenColor</code> is shown in <a href="#figure_materialsheen
|
||||
create a cloth-like or fabric-like appearance, it is more efficient to use the dedicated cloth
|
||||
model described in section <a href="#toc3.3">3.3</a>.</div>
|
||||
</p>
|
||||
<a class="target" name="sheenroughness"> </a><a class="target" name="materialmodels/litmodel/sheenroughness"> </a><a class="target" name="toc3.1.11"> </a><h3 id="sheen-roughness"><a class="header" href="#sheen-roughness">Sheen roughness</a></h3>
|
||||
<a class="target" name="sheenroughness"> </a><a class="target" name="materialmodels/litmodel/sheenroughness"> </a><a class="target" name="toc3.1.9"> </a><h3 id="sheen-roughness"><a class="header" href="#sheen-roughness">Sheen roughness</a></h3>
|
||||
<p>
|
||||
<p>The <code>sheenRoughness</code> property is similar to the <code>roughness</code> property but applies only to the
|
||||
sheen layer.</p>
|
||||
@@ -645,7 +625,7 @@ The effect of <code>sheenRoughness</code> on a rough metal is shown in <a href="
|
||||
<center><div class="image" style=""><a href="../images/materials/sheen_roughness.png" target="_blank"><img class="markdeep" src="../images/materials/sheen_roughness.png" /></a><center><span class="imagecaption"><a class="target" name="figure_sheenroughnessproperty"> </a><b style="font-style:normal;">Figure 8:</b> <code>sheenRoughness</code> varying from 0.0
|
||||
(left) to 1.0 (right)</span></center></div></center>
|
||||
</p>
|
||||
<a class="target" name="clearcoat"> </a><a class="target" name="materialmodels/litmodel/clearcoat"> </a><a class="target" name="toc3.1.12"> </a><h3 id="clear-coat"><a class="header" href="#clear-coat">Clear coat</a></h3>
|
||||
<a class="target" name="clearcoat"> </a><a class="target" name="materialmodels/litmodel/clearcoat"> </a><a class="target" name="toc3.1.10"> </a><h3 id="clear-coat"><a class="header" href="#clear-coat">Clear coat</a></h3>
|
||||
<p>
|
||||
<p>Multi-layer materials are fairly common, particularly materials with a thin translucent
|
||||
layer over a base layer. Real world examples of such materials include car paints, soda cans,
|
||||
@@ -672,7 +652,7 @@ The effect of <code>clearCoat</code> on a rough metal is shown in <a href="#figu
|
||||
</p><p>
|
||||
<div class="admonition note">The clear coat layer is added on top of the sheen layer if present.</div>
|
||||
</p>
|
||||
<a class="target" name="clearcoatroughness"> </a><a class="target" name="materialmodels/litmodel/clearcoatroughness"> </a><a class="target" name="toc3.1.13"> </a><h3 id="clear-coat-roughness"><a class="header" href="#clear-coat-roughness">Clear coat roughness</a></h3>
|
||||
<a class="target" name="clearcoatroughness"> </a><a class="target" name="materialmodels/litmodel/clearcoatroughness"> </a><a class="target" name="toc3.1.11"> </a><h3 id="clear-coat-roughness"><a class="header" href="#clear-coat-roughness">Clear coat roughness</a></h3>
|
||||
<p>
|
||||
<p>The <code>clearCoatRoughness</code> property is similar to the <code>roughness</code> property but applies only to the
|
||||
clear coat layer.</p>
|
||||
@@ -683,7 +663,7 @@ The effect of <code>clearCoatRoughness</code> on a rough metal is shown in <a hr
|
||||
<center><div class="image" style=""><a href="../images/materials/clear_coat_roughness.png" target="_blank"><img class="markdeep" src="../images/materials/clear_coat_roughness.png" /></a><center><span class="imagecaption"><a class="target" name="figure_clearcoatroughnessproperty"> </a><b style="font-style:normal;">Figure 11:</b> <code>clearCoatRoughness</code> varying from 0.0
|
||||
(left) to 1.0 (right)</span></center></div></center>
|
||||
</p>
|
||||
<a class="target" name="anisotropy"> </a><a class="target" name="materialmodels/litmodel/anisotropy"> </a><a class="target" name="toc3.1.14"> </a><h3 id="anisotropy"><a class="header" href="#anisotropy">Anisotropy</a></h3>
|
||||
<a class="target" name="anisotropy"> </a><a class="target" name="materialmodels/litmodel/anisotropy"> </a><a class="target" name="toc3.1.12"> </a><h3 id="anisotropy"><a class="header" href="#anisotropy">Anisotropy</a></h3>
|
||||
<p>
|
||||
<p>Many real-world materials, such as brushed metal, can only be replicated using an anisotropic
|
||||
reflectance model. A material can be changed from the default isotropic model to an anisotropic
|
||||
@@ -708,7 +688,7 @@ tangent direction and negative values in the bitangent direction.
|
||||
<div class="admonition tip">The anisotropic material model is slightly more expensive than the standard material model. Do
|
||||
not assign a value (even 0.0) to the <code>anisotropy</code> property if you don't need anisotropy.</div>
|
||||
</p>
|
||||
<a class="target" name="anisotropydirection"> </a><a class="target" name="materialmodels/litmodel/anisotropydirection"> </a><a class="target" name="toc3.1.15"> </a><h3 id="anisotropy-direction"><a class="header" href="#anisotropy-direction">Anisotropy direction</a></h3>
|
||||
<a class="target" name="anisotropydirection"> </a><a class="target" name="materialmodels/litmodel/anisotropydirection"> </a><a class="target" name="toc3.1.13"> </a><h3 id="anisotropy-direction"><a class="header" href="#anisotropy-direction">Anisotropy direction</a></h3>
|
||||
<p>
|
||||
<p>The <code>anisotropyDirection</code> property defines the direction of the surface at a given point and thus
|
||||
control the shape of the specular highlights. It is specified as vector of 3 values that usually
|
||||
@@ -726,7 +706,7 @@ in <a href="#figure_anisotropydirectionproperty">figure 16</a>.
|
||||
</p><p>
|
||||
<center><div class="image" style=""><a href="../images/screenshot_anisotropy_map.jpg" target="_blank"><img class="markdeep" src="../images/screenshot_anisotropy_map.jpg" /></a><center><span class="imagecaption"><a class="target" name="figure_anisotropydirectionproperty"> </a><b style="font-style:normal;">Figure 16:</b> Example of Lighting: specularAmbientOcclusiona direction map</span></center></div></center>
|
||||
</p>
|
||||
<a class="target" name="ambientocclusion"> </a><a class="target" name="materialmodels/litmodel/ambientocclusion"> </a><a class="target" name="toc3.1.16"> </a><h3 id="ambient-occlusion"><a class="header" href="#ambient-occlusion">Ambient occlusion</a></h3>
|
||||
<a class="target" name="ambientocclusion"> </a><a class="target" name="materialmodels/litmodel/ambientocclusion"> </a><a class="target" name="toc3.1.14"> </a><h3 id="ambient-occlusion"><a class="header" href="#ambient-occlusion">Ambient occlusion</a></h3>
|
||||
<p>
|
||||
<p>The <code>ambientOcclusion</code> property defines how much of the ambient light is accessible to a surface
|
||||
point. It is a per-pixel shadowing factor between 0.0 (fully shadowed) and 1.0 (fully lit). This
|
||||
@@ -736,7 +716,7 @@ directional, point and spot lights, nor specular lighting.</p>
|
||||
<center><div class="image" style=""><a href="../images/screenshot_ao.jpg" target="_blank"><img class="markdeep" src="../images/screenshot_ao.jpg" /></a><center><span class="imagecaption"><a class="target" name="figure_aoexample"> </a><b style="font-style:normal;">Figure 17:</b> Comparison of materials without diffuse ambient occlusion
|
||||
(left) and with (right)</span></center></div></center>
|
||||
</p>
|
||||
<a class="target" name="normal"> </a><a class="target" name="materialmodels/litmodel/normal"> </a><a class="target" name="toc3.1.17"> </a><h3 id="normal"><a class="header" href="#normal">Normal</a></h3>
|
||||
<a class="target" name="normal"> </a><a class="target" name="materialmodels/litmodel/normal"> </a><a class="target" name="toc3.1.15"> </a><h3 id="normal"><a class="header" href="#normal">Normal</a></h3>
|
||||
<p>
|
||||
<p>The <code>normal</code> property defines the normal of the surface at a given point. It usually comes from a
|
||||
<em class="underscore">normal map</em> texture, which allows to vary the property per-pixel. The normal is supplied in tangent
|
||||
@@ -754,7 +734,7 @@ and with (right)</span></center></div></center>
|
||||
</p><p>
|
||||
<div class="admonition warning">Using a normal map increases the runtime cost of the material model.</div>
|
||||
</p>
|
||||
<a class="target" name="bentnormal"> </a><a class="target" name="materialmodels/litmodel/bentnormal"> </a><a class="target" name="toc3.1.18"> </a><h3 id="bent-normal"><a class="header" href="#bent-normal">Bent normal</a></h3>
|
||||
<a class="target" name="bentnormal"> </a><a class="target" name="materialmodels/litmodel/bentnormal"> </a><a class="target" name="toc3.1.16"> </a><h3 id="bent-normal"><a class="header" href="#bent-normal">Bent normal</a></h3>
|
||||
<p>
|
||||
<p>The <code>bentNormal</code> property defines the average unoccluded direction at a point on the surface. It is
|
||||
used to improve the accuracy of indirect lighting. Bent normals can also improve the quality of
|
||||
@@ -768,7 +748,7 @@ instance.
|
||||
<center><div class="image" style=""><a href="../images/material_bent_normal.gif" target="_blank"><img class="markdeep" src="../images/material_bent_normal.gif" /></a><center><span class="imagecaption"><a class="target" name="figure_bentnormalmapped"> </a><b style="font-style:normal;">Figure 19:</b> Example of a model rendered with and without a bent normal map. Both
|
||||
versions use the same ambient occlusion map.</span></center></div></center>
|
||||
</p>
|
||||
<a class="target" name="clearcoatnormal"> </a><a class="target" name="materialmodels/litmodel/clearcoatnormal"> </a><a class="target" name="toc3.1.19"> </a><h3 id="clear-coat-normal"><a class="header" href="#clear-coat-normal">Clear coat normal</a></h3>
|
||||
<a class="target" name="clearcoatnormal"> </a><a class="target" name="materialmodels/litmodel/clearcoatnormal"> </a><a class="target" name="toc3.1.17"> </a><h3 id="clear-coat-normal"><a class="header" href="#clear-coat-normal">Clear coat normal</a></h3>
|
||||
<p>
|
||||
<p>The <code>clearCoatNormal</code> property defines the normal of the clear coat layer at a given point. It
|
||||
behaves otherwise like the <code>normal</code> property.</p>
|
||||
@@ -778,7 +758,7 @@ map and a surface normal map</span></center></div></center>
|
||||
</p><p>
|
||||
<div class="admonition warning">Using a clear coat normal map increases the runtime cost of the material model.</div>
|
||||
</p>
|
||||
<a class="target" name="emissive"> </a><a class="target" name="materialmodels/litmodel/emissive"> </a><a class="target" name="toc3.1.20"> </a><h3 id="emissive"><a class="header" href="#emissive">Emissive</a></h3>
|
||||
<a class="target" name="emissive"> </a><a class="target" name="materialmodels/litmodel/emissive"> </a><a class="target" name="toc3.1.18"> </a><h3 id="emissive"><a class="header" href="#emissive">Emissive</a></h3>
|
||||
<p>
|
||||
<p>The <code>emissive</code> property can be used to simulate additional light emitted by the surface. It is
|
||||
defined as a <code>float4</code> value that contains an RGB intensity in nits as well as an exposure
|
||||
@@ -797,7 +777,7 @@ force an emissive surface to bloom. When the exposure weight is set to 0, the em
|
||||
not affected by the camera exposure. When the weight is set to 1, the intensity is multiplied by
|
||||
the camera exposure like with any regular light.
|
||||
</p>
|
||||
<a class="target" name="post-lightingcolor"> </a><a class="target" name="materialmodels/litmodel/post-lightingcolor"> </a><a class="target" name="toc3.1.21"> </a><h3 id="post-lighting-color"><a class="header" href="#post-lighting-color">Post-lighting color</a></h3>
|
||||
<a class="target" name="post-lightingcolor"> </a><a class="target" name="materialmodels/litmodel/post-lightingcolor"> </a><a class="target" name="toc3.1.19"> </a><h3 id="post-lighting-color"><a class="header" href="#post-lighting-color">Post-lighting color</a></h3>
|
||||
<p>
|
||||
<p>The <code>postLightingColor</code> can be used to modify the surface color after lighting computations. This
|
||||
property has no physical meaning and only exists to implement specific effects or to help with
|
||||
@@ -811,7 +791,7 @@ this option for more information.
|
||||
<div class="admonition tip"><code>postLightingColor</code> can be used as a simpler <code>emissive</code> property by setting
|
||||
<code>postLightingBlending</code> to <code>add</code> and by providing an RGB color with alpha set to <code>0.0</code>.</div>
|
||||
</p>
|
||||
<a class="target" name="indexofrefraction"> </a><a class="target" name="materialmodels/litmodel/indexofrefraction"> </a><a class="target" name="toc3.1.22"> </a><h3 id="index-of-refraction"><a class="header" href="#index-of-refraction">Index of refraction</a></h3>
|
||||
<a class="target" name="indexofrefraction"> </a><a class="target" name="materialmodels/litmodel/indexofrefraction"> </a><a class="target" name="toc3.1.20"> </a><h3 id="index-of-refraction"><a class="header" href="#index-of-refraction">Index of refraction</a></h3>
|
||||
<p>
|
||||
<p>The <code>ior</code> property only affects non-metallic surfaces. This property can be used to control the
|
||||
index of refraction and the specular intensity of materials. The <code>ior</code> property is intended to
|
||||
@@ -864,7 +844,7 @@ See the <a href="#reflectance">Reflectance</a> section for more information on t
|
||||
light, creating a diffusion effect useful to recreate “blurry” appearances such as frosted
|
||||
glass, certain plastics, etc.</div>
|
||||
</p>
|
||||
<a class="target" name="transmission"> </a><a class="target" name="materialmodels/litmodel/transmission"> </a><a class="target" name="toc3.1.23"> </a><h3 id="transmission"><a class="header" href="#transmission">Transmission</a></h3>
|
||||
<a class="target" name="transmission"> </a><a class="target" name="materialmodels/litmodel/transmission"> </a><a class="target" name="toc3.1.21"> </a><h3 id="transmission"><a class="header" href="#transmission">Transmission</a></h3>
|
||||
<p>
|
||||
<p>The <code>transmission</code> property defines what ratio of diffuse light is transmitted through a refractive
|
||||
material. This property only affects materials with a <code>refractionMode</code> set to <code>cubemap</code> or
|
||||
@@ -884,7 +864,7 @@ The effect of <code>transmission</code> on a glossy dielectric (<code>ior</code>
|
||||
<div class="admonition tip">The <code>transmission</code> property is useful to create decals, paint, etc. at the surface of refractive
|
||||
materials.</div>
|
||||
</p>
|
||||
<a class="target" name="absorption"> </a><a class="target" name="materialmodels/litmodel/absorption"> </a><a class="target" name="toc3.1.24"> </a><h3 id="absorption"><a class="header" href="#absorption">Absorption</a></h3>
|
||||
<a class="target" name="absorption"> </a><a class="target" name="materialmodels/litmodel/absorption"> </a><a class="target" name="toc3.1.22"> </a><h3 id="absorption"><a class="header" href="#absorption">Absorption</a></h3>
|
||||
<p>
|
||||
<p>The <code>absorption</code> property defines the absorption coefficients of light transmitted through the
|
||||
material. <a href="#figure_absorptionexample">Figure 24</a> shows the effect of <code>absorption</code> on a refracting object with
|
||||
@@ -921,7 +901,7 @@ $$absorption = -\frac{ln(transmittanceColor)}{atDistance}$$
|
||||
While this computation can be done in the material itself we recommend doing it offline whenever
|
||||
possible. Filament provides an API for this purpose, <code>Color::absorptionAtDistance()</code>.
|
||||
</p>
|
||||
<a class="target" name="micro-thicknessandthickness"> </a><a class="target" name="materialmodels/litmodel/micro-thicknessandthickness"> </a><a class="target" name="toc3.1.25"> </a><h3 id="micro-thickness-and-thickness"><a class="header" href="#micro-thickness-and-thickness">Micro-thickness and thickness</a></h3>
|
||||
<a class="target" name="micro-thicknessandthickness"> </a><a class="target" name="materialmodels/litmodel/micro-thicknessandthickness"> </a><a class="target" name="toc3.1.23"> </a><h3 id="micro-thickness-and-thickness"><a class="header" href="#micro-thickness-and-thickness">Micro-thickness and thickness</a></h3>
|
||||
<p>
|
||||
<p>The <code>microThickness</code> and <code>thickness</code> properties define the optical depth of the material of a
|
||||
refracting object. <code>microThickness</code> is used when <code>refractionType</code> is set to <code>thin</code>, and <code>thickness</code>
|
||||
@@ -954,7 +934,7 @@ the <code>refractionType</code> is set to <code>solid</code> and <code>absorptio
|
||||
<center><div class="image" style=""><a href="../images/material_thickness.png" target="_blank"><img class="markdeep" src="../images/material_thickness.png" /></a><center><span class="imagecaption"><a class="target" name="figure_varyingthickness"> </a><b style="font-style:normal;">Figure 27:</b> <code>thickness</code> varying from 0.0 at the top of the prism to 3.0 at the
|
||||
bottom of the prism</span></center></div></center>
|
||||
</p>
|
||||
<a class="target" name="dispersion"> </a><a class="target" name="materialmodels/litmodel/dispersion"> </a><a class="target" name="toc3.1.26"> </a><h3 id="dispersion"><a class="header" href="#dispersion">Dispersion</a></h3>
|
||||
<a class="target" name="dispersion"> </a><a class="target" name="materialmodels/litmodel/dispersion"> </a><a class="target" name="toc3.1.24"> </a><h3 id="dispersion"><a class="header" href="#dispersion">Dispersion</a></h3>
|
||||
<p>
|
||||
<p>The dispersion property controls the angular separation of colors transmitting through a relatively
|
||||
clear volume. It can only be used when <code>refractionType</code> is set to <code>volume</code>.
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
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.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -3,3 +3,7 @@ src_mdbook/book
|
||||
src_mdbook/src/dup/*.md
|
||||
src_mdbook/src/main/filament.md
|
||||
src_mdbook/src/main/materials.md
|
||||
src_mdbook/src/samples/web/*.md
|
||||
src_mdbook/src/web
|
||||
src_mdbook/src/release/cocoapods.md
|
||||
src_mdbook/src/remote
|
||||
@@ -81,8 +81,6 @@ in table [standardProperties].
|
||||
**roughness** | Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections
|
||||
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
|
||||
**reflectance** | Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections
|
||||
**specularFactor** | Scales the amount of specular reflection for non-metallic surfaces
|
||||
**specularColorFactor** | Color of the specular reflection for non-metallic surfaces
|
||||
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
|
||||
**clearCoat** | Strength of the clear coat layer
|
||||
**clearCoatRoughness** | Perceived smoothness or roughness of the clear coat layer
|
||||
@@ -112,8 +110,6 @@ The type and range of each property is described in table [standardPropertiesTyp
|
||||
**metallic** | float | [0..1] | Should be 0 or 1
|
||||
**roughness** | float | [0..1] |
|
||||
**reflectance** | float | [0..1] | Prefer values > 0.35
|
||||
**specularFactor** | float | [0..1] | Optional, defaults to 1.0
|
||||
**specularColorFactor** | float3 | [0..1] | Linear RGB, defaults to 1.0
|
||||
**sheenColor** | float3 | [0..1] | Linear RGB
|
||||
**sheenRoughness** | float | [0..1] |
|
||||
**clearCoat** | float | [0..1] | Should be 0 or 1
|
||||
@@ -308,20 +304,6 @@ it easy to define from a texture.
|
||||
See section [Index of refraction] for more information about the `ior` property and refractive
|
||||
indices.
|
||||
|
||||
### Specular factor
|
||||
|
||||
The `specularFactor` property scales the amount of specular reflection for non-metallic surfaces.
|
||||
This property is often used to implement the `KHR_materials_specular` glTF extension. This value
|
||||
scales the Fresnel reflectance at normal incidence (F0), which is also computed from the
|
||||
`reflectance` property. It also sets the Fresnel reflectance at grazing angles (F90).
|
||||
|
||||
### Specular color factor
|
||||
|
||||
The `specularColorFactor` property defines the color of the specular reflection for non-metallic
|
||||
surfaces. This property is often used to implement the `KHR_materials_specular` glTF extension. This
|
||||
value scales the Fresnel reflectance at normal incidence (F0), which is also computed from the
|
||||
`reflectance` property.
|
||||
|
||||
### Sheen color
|
||||
|
||||
The sheen color controls the color appearance and strength of an optional sheen layer on top of the
|
||||
|
||||
@@ -23,23 +23,18 @@
|
||||
- [skinning](./samples/web/skinning.md)
|
||||
- [sky](./samples/web/sky.md)
|
||||
- [Technical Notes](./notes/README.md)
|
||||
- [Material Properties](./notes/material_properties.md)
|
||||
- [Release](./release/README.md)
|
||||
- [CocoaPods](./release/cocoapods.md)
|
||||
- [Maven](./release/maven.md)
|
||||
- [Github](./release/guide.md)
|
||||
- [Versioning](./release/versioning.md)
|
||||
- [Branching](./release/branching.md)
|
||||
- [Documentation](./dup/docs.md)
|
||||
- [Debugging](./notes/debugging.md)
|
||||
- [Metal](./notes/metal_debugging.md)
|
||||
- [Vulkan](./notes/vulkan_debugging.md)
|
||||
- [SPIR-V](./notes/spirv_debugging.md)
|
||||
- [Running with ASAN and UBSAN](./notes/asan_ubsan.md)
|
||||
- [Using Instruments on macOS](./notes/instruments.md)
|
||||
- [Code coverage analysis](./notes/coverage.md)
|
||||
- [Performance analysis](./notes/performance_analysis.md)
|
||||
- [ASAN and UBSAN](./notes/asan_ubsan.md)
|
||||
- [Instruments on macOS](./notes/instruments.md)
|
||||
- [Code coverage](./notes/coverage.md)
|
||||
- [Performance](./notes/performance_analysis.md)
|
||||
- [Documentation](./dup/docs.md)
|
||||
- [Material Properties](./notes/material_properties.md)
|
||||
- [Framegraph](./notes/framegraph.md)
|
||||
- [2D Coordinate Spaces](./notes/2d_coordinate_spaces.md)
|
||||
- [Tests](./notes/tests.md)
|
||||
- [backend](./dup/backend_test.md)
|
||||
- [CI: backend](./dup/test_ci_backend.md)
|
||||
@@ -66,3 +61,9 @@
|
||||
- [specgen](./dup/specgen.md)
|
||||
- [specular-color](./dup/specular_color.md)
|
||||
- [zbloat](./dup/zbloat.md)
|
||||
- [Release](./release/README.md)
|
||||
- [CocoaPods](./release/cocoapods.md)
|
||||
- [Maven](./release/maven.md)
|
||||
- [Github](./release/guide.md)
|
||||
- [Versioning](./release/versioning.md)
|
||||
- [Branching](./release/branching.md)
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
# 2D Coordinate Spaces
|
||||
|
||||
This page describes Filament's interpretation of 2D coordinate spaces. We address the differences
|
||||
between graphics APIs and provide explanations for custom logic and client APIs.
|
||||
|
||||
## 1. Texture coordinate space
|
||||
|
||||
To begin our discussion, we imagine looking at a rectangular image, like a photograph. To be able to
|
||||
describe to another person where things are on the photograph (say, a **tree**), we need to first
|
||||
define a consistent coordinate system that both parties understand. A natural choice is to pick one
|
||||
of the corners as the origin of the coordinate system (that is, corners are easy to identify on a
|
||||
rectangle, and do not require tools - like a ruler - for positioning). We can further refine this
|
||||
coordinate system by assigning 2D numbers to the four corners so that we can accurately
|
||||
describe the positions on the photograph.
|
||||
|
||||
So for example, we assign the corners in clockwise order, starting from the top-left corner:
|
||||
\\((0, 0)\\), \\((1, 0)\\), \\((1, 1)\\), \\((0, 1)\\).
|
||||
|
||||
Assuming we are sampling from a texture in a shader, we sample by using a 2D coordinate (UV)
|
||||
to identify a point on the rectangular space of the texture. We can make a statement such as, "the
|
||||
**tree** is near the point of \\(((0.65, 0.35)\\))", to communicate the position of a tree in
|
||||
the picture to another person.
|
||||
|
||||
Given the description above, we conclude this section with the following statements
|
||||
- Programmers communicate with computers using coordinate systems like the above to draw 2D images
|
||||
onto a display.
|
||||
- In 3D graphics, similar coordinate systems are used to texturize 3D models with 2D images. The
|
||||
2D images are called *textures*.
|
||||
- Filament defines its coordinate system using the *top-left* of the image as \\((0, 0)\\) and
|
||||
the *bottom-right* is \\((1, 1)\\).
|
||||
|
||||
### 1.1. OpenGL (The Bottom-Left API)
|
||||
|
||||
OpenGL has its texture coordinate origin \\((0, 0)\\) at the **bottom-left**, and
|
||||
\\((1, 1)\\) is at the top-right. Note that this differs from Filament's assumed texture
|
||||
coordinate system.
|
||||
|
||||
The GL function for uploading texture is `glTexImage2D`. Even though GL interprets \\((0, 0)\\) as
|
||||
the bottom-left of a 2D image, `glTexImage2D` uploads the lower-bits as the *bottom* of the image
|
||||
as opposed to the *top*. (This behavior is opposite of the other backends' texture upload
|
||||
APIs). The two differences cancel each other, meaning there is no difference between the different
|
||||
backends in upload and sampling of textures.
|
||||
|
||||
We can restate the above as
|
||||
|
||||
| |
|
||||
|---|
|
||||
| *Filament's internal texture coordinate space defines the **top-left** corner of the image as the origin.* |
|
||||
|
||||
### 1.2. Readback of rendertarget
|
||||
|
||||
While we've established that the APIs are not any different in the *uploading* and *sampling* of
|
||||
textures for Filament's backends, there are, unfortunately, cases where special handling of
|
||||
texture coordinate is necessary.
|
||||
|
||||
Reading the result of rendering into CPU memory can be very useful; for example, one might want to
|
||||
take snapshots of the rendering and output to image formats such as PNG or JPEG. Another use is to
|
||||
perform image analysis or processing over a rendered image. In Filament, the readback path
|
||||
corresponds to the `readPixels`. In the OpenGL backend, this is implemented using the `glReadPixels`
|
||||
function. GL's interpretation of texture coordinate space holds true for `glReadPixels`; this means
|
||||
that it treats the bottom-left of the image as the origin and the readback output *starts* from the
|
||||
origin. Hence, the first row of the image in CPU memory is the bottom-most row of the image (when
|
||||
displayed).
|
||||
|
||||
This creates two related inconsistencies:
|
||||
- First, (not a real use case), if one were to upload a texture, attach it as an attachment, and
|
||||
read the attachment, the GL backend would place the bottom of the image as the first row of
|
||||
pixels in CPU memory.
|
||||
- Second, the other backends consider the top-left corner as the origin, which means their
|
||||
readbacks would place the top of the image as the first row of the image in CPU memory.
|
||||
|
||||
To account for these inconsistencies, *the GL backend vertically flips the rows of the readback
|
||||
image*.
|
||||
|
||||
### 1.3. `flipUV` in Materials
|
||||
|
||||
In order to work with textures and associated primitives from different sources, Filament provides
|
||||
a simple, material-level client-set flag to flip the v-direction.
|
||||
|
||||
For example, if a 3D modeler
|
||||
exported a mesh with textures such that the bottom-left corner of the texture is \\(u=0, v=0\\) in
|
||||
texture space, then this is not consistent with Filament's assumption. Luckily, modern modelers will
|
||||
only choose either the top-left corner or the bottom-right corner as the origin. To address this
|
||||
inconsistency, Filament provides `flipUV` in the material definition. Setting this flag to true will
|
||||
flip the UV coordinate in the vertex attributes so that \\(v' = 1 - v\\), and the texture
|
||||
coordinate interpretation is then consistent with Filament's internal coordinates.
|
||||
|
||||
## 2. Clip-space
|
||||
|
||||
The clip-space is the bounded 3D space that a primitive vertex is transformed into at the end of a
|
||||
vertex shader. Abstractly, the XY-plane of the clip-space is parallel to the display, and its
|
||||
bounds correspond to the bounds of the window, surface, or buffer that we are rendering into. (This
|
||||
API-specific bounded space is also known as Normalized Device Coordinates or NDC).
|
||||
In different graphics API, the bounds and orientation of the NDC is also inconsistent. For all of
|
||||
the backends, the ranges of the XY-plane \\([-1, 1]\\) and the origin (\\((0, 0)\\)) are the
|
||||
same. However, the depth range and the Y-axis directions are not the same.
|
||||
|
||||
| Graphics API | Depth Range (Z) | Y-Axis Direction | Frame Buffer Origin |
|
||||
| -------------- | --------------- | ---------------- | ------------- |
|
||||
| OpenGL / WebGL | \\([−1, 1]\\) | Up | bottom-left |
|
||||
| Metal | \\([0, 1]\\) | Up | top-left |
|
||||
| WebGPU | \\([0, 1]\\) | Up | top-left |
|
||||
| Vulkan | \\([0, 1]\\) | Down | top-left |
|
||||
|
||||
(We will defer the discussion of GL's depth range difference to section 2.3.)
|
||||
|
||||
### 2.1. Vulkan y-axis correction
|
||||
|
||||
The XY ranges for all backends are the same, but Vulkan interprets the Y-Axis direction as
|
||||
"Down", meaning that \\(y=1\\) is at the bottom of the image. To account for this difference,
|
||||
Filament flips the projected \\(y\\) coordinate in the vertex shader for Vulkan
|
||||
|
||||
```c++
|
||||
#if defined(TARGET_VULKAN_ENVIRONMENT)
|
||||
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
|
||||
position.y = -position.y;
|
||||
#endif
|
||||
```
|
||||
|
||||
### 2.2 Fix Frame buffer origin difference with `uvToRenderTargetUV`
|
||||
|
||||
After the workaround in the above section, we can rewrite the table thus
|
||||
|
||||
| Graphics API | Y-Axis Direction | Frame Buffer Origin |
|
||||
| -------------- | ---------------- | ------------------- |
|
||||
| OpenGL / WebGL | Up | bottom-left |
|
||||
| Metal | Up | top-left |
|
||||
| WebGPU | Up | top-left |
|
||||
| Vulkan | Up (corrected) | top-left |
|
||||
|
||||
We can rephrase the Y-Axis Direction column to say that
|
||||
|
||||
| |
|
||||
|---|
|
||||
| *Filament's internal clip-space defines the Y-axis direction as **UP** (i.e. the physical world up direction pointing to \\(+1\\)).* |
|
||||
|
||||
Now consider the "Frame Buffer Origin" column. This column identifies the origin of the rendered
|
||||
image in the context of sampling by subsequent passes.
|
||||
|
||||
Assume
|
||||
- We have two passes \\(A\\) and \\(B\\)
|
||||
- \\(A\\) renders to a render target that attaches texture \\(T\\)
|
||||
- \\(B\\) renders a full-screen quad
|
||||
- In \\(B\\), top-left and bottom-right corners of the quad are assigned texturing UV coordinates
|
||||
\\((0, 0)\\) and \\((1, 1)\\), respectively.
|
||||
- The quad in \\(B\\) samples texture \\(T\\).
|
||||
|
||||
For GL, having the *bottom-left* corner as the origin means that rendering a pixel to clip-space
|
||||
\\(x=-1, y=-1\\) in \\(A\\) will output to pixel at \\(u=0, v=0\\) of \\(T\\). Note that GL
|
||||
clip-space's y-axis direction is consistent with the texture space v-axis direction. That is, in
|
||||
pass \\(B\\), sampling from \\(v=0\\) to \\(v=1\\) (moving in the positive direction) maps to going
|
||||
from \\(y=-1\\) to \\(y=1\\) in clip-space (also moving in positive direction).
|
||||
|
||||
This behavior is opposite for all other backends; sampling from \\(v=0\\) to \\(v=1\\) will map to
|
||||
clip-space movement from \\(y=1\\) to \\(y=-1\\) (going in the negative direction).
|
||||
|
||||
To account for this, Filament provides the following shader method that clients can call to
|
||||
correctly sample from a texture that was the rendertarget of a previous pass.
|
||||
|
||||
```c++
|
||||
highp vec2 uvToRenderTargetUV(const highp vec2 uv) {
|
||||
#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) || defined(TARGET_WEBGPU_ENVIRONMENT)
|
||||
return vec2(uv.x, 1.0 - uv.y);
|
||||
#else
|
||||
return uv;
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
It's meant to be used like so
|
||||
|
||||
```c++
|
||||
vec2 uv0 = uvToRenderTargetUV(getUV0());
|
||||
material.baseColor = texture(materialParams_baseColor, uv0);
|
||||
```
|
||||
|
||||
### 2.3. Clip-space depth adjustments for OpenGL
|
||||
|
||||
While the clip-space depth (or Z) dimension does not pertain to the 2D coordinate spaces that we have
|
||||
been discussing, the concept in this section parallels the space adjustment logic in the other
|
||||
sections. Hence, we have included it here.
|
||||
|
||||
Consider again this table
|
||||
|
||||
| Graphics API | Depth Range (Z) |
|
||||
| -------------- | --------------- |
|
||||
| OpenGL / WebGL | \\([−1, 1]\\) |
|
||||
| Metal | \\([0, 1]\\) |
|
||||
| WebGPU | \\([0, 1]\\) |
|
||||
| Vulkan | \\([0, 1]\\) |
|
||||
|
||||
For OpenGL, the NDC depth dimension ranges from \\(-1\\) to \\(1\\). Filament implements two
|
||||
paths to adjust this so that the internal clip-space definition remains consistent:
|
||||
|
||||
1. Use the `EXT_clip_control` extension to change the depth range to \\([0, 1]\\).
|
||||
|
||||
```c++
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
if (ext.EXT_clip_control) {
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL)
|
||||
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
|
||||
#elif defined(GL_EXT_clip_control)
|
||||
glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
2. For cases where the extension is not available, the following shader code will convert the
|
||||
internal (or noted as virtual in the code) coordinates to GL's expected depth coordinates.
|
||||
|
||||
```c++
|
||||
#if !defined(TARGET_VULKAN_ENVIRONMENT) && !defined(TARGET_METAL_ENVIRONMENT) && !defined(TARGET_WEBGPU_ENVIRONMENT)
|
||||
// This is not needed in Vulkan, Metal or WebGPU because clipControl is always (1, 0)
|
||||
position.z = position.z * frameUniforms.clipControl.x
|
||||
+ position.w * frameUniforms.clipControl.y;
|
||||
#endif
|
||||
```
|
||||
|
||||
This code uses a vector in the uniform buffer to scale and translate the resulting depth. This
|
||||
vector is written in runtime (so that we'd know whether the extension is present or not).
|
||||
@@ -56,7 +56,6 @@ set(SRCS
|
||||
src/materials/bloom/bloom.cpp
|
||||
src/materials/colorGrading/colorGrading.cpp
|
||||
src/materials/dof/dof.cpp
|
||||
src/materials/evsm/evsm.cpp
|
||||
src/materials/flare/flare.cpp
|
||||
src/materials/fog/fog.cpp
|
||||
src/materials/fsr/fsr.cpp
|
||||
@@ -286,8 +285,6 @@ set(MATERIAL_SRCS
|
||||
src/materials/dof/dofMipmap.mat
|
||||
src/materials/dof/dofTiles.mat
|
||||
src/materials/dof/dofTilesSwizzle.mat
|
||||
src/materials/evsm/gaussian.mat
|
||||
src/materials/evsm/vsmMipmap.mat
|
||||
src/materials/flare/flare.mat
|
||||
src/materials/fog/fog.mat
|
||||
src/materials/fsr/fsr_easu.mat
|
||||
@@ -304,6 +301,7 @@ set(MATERIAL_SRCS
|
||||
src/materials/ssao/mipmapDepth.mat
|
||||
src/materials/ssao/sao.mat
|
||||
src/materials/ssao/saoBentNormals.mat
|
||||
src/materials/vsmMipmap.mat
|
||||
)
|
||||
|
||||
if (NOT FILAMENT_DISABLE_GTAO)
|
||||
@@ -641,12 +639,6 @@ add_custom_command(
|
||||
APPEND
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${MATERIAL_DIR}/gaussian.filamat"
|
||||
DEPENDS src/materials/evsm/gaussian.fs
|
||||
APPEND
|
||||
)
|
||||
|
||||
file(MAKE_DIRECTORY "${GENERATION_ROOT}/generated/data/")
|
||||
|
||||
set(output_path_dfg_bin "${GENERATION_ROOT}/generated/data/dfg.bin")
|
||||
|
||||
@@ -86,8 +86,6 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
|
||||
src/opengl/OpenGLBlobCache.h
|
||||
src/opengl/OpenGLContext.cpp
|
||||
src/opengl/OpenGLContext.h
|
||||
src/opengl/OpenGLState.cpp
|
||||
src/opengl/OpenGLState.h
|
||||
src/opengl/OpenGLDriver.cpp
|
||||
src/opengl/OpenGLDriver.h
|
||||
src/opengl/OpenGLDriverFactory.h
|
||||
|
||||
@@ -48,7 +48,7 @@ class ostream;
|
||||
/**
|
||||
* Types and enums used by filament's driver.
|
||||
*
|
||||
* Effectively these types are public but should not be used directly. Instead, use public classes
|
||||
* Effectively these types are public but should not be used directly. Instead use public classes
|
||||
* internal redeclaration of these types.
|
||||
* For e.g. Use Texture::Sampler instead of filament::SamplerType.
|
||||
*/
|
||||
@@ -1147,19 +1147,6 @@ constexpr bool isDepthFormat(TextureFormat format) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
//! returns whether this format a 32-bit float format
|
||||
constexpr bool isFp32ColorFormat(TextureFormat format) noexcept {
|
||||
switch (format) {
|
||||
case TextureFormat::R32F:
|
||||
case TextureFormat::RG32F:
|
||||
case TextureFormat::RGB32F:
|
||||
case TextureFormat::RGBA32F:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool isStencilFormat(TextureFormat format) noexcept {
|
||||
switch (format) {
|
||||
case TextureFormat::STENCIL8:
|
||||
|
||||
@@ -398,7 +398,6 @@ DECL_DRIVER_API_SYNCHRONOUS_N(void, getPlatformSync, backend::SyncHandle, sh,
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatSupported, backend::TextureFormat, format)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isTextureSwizzleSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatMipmappable, backend::TextureFormat, format)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatFilterable, backend::TextureFormat, format)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isRenderTargetFormatSupported, backend::TextureFormat, format)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchMultiSampleSupported)
|
||||
|
||||
@@ -1259,21 +1259,6 @@ bool MetalDriver::isTextureFormatMipmappable(TextureFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
bool MetalDriver::isTextureFormatFilterable(TextureFormat format) {
|
||||
if (isFp32ColorFormat(format)) {
|
||||
if (@available(macOS 11.0, iOS 14.0, *)) {
|
||||
return mContext->device.supports32BitFloatFiltering;
|
||||
}
|
||||
return mContext->highestSupportedGpuFamily.apple >= 7 ||
|
||||
mContext->highestSupportedGpuFamily.mac >= 1;
|
||||
}
|
||||
if (isUnsignedIntFormat(format) || isSignedIntFormat(format) ||
|
||||
isDepthFormat(format) || isStencilFormat(format)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isRenderTargetFormatSupported(TextureFormat format) {
|
||||
MTLPixelFormat mtlFormat = getMetalFormat(mContext, format);
|
||||
// RGB9E5 isn't supported on Mac as a color render target.
|
||||
@@ -2298,14 +2283,12 @@ void MetalDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t inst
|
||||
// Bind the offset data.
|
||||
if (mContext->dynamicOffsets.isDirty()) {
|
||||
const auto [size, data] = mContext->dynamicOffsets.getOffsets();
|
||||
if (size > 0) {
|
||||
[mContext->currentRenderPassEncoder setFragmentBytes:data
|
||||
length:size * sizeof(uint32_t)
|
||||
atIndex:DYNAMIC_OFFSET_BINDING];
|
||||
[mContext->currentRenderPassEncoder setVertexBytes:data
|
||||
length:size * sizeof(uint32_t)
|
||||
atIndex:DYNAMIC_OFFSET_BINDING];
|
||||
}
|
||||
[mContext->currentRenderPassEncoder setFragmentBytes:data
|
||||
length:size * sizeof(uint32_t)
|
||||
atIndex:DYNAMIC_OFFSET_BINDING];
|
||||
[mContext->currentRenderPassEncoder setVertexBytes:data
|
||||
length:size * sizeof(uint32_t)
|
||||
atIndex:DYNAMIC_OFFSET_BINDING];
|
||||
mContext->dynamicOffsets.setDirty(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -194,10 +194,6 @@ bool NoopDriver::isTextureFormatMipmappable(TextureFormat format) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isTextureFormatFilterable(TextureFormat format) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isRenderTargetFormatSupported(TextureFormat format) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
#include "GLTexture.h"
|
||||
#include "GLUtils.h"
|
||||
#include "OpenGLDriver.h"
|
||||
#include "OpenGLContext.h"
|
||||
#include "OpenGLProgram.h"
|
||||
#include "OpenGLState.h"
|
||||
|
||||
#include "gl_headers.h"
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
GLDescriptorSet::GLDescriptorSet(OpenGLState& gl, DescriptorSetLayoutHandle dslh,
|
||||
GLDescriptorSet::GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle dslh,
|
||||
GLDescriptorSetLayout const* layout)
|
||||
: descriptors(layout->maxDescriptorBinding + 1),
|
||||
dslh(std::move(dslh)) {
|
||||
@@ -142,7 +142,7 @@ GLDescriptorSet::GLDescriptorSet(OpenGLState& gl, DescriptorSetLayoutHandle dslh
|
||||
}
|
||||
}
|
||||
|
||||
void GLDescriptorSet::update(OpenGLState&,
|
||||
void GLDescriptorSet::update(OpenGLContext&,
|
||||
descriptor_binding_t binding, GLBufferObject* bo, size_t offset, size_t size) noexcept {
|
||||
assert_invariant(binding < descriptors.size());
|
||||
std::visit([=](auto&& arg) {
|
||||
@@ -164,7 +164,7 @@ void GLDescriptorSet::update(OpenGLState&,
|
||||
}, descriptors[binding].desc);
|
||||
}
|
||||
|
||||
void GLDescriptorSet::update(OpenGLState& gl, HandleAllocatorGL& handleAllocator,
|
||||
void GLDescriptorSet::update(OpenGLContext& gl, HandleAllocatorGL& handleAllocator,
|
||||
descriptor_binding_t binding, TextureHandle th, SamplerParams params) noexcept {
|
||||
|
||||
GLTexture* t = th ? handleAllocator.handle_cast<GLTexture*>(th) : nullptr;
|
||||
@@ -183,18 +183,9 @@ void GLDescriptorSet::update(OpenGLState& gl, HandleAllocatorGL& handleAllocator
|
||||
params.wrapT = SamplerWrapMode::CLAMP_TO_EDGE;
|
||||
params.wrapR = SamplerWrapMode::CLAMP_TO_EDGE;
|
||||
}
|
||||
|
||||
// GLES3.x specification forbids depth textures to be filtered.
|
||||
bool const isDepthFmt = t && isDepthFormat(t->format);
|
||||
bool const depthTextureNoPcf = isDepthFmt &&
|
||||
params.compareMode == SamplerCompareMode::NONE;
|
||||
|
||||
// GLES3.x may not support filtering of fp32 textures
|
||||
bool const isFp32Fmt = t && isFp32ColorFormat(t->format);
|
||||
bool const fp32TextureNotFilterable = isFp32Fmt &&
|
||||
!gl.context().ext.OES_texture_float_linear;
|
||||
|
||||
if (t && (depthTextureNoPcf || fp32TextureNotFilterable)) {
|
||||
if (t && isDepthFormat(t->format)
|
||||
&& params.compareMode == SamplerCompareMode::NONE) {
|
||||
params.filterMag = SamplerMagFilter::NEAREST;
|
||||
switch (params.filterMin) {
|
||||
case SamplerMinFilter::LINEAR:
|
||||
@@ -232,7 +223,7 @@ void GLDescriptorSet::update(OpenGLState& gl, HandleAllocatorGL& handleAllocator
|
||||
}, descriptors[binding].desc);
|
||||
}
|
||||
|
||||
void GLDescriptorSet::updateTextureView(OpenGLState& gl,
|
||||
void GLDescriptorSet::updateTextureView(OpenGLContext& gl,
|
||||
HandleAllocatorGL& handleAllocator, GLuint unit, GLTexture const* t) noexcept {
|
||||
// The common case is that we don't have a ref handle (we only have one if
|
||||
// the texture ever had a View on it).
|
||||
@@ -269,7 +260,7 @@ void GLDescriptorSet::updateTextureView(OpenGLState& gl,
|
||||
}
|
||||
|
||||
void GLDescriptorSet::bind(
|
||||
OpenGLState& gl,
|
||||
OpenGLContext& gl,
|
||||
HandleAllocatorGL& handleAllocator,
|
||||
OpenGLProgram const& p,
|
||||
descriptor_set_t set, uint32_t const* offsets, bool offsetsOnly) const noexcept {
|
||||
@@ -387,7 +378,7 @@ void GLDescriptorSet::bind(
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER,
|
||||
(GLint)GLUtils::getTextureFilter(params.filterMin));
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER,
|
||||
|
||||
@@ -44,27 +44,27 @@ struct GLTexture;
|
||||
struct GLTextureRef;
|
||||
struct GLDescriptorSetLayout;
|
||||
class OpenGLProgram;
|
||||
class OpenGLContext;
|
||||
class OpenGLDriver;
|
||||
class OpenGLState;
|
||||
|
||||
struct GLDescriptorSet : public HwDescriptorSet {
|
||||
|
||||
using HwDescriptorSet::HwDescriptorSet;
|
||||
|
||||
GLDescriptorSet(OpenGLState& gl, DescriptorSetLayoutHandle dslh,
|
||||
GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle dslh,
|
||||
GLDescriptorSetLayout const* layout);
|
||||
|
||||
// update a buffer descriptor in the set
|
||||
void update(OpenGLState& gl,
|
||||
void update(OpenGLContext& gl,
|
||||
descriptor_binding_t binding, GLBufferObject* bo, size_t offset, size_t size) noexcept;
|
||||
|
||||
// update a sampler descriptor in the set
|
||||
void update(OpenGLState& gl, HandleAllocatorGL& handleAllocator,
|
||||
void update(OpenGLContext& gl, HandleAllocatorGL& handleAllocator,
|
||||
descriptor_binding_t binding, TextureHandle th, SamplerParams params) noexcept;
|
||||
|
||||
// conceptually bind the set to the command buffer
|
||||
void bind(
|
||||
OpenGLState& gl,
|
||||
OpenGLContext& gl,
|
||||
HandleAllocatorGL& handleAllocator,
|
||||
OpenGLProgram const& p,
|
||||
descriptor_set_t set, uint32_t const* offsets, bool offsetsOnly) const noexcept;
|
||||
@@ -138,7 +138,7 @@ private:
|
||||
};
|
||||
static_assert(sizeof(Descriptor) <= 32);
|
||||
|
||||
static void updateTextureView(OpenGLState& gl,
|
||||
static void updateTextureView(OpenGLContext& gl,
|
||||
HandleAllocatorGL& handleAllocator, GLuint unit, GLTexture const* t) noexcept;
|
||||
|
||||
utils::FixedCapacityVector<Descriptor> descriptors; // 16
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
#include "GLBufferObject.h"
|
||||
#include "GLUtils.h"
|
||||
#include "OpenGLContext.h"
|
||||
#include "OpenGLDriver.h"
|
||||
#include "OpenGLState.h"
|
||||
|
||||
#include "gl_headers.h"
|
||||
|
||||
@@ -45,7 +45,7 @@ GLMemoryMappedBuffer::GLMemoryMappedBuffer() = default;
|
||||
|
||||
GLMemoryMappedBuffer::~GLMemoryMappedBuffer() = default;
|
||||
|
||||
GLMemoryMappedBuffer::GLMemoryMappedBuffer(OpenGLState& gls, HandleAllocatorGL& handleAllocator,
|
||||
GLMemoryMappedBuffer::GLMemoryMappedBuffer(OpenGLContext& glc, HandleAllocatorGL& handleAllocator,
|
||||
BufferObjectHandle boh,
|
||||
size_t const offset, size_t const size, MapBufferAccessFlags const access)
|
||||
: boh(boh), access(access) {
|
||||
@@ -60,7 +60,7 @@ GLMemoryMappedBuffer::GLMemoryMappedBuffer(OpenGLState& gls, HandleAllocatorGL&
|
||||
assert_invariant(any(bo->usage & BufferUsage::SHARED_WRITE_BIT));
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(gls.isES2())) {
|
||||
if (UTILS_UNLIKELY(glc.isES2())) {
|
||||
gl.vaddr = static_cast<char*>(bo->gl.buffer) + offset;
|
||||
gl.size = size;
|
||||
gl.offset = offset;
|
||||
@@ -85,7 +85,7 @@ GLMemoryMappedBuffer::GLMemoryMappedBuffer(OpenGLState& gls, HandleAllocatorGL&
|
||||
gl_access |= GL_MAP_INVALIDATE_RANGE_BIT;
|
||||
}
|
||||
|
||||
gls.bindBuffer(bo->gl.binding, bo->gl.id);
|
||||
glc.bindBuffer(bo->gl.binding, bo->gl.id);
|
||||
|
||||
addr = glMapBufferRange(bo->gl.binding, GLsizeiptr(offset), GLsizeiptr(size), gl_access);
|
||||
|
||||
@@ -103,20 +103,20 @@ GLMemoryMappedBuffer::GLMemoryMappedBuffer(OpenGLState& gls, HandleAllocatorGL&
|
||||
#endif
|
||||
}
|
||||
|
||||
void GLMemoryMappedBuffer::unmap(OpenGLState& gls, HandleAllocatorGL& handleAllocator) const {
|
||||
void GLMemoryMappedBuffer::unmap(OpenGLContext& glc, HandleAllocatorGL& handleAllocator) const {
|
||||
GLBufferObject* const bo = handleAllocator.handle_cast<GLBufferObject*>(boh);
|
||||
assert_invariant(bo);
|
||||
assert_invariant(bo->mappingCount > 0);
|
||||
|
||||
bo->mappingCount--;
|
||||
|
||||
if (UTILS_UNLIKELY(gls.isES2())) {
|
||||
if (UTILS_UNLIKELY(glc.isES2())) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
gls.bindBuffer(gl.binding, gl.id);
|
||||
glc.bindBuffer(gl.binding, gl.id);
|
||||
# if !defined(__EMSCRIPTEN__)
|
||||
// don't unmap if we don't have a mapping or it didn't work
|
||||
if (UTILS_LIKELY(gl.vaddr)) {
|
||||
@@ -131,7 +131,7 @@ void GLMemoryMappedBuffer::unmap(OpenGLState& gls, HandleAllocatorGL& handleAllo
|
||||
#endif
|
||||
}
|
||||
|
||||
void GLMemoryMappedBuffer::copy(OpenGLState& gls, OpenGLDriver& gld,
|
||||
void GLMemoryMappedBuffer::copy(OpenGLContext& glc, OpenGLDriver& gld,
|
||||
size_t const offset, BufferDescriptor&& data) const {
|
||||
assert_invariant(any(access & MapBufferAccessFlags::WRITE_BIT));
|
||||
assert_invariant(offset + data.size <= gl.size);
|
||||
@@ -139,11 +139,11 @@ void GLMemoryMappedBuffer::copy(OpenGLState& gls, OpenGLDriver& gld,
|
||||
if (UTILS_LIKELY(gl.vaddr)) {
|
||||
memcpy(static_cast<char *>(gl.vaddr) + offset, data.buffer, data.size);
|
||||
} else {
|
||||
assert_invariant(!gls.isES2());
|
||||
assert_invariant(!glc.isES2());
|
||||
// we couldn't map (WebGL or error), revert to glBufferSubData. In the future we can
|
||||
// improve this by keeping around the BufferDescriptor and coalescing the calls
|
||||
// to glBufferSubData.
|
||||
gls.bindBuffer(gl.binding, gl.id);
|
||||
glc.bindBuffer(gl.binding, gl.id);
|
||||
glBufferSubData(gl.binding, GLintptr(gl.offset + offset), GLsizeiptr(data.size), data.buffer);
|
||||
CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class OpenGLState;
|
||||
class OpenGLContext;
|
||||
class OpenGLDriver;
|
||||
class BufferDescriptor;
|
||||
|
||||
@@ -50,14 +50,14 @@ struct GLMemoryMappedBuffer : public HwMemoryMappedBuffer {
|
||||
|
||||
GLMemoryMappedBuffer();
|
||||
|
||||
GLMemoryMappedBuffer(OpenGLState& gls, HandleAllocatorGL& handleAllocator,
|
||||
GLMemoryMappedBuffer(OpenGLContext& glc, HandleAllocatorGL& handleAllocator,
|
||||
BufferObjectHandle boh, size_t offset, size_t size, MapBufferAccessFlags access);
|
||||
|
||||
~GLMemoryMappedBuffer();
|
||||
|
||||
void unmap(OpenGLState& gls, HandleAllocatorGL& handleAllocator) const;
|
||||
void unmap(OpenGLContext& gl, HandleAllocatorGL& handleAllocator) const;
|
||||
|
||||
void copy(OpenGLState& gls, OpenGLDriver& gld,
|
||||
void copy(OpenGLContext& glc, OpenGLDriver& gld,
|
||||
size_t offset, BufferDescriptor&& data) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
#include "OpenGLContext.h"
|
||||
#include "OpenGLState.h"
|
||||
|
||||
#include "GLUtils.h"
|
||||
#include "OpenGLTimerQuery.h"
|
||||
@@ -30,7 +29,9 @@
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
@@ -67,37 +68,40 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
|
||||
OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
|
||||
Platform::DriverConfig const& driverConfig) noexcept
|
||||
: mPlatform(platform),
|
||||
mSamplerMap(32),
|
||||
mDriverConfig(driverConfig) {
|
||||
|
||||
// These queries work with all GL/GLES versions!
|
||||
vendor = (char const*)glGetString(GL_VENDOR);
|
||||
renderer = (char const*)glGetString(GL_RENDERER);
|
||||
version = (char const*)glGetString(GL_VERSION);
|
||||
shader = (char const*)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
||||
state.vao.p = &mDefaultVAO;
|
||||
|
||||
LOG(INFO) << "[" << vendor << "], [" << renderer << "], "
|
||||
"[" << version << "], [" << shader << "]";
|
||||
// These queries work with all GL/GLES versions!
|
||||
state.vendor = (char const*)glGetString(GL_VENDOR);
|
||||
state.renderer = (char const*)glGetString(GL_RENDERER);
|
||||
state.version = (char const*)glGetString(GL_VERSION);
|
||||
state.shader = (char const*)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
||||
|
||||
LOG(INFO) << "[" << state.vendor << "], [" << state.renderer << "], "
|
||||
"[" << state.version << "], [" << state.shader << "]";
|
||||
|
||||
/*
|
||||
* Figure out GL / GLES version, extensions and capabilities we need to
|
||||
* determine the feature level
|
||||
*/
|
||||
|
||||
queryOpenGLVersion(&major, &minor);
|
||||
queryOpenGLVersion(&state.major, &state.minor);
|
||||
|
||||
#if defined(BACKEND_OPENGL_VERSION_GLES)
|
||||
if (UTILS_UNLIKELY(driverConfig.forceGLES2Context)) {
|
||||
major = 2;
|
||||
minor = 0;
|
||||
state.major = 2;
|
||||
state.minor = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
initExtensions(&ext, major, minor);
|
||||
initExtensions(&ext, state.major, state.minor);
|
||||
|
||||
initProcs(&procs, ext, major, minor);
|
||||
initProcs(&procs, ext, state.major, state.minor);
|
||||
|
||||
initBugs(&bugs, ext, major, minor,
|
||||
vendor, renderer, version, shader);
|
||||
initBugs(&bugs, ext, state.major, state.minor,
|
||||
state.vendor, state.renderer, state.version, state.shader);
|
||||
|
||||
initWorkarounds(bugs, &ext);
|
||||
|
||||
@@ -109,7 +113,7 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
|
||||
glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &gets.max_3d_texture_size);
|
||||
glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &gets.max_array_texture_layers);
|
||||
|
||||
mFeatureLevel = resolveFeatureLevel(major, minor, ext, gets, bugs);
|
||||
mFeatureLevel = resolveFeatureLevel(state.major, state.minor, ext, gets, bugs);
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
mShaderModel = ShaderModel::MOBILE;
|
||||
@@ -197,6 +201,8 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
|
||||
assert_invariant(mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0 || gets.max_draw_buffers >= 4); // minspec
|
||||
#endif
|
||||
|
||||
setDefaultState();
|
||||
|
||||
#ifdef GL_EXT_texture_filter_anisotropic
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1 && ext.EXT_texture_filter_anisotropic) {
|
||||
@@ -257,20 +263,120 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
|
||||
glDebugMessageCallback(cb, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
mTimerQueryFactory = TimerQueryFactory::init(platform, *this);
|
||||
}
|
||||
|
||||
OpenGLContext::~OpenGLContext() noexcept = default;
|
||||
|
||||
OpenGLState* OpenGLContext::createState() noexcept {
|
||||
auto* state = new(std::nothrow) OpenGLState(*this);
|
||||
assert_invariant(state);
|
||||
return state;
|
||||
OpenGLContext::~OpenGLContext() noexcept {
|
||||
// note: this is called from the main thread. Can't do any GL calls.
|
||||
delete mTimerQueryFactory;
|
||||
}
|
||||
|
||||
void OpenGLContext::destroyState(OpenGLState* state) noexcept {
|
||||
if (state) {
|
||||
state->terminate();
|
||||
delete state;
|
||||
void OpenGLContext::terminate() noexcept {
|
||||
// note: this is called from the backend thread
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (!isES2()) {
|
||||
for (auto& item: mSamplerMap) {
|
||||
unbindSampler(item.second);
|
||||
glDeleteSamplers(1, &item.second);
|
||||
}
|
||||
mSamplerMap.clear();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLContext::destroyWithContext(
|
||||
size_t index, std::function<void(OpenGLContext&)> const& closure) {
|
||||
if (index == 0) {
|
||||
// Note: we only need to delay the destruction of objects on the unprotected context
|
||||
// (index 0) because the protected context is always immediately destroyed and all its
|
||||
// active objects and bindings are then automatically destroyed.
|
||||
// TODO: this is only guaranteed for EGLPlatform, but that's the only one we care about.
|
||||
mDestroyWithNormalContext.push_back(closure);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::unbindEverything() noexcept {
|
||||
// TODO: we're supposed to unbind everything here so that resources don't get
|
||||
// stuck in this context (contextIndex) when destroyed in the other context.
|
||||
// However, because EGLPlatform always immediately destroys the protected context (1),
|
||||
// the bindings will automatically be severed when we switch back to the default context.
|
||||
// Since bindings now only exist in one context, we don't have a ref-counting issue to
|
||||
// worry about.
|
||||
}
|
||||
|
||||
void OpenGLContext::synchronizeStateAndCache(size_t index) {
|
||||
|
||||
// if we're just switching back to context 0, run all the pending destructors
|
||||
if (index == 0) {
|
||||
auto list = std::move(mDestroyWithNormalContext);
|
||||
for (auto&& fn: list) {
|
||||
fn(*this);
|
||||
}
|
||||
}
|
||||
|
||||
// the default FBO could be invalid
|
||||
mDefaultFbo[index].reset();
|
||||
|
||||
contextIndex = index;
|
||||
resetState();
|
||||
}
|
||||
|
||||
void OpenGLContext::setDefaultState() noexcept {
|
||||
// We need to make sure our internal state matches the GL state when we start.
|
||||
// (some of these calls may be unneeded as they might be the gl defaults)
|
||||
GLenum const caps[] = {
|
||||
GL_BLEND,
|
||||
GL_CULL_FACE,
|
||||
GL_SCISSOR_TEST,
|
||||
GL_DEPTH_TEST,
|
||||
GL_STENCIL_TEST,
|
||||
GL_DITHER,
|
||||
GL_SAMPLE_ALPHA_TO_COVERAGE,
|
||||
GL_SAMPLE_COVERAGE,
|
||||
GL_POLYGON_OFFSET_FILL,
|
||||
};
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (auto const capi : caps) {
|
||||
size_t const capIndex = getIndexForCap(capi);
|
||||
if (state.enables.caps[capIndex]) {
|
||||
glEnable(capi);
|
||||
} else {
|
||||
glDisable(capi);
|
||||
}
|
||||
}
|
||||
|
||||
// Point sprite size and seamless cubemap filtering are disabled by default in desktop GL.
|
||||
// In OpenGL ES, these flags do not exist because they are always on.
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
glEnable(GL_PROGRAM_POINT_SIZE);
|
||||
enable(GL_PROGRAM_POINT_SIZE);
|
||||
#endif
|
||||
|
||||
#ifdef GL_ARB_seamless_cube_map
|
||||
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
||||
enable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
||||
#endif
|
||||
|
||||
#ifdef GL_FRAGMENT_SHADER_DERIVATIVE_HINT
|
||||
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
|
||||
#endif
|
||||
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
if (ext.EXT_clip_control) {
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL)
|
||||
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
|
||||
#elif defined(GL_EXT_clip_control)
|
||||
glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ext.EXT_clip_cull_distance
|
||||
&& mDriverConfig.stereoscopicType == StereoscopicType::INSTANCED) {
|
||||
glEnable(GL_CLIP_DISTANCE0);
|
||||
glEnable(GL_CLIP_DISTANCE1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -675,8 +781,6 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
|
||||
ext->OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
|
||||
ext->OES_rgb8_rgba8 = exts.has("GL_OES_rgb8_rgba8"sv);
|
||||
ext->OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"sv);
|
||||
ext->OES_texture_float_linear = exts.has("GL_OES_texture_float_linear"sv);
|
||||
ext->OES_texture_half_float_linear = exts.has("GL_OES_texture_half_float_linear"sv);
|
||||
ext->OES_texture_npot = exts.has("GL_OES_texture_npot"sv);
|
||||
ext->OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"sv);
|
||||
ext->OVR_multiview2 = exts.has("GL_OVR_multiview2"sv);
|
||||
@@ -684,15 +788,14 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
|
||||
ext->WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
|
||||
ext->WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
|
||||
|
||||
// ES 3.2 implies EXT_color_buffer_float (but not necessarily filterable)
|
||||
// ES 3.2 implies EXT_color_buffer_float
|
||||
if (major > 3 || (major == 3 && minor >= 2)) {
|
||||
ext->EXT_color_buffer_float = true;
|
||||
}
|
||||
// ES 3.x implies EXT_discard_framebuffer, OES_vertex_array_object and OES_texture_half_float_linear
|
||||
// ES 3.x implies EXT_discard_framebuffer and OES_vertex_array_object
|
||||
if (major >= 3) {
|
||||
ext->EXT_discard_framebuffer = true;
|
||||
ext->OES_vertex_array_object = true;
|
||||
ext->OES_texture_half_float_linear = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,8 +846,6 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor)
|
||||
ext->OES_EGL_image_external_essl3 = false;
|
||||
ext->OES_rgb8_rgba8 = true;
|
||||
ext->OES_standard_derivatives = true;
|
||||
ext->OES_texture_float_linear = true;
|
||||
ext->OES_texture_half_float_linear = true;
|
||||
ext->OES_texture_npot = true;
|
||||
ext->OES_vertex_array_object = true;
|
||||
ext->OVR_multiview2 = exts.has("GL_OVR_multiview2"sv);
|
||||
@@ -769,4 +870,413 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor)
|
||||
|
||||
#endif // BACKEND_OPENGL_VERSION_GL
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
GLuint OpenGLContext::bindFramebuffer(GLenum target, GLuint buffer) noexcept {
|
||||
if (UTILS_UNLIKELY(buffer == 0)) {
|
||||
// we're binding the default frame buffer, resolve its actual name
|
||||
auto& defaultFboForThisContext = mDefaultFbo[contextIndex];
|
||||
|
||||
if (UTILS_UNLIKELY(!defaultFboForThisContext.has_value())) {
|
||||
defaultFboForThisContext = GLuint(mPlatform.getDefaultFramebufferObject());
|
||||
}
|
||||
// by construction, defaultFboForThisContext has a value. value_or() avoids a throwing call.
|
||||
buffer = defaultFboForThisContext.value_or(0);
|
||||
}
|
||||
bindFramebufferResolved(target, buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void OpenGLContext::unbindFramebuffer(GLenum target) noexcept {
|
||||
bindFramebufferResolved(target, 0);
|
||||
}
|
||||
|
||||
void OpenGLContext::bindFramebufferResolved(GLenum target, GLuint buffer) noexcept {
|
||||
switch (target) {
|
||||
case GL_FRAMEBUFFER:
|
||||
if (state.draw_fbo != buffer || state.read_fbo != buffer) {
|
||||
state.draw_fbo = state.read_fbo = buffer;
|
||||
glBindFramebuffer(target, buffer);
|
||||
}
|
||||
break;
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
case GL_DRAW_FRAMEBUFFER:
|
||||
if (state.draw_fbo != buffer) {
|
||||
state.draw_fbo = buffer;
|
||||
glBindFramebuffer(target, buffer);
|
||||
}
|
||||
break;
|
||||
case GL_READ_FRAMEBUFFER:
|
||||
if (state.read_fbo != buffer) {
|
||||
state.read_fbo = buffer;
|
||||
glBindFramebuffer(target, buffer);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::bindBuffer(GLenum target, GLuint buffer) noexcept {
|
||||
if (target == GL_ELEMENT_ARRAY_BUFFER) {
|
||||
constexpr size_t targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
|
||||
// GL_ELEMENT_ARRAY_BUFFER is a special case, where the currently bound VAO remembers
|
||||
// the index buffer, unless there are no VAO bound (see: bindVertexArray)
|
||||
assert_invariant(state.vao.p);
|
||||
if (state.buffers.genericBinding[targetIndex] != buffer
|
||||
|| ((state.vao.p != &mDefaultVAO) && (state.vao.p->elementArray != buffer))) {
|
||||
state.buffers.genericBinding[targetIndex] = buffer;
|
||||
if (state.vao.p != &mDefaultVAO) {
|
||||
state.vao.p->elementArray = buffer;
|
||||
}
|
||||
glBindBuffer(target, buffer);
|
||||
}
|
||||
} else {
|
||||
size_t const targetIndex = getIndexForBufferTarget(target);
|
||||
update_state(state.buffers.genericBinding[targetIndex], buffer, [&]() {
|
||||
glBindBuffer(target, buffer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::pixelStore(GLenum pname, GLint param) noexcept {
|
||||
GLint* pcur;
|
||||
|
||||
// Note: GL_UNPACK_SKIP_PIXELS, GL_UNPACK_SKIP_ROWS and
|
||||
// GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_ROWS
|
||||
// are actually provided as conveniences to the programmer; they provide no functionality
|
||||
// that cannot be duplicated at the call site (e.g. glTexImage2D or glReadPixels)
|
||||
|
||||
switch (pname) {
|
||||
case GL_PACK_ALIGNMENT:
|
||||
pcur = &state.pack.alignment;
|
||||
break;
|
||||
case GL_UNPACK_ALIGNMENT:
|
||||
pcur = &state.unpack.alignment;
|
||||
break;
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
case GL_UNPACK_ROW_LENGTH:
|
||||
assert_invariant(state.major > 2);
|
||||
pcur = &state.unpack.row_length;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
goto default_case;
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(*pcur != param)) {
|
||||
*pcur = param;
|
||||
default_case:
|
||||
glPixelStorei(pname, param);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::unbindTexture(
|
||||
UTILS_UNUSED_IN_RELEASE GLenum target, GLuint texture_id) noexcept {
|
||||
// unbind this texture from all the units it might be bound to
|
||||
// no need unbind the texture from FBOs because we're not tracking that state (and there is
|
||||
// no need to).
|
||||
// Never attempt to unbind texture 0. This could happen with external textures w/ streaming if
|
||||
// never populated.
|
||||
if (texture_id) {
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
|
||||
if (state.textures.units[unit].id == texture_id) {
|
||||
// if this texture is bound, it should be at the same target
|
||||
assert_invariant(state.textures.units[unit].target == target);
|
||||
unbindTextureUnit(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::unbindTextureUnit(GLuint unit) noexcept {
|
||||
update_state(state.textures.units[unit].id, 0u, [&]() {
|
||||
activeTexture(unit);
|
||||
glBindTexture(state.textures.units[unit].target, 0u);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::unbindSampler(GLuint sampler) noexcept {
|
||||
// unbind this sampler from all the units it might be bound to
|
||||
UTILS_NOUNROLL // clang generates >800B of code!!!
|
||||
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
|
||||
if (state.textures.units[unit].sampler == sampler) {
|
||||
bindSampler(unit, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::deleteBuffer(GLuint buffer, GLenum target) noexcept {
|
||||
glDeleteBuffers(1, &buffer);
|
||||
|
||||
// bindings of bound buffers are reset to 0
|
||||
size_t const targetIndex = getIndexForBufferTarget(target);
|
||||
auto& genericBinding = state.buffers.genericBinding[targetIndex];
|
||||
if (genericBinding == buffer) {
|
||||
genericBinding = 0;
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(bugs.rebind_buffer_after_deletion)) {
|
||||
if (genericBinding) {
|
||||
glBindBuffer(target, genericBinding);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1 ||
|
||||
(target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER));
|
||||
|
||||
if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) {
|
||||
auto& indexedBinding = state.buffers.targets[targetIndex];
|
||||
UTILS_NOUNROLL
|
||||
for (auto& entry: indexedBinding.buffers) {
|
||||
if (entry.name == buffer) {
|
||||
entry.name = 0;
|
||||
entry.offset = 0;
|
||||
entry.size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLContext::deleteVertexArray(GLuint vao) noexcept {
|
||||
if (UTILS_LIKELY(vao)) {
|
||||
procs.deleteVertexArrays(1, &vao);
|
||||
// if the destroyed VAO is bound, clear the binding.
|
||||
if (state.vao.p->vao[contextIndex] == vao) {
|
||||
bindVertexArray(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLuint OpenGLContext::getSamplerSlow(SamplerParams params) const noexcept {
|
||||
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
|
||||
|
||||
using namespace GLUtils;
|
||||
|
||||
GLuint s;
|
||||
glGenSamplers(1, &s);
|
||||
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, (GLint)getTextureFilter(params.filterMin));
|
||||
glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, (GLint)getTextureFilter(params.filterMag));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_S, (GLint)getWrapMode(params.wrapS));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, (GLint)getWrapMode(params.wrapT));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, (GLint)getWrapMode(params.wrapR));
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, (GLint)getTextureCompareMode(params.compareMode));
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, (GLint)getTextureCompareFunc(params.compareFunc));
|
||||
|
||||
#if defined(GL_EXT_texture_filter_anisotropic)
|
||||
if (ext.EXT_texture_filter_anisotropic &&
|
||||
!bugs.texture_filter_anisotropic_broken_on_sampler) {
|
||||
GLfloat const anisotropy = float(1u << params.anisotropyLog2);
|
||||
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT,
|
||||
std::min(gets.max_anisotropy, anisotropy));
|
||||
}
|
||||
#endif
|
||||
CHECK_GL_ERROR()
|
||||
mSamplerMap[params] = s;
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void OpenGLContext::resetState() noexcept {
|
||||
// Force GL state to match the Filament state
|
||||
|
||||
// increase the state version so other parts of the state know to reset
|
||||
state.age++;
|
||||
|
||||
if (state.major > 2) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, state.draw_fbo);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, state.read_fbo);
|
||||
#endif
|
||||
} else {
|
||||
assert_invariant(state.read_fbo == state.draw_fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, state.draw_fbo);
|
||||
state.read_fbo = state.draw_fbo;
|
||||
}
|
||||
|
||||
|
||||
// state.program
|
||||
glUseProgram(state.program.use);
|
||||
|
||||
// state.vao
|
||||
state.vao.p = nullptr;
|
||||
bindVertexArray(nullptr);
|
||||
|
||||
// state.raster
|
||||
glFrontFace(state.raster.frontFace);
|
||||
glCullFace(state.raster.cullFace);
|
||||
glBlendEquationSeparate(state.raster.blendEquationRGB, state.raster.blendEquationA);
|
||||
glBlendFuncSeparate(
|
||||
state.raster.blendFunctionSrcRGB,
|
||||
state.raster.blendFunctionDstRGB,
|
||||
state.raster.blendFunctionSrcA,
|
||||
state.raster.blendFunctionDstA
|
||||
);
|
||||
glColorMask(
|
||||
state.raster.colorMask,
|
||||
state.raster.colorMask,
|
||||
state.raster.colorMask,
|
||||
state.raster.colorMask
|
||||
);
|
||||
glDepthMask(state.raster.depthMask);
|
||||
glDepthFunc(state.raster.depthFunc);
|
||||
|
||||
// state.stencil
|
||||
glStencilFuncSeparate(
|
||||
GL_FRONT,
|
||||
state.stencil.front.func.func,
|
||||
state.stencil.front.func.ref,
|
||||
state.stencil.front.func.mask
|
||||
);
|
||||
glStencilFuncSeparate(
|
||||
GL_BACK,
|
||||
state.stencil.back.func.func,
|
||||
state.stencil.back.func.ref,
|
||||
state.stencil.back.func.mask
|
||||
);
|
||||
glStencilOpSeparate(
|
||||
GL_FRONT,
|
||||
state.stencil.front.op.sfail,
|
||||
state.stencil.front.op.dpfail,
|
||||
state.stencil.front.op.dppass
|
||||
);
|
||||
glStencilOpSeparate(
|
||||
GL_BACK,
|
||||
state.stencil.back.op.sfail,
|
||||
state.stencil.back.op.dpfail,
|
||||
state.stencil.back.op.dppass
|
||||
);
|
||||
glStencilMaskSeparate(GL_FRONT, state.stencil.front.stencilMask);
|
||||
glStencilMaskSeparate(GL_BACK, state.stencil.back.stencilMask);
|
||||
|
||||
// state.polygonOffset
|
||||
glPolygonOffset(state.polygonOffset.factor, state.polygonOffset.units);
|
||||
|
||||
// state.enables
|
||||
setDefaultState();
|
||||
|
||||
// state.buffers
|
||||
// Reset state.buffers to its default state to avoid the complexity and error-prone
|
||||
// nature of resetting the GL state to its existing state
|
||||
state.buffers = {};
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
if (state.major > 2) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
for (auto const target: {
|
||||
GL_UNIFORM_BUFFER,
|
||||
GL_TRANSFORM_FEEDBACK_BUFFER,
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
GL_SHADER_STORAGE_BUFFER,
|
||||
#endif
|
||||
GL_PIXEL_PACK_BUFFER,
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
}) {
|
||||
glBindBuffer(target, 0);
|
||||
}
|
||||
|
||||
for (size_t bufferIndex = 0; bufferIndex < MAX_BUFFER_BINDINGS; ++bufferIndex) {
|
||||
if (bufferIndex < (size_t)gets.max_uniform_buffer_bindings) {
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, bufferIndex, 0);
|
||||
}
|
||||
|
||||
if (bufferIndex < (size_t)gets.max_transform_feedback_separate_attribs) {
|
||||
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, bufferIndex, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// state.textures
|
||||
// Reset state.textures to its default state to avoid the complexity and error-prone
|
||||
// nature of resetting the GL state to its existing state
|
||||
state.textures = {};
|
||||
const std::pair<GLuint, bool> textureTargets[] = {
|
||||
{ GL_TEXTURE_2D, true },
|
||||
{ GL_TEXTURE_2D_ARRAY, true },
|
||||
{ GL_TEXTURE_CUBE_MAP, true },
|
||||
{ GL_TEXTURE_3D, true },
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
{ GL_TEXTURE_2D_MULTISAMPLE, true },
|
||||
#endif
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
#if defined(GL_OES_EGL_image_external)
|
||||
{ GL_TEXTURE_EXTERNAL_OES, ext.OES_EGL_image_external_essl3 },
|
||||
#endif
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_texture_cube_map_array)
|
||||
{ GL_TEXTURE_CUBE_MAP_ARRAY, ext.EXT_texture_cube_map_array },
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
for (GLint unit = 0; unit < gets.max_combined_texture_image_units; ++unit) {
|
||||
glActiveTexture(GL_TEXTURE0 + unit);
|
||||
if (state.major > 2) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glBindSampler(unit, 0);
|
||||
#endif
|
||||
}
|
||||
for (auto [target, available] : textureTargets) {
|
||||
if (available) {
|
||||
glBindTexture(target, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
glActiveTexture(GL_TEXTURE0 + state.textures.active);
|
||||
|
||||
// state.unpack
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, state.unpack.alignment);
|
||||
if (state.major > 2) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, state.unpack.row_length);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// state.pack
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, state.pack.alignment);
|
||||
if (state.major > 2) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0); // we rely on GL_PACK_ROW_LENGTH being zero
|
||||
#endif
|
||||
}
|
||||
|
||||
// state.window
|
||||
glScissor(
|
||||
state.window.scissor.x,
|
||||
state.window.scissor.y,
|
||||
state.window.scissor.z,
|
||||
state.window.scissor.w
|
||||
);
|
||||
glViewport(
|
||||
state.window.viewport.x,
|
||||
state.window.viewport.y,
|
||||
state.window.viewport.z,
|
||||
state.window.viewport.w
|
||||
);
|
||||
glDepthRangef(state.window.depthRange.x, state.window.depthRange.y);
|
||||
}
|
||||
|
||||
void OpenGLContext::createTimerQuery(GLTimerQuery* query) {
|
||||
mTimerQueryFactory->createTimerQuery(query);
|
||||
}
|
||||
|
||||
void OpenGLContext::destroyTimerQuery(GLTimerQuery* query) {
|
||||
mTimerQueryFactory->destroyTimerQuery(query);
|
||||
}
|
||||
|
||||
void OpenGLContext::beginTimeElapsedQuery(GLTimerQuery* query) {
|
||||
mTimerQueryFactory->beginTimeElapsedQuery(query);
|
||||
}
|
||||
|
||||
void OpenGLContext::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) {
|
||||
mTimerQueryFactory->endTimeElapsedQuery(driver, query);
|
||||
}
|
||||
|
||||
} // namesapce filament
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H
|
||||
|
||||
|
||||
#include "OpenGLTimerQuery.h"
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
@@ -31,8 +34,13 @@
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -40,9 +48,8 @@
|
||||
namespace filament::backend {
|
||||
|
||||
class OpenGLPlatform;
|
||||
class OpenGLState;
|
||||
|
||||
class OpenGLContext final {
|
||||
class OpenGLContext final : public TimerQueryFactoryInterface {
|
||||
public:
|
||||
static constexpr const size_t MAX_TEXTURE_UNIT_COUNT = MAX_SAMPLER_COUNT;
|
||||
static constexpr const size_t DUMMY_TEXTURE_BINDING = 7; // highest binding guaranteed to work with ES2
|
||||
@@ -70,12 +77,12 @@ public:
|
||||
// be updated (see OpenGLDriver::updateVertexArrayObject())
|
||||
uint8_t vertexBufferVersion = 0; // 1
|
||||
|
||||
// if this differs from OpenGLState::state.age, this VAO needs to
|
||||
// if this differs from OpenGLContext::state.age, this VAO needs to
|
||||
// be updated (see OpenGLDriver::updateVertexArrayObject())
|
||||
uint8_t stateVersion = 0; // 1
|
||||
|
||||
// If this differs from OpenGLState::state.age, this VAO's name needs to be updated.
|
||||
// See OpenGLState::bindVertexArray()
|
||||
// If this differs from OpenGLContext::state.age, this VAO's name needs to be updated.
|
||||
// See OpenGLContext::bindVertexArray()
|
||||
uint8_t nameVersion = 0; // 1
|
||||
|
||||
// Size in bytes of indices in the index buffer (1 or 2)
|
||||
@@ -91,18 +98,24 @@ public:
|
||||
explicit OpenGLContext(OpenGLPlatform& platform,
|
||||
Platform::DriverConfig const& driverConfig) noexcept;
|
||||
|
||||
~OpenGLContext() noexcept;
|
||||
~OpenGLContext() noexcept final;
|
||||
|
||||
// State lifecycle - allocate/deallocate per-thread OpenGLState instances
|
||||
OpenGLState* createState() noexcept;
|
||||
void destroyState(OpenGLState* state) noexcept;
|
||||
void terminate() noexcept;
|
||||
|
||||
// TimerQueryInterface ------------------------------------------------------------------------
|
||||
|
||||
// note: OpenGLContext being final ensures (clang) these are not called through the vtable
|
||||
void createTimerQuery(GLTimerQuery* query) override;
|
||||
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
template<int MAJOR, int MINOR>
|
||||
inline bool isAtLeastGL() const noexcept {
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
return major > MAJOR || (major == MAJOR && minor >= MINOR);
|
||||
return state.major > MAJOR || (state.major == MAJOR && state.minor >= MINOR);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@@ -111,7 +124,7 @@ public:
|
||||
template<int MAJOR, int MINOR>
|
||||
inline bool isAtLeastGLES() const noexcept {
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
return major > MAJOR || (major == MAJOR && minor >= MINOR);
|
||||
return state.major > MAJOR || (state.major == MAJOR && state.minor >= MINOR);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@@ -141,13 +154,60 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr inline size_t getIndexForCap(GLenum cap) noexcept;
|
||||
constexpr static inline size_t getIndexForBufferTarget(GLenum target) noexcept;
|
||||
|
||||
ShaderModel getShaderModel() const noexcept { return mShaderModel; }
|
||||
FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; }
|
||||
|
||||
OpenGLPlatform& getPlatform() const noexcept { return mPlatform; }
|
||||
Platform::DriverConfig const& getDriverConfig() const noexcept { return mDriverConfig; }
|
||||
void resetState() noexcept;
|
||||
|
||||
bool isGpuTimeSupported() const noexcept { return mGpuTimeSupported; }
|
||||
|
||||
inline void useProgram(GLuint program) noexcept;
|
||||
|
||||
void pixelStore(GLenum, GLint) noexcept;
|
||||
inline void activeTexture(GLuint unit) noexcept;
|
||||
inline void bindTexture(GLuint unit, GLuint target, GLuint texId, bool external) noexcept;
|
||||
|
||||
void unbindTexture(GLenum target, GLuint id) noexcept;
|
||||
void unbindTextureUnit(GLuint unit) noexcept;
|
||||
inline void bindVertexArray(RenderPrimitive const* p) noexcept;
|
||||
inline void bindSampler(GLuint unit, GLuint sampler) noexcept;
|
||||
void unbindSampler(GLuint sampler) noexcept;
|
||||
|
||||
void bindBuffer(GLenum target, GLuint buffer) noexcept;
|
||||
inline void bindBufferRange(GLenum target, GLuint index, GLuint buffer,
|
||||
GLintptr offset, GLsizeiptr size) noexcept;
|
||||
|
||||
GLuint bindFramebuffer(GLenum target, GLuint buffer) noexcept;
|
||||
void unbindFramebuffer(GLenum target) noexcept;
|
||||
|
||||
inline void enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept;
|
||||
inline void disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept;
|
||||
inline void enable(GLenum cap) noexcept;
|
||||
inline void disable(GLenum cap) noexcept;
|
||||
inline void frontFace(GLenum mode) noexcept;
|
||||
inline void cullFace(GLenum mode) noexcept;
|
||||
inline void blendEquation(GLenum modeRGB, GLenum modeA) noexcept;
|
||||
inline void blendFunction(GLenum srcRGB, GLenum srcA, GLenum dstRGB, GLenum dstA) noexcept;
|
||||
inline void colorMask(GLboolean flag) noexcept;
|
||||
inline void depthMask(GLboolean flag) noexcept;
|
||||
inline void depthFunc(GLenum func) noexcept;
|
||||
inline void stencilFuncSeparate(GLenum funcFront, GLint refFront, GLuint maskFront,
|
||||
GLenum funcBack, GLint refBack, GLuint maskBack) noexcept;
|
||||
inline void stencilOpSeparate(GLenum sfailFront, GLenum dpfailFront, GLenum dppassFront,
|
||||
GLenum sfailBack, GLenum dpfailBack, GLenum dppassBack) noexcept;
|
||||
inline void stencilMaskSeparate(GLuint maskFront, GLuint maskBack) noexcept;
|
||||
inline void polygonOffset(GLfloat factor, GLfloat units) noexcept;
|
||||
|
||||
inline void setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept;
|
||||
inline void viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept;
|
||||
inline void depthRange(GLclampf near, GLclampf far) noexcept;
|
||||
|
||||
void deleteBuffer(GLuint buffer, GLenum target) noexcept;
|
||||
void deleteVertexArray(GLuint vao) noexcept;
|
||||
|
||||
void destroyWithContext(size_t index, std::function<void(OpenGLContext&)> const& closure);
|
||||
|
||||
// glGet*() values
|
||||
struct Gets {
|
||||
@@ -208,8 +268,6 @@ public:
|
||||
bool OES_packed_depth_stencil;
|
||||
bool OES_rgb8_rgba8;
|
||||
bool OES_standard_derivatives;
|
||||
bool OES_texture_float_linear;
|
||||
bool OES_texture_half_float_linear;
|
||||
bool OES_texture_npot;
|
||||
bool OES_vertex_array_object;
|
||||
bool OVR_multiview2;
|
||||
@@ -299,6 +357,143 @@ public:
|
||||
|
||||
} bugs = {};
|
||||
|
||||
// state getters -- as needed.
|
||||
vec4gli const& getViewport() const { return state.window.viewport; }
|
||||
|
||||
// function to handle state changes we don't control
|
||||
void updateTexImage(GLenum target, GLuint id) noexcept {
|
||||
assert_invariant(target == GL_TEXTURE_EXTERNAL_OES);
|
||||
// if another target is bound to this texture unit, unbind that texture
|
||||
if (UTILS_UNLIKELY(state.textures.units[state.textures.active].target != target)) {
|
||||
glBindTexture(state.textures.units[state.textures.active].target, 0);
|
||||
state.textures.units[state.textures.active].target = GL_TEXTURE_EXTERNAL_OES;
|
||||
}
|
||||
// the texture is already bound to `target`, we just update our internal state
|
||||
state.textures.units[state.textures.active].id = id;
|
||||
}
|
||||
void resetProgram() noexcept { state.program.use = 0; }
|
||||
|
||||
FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; }
|
||||
|
||||
// This is the index of the context in use. Must be either 0 (Unprotected) or 1 (Protected).
|
||||
// This is used to manage the OpenGL name of ContainerObjects within each context.
|
||||
uint32_t contextIndex = 0;
|
||||
|
||||
// Try to keep the State structure sorted by data-access patterns
|
||||
struct State {
|
||||
State() noexcept = default;
|
||||
// make sure we don't copy this state by accident
|
||||
State(State const& rhs) = delete;
|
||||
State(State&& rhs) noexcept = delete;
|
||||
State& operator=(State const& rhs) = delete;
|
||||
State& operator=(State&& rhs) noexcept = delete;
|
||||
|
||||
GLint major = 0;
|
||||
GLint minor = 0;
|
||||
|
||||
char const* vendor = nullptr;
|
||||
char const* renderer = nullptr;
|
||||
char const* version = nullptr;
|
||||
char const* shader = nullptr;
|
||||
|
||||
GLuint draw_fbo = 0;
|
||||
GLuint read_fbo = 0;
|
||||
|
||||
struct {
|
||||
GLuint use = 0;
|
||||
} program;
|
||||
|
||||
struct {
|
||||
RenderPrimitive* p = nullptr;
|
||||
} vao;
|
||||
|
||||
struct {
|
||||
GLenum frontFace = GL_CCW;
|
||||
GLenum cullFace = GL_BACK;
|
||||
GLenum blendEquationRGB = GL_FUNC_ADD;
|
||||
GLenum blendEquationA = GL_FUNC_ADD;
|
||||
GLenum blendFunctionSrcRGB = GL_ONE;
|
||||
GLenum blendFunctionSrcA = GL_ONE;
|
||||
GLenum blendFunctionDstRGB = GL_ZERO;
|
||||
GLenum blendFunctionDstA = GL_ZERO;
|
||||
GLboolean colorMask = GL_TRUE;
|
||||
GLboolean depthMask = GL_TRUE;
|
||||
GLenum depthFunc = GL_LESS;
|
||||
} raster;
|
||||
|
||||
struct {
|
||||
struct StencilFunc {
|
||||
GLenum func = GL_ALWAYS;
|
||||
GLint ref = 0;
|
||||
GLuint mask = ~GLuint(0);
|
||||
bool operator != (StencilFunc const& rhs) const noexcept {
|
||||
return func != rhs.func || ref != rhs.ref || mask != rhs.mask;
|
||||
}
|
||||
};
|
||||
struct StencilOp {
|
||||
GLenum sfail = GL_KEEP;
|
||||
GLenum dpfail = GL_KEEP;
|
||||
GLenum dppass = GL_KEEP;
|
||||
bool operator != (StencilOp const& rhs) const noexcept {
|
||||
return sfail != rhs.sfail || dpfail != rhs.dpfail || dppass != rhs.dppass;
|
||||
}
|
||||
};
|
||||
struct {
|
||||
StencilFunc func;
|
||||
StencilOp op;
|
||||
GLuint stencilMask = ~GLuint(0);
|
||||
} front, back;
|
||||
} stencil;
|
||||
|
||||
struct PolygonOffset {
|
||||
GLfloat factor = 0;
|
||||
GLfloat units = 0;
|
||||
bool operator != (PolygonOffset const& rhs) const noexcept {
|
||||
return factor != rhs.factor || units != rhs.units;
|
||||
}
|
||||
} polygonOffset;
|
||||
|
||||
struct {
|
||||
utils::bitset32 caps;
|
||||
} enables;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
struct {
|
||||
GLuint name = 0;
|
||||
GLintptr offset = 0;
|
||||
GLsizeiptr size = 0;
|
||||
} buffers[MAX_BUFFER_BINDINGS];
|
||||
} targets[3]; // there are only 3 indexed buffer targets
|
||||
GLuint genericBinding[7] = {};
|
||||
} buffers;
|
||||
|
||||
struct {
|
||||
GLuint active = 0; // zero-based
|
||||
struct {
|
||||
GLuint sampler = 0;
|
||||
GLuint target = 0;
|
||||
GLuint id = 0;
|
||||
} units[MAX_TEXTURE_UNIT_COUNT];
|
||||
} textures;
|
||||
|
||||
struct {
|
||||
GLint row_length = 0;
|
||||
GLint alignment = 4;
|
||||
} unpack;
|
||||
|
||||
struct {
|
||||
GLint alignment = 4;
|
||||
} pack;
|
||||
|
||||
struct {
|
||||
vec4gli scissor { 0 };
|
||||
vec4gli viewport { 0 };
|
||||
vec2glf depthRange { 0.0f, 1.0f };
|
||||
} window;
|
||||
uint8_t age = 0;
|
||||
} state;
|
||||
|
||||
struct Procs {
|
||||
void (* bindVertexArray)(GLuint array);
|
||||
void (* deleteVertexArrays)(GLsizei n, const GLuint* arrays);
|
||||
@@ -316,25 +511,43 @@ public:
|
||||
void (* maxShaderCompilerThreadsKHR)(GLuint count);
|
||||
} procs{};
|
||||
|
||||
// GL version info — immutable after construction
|
||||
GLint major = 0;
|
||||
GLint minor = 0;
|
||||
void unbindEverything() noexcept;
|
||||
void synchronizeStateAndCache(size_t index);
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLuint getSamplerSlow(SamplerParams sp) const noexcept;
|
||||
|
||||
inline GLuint getSampler(SamplerParams sp) const noexcept {
|
||||
assert_invariant(!sp.padding0);
|
||||
assert_invariant(!sp.padding1);
|
||||
assert_invariant(!sp.padding2);
|
||||
auto& samplerMap = mSamplerMap;
|
||||
auto pos = samplerMap.find(sp);
|
||||
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
|
||||
return getSamplerSlow(sp);
|
||||
}
|
||||
return pos->second;
|
||||
}
|
||||
#endif
|
||||
|
||||
// GL info strings — immutable after construction
|
||||
char const* vendor = nullptr;
|
||||
char const* renderer = nullptr;
|
||||
char const* version = nullptr;
|
||||
char const* shader = nullptr;
|
||||
|
||||
private:
|
||||
OpenGLPlatform& mPlatform;
|
||||
ShaderModel mShaderModel = ShaderModel::MOBILE;
|
||||
FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1;
|
||||
TimerQueryFactoryInterface* mTimerQueryFactory = nullptr;
|
||||
std::vector<std::function<void(OpenGLContext&)>> mDestroyWithNormalContext;
|
||||
RenderPrimitive mDefaultVAO;
|
||||
std::optional<GLuint> mDefaultFbo[2]; // 0:Unprotected, 1:Protected
|
||||
mutable tsl::robin_map<SamplerParams, GLuint,
|
||||
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
|
||||
|
||||
Platform::DriverConfig const mDriverConfig;
|
||||
|
||||
bool mGpuTimeSupported = false;
|
||||
|
||||
void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept;
|
||||
|
||||
const std::array<std::tuple<bool const&, char const*, char const*>, sizeof(bugs)> mBugDatabase{{
|
||||
{ bugs.disable_glFlush,
|
||||
"disable_glFlush",
|
||||
@@ -426,8 +639,315 @@ private:
|
||||
Extensions const& exts,
|
||||
Gets const& gets,
|
||||
Bugs const& bugs) noexcept;
|
||||
|
||||
template <typename T, typename F>
|
||||
static inline void update_state(T& state, T const& expected, F functor, bool force = false) noexcept {
|
||||
if (UTILS_UNLIKELY(force || state != expected)) {
|
||||
state = expected;
|
||||
functor();
|
||||
}
|
||||
}
|
||||
|
||||
void setDefaultState() noexcept;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
|
||||
size_t index = 0;
|
||||
switch (cap) {
|
||||
case GL_BLEND: index = 0; break;
|
||||
case GL_CULL_FACE: index = 1; break;
|
||||
case GL_SCISSOR_TEST: index = 2; break;
|
||||
case GL_DEPTH_TEST: index = 3; break;
|
||||
case GL_STENCIL_TEST: index = 4; break;
|
||||
case GL_DITHER: index = 5; break;
|
||||
case GL_SAMPLE_ALPHA_TO_COVERAGE: index = 6; break;
|
||||
case GL_SAMPLE_COVERAGE: index = 7; break;
|
||||
case GL_POLYGON_OFFSET_FILL: index = 8; break;
|
||||
#ifdef GL_ARB_seamless_cube_map
|
||||
case GL_TEXTURE_CUBE_MAP_SEAMLESS: index = 9; break;
|
||||
#endif
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
case GL_PROGRAM_POINT_SIZE: index = 10; break;
|
||||
#endif
|
||||
case GL_DEPTH_CLAMP: index = 11; break;
|
||||
default: break;
|
||||
}
|
||||
assert_invariant(index < state.enables.caps.size());
|
||||
return index;
|
||||
}
|
||||
|
||||
constexpr size_t OpenGLContext::getIndexForBufferTarget(GLenum target) noexcept {
|
||||
size_t index = 0;
|
||||
switch (target) {
|
||||
// The indexed buffers MUST be first in this list (those usable with bindBufferRange)
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
case GL_UNIFORM_BUFFER: index = 0; break;
|
||||
case GL_TRANSFORM_FEEDBACK_BUFFER: index = 1; break;
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
case GL_SHADER_STORAGE_BUFFER: index = 2; break;
|
||||
#endif
|
||||
#endif
|
||||
case GL_ARRAY_BUFFER: index = 3; break;
|
||||
case GL_ELEMENT_ARRAY_BUFFER: index = 4; break;
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
case GL_PIXEL_PACK_BUFFER: index = 5; break;
|
||||
case GL_PIXEL_UNPACK_BUFFER: index = 6; break;
|
||||
#endif
|
||||
default: break;
|
||||
}
|
||||
assert_invariant(index < sizeof(state.buffers.genericBinding)/sizeof(state.buffers.genericBinding[0])); // NOLINT(misc-redundant-expression)
|
||||
return index;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void OpenGLContext::activeTexture(GLuint unit) noexcept {
|
||||
assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT);
|
||||
update_state(state.textures.active, unit, [&]() {
|
||||
glActiveTexture(GL_TEXTURE0 + unit);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::bindSampler(GLuint unit, GLuint sampler) noexcept {
|
||||
assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT);
|
||||
assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1);
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
update_state(state.textures.units[unit].sampler, sampler, [&]() {
|
||||
glBindSampler(unit, sampler);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLContext::setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept {
|
||||
vec4gli const scissor(left, bottom, width, height);
|
||||
update_state(state.window.scissor, scissor, [&]() {
|
||||
glScissor(left, bottom, width, height);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept {
|
||||
vec4gli const viewport(left, bottom, width, height);
|
||||
update_state(state.window.viewport, viewport, [&]() {
|
||||
glViewport(left, bottom, width, height);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::depthRange(GLclampf near, GLclampf far) noexcept {
|
||||
vec2glf const depthRange(near, far);
|
||||
update_state(state.window.depthRange, depthRange, [&]() {
|
||||
glDepthRangef(near, far);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
|
||||
RenderPrimitive* vao = p ? const_cast<RenderPrimitive *>(p) : &mDefaultVAO;
|
||||
update_state(state.vao.p, vao, [&]() {
|
||||
|
||||
// See if we need to create a name for this VAO on the fly, this would happen if:
|
||||
// - we're not the default VAO, because its name is always 0
|
||||
// - our name is 0, this could happen if this VAO was created in the "other" context
|
||||
// - the nameVersion is out of date *and* we're on the protected context, in this case:
|
||||
// - the name must be stale from a previous use of this context because we always
|
||||
// destroy the protected context when we're done with it.
|
||||
bool const recreateVaoName = vao != &mDefaultVAO &&
|
||||
((vao->vao[contextIndex] == 0) ||
|
||||
(vao->nameVersion != state.age && contextIndex == 1));
|
||||
if (UTILS_UNLIKELY(recreateVaoName)) {
|
||||
vao->nameVersion = state.age;
|
||||
procs.genVertexArrays(1, &vao->vao[contextIndex]);
|
||||
}
|
||||
|
||||
procs.bindVertexArray(vao->vao[contextIndex]);
|
||||
// update GL_ELEMENT_ARRAY_BUFFER, which is updated by glBindVertexArray
|
||||
size_t const targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
|
||||
state.buffers.genericBinding[targetIndex] = vao->elementArray;
|
||||
if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding || recreateVaoName)) {
|
||||
// This shouldn't be needed, but it looks like some drivers don't do the implicit
|
||||
// glBindBuffer().
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->elementArray);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
|
||||
GLintptr offset, GLsizeiptr size) noexcept {
|
||||
assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1);
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
# ifdef BACKEND_OPENGL_LEVEL_GLES31
|
||||
assert_invariant(false
|
||||
|| target == GL_UNIFORM_BUFFER
|
||||
|| target == GL_TRANSFORM_FEEDBACK_BUFFER
|
||||
|| target == GL_SHADER_STORAGE_BUFFER);
|
||||
# else
|
||||
assert_invariant(false
|
||||
|| target == GL_UNIFORM_BUFFER
|
||||
|| target == GL_TRANSFORM_FEEDBACK_BUFFER);
|
||||
# endif
|
||||
size_t const targetIndex = getIndexForBufferTarget(target);
|
||||
// this ALSO sets the generic binding
|
||||
assert_invariant(targetIndex < sizeof(state.buffers.targets) / sizeof(*state.buffers.targets));
|
||||
if ( state.buffers.targets[targetIndex].buffers[index].name != buffer
|
||||
|| state.buffers.targets[targetIndex].buffers[index].offset != offset
|
||||
|| state.buffers.targets[targetIndex].buffers[index].size != size) {
|
||||
state.buffers.targets[targetIndex].buffers[index].name = buffer;
|
||||
state.buffers.targets[targetIndex].buffers[index].offset = offset;
|
||||
state.buffers.targets[targetIndex].buffers[index].size = size;
|
||||
state.buffers.genericBinding[targetIndex] = buffer;
|
||||
glBindBufferRange(target, index, buffer, offset, size);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLContext::bindTexture(GLuint unit, GLuint target, GLuint texId, bool external) noexcept {
|
||||
// another texture is bound to the same unit with a different target,
|
||||
// unbind the texture from the current target
|
||||
update_state(state.textures.units[unit].target, target, [&]() {
|
||||
activeTexture(unit);
|
||||
glBindTexture(state.textures.units[unit].target, 0);
|
||||
});
|
||||
update_state(state.textures.units[unit].id, texId, [&]() {
|
||||
activeTexture(unit);
|
||||
glBindTexture(target, texId);
|
||||
}, external);
|
||||
}
|
||||
|
||||
void OpenGLContext::useProgram(GLuint program) noexcept {
|
||||
update_state(state.program.use, program, [&]() {
|
||||
glUseProgram(program);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept {
|
||||
assert_invariant(rp);
|
||||
assert_invariant(index < rp->vertexAttribArray.size());
|
||||
bool const force = rp->stateVersion != state.age;
|
||||
if (UTILS_UNLIKELY(force || !rp->vertexAttribArray[index])) {
|
||||
rp->vertexAttribArray.set(index);
|
||||
glEnableVertexAttribArray(index);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept {
|
||||
assert_invariant(rp);
|
||||
assert_invariant(index < rp->vertexAttribArray.size());
|
||||
bool const force = rp->stateVersion != state.age;
|
||||
if (UTILS_UNLIKELY(force || rp->vertexAttribArray[index])) {
|
||||
rp->vertexAttribArray.unset(index);
|
||||
glDisableVertexAttribArray(index);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::enable(GLenum cap) noexcept {
|
||||
size_t const index = getIndexForCap(cap);
|
||||
if (UTILS_UNLIKELY(!state.enables.caps[index])) {
|
||||
state.enables.caps.set(index);
|
||||
glEnable(cap);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::disable(GLenum cap) noexcept {
|
||||
size_t const index = getIndexForCap(cap);
|
||||
if (UTILS_UNLIKELY(state.enables.caps[index])) {
|
||||
state.enables.caps.unset(index);
|
||||
glDisable(cap);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::frontFace(GLenum mode) noexcept {
|
||||
update_state(state.raster.frontFace, mode, [&]() {
|
||||
glFrontFace(mode);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::cullFace(GLenum mode) noexcept {
|
||||
update_state(state.raster.cullFace, mode, [&]() {
|
||||
glCullFace(mode);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::blendEquation(GLenum modeRGB, GLenum modeA) noexcept {
|
||||
if (UTILS_UNLIKELY(
|
||||
state.raster.blendEquationRGB != modeRGB || state.raster.blendEquationA != modeA)) {
|
||||
state.raster.blendEquationRGB = modeRGB;
|
||||
state.raster.blendEquationA = modeA;
|
||||
glBlendEquationSeparate(modeRGB, modeA);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::blendFunction(GLenum srcRGB, GLenum srcA, GLenum dstRGB, GLenum dstA) noexcept {
|
||||
if (UTILS_UNLIKELY(
|
||||
state.raster.blendFunctionSrcRGB != srcRGB ||
|
||||
state.raster.blendFunctionSrcA != srcA ||
|
||||
state.raster.blendFunctionDstRGB != dstRGB ||
|
||||
state.raster.blendFunctionDstA != dstA)) {
|
||||
state.raster.blendFunctionSrcRGB = srcRGB;
|
||||
state.raster.blendFunctionSrcA = srcA;
|
||||
state.raster.blendFunctionDstRGB = dstRGB;
|
||||
state.raster.blendFunctionDstA = dstA;
|
||||
glBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::colorMask(GLboolean flag) noexcept {
|
||||
update_state(state.raster.colorMask, flag, [&]() {
|
||||
glColorMask(flag, flag, flag, flag);
|
||||
});
|
||||
}
|
||||
void OpenGLContext::depthMask(GLboolean flag) noexcept {
|
||||
update_state(state.raster.depthMask, flag, [&]() {
|
||||
glDepthMask(flag);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::depthFunc(GLenum func) noexcept {
|
||||
update_state(state.raster.depthFunc, func, [&]() {
|
||||
glDepthFunc(func);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::stencilFuncSeparate(GLenum funcFront, GLint refFront, GLuint maskFront,
|
||||
GLenum funcBack, GLint refBack, GLuint maskBack) noexcept {
|
||||
update_state(state.stencil.front.func, {funcFront, refFront, maskFront}, [&]() {
|
||||
glStencilFuncSeparate(GL_FRONT, funcFront, refFront, maskFront);
|
||||
});
|
||||
update_state(state.stencil.back.func, {funcBack, refBack, maskBack}, [&]() {
|
||||
glStencilFuncSeparate(GL_BACK, funcBack, refBack, maskBack);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::stencilOpSeparate(GLenum sfailFront, GLenum dpfailFront, GLenum dppassFront,
|
||||
GLenum sfailBack, GLenum dpfailBack, GLenum dppassBack) noexcept {
|
||||
update_state(state.stencil.front.op, {sfailFront, dpfailFront, dppassFront}, [&]() {
|
||||
glStencilOpSeparate(GL_FRONT, sfailFront, dpfailFront, dppassFront);
|
||||
});
|
||||
update_state(state.stencil.back.op, {sfailBack, dpfailBack, dppassBack}, [&]() {
|
||||
glStencilOpSeparate(GL_BACK, sfailBack, dpfailBack, dppassBack);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::stencilMaskSeparate(GLuint maskFront, GLuint maskBack) noexcept {
|
||||
update_state(state.stencil.front.stencilMask, maskFront, [&]() {
|
||||
glStencilMaskSeparate(GL_FRONT, maskFront);
|
||||
});
|
||||
update_state(state.stencil.back.stencilMask, maskBack, [&]() {
|
||||
glStencilMaskSeparate(GL_BACK, maskBack);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::polygonOffset(GLfloat factor, GLfloat units) noexcept {
|
||||
update_state(state.polygonOffset, { factor, units }, [&]() {
|
||||
if (factor != 0 || units != 0) {
|
||||
glPolygonOffset(factor, units);
|
||||
enable(GL_POLYGON_OFFSET_FILL);
|
||||
} else {
|
||||
disable(GL_POLYGON_OFFSET_FILL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif //TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -74,7 +74,6 @@
|
||||
namespace filament::backend {
|
||||
|
||||
class OpenGLPlatform;
|
||||
class OpenGLState;
|
||||
class PixelBufferDescriptor;
|
||||
struct TargetBufferInfo;
|
||||
class OpenGLProgram;
|
||||
@@ -225,24 +224,11 @@ public:
|
||||
private:
|
||||
OpenGLPlatform& mPlatform;
|
||||
OpenGLContext mContext;
|
||||
OpenGLState* mBackendState = nullptr; // owned by backend thread
|
||||
OpenGLState* mWorkerState = nullptr; // owned by worker thread (initialized for async mode)
|
||||
ShaderCompilerService mShaderCompilerService;
|
||||
|
||||
friend class TimerQueryFactory;
|
||||
friend class TimerQueryNativeFactory;
|
||||
OpenGLContext& getContext() noexcept { return mContext; }
|
||||
OpenGLState& getBackendState() const noexcept {
|
||||
// mBackendState should always be available.
|
||||
assert_invariant(mBackendState);
|
||||
return *mBackendState;
|
||||
}
|
||||
OpenGLState& getWorkerState() const noexcept {
|
||||
// mWorkerState should be available only when the asynchronous mode is enabled.
|
||||
// Ideally, this method should be called within asynchronous jobs.
|
||||
assert_invariant(mWorkerState);
|
||||
return *mWorkerState;
|
||||
}
|
||||
|
||||
ShaderCompilerService& getShaderCompilerService() noexcept {
|
||||
return mShaderCompilerService;
|
||||
@@ -257,15 +243,15 @@ private:
|
||||
*/
|
||||
|
||||
utils::CString getVendorString() const noexcept override {
|
||||
return utils::CString{ mContext.vendor };
|
||||
return utils::CString{ mContext.state.vendor };
|
||||
}
|
||||
|
||||
utils::CString getRendererString() const noexcept override {
|
||||
return utils::CString{ mContext.renderer };
|
||||
return utils::CString{ mContext.state.renderer };
|
||||
}
|
||||
|
||||
utils::CString getVersionString() const noexcept override {
|
||||
return utils::CString{ mContext.version };
|
||||
return utils::CString{ mContext.state.version };
|
||||
}
|
||||
|
||||
JobQueue* getJobQueue() const noexcept { return mJobQueue.get(); }
|
||||
@@ -349,13 +335,13 @@ private:
|
||||
|
||||
void setStencilState(StencilState ss) noexcept;
|
||||
|
||||
void setTextureData(OpenGLState& gl, GLTexture const* t,
|
||||
void setTextureData(GLTexture const* t,
|
||||
uint32_t level,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
PixelBufferDescriptor&& p);
|
||||
|
||||
void setCompressedTextureData(OpenGLState& gl, GLTexture const* t,
|
||||
void setCompressedTextureData(GLTexture const* t,
|
||||
uint32_t level,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
@@ -364,7 +350,7 @@ private:
|
||||
void renderBufferStorage(GLuint rbo, GLenum internalformat, uint32_t width,
|
||||
uint32_t height, uint8_t samples) const noexcept;
|
||||
|
||||
void textureStorage(OpenGLState& gl, GLTexture* t, uint32_t width, uint32_t height,
|
||||
void textureStorage(GLTexture* t, uint32_t width, uint32_t height,
|
||||
uint32_t depth, bool useProtectedMemory) noexcept;
|
||||
|
||||
/* State tracking GL wrappers... */
|
||||
@@ -384,32 +370,32 @@ private:
|
||||
// Common methods
|
||||
void readPixelsFromBoundFramebuffer(uint32_t x, uint32_t y, uint32_t width, uint32_t height,
|
||||
PixelBufferDescriptor&& p);
|
||||
void createTextureCommon(OpenGLState& gl, Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
||||
void createTextureCommon(Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
||||
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
|
||||
TextureUsage usage, utils::ImmutableCString&& tag);
|
||||
void update3DImageCommon(OpenGLState& gl, Handle<HwTexture> th,
|
||||
void update3DImageCommon(Handle<HwTexture> th,
|
||||
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
PixelBufferDescriptor&& data);
|
||||
void createTextureViewSwizzleCommon(Handle<HwTexture> th, Handle<HwTexture> srch,
|
||||
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b,
|
||||
TextureSwizzle a, utils::ImmutableCString&& tag);
|
||||
void importTextureCommon(OpenGLState& gl, Handle<HwTexture> th, intptr_t id, SamplerType target, uint8_t levels,
|
||||
void importTextureCommon(Handle<HwTexture> th, intptr_t id, SamplerType target, uint8_t levels,
|
||||
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
|
||||
TextureUsage usage, utils::ImmutableCString&& tag);
|
||||
void createBufferObjectCommon(OpenGLState& gl, Handle<HwBufferObject> boh, uint32_t byteCount,
|
||||
void createBufferObjectCommon(Handle<HwBufferObject> boh, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType, BufferUsage usage, utils::ImmutableCString&& tag);
|
||||
void setVertexBufferObjectCommon(Handle<HwVertexBuffer> vbh, uint32_t index,
|
||||
Handle<HwBufferObject> boh);
|
||||
void updateBufferObjectCommon(OpenGLState& gl, Handle<HwBufferObject> boh, BufferDescriptor&& bd,
|
||||
void updateBufferObjectCommon(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
|
||||
uint32_t byteOffset);
|
||||
void createIndexBufferCommon(OpenGLState& gl, Handle<HwIndexBuffer> ibh, ElementType const elementType,
|
||||
void createIndexBufferCommon(Handle<HwIndexBuffer> ibh, ElementType const elementType,
|
||||
uint32_t indexCount, BufferUsage const usage, utils::ImmutableCString&& tag);
|
||||
void updateIndexBufferCommon(OpenGLState& gl, Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
|
||||
void updateIndexBufferCommon(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
|
||||
uint32_t const byteOffset);
|
||||
void destroyTextureCommon(OpenGLState& gl, Handle<HwTexture> th);
|
||||
void destroyBufferObjectCommon(OpenGLState& gl, Handle<HwBufferObject> boh);
|
||||
void destroyIndexBufferCommon(OpenGLState& gl, Handle<HwIndexBuffer> ibh);
|
||||
void destroyTextureCommon(Handle<HwTexture> th);
|
||||
void destroyBufferObjectCommon(Handle<HwBufferObject> boh);
|
||||
void destroyIndexBufferCommon(Handle<HwIndexBuffer> ibh);
|
||||
|
||||
// state required to represent the current render pass
|
||||
Handle<HwRenderTarget> mRenderPassTarget;
|
||||
|
||||
@@ -112,7 +112,7 @@ void OpenGLProgram::initialize(OpenGLDriver& gld) {
|
||||
assert_invariant(mToken == nullptr);
|
||||
if (gl.program) {
|
||||
assert_invariant(lazyInitializationData);
|
||||
initializeProgramState(gld.getBackendState(), gl.program, *lazyInitializationData);
|
||||
initializeProgramState(gld.getContext(), gl.program, *lazyInitializationData);
|
||||
delete lazyInitializationData;
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ void OpenGLProgram::initialize(OpenGLDriver& gld) {
|
||||
* Initializes our internal state from a valid program. This must only be called after
|
||||
* checkProgramStatus() has been successfully called.
|
||||
*/
|
||||
void OpenGLProgram::initializeProgramState(OpenGLState& gls, GLuint program,
|
||||
void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint program,
|
||||
LazyInitializationData& lazyInitializationData) {
|
||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
||||
|
||||
@@ -139,7 +139,7 @@ void OpenGLProgram::initializeProgramState(OpenGLState& gls, GLuint program,
|
||||
GLuint binding = 0;
|
||||
|
||||
// needed for samplers
|
||||
gls.useProgram(program);
|
||||
context.useProgram(program);
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (descriptor_set_t set = 0; set < MAX_DESCRIPTOR_SET_COUNT; set++) {
|
||||
@@ -149,7 +149,7 @@ void OpenGLProgram::initializeProgramState(OpenGLState& gls, GLuint program,
|
||||
case DescriptorType::SHADER_STORAGE_BUFFER: {
|
||||
if (!entry.name.empty()) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (UTILS_LIKELY(!gls.isES2())) {
|
||||
if (UTILS_LIKELY(!context.isES2())) {
|
||||
GLuint const index = glGetUniformBlockIndex(program,
|
||||
entry.name.c_str());
|
||||
if (index != GL_INVALID_INDEX) {
|
||||
@@ -219,7 +219,7 @@ void OpenGLProgram::initializeProgramState(OpenGLState& gls, GLuint program,
|
||||
CHECK_GL_ERROR()
|
||||
}
|
||||
|
||||
if (gls.isES2()) {
|
||||
if (context.isES2()) {
|
||||
// ES2 initialization of (fake) UBOs
|
||||
UniformsRecord* const uniformsRecords = new(std::nothrow) UniformsRecord[Program::UNIFORM_BINDING_COUNT];
|
||||
UTILS_NOUNROLL
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "BindingMap.h"
|
||||
#include "OpenGLState.h"
|
||||
#include "OpenGLContext.h"
|
||||
#include "ShaderCompilerService.h"
|
||||
|
||||
#include <private/backend/Driver.h>
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
|
||||
bool isValid() const noexcept { return mToken || gl.program != 0; }
|
||||
|
||||
bool use(OpenGLDriver* const gld, OpenGLState& gls) noexcept {
|
||||
bool use(OpenGLDriver* const gld, OpenGLContext& context) noexcept {
|
||||
// both non-null is impossible by construction
|
||||
assert_invariant(!mToken || !gl.program);
|
||||
|
||||
@@ -71,7 +71,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
gls.useProgram(gl.program);
|
||||
context.useProgram(gl.program);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ private:
|
||||
|
||||
void initialize(OpenGLDriver& gld);
|
||||
|
||||
void initializeProgramState(OpenGLState& gls, GLuint program,
|
||||
void initializeProgramState(OpenGLContext& context, GLuint program,
|
||||
LazyInitializationData& lazyInitializationData);
|
||||
|
||||
BindingMap mBindingMap; // 8 bytes + out-of-line 256 bytes
|
||||
|
||||
@@ -1,559 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "OpenGLState.h"
|
||||
|
||||
#include "GLUtils.h"
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace utils;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
OpenGLState::OpenGLState(OpenGLContext& context) noexcept
|
||||
: ext(context.ext),
|
||||
bugs(context.bugs),
|
||||
gets(context.gets),
|
||||
procs(context.procs),
|
||||
features(context.features),
|
||||
mContext(context),
|
||||
mTimerQueryFactory(TimerQueryFactory::init(mContext.getPlatform(), context)),
|
||||
mSamplerMap(32) {
|
||||
state.vao.p = &mDefaultVAO;
|
||||
|
||||
// Push GL defaults to the new context
|
||||
setDefaultState();
|
||||
}
|
||||
|
||||
OpenGLState::~OpenGLState() noexcept {
|
||||
delete mTimerQueryFactory;
|
||||
}
|
||||
|
||||
void OpenGLState::terminate() noexcept {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (!isES2()) {
|
||||
for (auto& item: mSamplerMap) {
|
||||
unbindSampler(item.second);
|
||||
glDeleteSamplers(1, &item.second);
|
||||
}
|
||||
mSamplerMap.clear();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLState::destroyWithContext(
|
||||
size_t index, std::function<void(OpenGLState&)> const& closure) {
|
||||
if (index == 0) {
|
||||
// Note: we only need to delay the destruction of objects on the unprotected context
|
||||
// (index 0) because the protected context is always immediately destroyed and all its
|
||||
// active objects and bindings are then automatically destroyed.
|
||||
// TODO: this is only guaranteed for EGLPlatform, but that's the only one we care about.
|
||||
mDestroyWithNormalContext.push_back(closure);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::unbindEverything() noexcept {
|
||||
// TODO: we're supposed to unbind everything here so that resources don't get
|
||||
// stuck in this context (contextIndex) when destroyed in the other context.
|
||||
// However, because EGLPlatform always immediately destroys the protected context (1),
|
||||
// the bindings will automatically be severed when we switch back to the default context.
|
||||
// Since bindings now only exist in one context, we don't have a ref-counting issue to
|
||||
// worry about.
|
||||
}
|
||||
|
||||
void OpenGLState::synchronizeStateAndCache(size_t index) {
|
||||
|
||||
// if we're just switching back to context 0, run all the pending destructors
|
||||
if (index == 0) {
|
||||
auto list = std::move(mDestroyWithNormalContext);
|
||||
for (auto&& fn: list) {
|
||||
fn(*this);
|
||||
}
|
||||
}
|
||||
|
||||
// the default FBO could be invalid
|
||||
mDefaultFbo[index].reset();
|
||||
contextIndex = index;
|
||||
syncState();
|
||||
}
|
||||
|
||||
void OpenGLState::setDefaultState() noexcept {
|
||||
// We need to make sure our internal state matches the GL state when we start.
|
||||
// (some of these calls may be unneeded as they might be the gl defaults)
|
||||
constexpr GLenum caps[] = {
|
||||
GL_BLEND,
|
||||
GL_CULL_FACE,
|
||||
GL_SCISSOR_TEST,
|
||||
GL_DEPTH_TEST,
|
||||
GL_STENCIL_TEST,
|
||||
GL_DITHER,
|
||||
GL_SAMPLE_ALPHA_TO_COVERAGE,
|
||||
GL_SAMPLE_COVERAGE,
|
||||
GL_POLYGON_OFFSET_FILL,
|
||||
};
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (auto const capi : caps) {
|
||||
size_t const capIndex = getIndexForCap(capi);
|
||||
if (state.enables.caps[capIndex]) {
|
||||
glEnable(capi);
|
||||
} else {
|
||||
glDisable(capi);
|
||||
}
|
||||
}
|
||||
|
||||
// Point sprite size and seamless cubemap filtering are disabled by default in desktop GL.
|
||||
// In OpenGL ES, these flags do not exist because they are always on.
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
glEnable(GL_PROGRAM_POINT_SIZE);
|
||||
enable(GL_PROGRAM_POINT_SIZE);
|
||||
#endif
|
||||
|
||||
#ifdef GL_ARB_seamless_cube_map
|
||||
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
||||
enable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
||||
#endif
|
||||
|
||||
#ifdef GL_FRAGMENT_SHADER_DERIVATIVE_HINT
|
||||
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
|
||||
#endif
|
||||
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
if (ext.EXT_clip_control) {
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL)
|
||||
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
|
||||
#elif defined(GL_EXT_clip_control)
|
||||
glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ext.EXT_clip_cull_distance
|
||||
&& mContext.getDriverConfig().stereoscopicType == StereoscopicType::INSTANCED) {
|
||||
glEnable(GL_CLIP_DISTANCE0);
|
||||
glEnable(GL_CLIP_DISTANCE1);
|
||||
}
|
||||
}
|
||||
|
||||
GLuint OpenGLState::bindFramebuffer(GLenum target, GLuint buffer) noexcept {
|
||||
if (UTILS_UNLIKELY(buffer == 0)) {
|
||||
// we're binding the default frame buffer, resolve its actual name
|
||||
auto& defaultFboForThisContext = mDefaultFbo[contextIndex];
|
||||
|
||||
if (UTILS_UNLIKELY(!defaultFboForThisContext.has_value())) {
|
||||
defaultFboForThisContext = GLuint(mContext.getPlatform().getDefaultFramebufferObject());
|
||||
}
|
||||
// by construction, defaultFboForThisContext has a value. value_or() avoids a throwing call.
|
||||
buffer = defaultFboForThisContext.value_or(0);
|
||||
}
|
||||
bindFramebufferResolved(target, buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void OpenGLState::unbindFramebuffer(GLenum target) noexcept {
|
||||
bindFramebufferResolved(target, 0);
|
||||
}
|
||||
|
||||
void OpenGLState::bindFramebufferResolved(GLenum target, GLuint buffer) noexcept {
|
||||
switch (target) {
|
||||
case GL_FRAMEBUFFER:
|
||||
if (state.draw_fbo != buffer || state.read_fbo != buffer) {
|
||||
state.draw_fbo = state.read_fbo = buffer;
|
||||
glBindFramebuffer(target, buffer);
|
||||
}
|
||||
break;
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
case GL_DRAW_FRAMEBUFFER:
|
||||
if (state.draw_fbo != buffer) {
|
||||
state.draw_fbo = buffer;
|
||||
glBindFramebuffer(target, buffer);
|
||||
}
|
||||
break;
|
||||
case GL_READ_FRAMEBUFFER:
|
||||
if (state.read_fbo != buffer) {
|
||||
state.read_fbo = buffer;
|
||||
glBindFramebuffer(target, buffer);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::bindBuffer(GLenum target, GLuint buffer) noexcept {
|
||||
if (target == GL_ELEMENT_ARRAY_BUFFER) {
|
||||
constexpr size_t targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
|
||||
// GL_ELEMENT_ARRAY_BUFFER is a special case, where the currently bound VAO remembers
|
||||
// the index buffer, unless there are no VAO bound (see: bindVertexArray)
|
||||
assert_invariant(state.vao.p);
|
||||
if (state.buffers.genericBinding[targetIndex] != buffer
|
||||
|| ((state.vao.p != &mDefaultVAO) && (state.vao.p->elementArray != buffer))) {
|
||||
state.buffers.genericBinding[targetIndex] = buffer;
|
||||
if (state.vao.p != &mDefaultVAO) {
|
||||
state.vao.p->elementArray = buffer;
|
||||
}
|
||||
glBindBuffer(target, buffer);
|
||||
}
|
||||
} else {
|
||||
size_t const targetIndex = getIndexForBufferTarget(target);
|
||||
update_state(state.buffers.genericBinding[targetIndex], buffer, [&]() {
|
||||
glBindBuffer(target, buffer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::pixelStore(GLenum pname, GLint param) noexcept {
|
||||
GLint* pcur;
|
||||
|
||||
// Note: GL_UNPACK_SKIP_PIXELS, GL_UNPACK_SKIP_ROWS and
|
||||
// GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_ROWS
|
||||
// are actually provided as conveniences to the programmer; they provide no functionality
|
||||
// that cannot be duplicated at the call site (e.g. glTexImage2D or glReadPixels)
|
||||
|
||||
switch (pname) {
|
||||
case GL_PACK_ALIGNMENT:
|
||||
pcur = &state.pack.alignment;
|
||||
break;
|
||||
case GL_UNPACK_ALIGNMENT:
|
||||
pcur = &state.unpack.alignment;
|
||||
break;
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
case GL_UNPACK_ROW_LENGTH:
|
||||
assert_invariant(mContext.major > 2);
|
||||
pcur = &state.unpack.row_length;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
goto default_case;
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(*pcur != param)) {
|
||||
*pcur = param;
|
||||
default_case:
|
||||
glPixelStorei(pname, param);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::unbindTexture(
|
||||
UTILS_UNUSED_IN_RELEASE GLenum target, GLuint texture_id) noexcept {
|
||||
// unbind this texture from all the units it might be bound to
|
||||
// no need unbind the texture from FBOs because we're not tracking that state (and there is
|
||||
// no need to).
|
||||
// Never attempt to unbind texture 0. This could happen with external textures w/ streaming if
|
||||
// never populated.
|
||||
if (texture_id) {
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
|
||||
if (state.textures.units[unit].id == texture_id) {
|
||||
assert_invariant(state.textures.units[unit].target == target);
|
||||
unbindTextureUnit(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::unbindTextureUnit(GLuint unit) noexcept {
|
||||
update_state(state.textures.units[unit].id, 0u, [&]() {
|
||||
activeTexture(unit);
|
||||
glBindTexture(state.textures.units[unit].target, 0u);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::unbindSampler(GLuint sampler) noexcept {
|
||||
// unbind this sampler from all the units it might be bound to
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
|
||||
if (state.textures.units[unit].sampler == sampler) {
|
||||
bindSampler(unit, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::deleteBuffer(GLuint buffer, GLenum target) noexcept {
|
||||
glDeleteBuffers(1, &buffer);
|
||||
|
||||
// bindings of bound buffers are reset to 0
|
||||
size_t const targetIndex = getIndexForBufferTarget(target);
|
||||
auto& genericBinding = state.buffers.genericBinding[targetIndex];
|
||||
if (genericBinding == buffer) {
|
||||
genericBinding = 0;
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(bugs.rebind_buffer_after_deletion)) {
|
||||
if (genericBinding) {
|
||||
glBindBuffer(target, genericBinding);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
assert_invariant(mContext.getFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1 ||
|
||||
(target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER));
|
||||
|
||||
if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) {
|
||||
auto& indexedBinding = state.buffers.targets[targetIndex];
|
||||
UTILS_NOUNROLL
|
||||
for (auto& entry: indexedBinding.buffers) {
|
||||
if (entry.name == buffer) {
|
||||
entry.name = 0;
|
||||
entry.offset = 0;
|
||||
entry.size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLState::deleteVertexArray(GLuint vao) noexcept {
|
||||
if (UTILS_LIKELY(vao)) {
|
||||
procs.deleteVertexArrays(1, &vao);
|
||||
// if the destroyed VAO is bound, clear the binding.
|
||||
if (state.vao.p->vao[contextIndex] == vao) {
|
||||
bindVertexArray(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLuint OpenGLState::getSamplerSlow(SamplerParams params) const noexcept {
|
||||
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
|
||||
|
||||
using namespace GLUtils;
|
||||
|
||||
GLuint s;
|
||||
glGenSamplers(1, &s);
|
||||
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, (GLint)getTextureFilter(params.filterMin));
|
||||
glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, (GLint)getTextureFilter(params.filterMag));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_S, (GLint)getWrapMode(params.wrapS));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_T, (GLint)getWrapMode(params.wrapT));
|
||||
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, (GLint)getWrapMode(params.wrapR));
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, (GLint)getTextureCompareMode(params.compareMode));
|
||||
glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC, (GLint)getTextureCompareFunc(params.compareFunc));
|
||||
|
||||
#if defined(GL_EXT_texture_filter_anisotropic)
|
||||
if (ext.EXT_texture_filter_anisotropic &&
|
||||
!bugs.texture_filter_anisotropic_broken_on_sampler) {
|
||||
GLfloat const anisotropy = float(1u << params.anisotropyLog2);
|
||||
glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT,
|
||||
std::min(gets.max_anisotropy, anisotropy));
|
||||
}
|
||||
#endif
|
||||
CHECK_GL_ERROR()
|
||||
mSamplerMap[params] = s;
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void OpenGLState::syncState() noexcept {
|
||||
// Force GL state to match the Filament state
|
||||
|
||||
// increase the state version so other parts of the state know to reset
|
||||
state.age++;
|
||||
|
||||
if (mContext.major > 2) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, state.draw_fbo);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, state.read_fbo);
|
||||
#endif
|
||||
} else {
|
||||
assert_invariant(state.read_fbo == state.draw_fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, state.draw_fbo);
|
||||
state.read_fbo = state.draw_fbo;
|
||||
}
|
||||
|
||||
// state.program
|
||||
glUseProgram(state.program.use);
|
||||
|
||||
// state.vao
|
||||
state.vao.p = nullptr;
|
||||
bindVertexArray(nullptr);
|
||||
|
||||
// state.raster
|
||||
glFrontFace(state.raster.frontFace);
|
||||
glCullFace(state.raster.cullFace);
|
||||
glBlendEquationSeparate(state.raster.blendEquationRGB, state.raster.blendEquationA);
|
||||
glBlendFuncSeparate(
|
||||
state.raster.blendFunctionSrcRGB,
|
||||
state.raster.blendFunctionDstRGB,
|
||||
state.raster.blendFunctionSrcA,
|
||||
state.raster.blendFunctionDstA
|
||||
);
|
||||
glColorMask(
|
||||
state.raster.colorMask,
|
||||
state.raster.colorMask,
|
||||
state.raster.colorMask,
|
||||
state.raster.colorMask
|
||||
);
|
||||
glDepthMask(state.raster.depthMask);
|
||||
glDepthFunc(state.raster.depthFunc);
|
||||
|
||||
// state.stencil
|
||||
glStencilFuncSeparate(
|
||||
GL_FRONT,
|
||||
state.stencil.front.func.func,
|
||||
state.stencil.front.func.ref,
|
||||
state.stencil.front.func.mask
|
||||
);
|
||||
glStencilFuncSeparate(
|
||||
GL_BACK,
|
||||
state.stencil.back.func.func,
|
||||
state.stencil.back.func.ref,
|
||||
state.stencil.back.func.mask
|
||||
);
|
||||
glStencilOpSeparate(
|
||||
GL_FRONT,
|
||||
state.stencil.front.op.sfail,
|
||||
state.stencil.front.op.dpfail,
|
||||
state.stencil.front.op.dppass
|
||||
);
|
||||
glStencilOpSeparate(
|
||||
GL_BACK,
|
||||
state.stencil.back.op.sfail,
|
||||
state.stencil.back.op.dpfail,
|
||||
state.stencil.back.op.dppass
|
||||
);
|
||||
glStencilMaskSeparate(GL_FRONT, state.stencil.front.stencilMask);
|
||||
glStencilMaskSeparate(GL_BACK, state.stencil.back.stencilMask);
|
||||
|
||||
// state.polygonOffset
|
||||
glPolygonOffset(state.polygonOffset.factor, state.polygonOffset.units);
|
||||
|
||||
// state.enables
|
||||
setDefaultState();
|
||||
|
||||
// state.buffers
|
||||
// Reset state.buffers to its default state to avoid the complexity and error-prone
|
||||
// nature of resetting the GL state to its existing state
|
||||
state.buffers = {};
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
if (mContext.major > 2) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
for (auto const target: {
|
||||
GL_UNIFORM_BUFFER,
|
||||
GL_TRANSFORM_FEEDBACK_BUFFER,
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
GL_SHADER_STORAGE_BUFFER,
|
||||
#endif
|
||||
GL_PIXEL_PACK_BUFFER,
|
||||
GL_PIXEL_UNPACK_BUFFER,
|
||||
}) {
|
||||
glBindBuffer(target, 0);
|
||||
}
|
||||
|
||||
for (size_t bufferIndex = 0; bufferIndex < MAX_BUFFER_BINDINGS; ++bufferIndex) {
|
||||
if (bufferIndex < (size_t)gets.max_uniform_buffer_bindings) {
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, bufferIndex, 0);
|
||||
}
|
||||
|
||||
if (bufferIndex < (size_t)gets.max_transform_feedback_separate_attribs) {
|
||||
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, bufferIndex, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// state.textures
|
||||
// Reset state.textures to its default state to avoid the complexity and error-prone
|
||||
// nature of resetting the GL state to its existing state
|
||||
state.textures = {};
|
||||
const std::pair<GLuint, bool> textureTargets[] = {
|
||||
{ GL_TEXTURE_2D, true },
|
||||
{ GL_TEXTURE_2D_ARRAY, true },
|
||||
{ GL_TEXTURE_CUBE_MAP, true },
|
||||
{ GL_TEXTURE_3D, true },
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
{ GL_TEXTURE_2D_MULTISAMPLE, true },
|
||||
#endif
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
#if defined(GL_OES_EGL_image_external)
|
||||
{ GL_TEXTURE_EXTERNAL_OES, ext.OES_EGL_image_external_essl3 },
|
||||
#endif
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_texture_cube_map_array)
|
||||
{ GL_TEXTURE_CUBE_MAP_ARRAY, ext.EXT_texture_cube_map_array },
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
for (GLint unit = 0; unit < gets.max_combined_texture_image_units; ++unit) {
|
||||
glActiveTexture(GL_TEXTURE0 + unit);
|
||||
if (mContext.major > 2) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glBindSampler(unit, 0);
|
||||
#endif
|
||||
}
|
||||
for (auto [target, available] : textureTargets) {
|
||||
if (available) {
|
||||
glBindTexture(target, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
glActiveTexture(GL_TEXTURE0 + state.textures.active);
|
||||
|
||||
// state.unpack
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, state.unpack.alignment);
|
||||
if (mContext.major > 2) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, state.unpack.row_length);
|
||||
#endif
|
||||
}
|
||||
|
||||
// state.pack
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, state.pack.alignment);
|
||||
if (mContext.major > 2) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
// state.window
|
||||
glScissor(
|
||||
state.window.scissor.x,
|
||||
state.window.scissor.y,
|
||||
state.window.scissor.z,
|
||||
state.window.scissor.w
|
||||
);
|
||||
glViewport(
|
||||
state.window.viewport.x,
|
||||
state.window.viewport.y,
|
||||
state.window.viewport.z,
|
||||
state.window.viewport.w
|
||||
);
|
||||
glDepthRangef(state.window.depthRange.x, state.window.depthRange.y);
|
||||
}
|
||||
|
||||
void OpenGLState::createTimerQuery(GLTimerQuery* query) {
|
||||
mTimerQueryFactory->createTimerQuery(query);
|
||||
}
|
||||
|
||||
void OpenGLState::destroyTimerQuery(GLTimerQuery* query) {
|
||||
mTimerQueryFactory->destroyTimerQuery(query);
|
||||
}
|
||||
|
||||
void OpenGLState::beginTimeElapsedQuery(GLTimerQuery* query) {
|
||||
mTimerQueryFactory->beginTimeElapsedQuery(query);
|
||||
}
|
||||
|
||||
void OpenGLState::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) {
|
||||
mTimerQueryFactory->endTimeElapsedQuery(driver, query);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -1,615 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGLSTATE_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGLSTATE_H
|
||||
|
||||
#include "OpenGLContext.h"
|
||||
#include "OpenGLTimerQuery.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include "gl_headers.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class OpenGLDriver;
|
||||
|
||||
class OpenGLState final : public TimerQueryFactoryInterface {
|
||||
public:
|
||||
using RenderPrimitive = OpenGLContext::RenderPrimitive;
|
||||
using Extensions = OpenGLContext::Extensions;
|
||||
using Bugs = OpenGLContext::Bugs;
|
||||
using Gets = OpenGLContext::Gets;
|
||||
using Procs = OpenGLContext::Procs;
|
||||
using vec4gli = OpenGLContext::vec4gli;
|
||||
using vec2glf = OpenGLContext::vec2glf;
|
||||
|
||||
static constexpr size_t MAX_TEXTURE_UNIT_COUNT = OpenGLContext::MAX_TEXTURE_UNIT_COUNT;
|
||||
static constexpr size_t DUMMY_TEXTURE_BINDING = OpenGLContext::DUMMY_TEXTURE_BINDING;
|
||||
static constexpr size_t MAX_BUFFER_BINDINGS = OpenGLContext::MAX_BUFFER_BINDINGS;
|
||||
|
||||
explicit OpenGLState(OpenGLContext& context) noexcept;
|
||||
~OpenGLState() noexcept override;
|
||||
|
||||
// TimerQueryInterface ------------------------------------------------------------------------
|
||||
|
||||
// note: OpenGLState being final ensures (clang) these are not called through the vtable
|
||||
void createTimerQuery(GLTimerQuery* query) override;
|
||||
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
// Immutable config forwarding (enables gl.ext.XXX, gl.bugs.XXX, etc)
|
||||
Extensions const& ext;
|
||||
Bugs const& bugs;
|
||||
Gets const& gets;
|
||||
Procs const& procs;
|
||||
decltype(OpenGLContext::features) const& features;
|
||||
|
||||
// Read-only query forwarding
|
||||
bool isES2() const noexcept { return mContext.isES2(); }
|
||||
bool hasFences() const noexcept { return mContext.hasFences(); }
|
||||
FeatureLevel getFeatureLevel() const noexcept { return mContext.getFeatureLevel(); }
|
||||
ShaderModel getShaderModel() const noexcept { return mContext.getShaderModel(); }
|
||||
template<int MAJOR, int MINOR>
|
||||
bool isAtLeastGL() const noexcept { return mContext.isAtLeastGL<MAJOR, MINOR>(); }
|
||||
template<int MAJOR, int MINOR>
|
||||
bool isAtLeastGLES() const noexcept { return mContext.isAtLeastGLES<MAJOR, MINOR>(); }
|
||||
|
||||
OpenGLContext const& context() const noexcept { return mContext; }
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
constexpr inline size_t getIndexForCap(GLenum cap) noexcept;
|
||||
constexpr static inline size_t getIndexForBufferTarget(GLenum target) noexcept;
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
void syncState() noexcept;
|
||||
|
||||
inline void useProgram(GLuint program) noexcept;
|
||||
|
||||
void pixelStore(GLenum, GLint) noexcept;
|
||||
inline void activeTexture(GLuint unit) noexcept;
|
||||
inline void bindTexture(GLuint unit, GLuint target, GLuint texId, bool external) noexcept;
|
||||
|
||||
void unbindTexture(GLenum target, GLuint id) noexcept;
|
||||
void unbindTextureUnit(GLuint unit) noexcept;
|
||||
inline void bindVertexArray(RenderPrimitive const* p) noexcept;
|
||||
inline void bindSampler(GLuint unit, GLuint sampler) noexcept;
|
||||
void unbindSampler(GLuint sampler) noexcept;
|
||||
|
||||
void bindBuffer(GLenum target, GLuint buffer) noexcept;
|
||||
inline void bindBufferRange(GLenum target, GLuint index, GLuint buffer,
|
||||
GLintptr offset, GLsizeiptr size) noexcept;
|
||||
|
||||
GLuint bindFramebuffer(GLenum target, GLuint buffer) noexcept;
|
||||
void unbindFramebuffer(GLenum target) noexcept;
|
||||
|
||||
inline void enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept;
|
||||
inline void disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept;
|
||||
inline void enable(GLenum cap) noexcept;
|
||||
inline void disable(GLenum cap) noexcept;
|
||||
inline void frontFace(GLenum mode) noexcept;
|
||||
inline void cullFace(GLenum mode) noexcept;
|
||||
inline void blendEquation(GLenum modeRGB, GLenum modeA) noexcept;
|
||||
inline void blendFunction(GLenum srcRGB, GLenum srcA, GLenum dstRGB, GLenum dstA) noexcept;
|
||||
inline void colorMask(GLboolean flag) noexcept;
|
||||
inline void depthMask(GLboolean flag) noexcept;
|
||||
inline void depthFunc(GLenum func) noexcept;
|
||||
inline void stencilFuncSeparate(GLenum funcFront, GLint refFront, GLuint maskFront,
|
||||
GLenum funcBack, GLint refBack, GLuint maskBack) noexcept;
|
||||
inline void stencilOpSeparate(GLenum sfailFront, GLenum dpfailFront, GLenum dppassFront,
|
||||
GLenum sfailBack, GLenum dpfailBack, GLenum dppassBack) noexcept;
|
||||
inline void stencilMaskSeparate(GLuint maskFront, GLuint maskBack) noexcept;
|
||||
inline void polygonOffset(GLfloat factor, GLfloat units) noexcept;
|
||||
|
||||
inline void setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept;
|
||||
inline void viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept;
|
||||
inline void depthRange(GLclampf near, GLclampf far) noexcept;
|
||||
|
||||
void deleteBuffer(GLuint buffer, GLenum target) noexcept;
|
||||
void deleteVertexArray(GLuint vao) noexcept;
|
||||
|
||||
void destroyWithContext(size_t index, std::function<void(OpenGLState&)> const& closure);
|
||||
|
||||
// state getters
|
||||
vec4gli const& getViewport() const { return state.window.viewport; }
|
||||
|
||||
// function to handle state changes we don't control
|
||||
void updateTexImage(GLenum target, GLuint id) noexcept {
|
||||
assert_invariant(target == GL_TEXTURE_EXTERNAL_OES);
|
||||
// if another target is bound to this texture unit, unbind that texture
|
||||
if (UTILS_UNLIKELY(state.textures.units[state.textures.active].target != target)) {
|
||||
glBindTexture(state.textures.units[state.textures.active].target, 0);
|
||||
state.textures.units[state.textures.active].target = GL_TEXTURE_EXTERNAL_OES;
|
||||
}
|
||||
// the texture is already bound to `target`, we just update our internal state
|
||||
state.textures.units[state.textures.active].id = id;
|
||||
}
|
||||
void resetProgram() noexcept { state.program.use = 0; }
|
||||
|
||||
void unbindEverything() noexcept;
|
||||
void synchronizeStateAndCache(size_t index);
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLuint getSamplerSlow(SamplerParams sp) const noexcept;
|
||||
|
||||
inline GLuint getSampler(SamplerParams sp) const noexcept {
|
||||
assert_invariant(!sp.padding0);
|
||||
assert_invariant(!sp.padding1);
|
||||
assert_invariant(!sp.padding2);
|
||||
auto& samplerMap = mSamplerMap;
|
||||
auto pos = samplerMap.find(sp);
|
||||
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
|
||||
return getSamplerSlow(sp);
|
||||
}
|
||||
return pos->second;
|
||||
}
|
||||
#endif
|
||||
|
||||
// This is the index of the context in use. Must be either 0 (Unprotected) or 1 (Protected).
|
||||
// This is used to manage the OpenGL name of ContainerObjects within each context.
|
||||
uint32_t contextIndex = 0;
|
||||
|
||||
// Try to keep the State structure sorted by data-access patterns
|
||||
struct State {
|
||||
State() noexcept = default;
|
||||
// make sure we don't copy this state by accident
|
||||
State(State const& rhs) = delete;
|
||||
State(State&& rhs) noexcept = delete;
|
||||
State& operator=(State const& rhs) = delete;
|
||||
State& operator=(State&& rhs) noexcept = delete;
|
||||
|
||||
GLuint draw_fbo = 0;
|
||||
GLuint read_fbo = 0;
|
||||
|
||||
struct {
|
||||
GLuint use = 0;
|
||||
} program;
|
||||
|
||||
struct {
|
||||
RenderPrimitive* p = nullptr;
|
||||
} vao;
|
||||
|
||||
struct {
|
||||
GLenum frontFace = GL_CCW;
|
||||
GLenum cullFace = GL_BACK;
|
||||
GLenum blendEquationRGB = GL_FUNC_ADD;
|
||||
GLenum blendEquationA = GL_FUNC_ADD;
|
||||
GLenum blendFunctionSrcRGB = GL_ONE;
|
||||
GLenum blendFunctionSrcA = GL_ONE;
|
||||
GLenum blendFunctionDstRGB = GL_ZERO;
|
||||
GLenum blendFunctionDstA = GL_ZERO;
|
||||
GLboolean colorMask = GL_TRUE;
|
||||
GLboolean depthMask = GL_TRUE;
|
||||
GLenum depthFunc = GL_LESS;
|
||||
} raster;
|
||||
|
||||
struct {
|
||||
struct StencilFunc {
|
||||
GLenum func = GL_ALWAYS;
|
||||
GLint ref = 0;
|
||||
GLuint mask = ~GLuint(0);
|
||||
bool operator != (StencilFunc const& rhs) const noexcept {
|
||||
return func != rhs.func || ref != rhs.ref || mask != rhs.mask;
|
||||
}
|
||||
};
|
||||
struct StencilOp {
|
||||
GLenum sfail = GL_KEEP;
|
||||
GLenum dpfail = GL_KEEP;
|
||||
GLenum dppass = GL_KEEP;
|
||||
bool operator != (StencilOp const& rhs) const noexcept {
|
||||
return sfail != rhs.sfail || dpfail != rhs.dpfail || dppass != rhs.dppass;
|
||||
}
|
||||
};
|
||||
struct {
|
||||
StencilFunc func;
|
||||
StencilOp op;
|
||||
GLuint stencilMask = ~GLuint(0);
|
||||
} front, back;
|
||||
} stencil;
|
||||
|
||||
struct PolygonOffset {
|
||||
GLfloat factor = 0;
|
||||
GLfloat units = 0;
|
||||
bool operator != (PolygonOffset const& rhs) const noexcept {
|
||||
return factor != rhs.factor || units != rhs.units;
|
||||
}
|
||||
} polygonOffset;
|
||||
|
||||
struct {
|
||||
utils::bitset32 caps;
|
||||
} enables;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
struct {
|
||||
GLuint name = 0;
|
||||
GLintptr offset = 0;
|
||||
GLsizeiptr size = 0;
|
||||
} buffers[MAX_BUFFER_BINDINGS];
|
||||
} targets[3]; // there are only 3 indexed buffer targets
|
||||
GLuint genericBinding[7] = {};
|
||||
} buffers;
|
||||
|
||||
struct {
|
||||
GLuint active = 0; // zero-based
|
||||
struct {
|
||||
GLuint sampler = 0;
|
||||
GLuint target = 0;
|
||||
GLuint id = 0;
|
||||
} units[MAX_TEXTURE_UNIT_COUNT];
|
||||
} textures;
|
||||
|
||||
struct {
|
||||
GLint row_length = 0;
|
||||
GLint alignment = 4;
|
||||
} unpack;
|
||||
|
||||
struct {
|
||||
GLint alignment = 4;
|
||||
} pack;
|
||||
|
||||
struct {
|
||||
vec4gli scissor { 0 };
|
||||
vec4gli viewport { 0 };
|
||||
vec2glf depthRange { 0.0f, 1.0f };
|
||||
} window;
|
||||
uint8_t age = 0;
|
||||
} state;
|
||||
|
||||
private:
|
||||
OpenGLContext const& mContext;
|
||||
TimerQueryFactoryInterface* mTimerQueryFactory = nullptr;
|
||||
std::vector<std::function<void(OpenGLState&)>> mDestroyWithNormalContext;
|
||||
RenderPrimitive mDefaultVAO;
|
||||
std::optional<GLuint> mDefaultFbo[2]; // 0:Unprotected, 1:Protected
|
||||
mutable tsl::robin_map<SamplerParams, GLuint,
|
||||
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
|
||||
|
||||
void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept;
|
||||
|
||||
void setDefaultState() noexcept;
|
||||
|
||||
template <typename T, typename F>
|
||||
static inline void update_state(T& state, T const& expected, F functor, bool force = false) noexcept {
|
||||
if (UTILS_UNLIKELY(force || state != expected)) {
|
||||
state = expected;
|
||||
functor();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
constexpr size_t OpenGLState::getIndexForCap(GLenum cap) noexcept { //NOLINT
|
||||
size_t index = 0;
|
||||
switch (cap) {
|
||||
case GL_BLEND: index = 0; break;
|
||||
case GL_CULL_FACE: index = 1; break;
|
||||
case GL_SCISSOR_TEST: index = 2; break;
|
||||
case GL_DEPTH_TEST: index = 3; break;
|
||||
case GL_STENCIL_TEST: index = 4; break;
|
||||
case GL_DITHER: index = 5; break;
|
||||
case GL_SAMPLE_ALPHA_TO_COVERAGE: index = 6; break;
|
||||
case GL_SAMPLE_COVERAGE: index = 7; break;
|
||||
case GL_POLYGON_OFFSET_FILL: index = 8; break;
|
||||
#ifdef GL_ARB_seamless_cube_map
|
||||
case GL_TEXTURE_CUBE_MAP_SEAMLESS: index = 9; break;
|
||||
#endif
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
case GL_PROGRAM_POINT_SIZE: index = 10; break;
|
||||
#endif
|
||||
case GL_DEPTH_CLAMP: index = 11; break;
|
||||
default: break;
|
||||
}
|
||||
assert_invariant(index < state.enables.caps.size());
|
||||
return index;
|
||||
}
|
||||
|
||||
constexpr size_t OpenGLState::getIndexForBufferTarget(GLenum target) noexcept {
|
||||
size_t index = 0;
|
||||
switch (target) {
|
||||
// The indexed buffers MUST be first in this list (those usable with bindBufferRange)
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
case GL_UNIFORM_BUFFER: index = 0; break;
|
||||
case GL_TRANSFORM_FEEDBACK_BUFFER: index = 1; break;
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
case GL_SHADER_STORAGE_BUFFER: index = 2; break;
|
||||
#endif
|
||||
#endif
|
||||
case GL_ARRAY_BUFFER: index = 3; break;
|
||||
case GL_ELEMENT_ARRAY_BUFFER: index = 4; break;
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
case GL_PIXEL_PACK_BUFFER: index = 5; break;
|
||||
case GL_PIXEL_UNPACK_BUFFER: index = 6; break;
|
||||
#endif
|
||||
default: break;
|
||||
}
|
||||
assert_invariant(index < sizeof(state.buffers.genericBinding)/sizeof(state.buffers.genericBinding[0])); // NOLINT(misc-redundant-expression)
|
||||
return index;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void OpenGLState::activeTexture(GLuint unit) noexcept {
|
||||
assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT);
|
||||
update_state(state.textures.active, unit, [&]() {
|
||||
glActiveTexture(GL_TEXTURE0 + unit);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::bindSampler(GLuint unit, GLuint sampler) noexcept {
|
||||
assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT);
|
||||
assert_invariant(mContext.getFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1);
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
update_state(state.textures.units[unit].sampler, sampler, [&]() {
|
||||
glBindSampler(unit, sampler);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLState::setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept {
|
||||
vec4gli const scissor(left, bottom, width, height);
|
||||
update_state(state.window.scissor, scissor, [&]() {
|
||||
glScissor(left, bottom, width, height);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept {
|
||||
vec4gli const viewport(left, bottom, width, height);
|
||||
update_state(state.window.viewport, viewport, [&]() {
|
||||
glViewport(left, bottom, width, height);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::depthRange(GLclampf near, GLclampf far) noexcept {
|
||||
vec2glf const depthRange(near, far);
|
||||
update_state(state.window.depthRange, depthRange, [&]() {
|
||||
glDepthRangef(near, far);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::bindVertexArray(RenderPrimitive const* p) noexcept {
|
||||
RenderPrimitive* vao = p ? const_cast<RenderPrimitive *>(p) : &mDefaultVAO;
|
||||
update_state(state.vao.p, vao, [&]() {
|
||||
|
||||
// See if we need to create a name for this VAO on the fly, this would happen if:
|
||||
// - we're not the default VAO, because its name is always 0
|
||||
// - our name is 0, this could happen if this VAO was created in the "other" context
|
||||
// - the nameVersion is out of date *and* we're on the protected context, in this case:
|
||||
// - the name must be stale from a previous use of this context because we always
|
||||
// destroy the protected context when we're done with it.
|
||||
bool const recreateVaoName = vao != &mDefaultVAO &&
|
||||
((vao->vao[contextIndex] == 0) ||
|
||||
(vao->nameVersion != state.age && contextIndex == 1));
|
||||
if (UTILS_UNLIKELY(recreateVaoName)) {
|
||||
vao->nameVersion = state.age;
|
||||
procs.genVertexArrays(1, &vao->vao[contextIndex]);
|
||||
}
|
||||
|
||||
procs.bindVertexArray(vao->vao[contextIndex]);
|
||||
// update GL_ELEMENT_ARRAY_BUFFER, which is updated by glBindVertexArray
|
||||
size_t const targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
|
||||
state.buffers.genericBinding[targetIndex] = vao->elementArray;
|
||||
if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding || recreateVaoName)) {
|
||||
// This shouldn't be needed, but it looks like some drivers don't do the implicit
|
||||
// glBindBuffer().
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->elementArray);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
|
||||
GLintptr offset, GLsizeiptr size) noexcept {
|
||||
assert_invariant(mContext.getFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1);
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
# ifdef BACKEND_OPENGL_LEVEL_GLES31
|
||||
assert_invariant(false
|
||||
|| target == GL_UNIFORM_BUFFER
|
||||
|| target == GL_TRANSFORM_FEEDBACK_BUFFER
|
||||
|| target == GL_SHADER_STORAGE_BUFFER);
|
||||
# else
|
||||
assert_invariant(false
|
||||
|| target == GL_UNIFORM_BUFFER
|
||||
|| target == GL_TRANSFORM_FEEDBACK_BUFFER);
|
||||
# endif
|
||||
size_t const targetIndex = getIndexForBufferTarget(target);
|
||||
// this ALSO sets the generic binding
|
||||
assert_invariant(targetIndex < sizeof(state.buffers.targets) / sizeof(*state.buffers.targets));
|
||||
if ( state.buffers.targets[targetIndex].buffers[index].name != buffer
|
||||
|| state.buffers.targets[targetIndex].buffers[index].offset != offset
|
||||
|| state.buffers.targets[targetIndex].buffers[index].size != size) {
|
||||
state.buffers.targets[targetIndex].buffers[index].name = buffer;
|
||||
state.buffers.targets[targetIndex].buffers[index].offset = offset;
|
||||
state.buffers.targets[targetIndex].buffers[index].size = size;
|
||||
state.buffers.genericBinding[targetIndex] = buffer;
|
||||
glBindBufferRange(target, index, buffer, offset, size);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLState::bindTexture(GLuint unit, GLuint target, GLuint texId, bool external) noexcept {
|
||||
// another texture is bound to the same unit with a different target,
|
||||
// unbind the texture from the current target
|
||||
update_state(state.textures.units[unit].target, target, [&]() {
|
||||
activeTexture(unit);
|
||||
glBindTexture(state.textures.units[unit].target, 0);
|
||||
});
|
||||
update_state(state.textures.units[unit].id, texId, [&]() {
|
||||
activeTexture(unit);
|
||||
glBindTexture(target, texId);
|
||||
}, external);
|
||||
}
|
||||
|
||||
void OpenGLState::useProgram(GLuint program) noexcept {
|
||||
update_state(state.program.use, program, [&]() {
|
||||
glUseProgram(program);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept {
|
||||
assert_invariant(rp);
|
||||
assert_invariant(index < rp->vertexAttribArray.size());
|
||||
bool const force = rp->stateVersion != state.age;
|
||||
if (UTILS_UNLIKELY(force || !rp->vertexAttribArray[index])) {
|
||||
rp->vertexAttribArray.set(index);
|
||||
glEnableVertexAttribArray(index);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept {
|
||||
assert_invariant(rp);
|
||||
assert_invariant(index < rp->vertexAttribArray.size());
|
||||
bool const force = rp->stateVersion != state.age;
|
||||
if (UTILS_UNLIKELY(force || rp->vertexAttribArray[index])) {
|
||||
rp->vertexAttribArray.unset(index);
|
||||
glDisableVertexAttribArray(index);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::enable(GLenum cap) noexcept {
|
||||
size_t const index = getIndexForCap(cap);
|
||||
if (UTILS_UNLIKELY(!state.enables.caps[index])) {
|
||||
state.enables.caps.set(index);
|
||||
glEnable(cap);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::disable(GLenum cap) noexcept {
|
||||
size_t const index = getIndexForCap(cap);
|
||||
if (UTILS_UNLIKELY(state.enables.caps[index])) {
|
||||
state.enables.caps.unset(index);
|
||||
glDisable(cap);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::frontFace(GLenum mode) noexcept {
|
||||
update_state(state.raster.frontFace, mode, [&]() {
|
||||
glFrontFace(mode);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::cullFace(GLenum mode) noexcept {
|
||||
update_state(state.raster.cullFace, mode, [&]() {
|
||||
glCullFace(mode);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::blendEquation(GLenum modeRGB, GLenum modeA) noexcept {
|
||||
if (UTILS_UNLIKELY(
|
||||
state.raster.blendEquationRGB != modeRGB || state.raster.blendEquationA != modeA)) {
|
||||
state.raster.blendEquationRGB = modeRGB;
|
||||
state.raster.blendEquationA = modeA;
|
||||
glBlendEquationSeparate(modeRGB, modeA);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::blendFunction(GLenum srcRGB, GLenum srcA, GLenum dstRGB, GLenum dstA) noexcept {
|
||||
if (UTILS_UNLIKELY(
|
||||
state.raster.blendFunctionSrcRGB != srcRGB ||
|
||||
state.raster.blendFunctionSrcA != srcA ||
|
||||
state.raster.blendFunctionDstRGB != dstRGB ||
|
||||
state.raster.blendFunctionDstA != dstA)) {
|
||||
state.raster.blendFunctionSrcRGB = srcRGB;
|
||||
state.raster.blendFunctionSrcA = srcA;
|
||||
state.raster.blendFunctionDstRGB = dstRGB;
|
||||
state.raster.blendFunctionDstA = dstA;
|
||||
glBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLState::colorMask(GLboolean flag) noexcept {
|
||||
update_state(state.raster.colorMask, flag, [&]() {
|
||||
glColorMask(flag, flag, flag, flag);
|
||||
});
|
||||
}
|
||||
void OpenGLState::depthMask(GLboolean flag) noexcept {
|
||||
update_state(state.raster.depthMask, flag, [&]() {
|
||||
glDepthMask(flag);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::depthFunc(GLenum func) noexcept {
|
||||
update_state(state.raster.depthFunc, func, [&]() {
|
||||
glDepthFunc(func);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::stencilFuncSeparate(GLenum funcFront, GLint refFront, GLuint maskFront,
|
||||
GLenum funcBack, GLint refBack, GLuint maskBack) noexcept {
|
||||
update_state(state.stencil.front.func, {funcFront, refFront, maskFront}, [&]() {
|
||||
glStencilFuncSeparate(GL_FRONT, funcFront, refFront, maskFront);
|
||||
});
|
||||
update_state(state.stencil.back.func, {funcBack, refBack, maskBack}, [&]() {
|
||||
glStencilFuncSeparate(GL_BACK, funcBack, refBack, maskBack);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::stencilOpSeparate(GLenum sfailFront, GLenum dpfailFront, GLenum dppassFront,
|
||||
GLenum sfailBack, GLenum dpfailBack, GLenum dppassBack) noexcept {
|
||||
update_state(state.stencil.front.op, {sfailFront, dpfailFront, dppassFront}, [&]() {
|
||||
glStencilOpSeparate(GL_FRONT, sfailFront, dpfailFront, dppassFront);
|
||||
});
|
||||
update_state(state.stencil.back.op, {sfailBack, dpfailBack, dppassBack}, [&]() {
|
||||
glStencilOpSeparate(GL_BACK, sfailBack, dpfailBack, dppassBack);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::stencilMaskSeparate(GLuint maskFront, GLuint maskBack) noexcept {
|
||||
update_state(state.stencil.front.stencilMask, maskFront, [&]() {
|
||||
glStencilMaskSeparate(GL_FRONT, maskFront);
|
||||
});
|
||||
update_state(state.stencil.back.stencilMask, maskBack, [&]() {
|
||||
glStencilMaskSeparate(GL_BACK, maskBack);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLState::polygonOffset(GLfloat factor, GLfloat units) noexcept {
|
||||
update_state(state.polygonOffset, { factor, units }, [&]() {
|
||||
if (factor != 0 || units != 0) {
|
||||
glPolygonOffset(factor, units);
|
||||
enable(GL_POLYGON_OFFSET_FILL);
|
||||
} else {
|
||||
disable(GL_POLYGON_OFFSET_FILL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_OPENGL_OPENGLSTATE_H
|
||||
@@ -237,7 +237,7 @@ void ShaderCompilerService::init() noexcept {
|
||||
uint32_t poolSize = 1;
|
||||
JobSystem::Priority priority = JobSystem::Priority::DISPLAY;
|
||||
|
||||
auto const& renderer = mDriver.getContext().renderer;
|
||||
auto const& renderer = mDriver.getContext().state.renderer;
|
||||
// Some drivers support parallel shader compilation well, so we use N
|
||||
// threads, we can use lower priority threads here because urgent compilations
|
||||
// will most likely happen on the main gl thread. Using too many thread can
|
||||
|
||||
@@ -1548,16 +1548,6 @@ bool VulkanDriver::isTextureFormatMipmappable(TextureFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
bool VulkanDriver::isTextureFormatFilterable(TextureFormat format) {
|
||||
VkFormat vkformat = fvkutils::getVkFormat(format);
|
||||
if (vkformat == VK_FORMAT_UNDEFINED) {
|
||||
return false;
|
||||
}
|
||||
VkFormatProperties info;
|
||||
vkGetPhysicalDeviceFormatProperties(mPlatform->getPhysicalDevice(), vkformat, &info);
|
||||
return (info.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isRenderTargetFormatSupported(TextureFormat format) {
|
||||
VkFormat vkformat = fvkutils::getVkFormat(format);
|
||||
if (vkformat == VK_FORMAT_UNDEFINED) {
|
||||
|
||||
@@ -822,17 +822,6 @@ bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
|
||||
return WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(webGpuFormat);
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isTextureFormatFilterable(TextureFormat format) {
|
||||
if (isFp32ColorFormat(format)) {
|
||||
return mDevice.HasFeature(wgpu::FeatureName::Float32Filterable);
|
||||
}
|
||||
if (isUnsignedIntFormat(format) || isSignedIntFormat(format) ||
|
||||
isDepthFormat(format) || isStencilFormat(format)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isRenderTargetFormatSupported(const TextureFormat format) {
|
||||
//todo
|
||||
return true;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -31,7 +31,10 @@ using namespace filament;
|
||||
using namespace filament::backend;
|
||||
using namespace filament::math;
|
||||
|
||||
TEST_F(BackendTest, Autoresolve) {
|
||||
TEST_F(BackendTest, AutoresolveDifferingSampleCounts) {
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "see b/486954356");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::VULKAN), "see b/486954356");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
constexpr int kRenderTargetSize = 512;
|
||||
|
||||
@@ -58,18 +61,18 @@ TEST_F(BackendTest, Autoresolve) {
|
||||
TextureFormat::RGBA8, 1, kRenderTargetSize, kRenderTargetSize, 1,
|
||||
TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE));
|
||||
|
||||
// render a red triangle to a RenderTarget with 4 samples.
|
||||
// First render pass: render a red triangle to a RenderTarget with 8 samples.
|
||||
{
|
||||
Handle<HwRenderTarget> renderTarget =
|
||||
addCleanup(api.createRenderTarget(TargetBufferFlags::COLOR, kRenderTargetSize,
|
||||
kRenderTargetSize, 4, 1, { { texture } }, {}, {}));
|
||||
kRenderTargetSize, 8, 1, { { texture } }, {}, {}));
|
||||
|
||||
shader.uploadUniform(api, ubuffer,
|
||||
SimpleMaterialParams{
|
||||
.color = float4(1, 0, 0, 1),
|
||||
});
|
||||
|
||||
RenderPassParams params = getClearColorRenderPass(float4(1));
|
||||
RenderPassParams params = getClearColorRenderPass(float4(0));
|
||||
params.viewport = { 0, 0, kRenderTargetSize, kRenderTargetSize };
|
||||
|
||||
RenderFrame frame(api);
|
||||
@@ -81,9 +84,36 @@ TEST_F(BackendTest, Autoresolve) {
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// Second render pass: render a green triangle to a RenderTarget with 4 samples, attached to
|
||||
// the same texture.
|
||||
{
|
||||
Handle<HwRenderTarget> renderTarget =
|
||||
addCleanup(api.createRenderTarget(TargetBufferFlags::COLOR, kRenderTargetSize,
|
||||
kRenderTargetSize, 4, 1, { { texture } }, {}, {}));
|
||||
|
||||
shader.uploadUniform(api, ubuffer,
|
||||
SimpleMaterialParams{
|
||||
.color = float4(0, 1, 0, 1),
|
||||
});
|
||||
|
||||
RenderPassParams params = getClearColorRenderPass(float4(0));
|
||||
params.viewport = { 0, 0, kRenderTargetSize, kRenderTargetSize };
|
||||
|
||||
RenderFrame frame(api);
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
ps.primitiveType = PrimitiveType::TRIANGLES;
|
||||
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(ps);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
EXPECT_IMAGE(renderTarget, ScreenshotParams(kRenderTargetSize, kRenderTargetSize,
|
||||
"Autoresolve", 545665143));
|
||||
"AutoresolveDifferingSampleCounts", 1048576));
|
||||
|
||||
api.commit(swapChain);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, ColorMagnify) {
|
||||
// b/453757697: MESA glBlitFramebuffer linear interpolation fallback precision slightly mismatches.
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "b/453757697");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::VULKAN), "b/453777397");
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
|
||||
@@ -206,13 +206,15 @@ TEST_F(BlitTest, ColorMagnify) {
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
EXPECT_IMAGE(dstRenderTargets[0],
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31, false, 8000, 5));
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
|
||||
api.commit(swapChain);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, ColorMinify) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "b/453758045");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::VULKAN), "b/453776623");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
@@ -266,7 +268,7 @@ TEST_F(BlitTest, ColorMinify) {
|
||||
SamplerMagFilter::LINEAR);
|
||||
|
||||
EXPECT_IMAGE(dstRenderTargets[0],
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f, false, 8000, 5));
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, ColorResolve) {
|
||||
@@ -353,7 +355,7 @@ TEST_F(BlitTest, ColorResolve) {
|
||||
SamplerMagFilter::NEAREST);
|
||||
|
||||
EXPECT_IMAGE(dstRenderTarget,
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 531759687, false, 8000, 5));
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 531759687));
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, Blit2DTextureArray) {
|
||||
|
||||
@@ -391,6 +391,7 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
}
|
||||
|
||||
TEST_F(LoadImageTest, UpdateImageSRGB) {
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "see b/453756688");
|
||||
SKIP_IF(Backend::VULKAN, "b/454040142");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
@@ -467,7 +468,7 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
|
||||
api.endRenderPass();
|
||||
|
||||
EXPECT_IMAGE(defaultRenderTarget,
|
||||
ScreenshotParams(kTexSize, kTexSize, "UpdateImageSRGB", 3300305265, false, 8000, 5));
|
||||
ScreenshotParams(kTexSize, kTexSize, "UpdateImageSRGB", 3300305265));
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
@@ -177,7 +177,9 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
||||
|
||||
TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN), "Stencil not preserved");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL), "Stencil isn't applied");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "see b/453757787");
|
||||
SKIP_IF(Backend::VULKAN, "b/453777217");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/Slice.h>
|
||||
#include <utils/tribool.h>
|
||||
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
@@ -442,24 +441,6 @@ public:
|
||||
* invoking asynchronous methods.
|
||||
*/
|
||||
AsynchronousMode asynchronousMode = AsynchronousMode::NONE;
|
||||
|
||||
/**
|
||||
* Capacity of the LRU cache for material definitions.
|
||||
*
|
||||
* A value of 0 indicates that definitions will be destroyed immediately when they are no
|
||||
* longer referenced by any material instances or scenes. A value greater than 0 defines
|
||||
* the maximum number of unreferenced definitions to keep alive to avoid re-compilation.
|
||||
*/
|
||||
uint32_t materialCacheCapacity = 0;
|
||||
|
||||
/**
|
||||
* Capacity of the LRU cache for program specializations.
|
||||
*
|
||||
* Similar to materialCacheCapacity, but applies to the underlying shader programs generated
|
||||
* for materials. A value of 0 means immediate destruction of unreferenced programs. A
|
||||
* positive value caches up to that number of programs.
|
||||
*/
|
||||
uint32_t programCacheCapacity = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -1303,53 +1284,6 @@ public:
|
||||
*/
|
||||
bool* UTILS_NULLABLE getFeatureFlagPtr(char const* UTILS_NONNULL name) const noexcept;
|
||||
|
||||
|
||||
/**
|
||||
* Asynchronously ensures that the variants of the specified Material needed to render it
|
||||
* in the provided View are compiled. This takes into account the view's features
|
||||
* (e.g. dynamic lighting, fog, stereo, shadowing), alongside the specified shadow receiver
|
||||
* and skinning configurations.
|
||||
*
|
||||
* After issuing several Engine::compile() calls in a row, it is recommended to call
|
||||
* Engine::flush() such that the backend can start the compilation work as soon as possible.
|
||||
* The provided callback is guaranteed to be called on the main thread after all computed
|
||||
* variants of the material are compiled. This can take hundreds of milliseconds.
|
||||
*
|
||||
* If all the needed variants are already compiled, the callback will be scheduled as
|
||||
* soon as possible, but this might take a few dozen milliseconds, corresponding to how
|
||||
* many previous frames are enqueued in the backend.
|
||||
*
|
||||
* If the same variant is scheduled for compilation multiple times, the first scheduling
|
||||
* takes precedence; later scheduling are ignored.
|
||||
*
|
||||
* The callback is guaranteed to be called. If the engine is destroyed while some material
|
||||
* variants are still compiling or in the queue, these will be discarded and the corresponding
|
||||
* callback will be called. In that case however the Material pointer passed to the callback
|
||||
* is guaranteed to be invalid (either because it's been destroyed by the user already, or,
|
||||
* because it's been cleaned-up by the Engine).
|
||||
*
|
||||
* @param priority Which priority queue to use, LOW or HIGH.
|
||||
* @param material The Material to compile.
|
||||
* @param view The View in which the material will be rendered.
|
||||
* @param shadowReceiver Indicates whether to compile the shadow-receiving variants.
|
||||
* Pass \p utils::tribool::indeterminate to compile both permutations.
|
||||
* @param skinning Indicates whether to compile the skinning variants.
|
||||
* Pass \p utils::tribool::indeterminate to compile both permutations.
|
||||
* @param handler Handler to dispatch the callback or nullptr for the default handler.
|
||||
* @param callback Callback called on the main thread when the compilation is done
|
||||
* by the backend.
|
||||
*
|
||||
* @see Material::compile
|
||||
*/
|
||||
void compile(
|
||||
backend::CompilerPriorityQueue priority,
|
||||
Material const* UTILS_NONNULL material,
|
||||
View const* UTILS_NONNULL view,
|
||||
utils::tribool shadowReceiver,
|
||||
utils::tribool skinning,
|
||||
backend::CallbackHandler* UTILS_NULLABLE handler = nullptr,
|
||||
utils::Invocable<void(Material* UTILS_NONNULL)>&& callback = {});
|
||||
|
||||
protected:
|
||||
//! \privatesection
|
||||
Engine() noexcept = default;
|
||||
|
||||
@@ -139,8 +139,10 @@ public:
|
||||
Builder& package(const void* UTILS_NONNULL payload, size_t size);
|
||||
|
||||
template<typename T>
|
||||
using is_supported_constant_parameter_t =
|
||||
MaterialInstance::is_supported_constant_parameter_t<T>;
|
||||
using is_supported_constant_parameter_t = std::enable_if_t<
|
||||
std::is_same_v<int32_t, T> ||
|
||||
std::is_same_v<float, T> ||
|
||||
std::is_same_v<bool, T>>;
|
||||
|
||||
/**
|
||||
* Specialize a constant parameter specified in the material definition with a concrete
|
||||
@@ -241,18 +243,11 @@ public:
|
||||
* for stereoscopic rendering. If an application is not planning to render in stereo, this bit
|
||||
* should be turned off to avoid unnecessary material compilations.
|
||||
*
|
||||
* Note that it is possible to override specialization constants on a per-MaterialInstance basis
|
||||
* (@see MaterialInstance::setConstant). In that case, the programs compiled by a call to
|
||||
* Material::compile() may not be reusable by that MaterialInstance. It's better to call
|
||||
* MaterialInstance::compile() in cases where you intend to override specialization constants.
|
||||
*
|
||||
* @param priority Which priority queue to use, LOW or HIGH.
|
||||
* @param variants Variants to include to the compile command.
|
||||
* @param handler Handler to dispatch the callback or nullptr for the default handler
|
||||
* @param callback callback called on the main thread when the compilation is done on
|
||||
* by backend.
|
||||
*
|
||||
* @see Material::compile
|
||||
*/
|
||||
void compile(CompilerPriorityQueue priority,
|
||||
UserVariantFilterMask variants,
|
||||
|
||||
@@ -94,12 +94,6 @@ public:
|
||||
std::is_same_v<math::mat3f, T>
|
||||
>;
|
||||
|
||||
template<typename T>
|
||||
using is_supported_constant_parameter_t = std::enable_if_t<
|
||||
std::is_same_v<int32_t, T> ||
|
||||
std::is_same_v<float, T> ||
|
||||
std::is_same_v<bool, T>>;
|
||||
|
||||
/**
|
||||
* Creates a new MaterialInstance using another MaterialInstance as a template for initialization.
|
||||
* The new MaterialInstance is an instance of the same Material of the template instance and
|
||||
@@ -282,91 +276,6 @@ public:
|
||||
return getParameter<T>(name, strlen(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides a specialization constant of this material instance.
|
||||
*
|
||||
* @tparam T The type of the constant. Must be int32_t, float, or bool.
|
||||
* @param name The name of the constant as defined in the material. Cannot be nullptr.
|
||||
* @param nameLength Length in `char` of the name parameter.
|
||||
* @param value The value of the constant.
|
||||
*
|
||||
* @see Material::Builder::constant
|
||||
*/
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
void setConstant(const char* UTILS_NONNULL name, size_t nameLength, T value);
|
||||
|
||||
/** inline helper to provide the name as a null-terminated string literal */
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
void setConstant(StringLiteral const name, T value) {
|
||||
setConstant<T>(name.data, name.size, value);
|
||||
}
|
||||
|
||||
/** inline helper to provide the name as a null-terminated C string */
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
void setConstant(const char* UTILS_NONNULL name, T value) {
|
||||
setConstant<T>(name, strlen(name), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a specialization constant by name.
|
||||
*
|
||||
* @tparam T The type of the constant. Must be int32_t, float, or bool.
|
||||
* @param name The name of the constant as defined in the material. Cannot be nullptr.
|
||||
* @param nameLength Length in `char` of the name parameter.
|
||||
* @return The value of the constant.
|
||||
*/
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
T getConstant(const char* UTILS_NONNULL name, size_t nameLength) const;
|
||||
|
||||
/** inline helper to provide the name as a null-terminated C string */
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
T getConstant(StringLiteral const name) const {
|
||||
return getConstant<T>(name.data, name.size);
|
||||
}
|
||||
|
||||
/** inline helper to provide the name as a null-terminated C string */
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
T getConstant(const char* UTILS_NONNULL name) const {
|
||||
return getConstant<T>(name, strlen(name));
|
||||
}
|
||||
|
||||
using CompilerPriorityQueue = backend::CompilerPriorityQueue;
|
||||
|
||||
/**
|
||||
* Asynchronously ensures that a subset of this MaterialInstance's variants are compiled.
|
||||
*
|
||||
* This function behaves identically to Material::compile(), but takes into account the
|
||||
* specific constants overridden by setConstant().
|
||||
*
|
||||
* @param priority Which priority queue to use, LOW or HIGH.
|
||||
* @param variants Variants to include to the compile command.
|
||||
* @param handler Handler to dispatch the callback or nullptr for the default handler
|
||||
* @param callback callback called on the main thread when the compilation is done on
|
||||
* by backend.
|
||||
*
|
||||
* @see Material::compile
|
||||
* @see setConstant
|
||||
*/
|
||||
void compile(CompilerPriorityQueue priority,
|
||||
UserVariantFilterMask variants,
|
||||
backend::CallbackHandler* UTILS_NULLABLE handler = nullptr,
|
||||
utils::Invocable<void(MaterialInstance* UTILS_NONNULL)>&& callback = {}) noexcept;
|
||||
|
||||
inline void compile(CompilerPriorityQueue priority,
|
||||
UserVariantFilterBit variants,
|
||||
backend::CallbackHandler* UTILS_NULLABLE handler = nullptr,
|
||||
utils::Invocable<void(MaterialInstance* UTILS_NONNULL)>&& callback = {}) noexcept {
|
||||
compile(priority, UserVariantFilterMask(variants), handler,
|
||||
std::forward<utils::Invocable<void(MaterialInstance* UTILS_NONNULL)>>(callback));
|
||||
}
|
||||
|
||||
inline void compile(CompilerPriorityQueue priority,
|
||||
backend::CallbackHandler* UTILS_NULLABLE handler = nullptr,
|
||||
utils::Invocable<void(MaterialInstance* UTILS_NONNULL)>&& callback = {}) noexcept {
|
||||
compile(priority, UserVariantFilterBit::ALL, handler,
|
||||
std::forward<utils::Invocable<void(MaterialInstance* UTILS_NONNULL)>>(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set-up a custom scissor rectangle; by default it is disabled.
|
||||
*
|
||||
|
||||
@@ -728,10 +728,9 @@ struct VsmShadowOptions {
|
||||
|
||||
/**
|
||||
* Whether to use a 32-bits or 16-bits texture format for VSM shadow maps. 32-bits
|
||||
* precision is rarely needed, but it does reduce light leaks as well as "fading"
|
||||
* precision is rarely needed, but it does reduces light leaks as well as "fading"
|
||||
* of the shadows in some situations. Setting highPrecision to true for a single
|
||||
* shadow map will double the memory usage of all shadow maps.
|
||||
* This may not be supported on all mobile devices.
|
||||
*/
|
||||
bool highPrecision = false;
|
||||
|
||||
|
||||
@@ -44,10 +44,8 @@
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Slice.h>
|
||||
#include <utils/tribool.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
@@ -498,14 +496,6 @@ bool* Engine::getFeatureFlagPtr(char const* UTILS_NONNULL name) const noexcept {
|
||||
return downcast(this)->getFeatureFlagPtr(name);
|
||||
}
|
||||
|
||||
void Engine::compile(CompilerPriorityQueue priority, Material const* material, View const* view,
|
||||
tribool shadowReceiver, tribool skinning, CallbackHandler* handler,
|
||||
Invocable<void(Material*)>&& callback) {
|
||||
downcast(this)->compile(priority,
|
||||
downcast(material), downcast(view), shadowReceiver, skinning,
|
||||
handler, std::move(callback));
|
||||
}
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
void Engine::resetBackendState() noexcept {
|
||||
downcast(this)->resetBackendState();
|
||||
|
||||
@@ -736,7 +736,7 @@ void Froxelizer::froxelizeAssignRecordsCompress() noexcept {
|
||||
using container_type = LightRecord::bitset::container_type;
|
||||
constexpr size_t r = sizeof(container_type) / sizeof(LightGroupType);
|
||||
UTILS_UNROLL
|
||||
for (size_t i = 0; i < LightRecord::bitset::WORD_COUNT; i++) {
|
||||
for (size_t i = 0; i < LightRecord::bitset::WORLD_COUNT; i++) {
|
||||
container_type b = froxelThreadData[i * r][j];
|
||||
UTILS_UNROLL
|
||||
for (size_t k = 0; k < r; k++) {
|
||||
|
||||
@@ -182,11 +182,15 @@ Program::SpecializationConstant LocalProgramCache::getConstantImpl(
|
||||
std::string_view name) const noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
auto const& constants = mMaterial->getDefinition().specializationConstantsNameToIndex;
|
||||
auto it = constants.find(name);
|
||||
FILAMENT_CHECK_PRECONDITION(it != constants.end()) << "Constant " << name << " does not exist";
|
||||
MaterialDefinition const& definition = mMaterial->getDefinition();
|
||||
auto it = definition.specializationConstantsNameToIndex.find(name);
|
||||
if (it != definition.specializationConstantsNameToIndex.cend()) {
|
||||
return getConstantImpl(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
|
||||
}
|
||||
|
||||
return getConstantImpl(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
|
||||
CString name_cstring(name);
|
||||
PANIC_PRECONDITION("No such constant exists: %s", name_cstring.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
void LocalProgramCache::setConstants(
|
||||
@@ -236,13 +240,6 @@ void LocalProgramCache::setConstants(
|
||||
}
|
||||
}
|
||||
|
||||
void LocalProgramCache::setConstants(
|
||||
FixedCapacityVector<Program::SpecializationConstant> constants) noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
setConstantsImpl(std::move(constants));
|
||||
}
|
||||
|
||||
void LocalProgramCache::setConstantsImpl(
|
||||
FixedCapacityVector<Program::SpecializationConstant> constants) noexcept {
|
||||
FEngine& engine = mMaterial->getEngine();
|
||||
|
||||
@@ -119,14 +119,7 @@ public:
|
||||
std::pair<std::string_view, backend::Program::SpecializationConstant>>
|
||||
constants) noexcept;
|
||||
|
||||
// Set constants list directly.
|
||||
void setConstants(utils::FixedCapacityVector<backend::Program::SpecializationConstant>
|
||||
constants) noexcept;
|
||||
|
||||
private:
|
||||
// Apply any pending specialization constants. Invalidates programs as necessary.
|
||||
void flushConstants() const;
|
||||
|
||||
backend::Handle<backend::HwProgram> prepareProgramSlow(backend::DriverApi& driver,
|
||||
Variant const variant,
|
||||
backend::CompilerPriorityQueue const priorityQueue) const noexcept;
|
||||
|
||||
@@ -39,9 +39,9 @@ bool MaterialCache::MaterialKey::operator==(MaterialKey const& rhs) const noexce
|
||||
return parser == rhs.parser;
|
||||
}
|
||||
|
||||
MaterialCache::MaterialCache(uint32_t materialCapacity, uint32_t programCapacity)
|
||||
: mDefinitions("MaterialCache::mDefinitions", materialCapacity),
|
||||
mPrograms("MaterialCache::mPrograms", programCapacity) {}
|
||||
MaterialCache::MaterialCache()
|
||||
: mDefinitions("MaterialCache::mDefinitions", 0),
|
||||
mPrograms("MaterialCache::mPrograms", 0) {}
|
||||
|
||||
MaterialCache::~MaterialCache() {
|
||||
assert_invariant(mDefinitions.empty());
|
||||
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
using ProgramCache =
|
||||
utils::RefCountedMap<ProgramSpecialization, backend::Handle<backend::HwProgram>>;
|
||||
|
||||
MaterialCache(uint32_t materialCapacity, uint32_t programCapacity);
|
||||
MaterialCache();
|
||||
~MaterialCache();
|
||||
|
||||
// All reference-counted resources should be freed by the time MaterialCache is destructed, but
|
||||
|
||||
@@ -233,26 +233,6 @@ template UTILS_PUBLIC mat3f MaterialInstance::getParameter<mat3f> (const ch
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
template<typename T, typename>
|
||||
void MaterialInstance::setConstant(const char* name, size_t nameLength, T value) {
|
||||
downcast(this)->setConstantImpl(std::string_view{name, nameLength}, value);
|
||||
}
|
||||
|
||||
template UTILS_PUBLIC void MaterialInstance::setConstant<int32_t>(const char* name, size_t nameLength, int32_t value);
|
||||
template UTILS_PUBLIC void MaterialInstance::setConstant<float>(const char* name, size_t nameLength, float value);
|
||||
template UTILS_PUBLIC void MaterialInstance::setConstant<bool>(const char* name, size_t nameLength, bool value);
|
||||
|
||||
template<typename T, typename>
|
||||
T MaterialInstance::getConstant(const char* name, size_t nameLength) const {
|
||||
return downcast(this)->getConstantImpl<T>(std::string_view{name, nameLength});
|
||||
}
|
||||
|
||||
template UTILS_PUBLIC int32_t MaterialInstance::getConstant<int32_t>(const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC float MaterialInstance::getConstant<float>(const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC bool MaterialInstance::getConstant<bool>(const char* name, size_t nameLength) const;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
Material const* MaterialInstance::getMaterial() const noexcept {
|
||||
return downcast(this)->getMaterial();
|
||||
}
|
||||
@@ -423,10 +403,4 @@ void MaterialInstance::commit(Engine& engine) const {
|
||||
downcast(this)->commit(downcast(engine));
|
||||
}
|
||||
|
||||
void MaterialInstance::compile(CompilerPriorityQueue const priority,
|
||||
UserVariantFilterMask const variants, CallbackHandler* handler,
|
||||
utils::Invocable<void(MaterialInstance*)>&& callback) noexcept {
|
||||
downcast(this)->compile(priority, variants, handler, std::move(callback));
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include "materials/bloom/bloom.h"
|
||||
#include "materials/colorGrading/colorGrading.h"
|
||||
#include "materials/dof/dof.h"
|
||||
#include "materials/evsm/evsm.h"
|
||||
#include "materials/flare/flare.h"
|
||||
#include "materials/fog/fog.h"
|
||||
#include "materials/fsr/fsr.h"
|
||||
@@ -186,10 +185,12 @@ void PostProcessManager::PostProcessMaterial::loadMaterial(FEngine& engine) cons
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine) const noexcept {
|
||||
FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine,
|
||||
DriverApi& driver, Variant::type_t const variant) const noexcept {
|
||||
if (UTILS_UNLIKELY(mSize)) {
|
||||
loadMaterial(engine);
|
||||
}
|
||||
mMaterial->prepareProgram(driver, Variant{ variant }, CompilerPriorityQueue::CRITICAL);
|
||||
return mMaterial;
|
||||
}
|
||||
|
||||
@@ -247,20 +248,6 @@ void PostProcessManager::bindPerRenderableDescriptorSet(DriverApi& driver) const
|
||||
{ { 0, 0 }, driver });
|
||||
}
|
||||
|
||||
FMaterialInstance* PostProcessManager::getMaterialInstance(backend::DriverApi& driver,
|
||||
FMaterial const* ma, Variant::type_t variant) const {
|
||||
FMaterialInstance* mi = mMaterialInstanceManager.getMaterialInstance(ma);
|
||||
mi->prepareProgram(driver, Variant{ variant }, backend::CompilerPriorityQueue::CRITICAL);
|
||||
return mi;
|
||||
}
|
||||
|
||||
FMaterialInstance* PostProcessManager::getMaterialInstanceWithTag(backend::DriverApi& driver,
|
||||
FMaterial const* ma, uint32_t tag, Variant::type_t variant) const {
|
||||
FMaterialInstance* mi = mMaterialInstanceManager.getMaterialInstance(ma, tag);
|
||||
mi->prepareProgram(driver, Variant { variant }, backend::CompilerPriorityQueue::CRITICAL);
|
||||
return mi;
|
||||
}
|
||||
|
||||
UboManager* PostProcessManager::getUboManager() const noexcept {
|
||||
return mEngine.getUboManager();
|
||||
}
|
||||
@@ -310,6 +297,7 @@ static const PostProcessManager::StaticMaterialInfo sMaterialList[] = {
|
||||
{ {"arraySampler", false}, {"componentCount", 4} } },
|
||||
{ "separableGaussianBlur4L", MATERIAL(MATERIALS, SEPARABLEGAUSSIANBLUR),
|
||||
{ {"arraySampler", true }, {"componentCount", 4} } },
|
||||
{ "vsmMipmap", MATERIAL(MATERIALS, VSMMIPMAP) },
|
||||
{ "debugShadowCascades", MATERIAL(MATERIALS, DEBUGSHADOWCASCADES) },
|
||||
{ "resolveDepth", MATERIAL(MATERIALS, RESOLVEDEPTH) },
|
||||
{ "shadowmap", MATERIAL(MATERIALS, SHADOWMAP) },
|
||||
@@ -381,9 +369,6 @@ void PostProcessManager::init() noexcept {
|
||||
for (auto const& info: sMaterialList) {
|
||||
registerPostProcessMaterial(info.name, info);
|
||||
}
|
||||
for (auto const& info: getEvsmMaterialList()) {
|
||||
registerPostProcessMaterial(info.name, info);
|
||||
}
|
||||
for (auto const& info: getBloomMaterialList()) {
|
||||
registerPostProcessMaterial(info.name, info);
|
||||
}
|
||||
@@ -480,10 +465,9 @@ void PostProcessManager::unbindAllDescriptorSets(DriverApi& driver) noexcept {
|
||||
|
||||
UTILS_NOINLINE
|
||||
PipelineState PostProcessManager::getPipelineState(
|
||||
FMaterialInstance const* const mi, Variant::type_t const variant) const noexcept {
|
||||
FMaterial const* const ma = mi->getMaterial();
|
||||
FMaterial const* const ma, Variant::type_t const variant) const noexcept {
|
||||
return {
|
||||
.program = mi->getProgram(Variant{ variant }),
|
||||
.program = ma->getProgram(Variant{ variant }),
|
||||
.vertexBufferInfo = mFullScreenQuadVbih,
|
||||
.pipelineLayout = {
|
||||
.setLayout = {
|
||||
@@ -491,7 +475,7 @@ PipelineState PostProcessManager::getPipelineState(
|
||||
mPerRenderableDslh,
|
||||
ma->getDescriptorSetLayout().getHandle()
|
||||
}},
|
||||
.rasterState = mi->getRasterState()
|
||||
.rasterState = ma->getRasterState()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -531,7 +515,8 @@ void PostProcessManager::commitAndRenderFullScreenQuad(DriverApi& driver,
|
||||
PostProcessVariant const variant) const noexcept {
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
PipelineState const pipeline = getPipelineState(mi, variant);
|
||||
FMaterial const* const ma = mi->getMaterial();
|
||||
PipelineState const pipeline = getPipelineState(ma, variant);
|
||||
|
||||
assert_invariant(
|
||||
((out.params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH)
|
||||
@@ -648,12 +633,12 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
|
||||
|
||||
auto in = resources.getTexture(data.depth);
|
||||
auto& material = getPostProcessMaterial("mipmapDepth");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
FMaterialInstance* const mi = getMaterialInstance(ma);
|
||||
// Only the depth texture is changing in the material instance (no UBO updates),
|
||||
// we do not move getMaterialInstance() inside the loop.
|
||||
|
||||
auto pipeline = getPipelineState(mi);
|
||||
auto pipeline = getPipelineState(ma);
|
||||
|
||||
// The first mip already exists, so we process n-1 lods
|
||||
for (size_t level = 0; level < levelCount - 1; level++) {
|
||||
@@ -1013,13 +998,14 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
|
||||
#endif
|
||||
auto& material = getPostProcessMaterial(materialName);
|
||||
|
||||
FMaterial* ma = material.getMaterial(mEngine);
|
||||
FMaterial* ma = material.getMaterial(mEngine, driver);
|
||||
ma->getPrograms().setConstants({
|
||||
{ "useVisibilityBitmasks", options.gtao.useVisibilityBitmasks },
|
||||
{ "linearThickness", options.gtao.linearThickness },
|
||||
});
|
||||
|
||||
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
|
||||
ma = material.getMaterial(mEngine, driver);
|
||||
FMaterialInstance* const mi = getMaterialInstance(ma);
|
||||
|
||||
// Set AO type specific material parameters
|
||||
switch (aoType) {
|
||||
@@ -1095,7 +1081,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(mi);
|
||||
auto pipeline = getPipelineState(ma);
|
||||
pipeline.rasterState.depthFunc = RasterState::DepthFunc::L;
|
||||
assert_invariant(ssao.params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH);
|
||||
renderFullScreenQuad(ssao, pipeline, driver);
|
||||
@@ -1205,8 +1191,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
|
||||
auto& material = config.bentNormals ?
|
||||
getPostProcessMaterial("bilateralBlurBentNormals") :
|
||||
getPostProcessMaterial("bilateralBlur");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
FMaterialInstance* const mi = getMaterialInstance(ma);
|
||||
mi->setParameter("ssao", ssao, { /* only reads level 0 */ });
|
||||
mi->setParameter("axis", axis / float2{desc.width, desc.height});
|
||||
mi->setParameter("kernel", kGaussianSamples, kGaussianCount);
|
||||
@@ -1216,7 +1202,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(mi);
|
||||
auto pipeline = getPipelineState(ma);
|
||||
pipeline.rasterState.depthFunc = RasterState::DepthFunc::L;
|
||||
renderFullScreenQuad(blurred, pipeline, driver);
|
||||
unbindAllDescriptorSets(driver);
|
||||
@@ -1263,8 +1249,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::generateGaussianMipmap(Frame
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> const input,
|
||||
FrameGraphId<FrameGraphTexture> output,
|
||||
bool const reinhard, size_t kernelWidth, const float sigma,
|
||||
std::optional<backend::Viewport> scissor) noexcept {
|
||||
bool const reinhard, size_t kernelWidth, const float sigma) noexcept {
|
||||
|
||||
auto computeGaussianCoefficients =
|
||||
[kernelWidth, sigma](float2* kernel, size_t const size) -> size_t {
|
||||
@@ -1375,7 +1360,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
|
||||
"separableGaussianBlur4L"sv : "separableGaussianBlur4"sv; break;
|
||||
}
|
||||
auto const& separableGaussianBlur = getPostProcessMaterial(materialName);
|
||||
auto ma = separableGaussianBlur.getMaterial(mEngine);
|
||||
auto ma = separableGaussianBlur.getMaterial(mEngine, driver);
|
||||
|
||||
const size_t kernelStorageSize = ma->reflect("kernel")->size;
|
||||
float2 kernel[64];
|
||||
@@ -1402,10 +1387,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
|
||||
{
|
||||
// horizontal pass
|
||||
auto mi = getMaterialInstance(mEngine, driver, separableGaussianBlur);
|
||||
if (scissor.has_value()) {
|
||||
mi->setScissor(scissor.value());
|
||||
}
|
||||
|
||||
setCommonParams(mi);
|
||||
mi->setParameter(sourceParameterName, hwIn, SamplerParams{
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
@@ -1424,9 +1405,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
|
||||
{
|
||||
// vertical pass
|
||||
auto mi = getMaterialInstance(mEngine, driver, separableGaussianBlur);
|
||||
if (scissor.has_value()) {
|
||||
mi->setScissor(scissor.value());
|
||||
}
|
||||
setCommonParams(mi);
|
||||
UTILS_UNUSED_IN_RELEASE auto width = outDesc.width;
|
||||
UTILS_UNUSED_IN_RELEASE auto height = outDesc.height;
|
||||
@@ -1898,10 +1876,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
auto inOutCoc = resources.getTexture(data.inOutCoc);
|
||||
|
||||
auto const& material = getPostProcessMaterial("dofMipmap");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
|
||||
auto const pipeline = getPipelineState(mi, variant);
|
||||
auto const pipeline = getPipelineState(ma, variant);
|
||||
|
||||
for (size_t level = 0 ; level < mipmapCount - 1u ; level++) {
|
||||
const float w = FTexture::valueForLevel(level, desc.width);
|
||||
@@ -1909,8 +1886,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
auto const& out = resources.getRenderPassInfo(data.rp[level]);
|
||||
auto inColor = driver.createTextureView(inOutColor, level, 1);
|
||||
auto inCoc = driver.createTextureView(inOutCoc, level, 1);
|
||||
// FIXME: is this necessary?
|
||||
FMaterialInstance* const mi = getMaterialInstance(driver, ma);
|
||||
FMaterialInstance* const mi = getMaterialInstance(ma);
|
||||
|
||||
mi->setParameter("color", inColor, SamplerParams{
|
||||
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
|
||||
@@ -2439,9 +2415,9 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
auto const& outDesc = resources.getDescriptor(data.out);
|
||||
|
||||
auto const& material = getPostProcessMaterial("bloomUpsample");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
|
||||
auto pipeline = getPipelineState(getMaterialInstance(driver, ma));
|
||||
auto pipeline = getPipelineState(ma);
|
||||
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE;
|
||||
|
||||
@@ -2449,7 +2425,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
// Note that we wouldn't want to use the same instance for each pass since that
|
||||
// would imply using the same UBOs, which implies synchronization across the
|
||||
// passes.
|
||||
FMaterialInstance* mi = getMaterialInstance(driver, ma);
|
||||
FMaterialInstance* mi = getMaterialInstance(ma);
|
||||
auto hwDstRT = resources.getRenderPassInfo(data.outRT[i - 1]);
|
||||
hwDstRT.params.flags.discardStart = TargetBufferFlags::NONE; // b/c we'll blend
|
||||
hwDstRT.params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
@@ -2582,12 +2558,11 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver,
|
||||
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE;
|
||||
|
||||
auto const& material = getPostProcessMaterial("colorGradingAsSubpass");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver, variant);
|
||||
// the UBO has been set and committed in colorGradingPrepareSubpass()
|
||||
FMaterialInstance const* mi =
|
||||
getMaterialInstanceWithTag(driver, ma, colorGradingConfig.translucent, variant);
|
||||
FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma, colorGradingConfig.translucent);
|
||||
mi->use(driver);
|
||||
auto const pipeline = getPipelineState(mi, variant);
|
||||
auto const pipeline = getPipelineState(ma, variant);
|
||||
driver.nextSubpass();
|
||||
driver.scissor(mi->getScissor());
|
||||
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
|
||||
@@ -2595,8 +2570,8 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver,
|
||||
|
||||
void PostProcessManager::customResolvePrepareSubpass(DriverApi& driver, CustomResolveOp const op) noexcept {
|
||||
auto const& material = getPostProcessMaterial("customResolveAsSubpass");
|
||||
auto const ma = material.getMaterial(mEngine);
|
||||
auto* const mi = getMaterialInstance(driver, ma, 0);
|
||||
auto const ma = material.getMaterial(mEngine, driver, PostProcessVariant::OPAQUE);
|
||||
auto* const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
|
||||
mi->setParameter("direction", op == CustomResolveOp::COMPRESS ? 1.0f : -1.0f),
|
||||
mi->commit(driver, getUboManager());
|
||||
}
|
||||
@@ -2606,12 +2581,12 @@ void PostProcessManager::customResolveSubpass(DriverApi& driver) noexcept {
|
||||
bindPerRenderableDescriptorSet(driver);
|
||||
|
||||
auto const& material = getPostProcessMaterial("customResolveAsSubpass");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
// the UBO has been set and committed in customResolvePrepareSubpass()
|
||||
FMaterialInstance const* mi = getMaterialInstance(driver, ma, 0);
|
||||
FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
|
||||
mi->use(driver);
|
||||
|
||||
auto const pipeline = getPipelineState(mi);
|
||||
auto const pipeline = getPipelineState(ma);
|
||||
driver.nextSubpass();
|
||||
driver.scissor(mi->getScissor());
|
||||
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
|
||||
@@ -2647,8 +2622,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::customResolveUncompressPass(
|
||||
void PostProcessManager::clearAncillaryBuffersPrepare(DriverApi& driver,
|
||||
Variant::type_t variant) noexcept {
|
||||
auto const& material = getPostProcessMaterial("clearDepth");
|
||||
auto const ma = material.getMaterial(mEngine);
|
||||
auto const mi = getMaterialInstanceWithTag(driver, ma, 0, variant);
|
||||
auto const ma = material.getMaterial(mEngine, driver, variant);
|
||||
auto const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
|
||||
mi->commit(driver, getUboManager());
|
||||
}
|
||||
|
||||
@@ -2663,13 +2638,13 @@ void PostProcessManager::clearAncillaryBuffers(DriverApi& driver,
|
||||
bindPerRenderableDescriptorSet(driver);
|
||||
|
||||
auto const& material = getPostProcessMaterial("clearDepth");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver, variant);
|
||||
|
||||
// the UBO has been set and committed in clearAncillaryBuffersPrepare()
|
||||
FMaterialInstance const* const mi = getMaterialInstanceWithTag(driver, ma, 0, variant);
|
||||
FMaterialInstance const* const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(mi, variant);
|
||||
auto pipeline = getPipelineState(ma, variant);
|
||||
pipeline.rasterState.depthFunc = RasterState::DepthFunc::A;
|
||||
|
||||
driver.scissor(mi->getScissor());
|
||||
@@ -2679,7 +2654,7 @@ void PostProcessManager::clearAncillaryBuffers(DriverApi& driver,
|
||||
void PostProcessManager::fogPrepare(DriverApi& driver) noexcept {
|
||||
// ensures the material is loaded and material instance created
|
||||
auto const& material = getPostProcessMaterial("fog");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver, PostProcessVariant::OPAQUE);
|
||||
FMaterialInstance const* mi = ma->getDefaultInstance();
|
||||
mi->commit(driver, getUboManager());
|
||||
}
|
||||
@@ -2690,11 +2665,11 @@ void PostProcessManager::fog(DriverApi& driver) noexcept {
|
||||
bindPerRenderableDescriptorSet(driver);
|
||||
|
||||
auto const& material = getPostProcessMaterial("fog");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
FMaterialInstance const* mi = ma->getDefaultInstance();
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(mi, Variant::NO_VARIANT);
|
||||
auto pipeline = getPipelineState(ma, Variant::NO_VARIANT);
|
||||
driver.scissor(mi->getScissor());
|
||||
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
|
||||
}
|
||||
@@ -2951,7 +2926,7 @@ void PostProcessManager::TaaJitterCamera(
|
||||
void PostProcessManager::configureTemporalAntiAliasingMaterial(backend::DriverApi& driver,
|
||||
TemporalAntiAliasingOptions const& taaOptions) noexcept {
|
||||
|
||||
FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine);
|
||||
FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine, driver);
|
||||
ma->getPrograms().setConstants({
|
||||
{ "upscaling", taaOptions.upscaling > 1.0f },
|
||||
{ "historyReprojection", taaOptions.historyReprojection },
|
||||
@@ -2970,7 +2945,7 @@ FMaterialInstance* PostProcessManager::configureColorGradingMaterial(backend::Dr
|
||||
PostProcessMaterial const& material, FColorGrading const* colorGrading,
|
||||
ColorGradingConfig const& colorGradingConfig, VignetteOptions const& vignetteOptions,
|
||||
uint32_t const width, uint32_t const height) noexcept {
|
||||
FMaterial* ma = material.getMaterial(mEngine);
|
||||
FMaterial* ma = material.getMaterial(mEngine, driver);
|
||||
ma->getPrograms().setConstants({
|
||||
{ "isOneDimensional", colorGrading->isOneDimensional() },
|
||||
{ "isLDR", colorGrading->isLDR() },
|
||||
@@ -2979,9 +2954,8 @@ FMaterialInstance* PostProcessManager::configureColorGradingMaterial(backend::Dr
|
||||
PostProcessVariant const variant = colorGradingConfig.translucent
|
||||
? PostProcessVariant::TRANSLUCENT
|
||||
: PostProcessVariant::OPAQUE;
|
||||
ma = material.getMaterial(mEngine);
|
||||
FMaterialInstance* mi =
|
||||
getMaterialInstanceWithTag(driver, ma, colorGradingConfig.translucent, variant);
|
||||
ma = material.getMaterial(mEngine, driver, variant);
|
||||
FMaterialInstance* mi = mMaterialInstanceManager.getMaterialInstance(ma, colorGradingConfig.translucent);
|
||||
|
||||
const SamplerParams params = SamplerParams{
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
@@ -3093,9 +3067,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
PostProcessVariant const variant = colorGradingConfig.translucent ?
|
||||
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE;
|
||||
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver, variant);
|
||||
|
||||
FMaterialInstance* mi = getMaterialInstance(driver, ma);
|
||||
FMaterialInstance* mi = getMaterialInstance(ma);
|
||||
mi->setParameter("color", color, SamplerParams{}); // nearest
|
||||
mi->setParameter("depth", depth, SamplerParams{}); // nearest
|
||||
mi->setParameter("history", history, SamplerParams{
|
||||
@@ -3135,7 +3109,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
if (colorGradingConfig.asSubpass) {
|
||||
out.params.subpassMask = 1;
|
||||
}
|
||||
auto const pipeline = getPipelineState(mi, variant);
|
||||
auto const pipeline = getPipelineState(ma, variant);
|
||||
|
||||
driver.beginRenderPass(out.target, out.params);
|
||||
driver.draw(pipeline, mFullScreenQuadRph, 0, 3, 1);
|
||||
@@ -3217,7 +3191,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::rcas(
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(mi, variant);
|
||||
auto pipeline = getPipelineState(material.getMaterial(mEngine, driver), variant);
|
||||
if (mode == RcasMode::BLENDED) {
|
||||
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
|
||||
@@ -3310,7 +3284,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
|
||||
|
||||
auto out = resources.getRenderPassInfo();
|
||||
|
||||
auto pipeline = getPipelineState(mi);
|
||||
auto pipeline = getPipelineState(material.getMaterial(mEngine, driver));
|
||||
if (blended) {
|
||||
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
|
||||
@@ -3524,15 +3498,15 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleFSR1(FrameGraph& fg,
|
||||
auto out = resources.getRenderPassInfo();
|
||||
|
||||
if (UTILS_UNLIKELY(twoPassesEASU)) {
|
||||
auto pipeline0 = getPipelineState(getMaterialInstance(mEngine, driver, *splitEasuMaterial));
|
||||
auto pipeline1 = getPipelineState(getMaterialInstance(mEngine, driver, *easuMaterial));
|
||||
auto pipeline0 = getPipelineState(splitEasuMaterial->getMaterial(mEngine, driver));
|
||||
auto pipeline1 = getPipelineState(easuMaterial->getMaterial(mEngine, driver));
|
||||
pipeline1.rasterState.depthFunc = SamplerCompareFunc::NE;
|
||||
driver.beginRenderPass(out.target, out.params);
|
||||
driver.draw(pipeline0, mFullScreenQuadRph, 0, 3, 1);
|
||||
driver.draw(pipeline1, mFullScreenQuadRph, 0, 3, 1);
|
||||
driver.endRenderPass();
|
||||
} else {
|
||||
auto pipeline = getPipelineState(getMaterialInstance(mEngine, driver, *easuMaterial));
|
||||
auto pipeline = getPipelineState(easuMaterial->getMaterial(mEngine, driver));
|
||||
renderFullScreenQuad(out, pipeline, driver);
|
||||
}
|
||||
unbindAllDescriptorSets(driver);
|
||||
@@ -3584,8 +3558,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool co
|
||||
|
||||
PostProcessMaterial const& material =
|
||||
getPostProcessMaterial(layer ? "blitArray" : "blitLow");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
auto* mi = getMaterialInstance(driver, ma);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
auto* mi = getMaterialInstance(ma);
|
||||
mi->setParameter("color", color, SamplerParams{
|
||||
.filterMag = filterMag,
|
||||
.filterMin = filterMin
|
||||
@@ -3603,7 +3577,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool co
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(mi);
|
||||
auto pipeline = getPipelineState(ma);
|
||||
if (translucent) {
|
||||
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
|
||||
@@ -3807,6 +3781,67 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepthWithShader(Frame
|
||||
return ppResolve->output;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> const input, uint8_t layer, size_t const level,
|
||||
float4 clearColor) noexcept {
|
||||
|
||||
struct VsmMipData {
|
||||
FrameGraphId<FrameGraphTexture> in;
|
||||
};
|
||||
|
||||
auto const& depthMipmapPass = fg.addPass<VsmMipData>("VSM Generate Mipmap Pass",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
StaticString const name = builder.getName(input);
|
||||
data.in = builder.sample(input);
|
||||
|
||||
auto out = builder.createSubresource(data.in, "Mip level", {
|
||||
.level = uint8_t(level + 1), .layer = layer });
|
||||
|
||||
out = builder.write(out, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
builder.declareRenderPass(name, {
|
||||
.attachments = { .color = { out }},
|
||||
.clearColor = clearColor,
|
||||
.clearFlags = TargetBufferFlags::COLOR
|
||||
});
|
||||
},
|
||||
[=, this](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
bindPostProcessDescriptorSet(driver);
|
||||
bindPerRenderableDescriptorSet(driver);
|
||||
|
||||
auto in = driver.createTextureView(resources.getTexture(data.in), level, 1);
|
||||
auto out = resources.getRenderPassInfo();
|
||||
|
||||
auto const& inDesc = resources.getDescriptor(data.in);
|
||||
auto width = inDesc.width;
|
||||
assert_invariant(width == inDesc.height);
|
||||
uint32_t const dim = std::max(1u, width >> (level + 1));
|
||||
|
||||
auto& material = getPostProcessMaterial("vsmMipmap");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
|
||||
auto const pipeline = getPipelineState(ma);
|
||||
backend::Viewport const scissor = { 0, 0, dim, dim };
|
||||
|
||||
FMaterialInstance* const mi = getMaterialInstance(ma);
|
||||
mi->setParameter("color", in, SamplerParams{
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
mi->setParameter("layer", uint32_t(layer));
|
||||
mi->setParameter("uvscale", 1.0f / float(dim));
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
renderFullScreenQuadWithScissor(out, pipeline, scissor, driver);
|
||||
unbindAllDescriptorSets(driver);
|
||||
|
||||
driver.destroyTexture(in); // `in` is just a view on `data.in`
|
||||
});
|
||||
|
||||
return depthMipmapPass->in;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::debugShadowCascades(FrameGraph& fg,
|
||||
ShadowMapManager const& smm,
|
||||
FrameGraphId<FrameGraphTexture> const input,
|
||||
@@ -3898,10 +3933,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
|
||||
// set uniforms
|
||||
|
||||
PostProcessMaterial const& material = getPostProcessMaterial("blitArray");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine);
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
// It should be ok to not move this getMaterialInstance to inside the loop, since
|
||||
// this is a pass meant for debug.
|
||||
auto* mi = getMaterialInstance(driver, ma);
|
||||
auto* mi = getMaterialInstance(ma);
|
||||
mi->setParameter("color", color, SamplerParams{
|
||||
.filterMag = filterMag,
|
||||
.filterMin = filterMin
|
||||
@@ -3915,7 +3950,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(mi);
|
||||
auto pipeline = getPipelineState(ma);
|
||||
if (translucent) {
|
||||
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
|
||||
|
||||
@@ -312,11 +312,15 @@ public:
|
||||
utils::StaticString outputBufferName, FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor outDesc) noexcept;
|
||||
|
||||
// VSM shadow mipmap pass
|
||||
FrameGraphId<FrameGraphTexture> vsmMipmapPass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, uint8_t layer, size_t level,
|
||||
math::float4 clearColor) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> gaussianBlurPass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> output,
|
||||
bool reinhard, size_t kernelWidth, float sigma,
|
||||
std::optional<backend::Viewport> scissor = {}) noexcept;
|
||||
bool reinhard, size_t kernelWidth, float sigma) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> debugShadowCascades(FrameGraph& fg,
|
||||
ShadowMapManager const& smm,
|
||||
@@ -356,7 +360,13 @@ public:
|
||||
|
||||
void terminate(FEngine& engine) noexcept;
|
||||
|
||||
FMaterial* getMaterial(FEngine& engine) const noexcept;
|
||||
FMaterial* getMaterial(FEngine& engine, backend::DriverApi& driver,
|
||||
Variant::type_t variant) const noexcept;
|
||||
|
||||
FMaterial* getMaterial(FEngine& engine, backend::DriverApi& driver,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept {
|
||||
return getMaterial(engine, driver, Variant::type_t(variant));
|
||||
}
|
||||
|
||||
private:
|
||||
void loadMaterial(FEngine& engine) const noexcept;
|
||||
@@ -381,14 +391,11 @@ public:
|
||||
|
||||
void bindPostProcessDescriptorSet(backend::DriverApi& driver) const noexcept;
|
||||
|
||||
void bindPerRenderableDescriptorSet(backend::DriverApi& driver) const noexcept;
|
||||
backend::PipelineState getPipelineState(FMaterial const* ma, Variant::type_t variant) const noexcept;
|
||||
|
||||
backend::PipelineState getPipelineState(FMaterialInstance const* mi,
|
||||
Variant::type_t variant) const noexcept;
|
||||
|
||||
backend::PipelineState getPipelineState(FMaterialInstance const* mi,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept {
|
||||
return getPipelineState(mi, Variant::type_t(variant));
|
||||
backend::PipelineState getPipelineState(FMaterial const* ma,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept {
|
||||
return getPipelineState(ma, Variant::type_t(variant));
|
||||
}
|
||||
|
||||
void renderFullScreenQuad(FrameGraphResources::RenderPassInfo const& out,
|
||||
@@ -418,45 +425,22 @@ public:
|
||||
|
||||
void resetForRender();
|
||||
|
||||
MaterialInstanceManager& getMaterialInstanceManager() noexcept {
|
||||
return mMaterialInstanceManager;
|
||||
}
|
||||
|
||||
private:
|
||||
static void unbindAllDescriptorSets(backend::DriverApi& driver) noexcept;
|
||||
|
||||
private:
|
||||
void bindPerRenderableDescriptorSet(backend::DriverApi& driver) const noexcept;
|
||||
|
||||
// Helpers to get MaterialInstances.
|
||||
//
|
||||
// These funcions additionally ensure that the necessary shader programs are compiled via
|
||||
// prepareProgram().
|
||||
FMaterialInstance* getMaterialInstance(backend::DriverApi& driver, FMaterial const* ma,
|
||||
Variant::type_t variant) const;
|
||||
|
||||
FMaterialInstance* getMaterialInstance(backend::DriverApi& driver, FMaterial const* ma,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) const {
|
||||
return getMaterialInstance(driver, ma, Variant::type_t(variant));
|
||||
// Helper to get a MaterialInstance from a FMaterial
|
||||
// This currently just call FMaterial::getDefaultInstance().
|
||||
FMaterialInstance* getMaterialInstance(FMaterial const* ma) {
|
||||
return mMaterialInstanceManager.getMaterialInstance(ma);
|
||||
}
|
||||
|
||||
FMaterialInstance* getMaterialInstance(FEngine& engine, backend::DriverApi& driver,
|
||||
PostProcessMaterial const& material,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) const {
|
||||
return getMaterialInstance(driver, material.getMaterial(engine), Variant::type_t(variant));
|
||||
}
|
||||
|
||||
FMaterialInstance* getMaterialInstanceWithTag(backend::DriverApi& driver, FMaterial const* ma,
|
||||
uint32_t tag, Variant::type_t variant) const;
|
||||
|
||||
FMaterialInstance* getMaterialInstanceWithTag(backend::DriverApi& driver, FMaterial const* ma,
|
||||
uint32_t tag, PostProcessVariant variant = PostProcessVariant::OPAQUE) const {
|
||||
return getMaterialInstanceWithTag(driver, ma, tag, Variant::type_t(variant));
|
||||
}
|
||||
|
||||
FMaterialInstance* getMaterialInstanceWithTag(FEngine& engine, backend::DriverApi& driver,
|
||||
PostProcessMaterial const& material, uint32_t tag,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) const {
|
||||
return getMaterialInstanceWithTag(driver, material.getMaterial(engine), tag,
|
||||
Variant::type_t(variant));
|
||||
// Helper to get a MaterialInstance from a PostProcessMaterial.
|
||||
FMaterialInstance* getMaterialInstance(FEngine& engine, backend::DriverApi& driver, PostProcessMaterial const& material,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) {
|
||||
FMaterial const* ma = material.getMaterial(engine, driver, variant);
|
||||
return getMaterialInstance(ma);
|
||||
}
|
||||
|
||||
UboManager* getUboManager() const noexcept;
|
||||
|
||||
@@ -243,8 +243,8 @@ void RenderPass::appendCommands(FEngine const& engine, backend::DriverApi& drive
|
||||
// This must be done from the main thread.
|
||||
for (Command const* first = curr, *last = curr + commandCount ; first != last ; ++first) {
|
||||
if (UTILS_LIKELY((first->key & CUSTOM_MASK) == uint64_t(CustomCommand::PASS))) {
|
||||
first->info.mi->prepareProgram(driver, first->info.materialVariant,
|
||||
CompilerPriorityQueue::CRITICAL);
|
||||
auto ma = first->info.mi->getMaterial();
|
||||
ma->prepareProgram(driver, first->info.materialVariant, CompilerPriorityQueue::CRITICAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,18 +426,16 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
|
||||
bool const isBlendingCommand = !hasScreenSpaceRefraction &&
|
||||
(blendingMode != BlendingMode::OPAQUE && blendingMode != BlendingMode::MASKED);
|
||||
|
||||
RasterState rasterState = mi->getRasterState();
|
||||
|
||||
uint64_t keyDraw = cmdDraw.key;
|
||||
keyDraw &= ~(PASS_MASK | BLENDING_MASK | MATERIAL_MASK);
|
||||
keyDraw |= uint64_t(hasScreenSpaceRefraction ? Pass::REFRACT : Pass::COLOR);
|
||||
keyDraw |= uint64_t(CustomCommand::PASS);
|
||||
keyDraw |= mi->getSortingKey(); // already all set-up for direct or'ing
|
||||
keyDraw |= makeField(variant.key, MATERIAL_VARIANT_KEY_MASK, MATERIAL_VARIANT_KEY_SHIFT);
|
||||
keyDraw |= makeField(rasterState.alphaToCoverage, BLENDING_MASK, BLENDING_SHIFT);
|
||||
keyDraw |= makeField(ma->getRasterState().alphaToCoverage, BLENDING_MASK, BLENDING_SHIFT);
|
||||
|
||||
cmdDraw.key = isBlendingCommand ? keyBlending : keyDraw;
|
||||
cmdDraw.info.rasterState = rasterState;
|
||||
cmdDraw.info.rasterState = ma->getRasterState();
|
||||
|
||||
// for SSR pass, the blending mode of opaques (including MASKED) must be off
|
||||
// see Material.cpp.
|
||||
@@ -448,6 +446,10 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
|
||||
BlendFunction::ZERO : cmdDraw.info.rasterState.blendFunctionDstAlpha;
|
||||
|
||||
cmdDraw.info.rasterState.inverseFrontFaces = inverseFrontFaces;
|
||||
cmdDraw.info.rasterState.culling = mi->getCullingMode();
|
||||
cmdDraw.info.rasterState.colorWrite = mi->isColorWriteEnabled();
|
||||
cmdDraw.info.rasterState.depthWrite = mi->isDepthWriteEnabled();
|
||||
cmdDraw.info.rasterState.depthFunc = mi->getDepthFunc();
|
||||
cmdDraw.info.rasterState.depthClamp = hasDepthClamp;
|
||||
cmdDraw.info.materialVariant = variant;
|
||||
// we keep "RasterState::colorWrite" to the value set by material (could be disabled)
|
||||
@@ -1088,7 +1090,8 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
|
||||
mi->use(driver, info.materialVariant);
|
||||
}
|
||||
|
||||
pipeline.program = mi->getProgram(info.materialVariant);
|
||||
assert_invariant(ma);
|
||||
pipeline.program = ma->getProgram(info.materialVariant);
|
||||
|
||||
if (UTILS_UNLIKELY(memcmp(&pipeline, ¤tPipeline, sizeof(PipelineState)) != 0)) {
|
||||
currentPipeline = pipeline;
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
#include "details/Camera.h"
|
||||
#include "details/DebugRegistry.h"
|
||||
#include "details/MaterialInstance.h"
|
||||
#include "details/Texture.h"
|
||||
#include "details/View.h"
|
||||
|
||||
@@ -43,7 +42,6 @@
|
||||
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/PipelineState.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
@@ -55,7 +53,6 @@
|
||||
#include <math/half.h>
|
||||
#include <math/mat4.h>
|
||||
#include <math/vec4.h>
|
||||
#include <math/vec2.h>
|
||||
#include <math/scalar.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -66,17 +63,14 @@
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace backend;
|
||||
using namespace math;
|
||||
using namespace utils;
|
||||
|
||||
ShadowMapManager::ShadowMapManager(FEngine& engine)
|
||||
: mIsDepthClampSupported(engine.getDriverApi().isDepthClampSupported()),
|
||||
@@ -254,13 +248,13 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
struct ShadowPass { // 112 bytes
|
||||
mutable RenderPass::Executor executor;
|
||||
ShadowMap* shadowMap;
|
||||
Range<uint32_t> range;
|
||||
utils::Range<uint32_t> range;
|
||||
FScene::VisibleMaskType visibilityMask;
|
||||
};
|
||||
// the actual shadow map atlas (currently a 2D texture array)
|
||||
FrameGraphId<FrameGraphTexture> shadows;
|
||||
// a RenderPass per shadow map
|
||||
FixedCapacityVector<ShadowPass> passList;
|
||||
utils::FixedCapacityVector<ShadowPass> passList;
|
||||
};
|
||||
|
||||
auto& prepareShadowPass = fg.addPass<PrepareShadowPassData>("Prepare Shadow Pass",
|
||||
@@ -459,6 +453,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
fg.getBlackboard()["shadowmap"] = prepareShadowPass->shadows;
|
||||
|
||||
struct ShadowPassData {
|
||||
FrameGraphId<FrameGraphTexture> tempBlurSrc{}; // temporary shadowmap when blurring
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
uint32_t rt{};
|
||||
};
|
||||
@@ -469,7 +464,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
auto const& entry = *first;
|
||||
|
||||
const uint8_t layer = entry.shadowMap->getLayer();
|
||||
const auto& options = entry.shadowMap->getShadowOptions();
|
||||
const auto msaaSamples = textureRequirements.msaaSamples;
|
||||
const bool blur = entry.shadowMap->hasVisibleShadows() &&
|
||||
view.hasVSM() && options.vsm.blurWidth > 0.0f;
|
||||
|
||||
auto last = first;
|
||||
// loop over each shadow pass to find its layer range
|
||||
@@ -477,7 +475,8 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
++last;
|
||||
}
|
||||
|
||||
assert_invariant(mFeatureShadowAllocator || std::distance(first, last) == 1);
|
||||
assert_invariant(mFeatureShadowAllocator ||
|
||||
std::distance(first, last) == 1);
|
||||
|
||||
// And render all shadow pass of a given layer as a single render pass
|
||||
auto& shadowPass = fg.addPass<ShadowPassData>("Shadow Pass",
|
||||
@@ -512,6 +511,28 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
// we need to clear the shadow map with the max EVSM moments
|
||||
renderTargetDesc.clearColor = textureRequirements.clearColor;
|
||||
renderTargetDesc.samples = msaaSamples;
|
||||
|
||||
if (UTILS_UNLIKELY(blur)) {
|
||||
// Temporary (resolved) texture used to render the shadowmap when blurring
|
||||
// is needed -- it'll be used as the source of the blur.
|
||||
data.tempBlurSrc = builder.createTexture("Temporary Shadowmap", {
|
||||
.width = textureRequirements.size,
|
||||
.height = textureRequirements.size,
|
||||
.format = textureRequirements.format
|
||||
});
|
||||
|
||||
data.tempBlurSrc = builder.write(data.tempBlurSrc,
|
||||
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
|
||||
data.rt = builder.declareRenderPass("Temp Shadow RT", {
|
||||
.attachments = {
|
||||
.color = { data.tempBlurSrc },
|
||||
.depth = depth },
|
||||
.clearColor = textureRequirements.clearColor,
|
||||
.samples = msaaSamples,
|
||||
.clearFlags = TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// the shadowmap layer
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
@@ -520,7 +541,11 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
}
|
||||
|
||||
// finally, create the shadowmap render target -- one per layer.
|
||||
data.rt = builder.declareRenderPass("Shadow RT", renderTargetDesc);
|
||||
auto rt = builder.declareRenderPass("Shadow RT", renderTargetDesc);
|
||||
|
||||
// render either directly into the shadowmap, or to the temporary texture for
|
||||
// blurring.
|
||||
data.rt = blur ? data.rt : rt;
|
||||
},
|
||||
[=, &engine](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
@@ -555,21 +580,27 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
|
||||
});
|
||||
|
||||
first = last;
|
||||
|
||||
// now emit the blurring passes if needed
|
||||
if (UTILS_UNLIKELY(view.hasVSM())) {
|
||||
auto list = FixedCapacityVector<ShadowMap const*>::with_capacity(std::distance(first, last));
|
||||
for (auto curr = first; curr != last; curr++) {
|
||||
auto const sm = curr->shadowMap;
|
||||
const float blurWidth = sm->getShadowOptions().vsm.blurWidth;
|
||||
if (sm->hasVisibleShadows() && blurWidth > 0.0f) {
|
||||
list.push_back(sm);
|
||||
}
|
||||
}
|
||||
if (!list.empty()) {
|
||||
// horizontal passes...
|
||||
auto const horizontal = gaussianBlurSeparatedPass(engine, fg, shadowPass->output, {}, list, {1, 0});
|
||||
// vertical passes...
|
||||
gaussianBlurSeparatedPass(engine, fg, horizontal, shadowPass->output, std::move(list), {0, 1});
|
||||
if (UTILS_UNLIKELY(blur)) {
|
||||
auto& ppm = engine.getPostProcessManager();
|
||||
|
||||
// FIXME: this `options` is for the first shadowmap in the list, but it applies to
|
||||
// the whole layer. Blurring should happen per shadowmap, not for the whole
|
||||
// layer.
|
||||
|
||||
// FIXME: this Gaussian blur is not precise enough for EVSM
|
||||
|
||||
const float blurWidth = options.vsm.blurWidth;
|
||||
if (blurWidth > 0.0f) {
|
||||
const float sigma = (blurWidth + 1.0f) / 6.0f;
|
||||
size_t kernelWidth = size_t(std::ceil((blurWidth - 5.0f) / 4.0f));
|
||||
kernelWidth = kernelWidth * 4 + 5;
|
||||
ppm.gaussianBlurPass(fg,
|
||||
shadowPass->tempBlurSrc,
|
||||
shadowPass->output,
|
||||
false, kernelWidth, sigma);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,192 +608,16 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
// or indirectly via anisotropic filtering.
|
||||
// So generate the mipmaps for each layer
|
||||
if (UTILS_UNLIKELY(textureRequirements.levels > 1)) {
|
||||
auto& ppm = engine.getPostProcessManager();
|
||||
for (size_t level = 0; level < textureRequirements.levels - 1; level++) {
|
||||
// TODO: we could take a list of scissor maybe
|
||||
vsmMipmapPass(engine, fg, prepareShadowPass->shadows, layer, level, textureRequirements.clearColor);
|
||||
ppm.vsmMipmapPass(fg, prepareShadowPass->shadows, layer, level, textureRequirements.clearColor);
|
||||
}
|
||||
}
|
||||
|
||||
// next batch of passes
|
||||
first = last;
|
||||
}
|
||||
|
||||
return prepareShadowPass->shadows;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> ShadowMapManager::gaussianBlurSeparatedPass(
|
||||
FEngine& engine,
|
||||
FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> const input,
|
||||
FrameGraphId<FrameGraphTexture> output,
|
||||
FixedCapacityVector<ShadowMap const*> shadowMapList,
|
||||
int2 const dir) {
|
||||
|
||||
struct BlurPassData {
|
||||
FrameGraphId<FrameGraphTexture> in;
|
||||
FrameGraphId<FrameGraphTexture> out;
|
||||
FixedCapacityVector<ShadowMap const*> list;
|
||||
};
|
||||
|
||||
auto const& separatedBlurPasses = fg.addPass<BlurPassData>("Separated Gaussian Blur Pass",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
auto const inDesc = builder.getDescriptor(input);
|
||||
data.list = std::move(shadowMapList);
|
||||
data.in = builder.sample(input);
|
||||
if (!output) {
|
||||
auto outDesc = inDesc;
|
||||
outDesc.type = SamplerType::SAMPLER_2D_ARRAY;
|
||||
outDesc.levels = 1;
|
||||
outDesc.depth = 1;
|
||||
output = builder.createTexture("Temporary Separated Blur Texture", outDesc);
|
||||
} else {
|
||||
// TODO: When we write back into the destination, we need to preserve its content
|
||||
// because we don't know if we will write all of it or just parts (b/c it's
|
||||
// an atlas). We could avoid this if we knew we didn't care about what's not
|
||||
// written in this pass. This read() forces preservation of the content.
|
||||
output = builder.read(output, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
}
|
||||
data.out = builder.declareRenderPass(output);
|
||||
},
|
||||
[=, &engine](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
using FGTSD = FrameGraphTexture::SubResourceDescriptor;
|
||||
FGTSD const& inSubDesc = resources.getSubResourceDescriptor(data.in);
|
||||
auto hwOutRT = resources.getRenderPassInfo(0);
|
||||
auto hwIn = resources.getTexture(data.in);
|
||||
|
||||
auto& ppm = engine.getPostProcessManager();
|
||||
ppm.bindPostProcessDescriptorSet(driver);
|
||||
ppm.bindPerRenderableDescriptorSet(driver);
|
||||
|
||||
// get the material
|
||||
auto const& separableGaussianBlur = ppm.getPostProcessMaterial("gaussian");
|
||||
auto const ma = separableGaussianBlur.getMaterial(engine);
|
||||
|
||||
// Generates half of a Gaussian kernel (center + one side)
|
||||
auto generateGaussianWeights = [](std::array<float, 32>& weights,
|
||||
int const radius, float const sigma) {
|
||||
float sum = 0.0f;
|
||||
for (int i = 0; i <= radius; ++i) {
|
||||
weights[i] = std::exp(-(float(i * i) / (2.0f * sigma * sigma)));
|
||||
sum += (i == 0) ? weights[i] : (2.0f * weights[i]);
|
||||
}
|
||||
float const invSum = 1.0f / sum;
|
||||
for (int i = 0; i <= radius; ++i) {
|
||||
weights[i] *= invSum;
|
||||
}
|
||||
};
|
||||
|
||||
auto miList = FixedCapacityVector<FMaterialInstance*>::with_capacity(data.list.size());
|
||||
|
||||
// set-up all the MaterialInstance for each shadow map
|
||||
for (ShadowMap const* const shadowMap : data.list) {
|
||||
std::array<float, 32> kernel;
|
||||
float const blurWidth = shadowMap->getShadowOptions().vsm.blurWidth;
|
||||
float const sigma = blurWidth / 2.5f;
|
||||
int const radius = std::min(int(std::ceil(blurWidth)), 31);
|
||||
generateGaussianWeights(kernel, radius, sigma);
|
||||
|
||||
backend::Viewport const scissor = shadowMap->getScissor();
|
||||
auto const mi = ppm.getMaterialInstanceManager().getMaterialInstance(ma);
|
||||
mi->setScissor(scissor);
|
||||
mi->setParameter("input", hwIn, SamplerParams{
|
||||
.filterMag = SamplerMagFilter::NEAREST,
|
||||
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST
|
||||
});
|
||||
mi->setParameter("kernel", kernel.data(), radius + 1);
|
||||
mi->setParameter("bounds",
|
||||
dir.x * int2{ scissor.left, scissor.left + scissor.width - 1 } +
|
||||
dir.y * int2{ scissor.bottom, scissor.bottom + scissor.height - 1 });
|
||||
mi->setParameter("dir", dir);
|
||||
mi->setParameter("layer", int32_t(inSubDesc.layer));
|
||||
mi->setParameter("radius", radius);
|
||||
mi->commit(driver, engine.getUboManager());
|
||||
miList.push_back(mi);
|
||||
}
|
||||
|
||||
// Do all the horizontal or vertical blur passes for this layer at once
|
||||
driver.beginRenderPass(hwOutRT.target, hwOutRT.params);
|
||||
for (auto const mi : miList) {
|
||||
mi->use(driver);
|
||||
driver.scissor(mi->getScissor());
|
||||
driver.draw(ppm.getPipelineState(mi), engine.getFullScreenRenderPrimitive(), 0, 3, 1);
|
||||
}
|
||||
driver.endRenderPass();
|
||||
|
||||
ppm.unbindAllDescriptorSets(driver);
|
||||
});
|
||||
|
||||
return separatedBlurPasses->out;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> ShadowMapManager::vsmMipmapPass(
|
||||
FEngine& engine,
|
||||
FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> const input, uint8_t layer, size_t const level,
|
||||
float4 clearColor) noexcept {
|
||||
|
||||
struct VsmMipData {
|
||||
FrameGraphId<FrameGraphTexture> in;
|
||||
};
|
||||
|
||||
auto const& depthMipmapPass = fg.addPass<VsmMipData>("EVSM Mipmap Pass",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.in = builder.sample(input);
|
||||
auto out = builder.createSubresource(data.in, "EVSM Mipmap level", {
|
||||
.level = uint8_t(level + 1), .layer = layer });
|
||||
out = builder.write(out, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
builder.declareRenderPass(builder.getName(input), {
|
||||
.attachments = { .color = { out }},
|
||||
.clearColor = clearColor,
|
||||
.clearFlags = TargetBufferFlags::COLOR
|
||||
});
|
||||
},
|
||||
[=, &engine](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
|
||||
auto in = driver.createTextureView(resources.getTexture(data.in), level, 1);
|
||||
auto out = resources.getRenderPassInfo();
|
||||
|
||||
auto const& inDesc = resources.getDescriptor(data.in);
|
||||
auto width = inDesc.width;
|
||||
assert_invariant(width == inDesc.height);
|
||||
uint32_t const dim = std::max(1u, width >> (level + 1));
|
||||
|
||||
auto& ppm = engine.getPostProcessManager();
|
||||
ppm.bindPostProcessDescriptorSet(driver);
|
||||
ppm.bindPerRenderableDescriptorSet(driver);
|
||||
|
||||
auto& material = ppm.getPostProcessMaterial("vsmMipmap");
|
||||
FMaterial const* const ma = material.getMaterial(engine);
|
||||
auto const mi = ppm.getMaterialInstanceManager().getMaterialInstance(ma);
|
||||
|
||||
auto const pipeline = ppm.getPipelineState(mi);
|
||||
backend::Viewport const scissor = { 0, 0, dim, dim };
|
||||
|
||||
mi->setParameter("color", in, SamplerParams{
|
||||
.filterMag = SamplerMagFilter::NEAREST,
|
||||
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST
|
||||
});
|
||||
mi->setParameter("layer", uint32_t(layer));
|
||||
mi->commit(driver, engine.getUboManager());
|
||||
|
||||
|
||||
// Do all the horizontal or vertical blur passes for this layer at once
|
||||
driver.beginRenderPass(out.target, out.params);
|
||||
{
|
||||
mi->use(driver);
|
||||
driver.scissor(scissor);
|
||||
driver.draw(pipeline, engine.getFullScreenRenderPrimitive(), 0, 3, 1);
|
||||
}
|
||||
driver.endRenderPass();
|
||||
|
||||
driver.destroyTexture(in); // `in` is just a view on `data.in`
|
||||
ppm.unbindAllDescriptorSets(driver);
|
||||
});
|
||||
|
||||
return depthMipmapPass->in;
|
||||
}
|
||||
|
||||
ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEngine& engine,
|
||||
FView& view, CameraInfo cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FScene::LightSoa const& lightData, ShadowMap::SceneInfo sceneInfo) noexcept {
|
||||
@@ -792,7 +647,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
};
|
||||
|
||||
bool hasVisibleShadows = false;
|
||||
Slice<ShadowMap> const cascadedShadowMaps = getCascadedShadowMap();
|
||||
utils::Slice<ShadowMap> const 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.
|
||||
@@ -1002,7 +857,7 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
||||
|
||||
void ShadowMapManager::cullSpotShadowMap(ShadowMap const& shadowMap,
|
||||
FEngine const& engine, FView const& view,
|
||||
FScene::RenderableSoa& renderableData, Range<uint32_t> range,
|
||||
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> range,
|
||||
FScene::LightSoa const& lightData) noexcept {
|
||||
auto& lcm = engine.getLightManager();
|
||||
|
||||
@@ -1094,7 +949,7 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
||||
}
|
||||
|
||||
void ShadowMapManager::cullPointShadowMap(ShadowMap const& shadowMap, FView const& view,
|
||||
FScene::RenderableSoa& renderableData, Range<uint32_t> const range,
|
||||
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> const range,
|
||||
FScene::LightSoa const& lightData) noexcept {
|
||||
|
||||
uint8_t const face = shadowMap.getFace();
|
||||
@@ -1144,7 +999,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine
|
||||
lightData.data<FScene::SHADOW_INFO>());
|
||||
|
||||
ShadowTechnique shadowTechnique{};
|
||||
Slice<const ShadowMap> const spotShadowMaps = getSpotShadowMaps();
|
||||
utils::Slice<const ShadowMap> const spotShadowMaps = getSpotShadowMaps();
|
||||
if (!spotShadowMaps.empty()) {
|
||||
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
|
||||
for (ShadowMap const& shadowMap : spotShadowMaps) {
|
||||
@@ -1341,11 +1196,11 @@ void ShadowMapManager::updateNearFarPlanes(mat4f* projection,
|
||||
p[3].z = (sf * F.w - sn * N.w) * 0.5f;
|
||||
}
|
||||
|
||||
FixedCapacityVector<Camera const*>
|
||||
utils::FixedCapacityVector<Camera const*>
|
||||
ShadowMapManager::getDirectionalShadowCameras() const noexcept {
|
||||
if (!mInitialized) return {};
|
||||
auto const csm = getCascadedShadowMap();
|
||||
auto result = FixedCapacityVector<Camera const*>::with_capacity(csm.size());
|
||||
auto result = utils::FixedCapacityVector<Camera const*>::with_capacity(csm.size());
|
||||
for (ShadowMap const& sm : csm) {
|
||||
result.push_back(sm.hasVisibleShadows() ? sm.getDebugCamera() : nullptr);
|
||||
}
|
||||
|
||||
@@ -138,21 +138,6 @@ public:
|
||||
RenderPassBuilder const& passBuilder,
|
||||
FView& view, CameraInfo const& mainCameraInfo, math::float4 const& userTime) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> gaussianBlurSeparatedPass(
|
||||
FEngine& engine,
|
||||
FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> output,
|
||||
utils::FixedCapacityVector<ShadowMap const*> shadowMapList,
|
||||
math::int2 dir);
|
||||
|
||||
FrameGraphId<FrameGraphTexture> vsmMipmapPass(
|
||||
FEngine& engine,
|
||||
FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, uint8_t layer, size_t level,
|
||||
math::float4 clearColor) noexcept;
|
||||
|
||||
|
||||
// valid after calling update() above
|
||||
ShadowMappingUniforms getShadowMappingUniforms() const noexcept {
|
||||
return mShadowMappingUniforms;
|
||||
|
||||
@@ -59,19 +59,14 @@ UniformBuffer::UniformBuffer(UniformBuffer&& rhs) noexcept
|
||||
UniformBuffer& UniformBuffer::operator=(UniformBuffer&& rhs) noexcept {
|
||||
if (this != &rhs) {
|
||||
mSomethingDirty = rhs.mSomethingDirty;
|
||||
if (mBuffer && !isLocalStorage()) {
|
||||
free(mBuffer, mSize);
|
||||
}
|
||||
if (UTILS_LIKELY(rhs.isLocalStorage())) {
|
||||
mBuffer = mStorage;
|
||||
mSize = rhs.mSize;
|
||||
memcpy(mBuffer, rhs.mBuffer, rhs.mSize);
|
||||
} else {
|
||||
mBuffer = rhs.mBuffer;
|
||||
mSize = rhs.mSize;
|
||||
std::swap(mBuffer, rhs.mBuffer);
|
||||
std::swap(mSize, rhs.mSize);
|
||||
}
|
||||
rhs.mBuffer = nullptr;
|
||||
rhs.mSize = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -173,10 +173,16 @@ public:
|
||||
// helper functions
|
||||
|
||||
backend::BufferDescriptor toBufferDescriptor(backend::DriverApi& driver) const noexcept {
|
||||
return toBufferDescriptor(driver, 0, getSize());
|
||||
}
|
||||
|
||||
// copy the UBO data and cleans the dirty bits
|
||||
backend::BufferDescriptor toBufferDescriptor(
|
||||
backend::DriverApi& driver, size_t const offset, size_t const size) const noexcept {
|
||||
backend::BufferDescriptor p;
|
||||
p.size = getSize();
|
||||
p.size = size;
|
||||
p.buffer = driver.allocate(p.size); // TODO: use out-of-line buffer if too large
|
||||
memcpy(p.buffer, getBuffer(), p.size); // inlined
|
||||
memcpy(p.buffer, static_cast<const char*>(getBuffer()) + offset, p.size); // inlined
|
||||
clean();
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ void FCameraManager::terminate(FEngine& engine) noexcept {
|
||||
auto& manager = mManager;
|
||||
if (!manager.empty()) {
|
||||
DLOG(INFO) << "cleaning up " << manager.getComponentCount() << " leaked Camera components";
|
||||
while (!manager.empty()) {
|
||||
Instance const ci = manager.end() - 1;
|
||||
destroy(engine, manager.getEntity(ci));
|
||||
Slice<const Entity> entities{ manager.getEntities(), manager.getComponentCount() };
|
||||
for (Entity const e : entities) {
|
||||
destroy(engine, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
@@ -478,9 +477,6 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mSkinningBoneCount <= CONFIG_MAX_BONE_COUNT)
|
||||
<< "bone count > " << CONFIG_MAX_BONE_COUNT;
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mSkinningBufferOffset <= std::numeric_limits<uint16_t>::max())
|
||||
<< "skinning buffer offset > " << std::numeric_limits<uint16_t>::max();
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
mImpl->mInstanceCount <= CONFIG_MAX_INSTANCES || !mImpl->mInstanceBuffer)
|
||||
<< "instance count is " << mImpl->mInstanceCount
|
||||
@@ -932,9 +928,6 @@ void FRenderableManager::setBones(Instance const ci,
|
||||
FILAMENT_CHECK_PRECONDITION(!bones.skinningBufferMode)
|
||||
<< "Disable skinning buffer mode to use this API";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(offset <= bones.count)
|
||||
<< "bone offset is out of bounds (" << offset << " > " << bones.count << ")";
|
||||
|
||||
assert_invariant(bones.handle && offset + boneCount <= bones.count);
|
||||
if (bones.handle) {
|
||||
boneCount = std::min(boneCount, bones.count - offset);
|
||||
@@ -951,9 +944,6 @@ void FRenderableManager::setBones(Instance const ci,
|
||||
FILAMENT_CHECK_PRECONDITION(!bones.skinningBufferMode)
|
||||
<< "Disable skinning buffer mode to use this API";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(offset <= bones.count)
|
||||
<< "bone offset is out of bounds (" << offset << " > " << bones.count << ")";
|
||||
|
||||
assert_invariant(bones.handle && offset + boneCount <= bones.count);
|
||||
if (bones.handle) {
|
||||
boneCount = std::min(boneCount, bones.count - offset);
|
||||
|
||||
@@ -845,9 +845,8 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) {
|
||||
half4* UTILS_RESTRICT src = (half4*) data +
|
||||
b * config.lutDimension * config.lutDimension;
|
||||
// we use a vectorize width of 8 because, on ARMv8 it allows the compiler to
|
||||
// write eight 32-bits results in one go. The compiler will automatically generate
|
||||
// a scalar epilogue loop for any remainder elements.
|
||||
const size_t count = config.lutDimension * config.lutDimension;
|
||||
// write eight 32-bits results in one go.
|
||||
const size_t count = (config.lutDimension * config.lutDimension) & ~0x7u; // tell the compiler that we're a multiple of 8
|
||||
#if defined(__clang__)
|
||||
#pragma clang loop vectorize_width(8)
|
||||
#endif
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/CallStack.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Logger.h>
|
||||
@@ -73,7 +72,6 @@
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/tribool.h>
|
||||
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
@@ -271,7 +269,6 @@ FEngine::FEngine(Builder const& builder) :
|
||||
mRenderableManager(*this),
|
||||
mLightManager(*this),
|
||||
mCameraManager(*this),
|
||||
mMaterialCache(builder->mConfig.materialCacheCapacity, builder->mConfig.programCacheCapacity),
|
||||
mCommandBufferQueue(
|
||||
builder->mConfig.minCommandBufferSizeMB * MiB,
|
||||
builder->mConfig.commandBufferSizeMB * MiB,
|
||||
@@ -1676,89 +1673,6 @@ bool* FEngine::getFeatureFlagPtr(std::string_view name, bool const allowConstant
|
||||
return FeatureFlagManager::getFeatureFlagPtr(name, allowConstant);
|
||||
}
|
||||
|
||||
FixedCapacityVector<Variant> FEngine::getMaterialCompileVariants(
|
||||
FView const* view,
|
||||
tribool const shadowReceiver,
|
||||
tribool const skinning) noexcept {
|
||||
|
||||
// the maximum possible is 6 variants (4 color + 2 depth)
|
||||
auto variants = FixedCapacityVector<Variant>::with_capacity(6);
|
||||
|
||||
// apply() iteratively expands the `variants` vector by mutating existing elements
|
||||
// in-place if `value` is determinate, or by duplicating them if `value` is indeterminate.
|
||||
//
|
||||
// @param start The index in `variants` to begin the operation.
|
||||
// @param value The tribool state (false, true, or indeterminate) of the feature.
|
||||
// @param setter A pointer to a member function of Variant that sets the feature bit.
|
||||
auto apply = [&variants](size_t const start, tribool const value, auto setter) {
|
||||
if (value.is_indeterminate()) {
|
||||
size_t const count = variants.size();
|
||||
for (size_t i = start; i < count; ++i) {
|
||||
// To maintain permutations, we first grab a copy of the variant
|
||||
Variant v = variants[i];
|
||||
// Mutate the original in-place for the `false` state
|
||||
(variants[i].*setter)(false);
|
||||
// Set the `true` state on our copy and push it as a new permutation
|
||||
(v.*setter)(true);
|
||||
variants.push_back(v);
|
||||
}
|
||||
} else {
|
||||
// The feature is statically known, so just mutate all existing elements in-place
|
||||
bool const b = value.is_true();
|
||||
for (size_t i = start; i < variants.size(); ++i) {
|
||||
(variants[i].*setter)(b);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Variant baseVariant{};
|
||||
baseVariant.setDirectionalLighting(view->hasDirectionalLighting());
|
||||
baseVariant.setDynamicLighting(view->hasDynamicLighting());
|
||||
baseVariant.setFog(view->hasFog());
|
||||
baseVariant.setShadowSampler2D(view->hasShadowing() && (view->getShadowType() != ShadowType::PCF));
|
||||
baseVariant.setStereo(view->hasStereo());
|
||||
|
||||
variants.push_back(baseVariant);
|
||||
apply(0, skinning, &Variant::setSkinning);
|
||||
apply(0, shadowReceiver, &Variant::setShadowReceiver);
|
||||
|
||||
Variant depthVariant{};
|
||||
if (view->hasShadowing()) {
|
||||
// needsShadowMap() is no good here, because it can change based on the visibility of the shadow maps
|
||||
depthVariant = Variant{Variant::DEPTH_VARIANT};
|
||||
depthVariant.setDepthMoments(view->getShadowType() == ShadowType::VSM);
|
||||
}
|
||||
if (view->hasPostProcessPass()) {
|
||||
// This is needed if we're going to generate the structure pass. That is however very hard to tell
|
||||
// because the logic exists only in the FrameGraph. For now, we assume that if postFx is enabled, we'll
|
||||
// need the depth variant.
|
||||
depthVariant = Variant{Variant::DEPTH_VARIANT};
|
||||
}
|
||||
|
||||
if (Variant::isValidDepthVariant(depthVariant)) {
|
||||
// if we have a valid depth variant, add the stereo and skinning bits
|
||||
depthVariant.setStereo(view->hasStereo());
|
||||
|
||||
size_t const depthStart = variants.size();
|
||||
variants.push_back(depthVariant);
|
||||
apply(depthStart, skinning, &Variant::setSkinning);
|
||||
}
|
||||
|
||||
return variants;
|
||||
}
|
||||
|
||||
void FEngine::compile(
|
||||
CompilerPriorityQueue const priority,
|
||||
FMaterial const* material,
|
||||
FView const* view,
|
||||
tribool const shadowReceiver,
|
||||
tribool const skinning,
|
||||
CallbackHandler* handler,
|
||||
Invocable<void(Material*)>&& callback) {
|
||||
auto const variants = getMaterialCompileVariants(view, shadowReceiver, skinning);
|
||||
const_cast<FMaterial*>(material)->compile(priority, variants, handler, std::move(callback));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
Engine::Builder::Builder() noexcept = default;
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Slice.h>
|
||||
#include <utils/tribool.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
@@ -575,20 +574,6 @@ public:
|
||||
std::optional<bool> getFeatureFlag(char const* name) const noexcept;
|
||||
bool* getFeatureFlagPtr(std::string_view name, bool allowConstant = false) const noexcept;
|
||||
|
||||
void compile(
|
||||
backend::CompilerPriorityQueue priority,
|
||||
FMaterial const* material,
|
||||
FView const* view,
|
||||
utils::tribool shadowReceiver,
|
||||
utils::tribool skinning,
|
||||
backend::CallbackHandler* handler = nullptr,
|
||||
utils::Invocable<void(Material*)>&& callback = {});
|
||||
|
||||
static utils::FixedCapacityVector<Variant> getMaterialCompileVariants(
|
||||
FView const* view,
|
||||
utils::tribool shadowReceiver,
|
||||
utils::tribool skinning) noexcept;
|
||||
|
||||
private:
|
||||
explicit FEngine(Builder const& builder);
|
||||
void init();
|
||||
|
||||
@@ -170,6 +170,8 @@ FMaterial::FMaterial(FEngine& engine, const Builder& builder, MaterialDefinition
|
||||
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
mIsStereoSupported = driver.isStereoSupported();
|
||||
mIsParallelShaderCompileSupported = driver.isParallelShaderCompileSupported();
|
||||
mDepthPrecacheDisabled =
|
||||
driver.isWorkaroundNeeded(Workaround::DISABLE_DEPTH_PRECACHE_FOR_DEFAULT_MATERIAL);
|
||||
mDefaultMaterial = engine.getDefaultMaterial();
|
||||
@@ -239,44 +241,29 @@ void FMaterial::compile(CompilerPriorityQueue const priority,
|
||||
UserVariantFilterMask variantSpec,
|
||||
CallbackHandler* handler,
|
||||
Invocable<void(Material*)>&& callback) noexcept {
|
||||
FMaterialInstance* mi = getDefaultInstance();
|
||||
if (callback) {
|
||||
mi->compile(priority, variantSpec, handler,
|
||||
[this, callback = std::move(callback)](MaterialInstance*) {
|
||||
callback(this);
|
||||
});
|
||||
} else {
|
||||
mi->compile(priority, variantSpec, handler, {});
|
||||
}
|
||||
}
|
||||
|
||||
void FMaterial::compile(CompilerPriorityQueue const priority,
|
||||
FixedCapacityVector<Variant> const& variants,
|
||||
CallbackHandler* handler,
|
||||
Invocable<void(Material*)>&& callback) noexcept {
|
||||
DriverApi& driver = mEngine.getDriverApi();
|
||||
FMaterialInstance* mi = getDefaultInstance();
|
||||
|
||||
// Turn off the STE variant if stereo is not supported.
|
||||
if (!mIsStereoSupported) {
|
||||
variantSpec &= ~UserVariantFilterMask(UserVariantFilterBit::STE);
|
||||
}
|
||||
|
||||
UserVariantFilterMask const variantFilter =
|
||||
~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL);
|
||||
ShaderModel const shaderModel = mEngine.getShaderModel();
|
||||
bool const isStereoSupported = driver.isStereoSupported();
|
||||
bool const isParallelShaderCompileSupported = driver.isParallelShaderCompileSupported();
|
||||
bool const isStereoSupported = mEngine.getDriverApi().isStereoSupported();
|
||||
|
||||
if (UTILS_LIKELY(isParallelShaderCompileSupported)) {
|
||||
for (auto const variant : variants) {
|
||||
if (mDefinition.hasVariant(variant, shaderModel, isStereoSupported)) {
|
||||
mi->prepareProgram(driver, variant, priority);
|
||||
if (UTILS_LIKELY(mIsParallelShaderCompileSupported)) {
|
||||
for (auto const variant: mDefinition.getVariants()) {
|
||||
if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
|
||||
if (mDefinition.hasVariant(variant, shaderModel, isStereoSupported)) {
|
||||
prepareProgram(driver, variant, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileAllPrograms(priority, handler, std::move(callback));
|
||||
}
|
||||
|
||||
void FMaterial::compileAllPrograms(CompilerPriorityQueue const priority,
|
||||
CallbackHandler* handler,
|
||||
Invocable<void(Material*)>&& callback) noexcept {
|
||||
DriverApi& driver = mEngine.getDriverApi();
|
||||
|
||||
if (callback) {
|
||||
struct Callback {
|
||||
Invocable<void(Material*)> f;
|
||||
@@ -287,7 +274,7 @@ void FMaterial::compileAllPrograms(CompilerPriorityQueue const priority,
|
||||
delete c;
|
||||
}
|
||||
};
|
||||
auto* const user = new(std::nothrow) Callback{ std::move(callback), const_cast<FMaterial*>(this) };
|
||||
auto* const user = new(std::nothrow) Callback{ std::move(callback), this };
|
||||
driver.compilePrograms(priority, handler, &Callback::func, user);
|
||||
} else {
|
||||
driver.compilePrograms(priority, nullptr, nullptr, nullptr);
|
||||
|
||||
@@ -108,11 +108,6 @@ public:
|
||||
backend::CallbackHandler* handler,
|
||||
utils::Invocable<void(Material*)>&& callback) noexcept;
|
||||
|
||||
void compile(CompilerPriorityQueue priority,
|
||||
utils::FixedCapacityVector<Variant> const& variants,
|
||||
backend::CallbackHandler* handler,
|
||||
utils::Invocable<void(Material*)>&& callback) noexcept;
|
||||
|
||||
// Creates an instance of this material, specifying the batching mode.
|
||||
FMaterialInstance* createInstance(const char* name) const noexcept;
|
||||
|
||||
@@ -130,6 +125,40 @@ public:
|
||||
|
||||
FEngine& getEngine() const noexcept { return mEngine; }
|
||||
|
||||
// prepareProgram creates the program for the material's given variant at the backend level.
|
||||
// Must be called outside of backend render pass.
|
||||
// Must be called before getProgram() below.
|
||||
backend::Handle<backend::HwProgram> prepareProgram(backend::DriverApi& driver,
|
||||
Variant const variant,
|
||||
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
|
||||
return mPrograms.prepareProgram(driver, variant, priorityQueue);
|
||||
}
|
||||
|
||||
// getProgram returns the backend program for the material's given variant.
|
||||
// Must be called after prepareProgram().
|
||||
[[nodiscard]]
|
||||
backend::Handle<backend::HwProgram> getProgram(Variant variant) const noexcept {
|
||||
|
||||
if (UTILS_UNLIKELY(mEngine.features.material.enable_fog_as_postprocess)) {
|
||||
// if the fog as post-process feature is enabled, we need to proceed "as-if" the material
|
||||
// didn't have the FOG variant bit.
|
||||
if (getMaterialDomain() == MaterialDomain::SURFACE) {
|
||||
BlendingMode const blendingMode = getBlendingMode();
|
||||
bool const hasScreenSpaceRefraction = getRefractionMode() == RefractionMode::SCREEN_SPACE;
|
||||
bool const isBlendingCommand = !hasScreenSpaceRefraction &&
|
||||
(blendingMode != BlendingMode::OPAQUE && blendingMode != BlendingMode::MASKED);
|
||||
if (!isBlendingCommand) {
|
||||
variant.setFog(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
updateActiveProgramsForMatdbg(variant);
|
||||
#endif
|
||||
return mPrograms.getProgram(variant);
|
||||
}
|
||||
|
||||
// MaterialInstance::use() binds descriptor sets before drawing. For shared variants,
|
||||
// however, the material instance will call useShared() to bind the default material's sets
|
||||
// instead.
|
||||
@@ -276,24 +305,23 @@ public:
|
||||
}
|
||||
|
||||
/** @}*/
|
||||
|
||||
// Called by getProgram() to update active program list for matdbg UI.
|
||||
void updateActiveProgramsForMatdbg(Variant const variant) const noexcept;
|
||||
#endif
|
||||
|
||||
private:
|
||||
utils::FixedCapacityVector<backend::Program::SpecializationConstant>
|
||||
processSpecializationConstants(Builder const& builder);
|
||||
|
||||
void compileAllPrograms(CompilerPriorityQueue priority,
|
||||
backend::CallbackHandler* handler,
|
||||
utils::Invocable<void(Material*)>&& callback) noexcept;
|
||||
void precacheDepthVariants(backend::DriverApi& driver);
|
||||
|
||||
void createAndCacheProgram(backend::DriverApi& driver, backend::Program&& p, Variant variant) const noexcept;
|
||||
|
||||
MaterialDefinition const& mDefinition;
|
||||
|
||||
bool mIsDefaultMaterial = false;
|
||||
|
||||
bool mUseUboBatching = false;
|
||||
bool mIsStereoSupported = false;
|
||||
bool mIsParallelShaderCompileSupported = false;
|
||||
bool mDepthPrecacheDisabled = false;
|
||||
|
||||
FMaterial const* mDefaultMaterial = nullptr;
|
||||
@@ -308,6 +336,8 @@ private:
|
||||
mutable utils::Mutex mPendingEditsLock;
|
||||
std::unique_ptr<MaterialParser> mPendingEdits;
|
||||
std::unique_ptr<MaterialParser> mEditedMaterialParser;
|
||||
// Called by getProgram() to update active program list for matdbg UI.
|
||||
void updateActiveProgramsForMatdbg(Variant const variant) const noexcept;
|
||||
void setPendingEdits(std::unique_ptr<MaterialParser> pendingEdits) noexcept;
|
||||
bool hasPendingEdits() const noexcept;
|
||||
void latchPendingEdits() noexcept;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <filament/MaterialInstance.h>
|
||||
|
||||
#include "RenderPass.h"
|
||||
#include "MaterialParser.h"
|
||||
|
||||
#include "ds/DescriptorSetLayout.h"
|
||||
|
||||
@@ -132,7 +131,6 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
|
||||
mTextureParameters(other->mTextureParameters),
|
||||
mDescriptorSet(other->mDescriptorSet.duplicate(
|
||||
"MaterialInstance", mMaterial->getDescriptorSetLayout())),
|
||||
mPrograms(other->mPrograms),
|
||||
mPolygonOffset(other->mPolygonOffset),
|
||||
mStencilState(other->mStencilState),
|
||||
mMaskThreshold(other->mMaskThreshold),
|
||||
@@ -209,10 +207,6 @@ void FMaterialInstance::terminate(FEngine& engine) {
|
||||
if (ubHandle){
|
||||
driver.destroyBufferObject(*ubHandle);
|
||||
}
|
||||
|
||||
if (mPrograms.isInitialized()) {
|
||||
mPrograms.terminate(engine);
|
||||
}
|
||||
}
|
||||
|
||||
void FMaterialInstance::commit(FEngine& engine) const {
|
||||
@@ -265,39 +259,6 @@ void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManage
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
template<typename T>
|
||||
void FMaterialInstance::setConstantImpl(std::string_view name, T value) {
|
||||
auto const& constants = mMaterial->getDefinition().specializationConstantsNameToIndex;
|
||||
auto it = constants.find(name);
|
||||
FILAMENT_CHECK_PRECONDITION(it != constants.end()) << "Constant " << name << " does not exist";
|
||||
|
||||
if (UTILS_UNLIKELY(mPendingSpecializationConstants.empty())) {
|
||||
mPendingSpecializationConstants =
|
||||
FixedCapacityVector<backend::Program::SpecializationConstant>(
|
||||
getPrograms().getSpecializationConstants());
|
||||
}
|
||||
|
||||
uint32_t id = it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
|
||||
mPendingSpecializationConstants[id] = value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T FMaterialInstance::getConstantImpl(std::string_view name) const {
|
||||
auto const& constants = mMaterial->getDefinition().specializationConstantsNameToIndex;
|
||||
auto it = constants.find(name);
|
||||
FILAMENT_CHECK_PRECONDITION(it != constants.end()) << "Constant " << name << " does not exist";
|
||||
|
||||
uint32_t id = it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
|
||||
|
||||
if (UTILS_UNLIKELY(!mPendingSpecializationConstants.empty())) {
|
||||
return std::get<T>(mPendingSpecializationConstants[id]);
|
||||
}
|
||||
|
||||
return getPrograms().getConstant<T>(id);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void FMaterialInstance::setParameter(std::string_view const name,
|
||||
Handle<HwTexture> texture, SamplerParams const params) {
|
||||
auto const binding = mMaterial->getSamplerBinding(name);
|
||||
@@ -417,15 +378,6 @@ void FMaterialInstance::setTransparencyMode(TransparencyMode const mode) noexcep
|
||||
mTransparencyMode = mode;
|
||||
}
|
||||
|
||||
RasterState FMaterialInstance::getRasterState() const noexcept {
|
||||
RasterState rs = mMaterial->getRasterState();
|
||||
rs.culling = mCulling;
|
||||
rs.depthWrite = mDepthWrite;
|
||||
rs.depthFunc = mDepthFunc;
|
||||
rs.colorWrite = mColorWrite;
|
||||
return rs;
|
||||
}
|
||||
|
||||
void FMaterialInstance::setDepthCulling(bool const enable) noexcept {
|
||||
mDepthFunc = enable ? RasterState::DepthFunc::GE : RasterState::DepthFunc::A;
|
||||
}
|
||||
@@ -446,51 +398,6 @@ const char* FMaterialInstance::getName() const noexcept {
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void FMaterialInstance::compile(CompilerPriorityQueue const priority,
|
||||
UserVariantFilterMask variantSpec, CallbackHandler* handler,
|
||||
Invocable<void(MaterialInstance*)>&& callback) noexcept {
|
||||
FEngine& engine = mMaterial->getEngine();
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
MaterialDefinition const& definition = mMaterial->getDefinition();
|
||||
|
||||
bool const isStereoSupported = driver.isStereoSupported();
|
||||
|
||||
// Turn off the STE variant if stereo is not supported.
|
||||
if (UTILS_LIKELY(!isStereoSupported)) {
|
||||
variantSpec &= ~UserVariantFilterMask(UserVariantFilterBit::STE);
|
||||
}
|
||||
|
||||
UserVariantFilterMask const variantFilter =
|
||||
~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL);
|
||||
ShaderModel const shaderModel = engine.getShaderModel();
|
||||
|
||||
if (UTILS_LIKELY(driver.isParallelShaderCompileSupported())) {
|
||||
for (auto const variant: definition.getVariants()) {
|
||||
if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
|
||||
if (definition.hasVariant(variant, shaderModel, isStereoSupported)) {
|
||||
prepareProgram(driver, variant, priority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
struct Callback {
|
||||
Invocable<void(MaterialInstance*)> f;
|
||||
MaterialInstance* m;
|
||||
static void func(void* user) {
|
||||
auto* const c = static_cast<Callback*>(user);
|
||||
c->f(c->m);
|
||||
delete c;
|
||||
}
|
||||
};
|
||||
auto* const user = new (std::nothrow) Callback{ std::move(callback), this };
|
||||
driver.compilePrograms(priority, handler, &Callback::func, user);
|
||||
} else {
|
||||
driver.compilePrograms(priority, nullptr, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
|
||||
assert_invariant(mDescriptorSet.getHandle());
|
||||
assert_invariant(!isUsingUboBatching() || BufferAllocator::isValid(getAllocationId()));
|
||||
@@ -595,36 +502,4 @@ void FMaterialInstance::fixMissingSamplers() const {
|
||||
}
|
||||
}
|
||||
|
||||
LocalProgramCache const& FMaterialInstance::getPrograms() const noexcept {
|
||||
return mPrograms.isInitialized() ? mPrograms : mMaterial->getPrograms();
|
||||
}
|
||||
|
||||
void FMaterialInstance::flushSpecializationConstants() const noexcept {
|
||||
if (mPendingSpecializationConstants.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mPrograms.isInitialized()) {
|
||||
mPrograms.initializeForMaterialInstance(mMaterial->getEngine(), *mMaterial);
|
||||
}
|
||||
mPrograms.setConstants(std::move(mPendingSpecializationConstants));
|
||||
mPendingSpecializationConstants.clear();
|
||||
}
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
|
||||
void FMaterialInstance::updateActiveProgramsForMatdbg(Variant const variant) const noexcept {
|
||||
mMaterial->updateActiveProgramsForMatdbg(variant);
|
||||
}
|
||||
|
||||
#endif // FILAMENT_ENABLE_MATDBG
|
||||
|
||||
template void FMaterialInstance::setConstantImpl<int32_t>(std::string_view name, int32_t value);
|
||||
template void FMaterialInstance::setConstantImpl<float>(std::string_view name, float value);
|
||||
template void FMaterialInstance::setConstantImpl<bool>(std::string_view name, bool value);
|
||||
|
||||
template int32_t FMaterialInstance::getConstantImpl<int32_t>(std::string_view name) const;
|
||||
template float FMaterialInstance::getConstantImpl<float>(std::string_view name) const;
|
||||
template bool FMaterialInstance::getConstantImpl<bool>(std::string_view name) const;
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#define TNT_FILAMENT_DETAILS_MATERIALINSTANCE_H
|
||||
|
||||
#include "downcast.h"
|
||||
#include "LocalProgramCache.h"
|
||||
|
||||
#include "UniformBuffer.h"
|
||||
|
||||
#include "ds/DescriptorSet.h"
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "details/BufferAllocator.h"
|
||||
#include "details/Engine.h"
|
||||
|
||||
#include "private/backend/DriverApi.h"
|
||||
|
||||
#include <filament/MaterialInstance.h>
|
||||
|
||||
#include <private/filament/Variant.h>
|
||||
@@ -65,7 +67,7 @@ public:
|
||||
~FMaterialInstance() noexcept;
|
||||
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
|
||||
void commit(FEngine& engine) const;
|
||||
|
||||
void commit(FEngine::DriverApi& driver, UboManager* uboManager) const;
|
||||
@@ -84,32 +86,6 @@ public:
|
||||
|
||||
UniformBuffer const& getUniformBuffer() const noexcept { return mUniforms; }
|
||||
|
||||
void compile(backend::CompilerPriorityQueue priority, UserVariantFilterMask variantSpec,
|
||||
backend::CallbackHandler* handler,
|
||||
utils::Invocable<void(MaterialInstance*)>&& callback) noexcept;
|
||||
|
||||
// prepareProgram creates the program for the material's given variant at the backend level.
|
||||
// Must be called outside of backend render pass.
|
||||
// Must be called before getProgram() below.
|
||||
backend::Handle<backend::HwProgram> prepareProgram(backend::DriverApi& driver,
|
||||
Variant const variant,
|
||||
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
|
||||
flushSpecializationConstants();
|
||||
return getPrograms().prepareProgram(driver, variant, priorityQueue);
|
||||
}
|
||||
|
||||
// getProgram returns the backend program for the material's given variant.
|
||||
// Must be called after prepareProgram().
|
||||
//
|
||||
// See also Material::getProgram().
|
||||
[[nodiscard]]
|
||||
backend::Handle<backend::HwProgram> getProgram(Variant const variant) const noexcept {
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
updateActiveProgramsForMatdbg(variant);
|
||||
#endif
|
||||
return getPrograms().getProgram(variant);
|
||||
}
|
||||
|
||||
void setScissor(uint32_t const left, uint32_t const bottom, uint32_t const width, uint32_t const height) noexcept {
|
||||
constexpr uint32_t maxvalu = std::numeric_limits<int32_t>::max();
|
||||
mScissorRect = { int32_t(left), int32_t(bottom),
|
||||
@@ -131,8 +107,6 @@ public:
|
||||
|
||||
bool hasScissor() const noexcept { return mHasScissor; }
|
||||
|
||||
backend::RasterState getRasterState() const noexcept;
|
||||
|
||||
backend::CullingMode getCullingMode() const noexcept { return mCulling; }
|
||||
|
||||
backend::CullingMode getShadowCullingMode() const noexcept { return mShadowCulling; }
|
||||
@@ -280,15 +254,11 @@ public:
|
||||
backend::Handle<backend::HwTexture> texture, backend::SamplerParams params);
|
||||
|
||||
using MaterialInstance::setParameter;
|
||||
using MaterialInstance::setConstant;
|
||||
|
||||
private:
|
||||
friend class FMaterial;
|
||||
friend class MaterialInstance;
|
||||
|
||||
// Cannot inline since it inspects the FMaterial class.
|
||||
LocalProgramCache const& getPrograms() const noexcept;
|
||||
|
||||
template<size_t Size>
|
||||
void setParameterUntypedImpl(std::string_view name, const void* value);
|
||||
|
||||
@@ -307,19 +277,6 @@ private:
|
||||
template<typename T>
|
||||
T getParameterImpl(std::string_view name) const;
|
||||
|
||||
template<typename T>
|
||||
void setConstantImpl(std::string_view name, T value);
|
||||
|
||||
template<typename T>
|
||||
T getConstantImpl(std::string_view name) const;
|
||||
|
||||
void flushSpecializationConstants() const noexcept;
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
// Called by getProgram() to update active program list for matdbg UI.
|
||||
void updateActiveProgramsForMatdbg(Variant const variant) const noexcept;
|
||||
#endif
|
||||
|
||||
// keep these grouped, they're accessed together in the render-loop
|
||||
FMaterial const* mMaterial = nullptr;
|
||||
|
||||
@@ -334,11 +291,6 @@ private:
|
||||
mutable DescriptorSet mDescriptorSet;
|
||||
UniformBuffer mUniforms;
|
||||
|
||||
// HACK: Mutable so that prepareProgram() can update specialization constants.
|
||||
mutable LocalProgramCache mPrograms;
|
||||
mutable utils::FixedCapacityVector<backend::Program::SpecializationConstant>
|
||||
mPendingSpecializationConstants;
|
||||
|
||||
backend::PolygonOffset mPolygonOffset{};
|
||||
backend::StencilState mStencilState{};
|
||||
|
||||
|
||||
@@ -206,10 +206,7 @@ void updateDataAt(DriverApi& driver,
|
||||
size_t const elementSize = sizeof(float2);
|
||||
size_t const size = getSkinningBufferSize(count);
|
||||
auto* out = (float2*)malloc(size);
|
||||
memcpy(out, pairs.begin(), count * elementSize);
|
||||
if (size > count * elementSize) {
|
||||
memset(out + count, 0, size - count * elementSize);
|
||||
}
|
||||
memcpy(out, pairs.begin(), size);
|
||||
|
||||
size_t const textureWidth = getSkinningBufferWidth(count);
|
||||
size_t const lineCount = count / textureWidth;
|
||||
|
||||
@@ -163,8 +163,7 @@ void UboManager::finishBeginFrame(DriverApi& driver) {
|
||||
|
||||
void UboManager::endFrame(DriverApi& driver) {
|
||||
auto allocationIds =
|
||||
FenceManager::AllocationIdContainer::with_capacity(
|
||||
mManagedInstances.size() + mFreedAllocations.size());
|
||||
FenceManager::AllocationIdContainer::with_capacity(mManagedInstances.size());
|
||||
for (const auto* mi: mManagedInstances) {
|
||||
const AllocationId id = mi->getAllocationId();
|
||||
if (UTILS_UNLIKELY(!BufferAllocator::isValid(id))) {
|
||||
@@ -175,12 +174,6 @@ void UboManager::endFrame(DriverApi& driver) {
|
||||
allocationIds.push_back(id);
|
||||
}
|
||||
|
||||
// Also track MIs that were freed during this frame.
|
||||
for (const AllocationId id: mFreedAllocations) {
|
||||
mAllocator.acquireGpu(id);
|
||||
allocationIds.push_back(id);
|
||||
}
|
||||
|
||||
mFenceManager.track(driver, std::move(allocationIds));
|
||||
}
|
||||
|
||||
|
||||
@@ -106,8 +106,6 @@ FView::FView(FEngine& engine)
|
||||
{
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
mIsHighPrecisionEvsmSupported = driver.isTextureFormatFilterable(TextureFormat::RGBA32F);
|
||||
|
||||
mFeatureLevel = engine.getSupportedFeatureLevel();
|
||||
|
||||
auto const& layout = engine.getPerRenderableDescriptorSetLayout();
|
||||
@@ -1485,9 +1483,6 @@ void FView::setAmbientOcclusionOptions(AmbientOcclusionOptions options) noexcept
|
||||
}
|
||||
void FView::setVsmShadowOptions(VsmShadowOptions options) noexcept {
|
||||
options.msaaSamples = std::max(uint8_t(0), options.msaaSamples);
|
||||
if (!mIsHighPrecisionEvsmSupported) {
|
||||
options.highPrecision = false;
|
||||
}
|
||||
mVsmShadowOptions = options;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user