Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea9fdbeb5c | ||
|
|
5fd7a4e153 | ||
|
|
20ff230b92 | ||
|
|
44ff79ad34 | ||
|
|
102d2db008 | ||
|
|
5c9039e650 | ||
|
|
1203c24f06 | ||
|
|
9704d27aeb | ||
|
|
c6f2c3fc1c | ||
|
|
2faf868341 | ||
|
|
81f6260843 | ||
|
|
1a9063d53a | ||
|
|
6062b3c8c6 | ||
|
|
d9186c44ba | ||
|
|
6d06e00934 | ||
|
|
b4021a1fbc | ||
|
|
2c9559f9b8 | ||
|
|
e2da13f817 | ||
|
|
addae314b4 | ||
|
|
5e3c51822d | ||
|
|
9d817a0cc9 | ||
|
|
95ecc3d1e4 | ||
|
|
cabaa4323e | ||
|
|
d6b2ec49b0 | ||
|
|
93004aa5bc | ||
|
|
27fa8ab46d | ||
|
|
614990f413 | ||
|
|
da058028fd | ||
|
|
55c75453df | ||
|
|
f5eff10532 | ||
|
|
56a0364534 | ||
|
|
c8aae3cdaa | ||
|
|
4f94c575c9 | ||
|
|
1a29f020c9 | ||
|
|
d5847a3c6a | ||
|
|
3dbae482d3 | ||
|
|
27572e3be6 | ||
|
|
dedf276e26 | ||
|
|
111ad96134 | ||
|
|
2a67152dda | ||
|
|
2f62978e4c | ||
|
|
fdb90f2f49 | ||
|
|
5418b8c4cc | ||
|
|
724cab3623 | ||
|
|
d9faea264a | ||
|
|
6116c2f9eb | ||
|
|
95fdba0c5d | ||
|
|
9c56517370 | ||
|
|
08f06f4298 | ||
|
|
b1794a82ca | ||
|
|
56bd2871ec | ||
|
|
5a9c47bcb5 | ||
|
|
51c2fa2728 | ||
|
|
334c71de52 | ||
|
|
001098098b |
+24
-1
@@ -41,6 +41,8 @@ option(FILAMENT_LINUX_IS_MOBILE "Treat Linux as Mobile" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_ASAN_UBSAN "Enable Address and Undefined Behavior Sanitizers" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_TSAN "Enable Thread Sanitizer" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON)
|
||||
|
||||
set(FILAMENT_NDK_VERSION "" CACHE STRING
|
||||
@@ -230,6 +232,21 @@ if (WIN32)
|
||||
# we don't need them on CI.
|
||||
string(REPLACE "/INCREMENTAL" "/INCREMENTAL:NO" ${LinkerFlag} ${${LinkerFlag}})
|
||||
endforeach()
|
||||
|
||||
# We turn off compile-time optimizations for CI, as options that speed up the compile-time
|
||||
# (e.g. /MP) might increase memory usage, leading to instabilities on limited CI machines.
|
||||
option(FILAMENT_SHORTEN_MSVC_COMPILATION "Shorten compile-time in Visual Studio" OFF)
|
||||
else()
|
||||
option(FILAMENT_SHORTEN_MSVC_COMPILATION "Shorten compile-time in Visual Studio" ON)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
if (FILAMENT_SHORTEN_MSVC_COMPILATION)
|
||||
# enable multi-processor compilation
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||
# disable run-time STL checks to improve tools (e.g. matc) performance
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_ITERATOR_DEBUG_LEVEL=0")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -411,7 +428,13 @@ endif()
|
||||
if (FILAMENT_ENABLE_ASAN_UBSAN)
|
||||
set(EXTRA_SANITIZE_OPTIONS "-fsanitize=address -fsanitize=undefined")
|
||||
endif()
|
||||
|
||||
if (FILAMENT_ENABLE_TSAN)
|
||||
set(EXTRA_SANITIZE_OPTIONS "-fsanitize=thread")
|
||||
endif()
|
||||
if (ANDROID)
|
||||
# keep STL debug infos (mimics what the NDK does)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-limit-debug-info")
|
||||
endif()
|
||||
if (NOT MSVC AND NOT WEBGL)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fstack-protector")
|
||||
endif()
|
||||
|
||||
@@ -8,3 +8,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- Metal: fix some shader artifacts by disabling fast math optimizations.
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.49.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.49.3'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.49.0'
|
||||
pod 'Filament', '~> 1.49.3'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
+13
-1
@@ -7,10 +7,22 @@ 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.50.0
|
||||
- engine: TAA now supports 4x upscaling [BETA] [⚠️ **New Material Version**]
|
||||
|
||||
## v1.49.3
|
||||
|
||||
- matc: Generate stereo variants for FL0 materials [⚠️ **Recompile materials**]
|
||||
|
||||
## v1.49.2
|
||||
|
||||
|
||||
## v1.49.1
|
||||
|
||||
|
||||
## v1.49.0
|
||||
|
||||
- matc: Fix ESSL 1.0 codegen when using external samplers [⚠️ **Recompile materials**]
|
||||
- matc: Generate skinning variants for FL0 materials [⚠️ **Recompile materials**]
|
||||
|
||||
## v1.48.0
|
||||
|
||||
|
||||
@@ -81,14 +81,14 @@ buildscript {
|
||||
ext.versions = [
|
||||
'jdk': 17,
|
||||
'minSdk': 19,
|
||||
'targetSdk': 33,
|
||||
'compileSdk': 33,
|
||||
'kotlin': '1.9.0',
|
||||
'kotlin_coroutines': '1.7.2',
|
||||
'targetSdk': 34,
|
||||
'compileSdk': 34,
|
||||
'kotlin': '1.9.21',
|
||||
'kotlin_coroutines': '1.7.3',
|
||||
'buildTools': '34.0.0',
|
||||
'ndk': '25.1.8937393',
|
||||
'androidx_core': '1.10.1',
|
||||
'androidx_annotations': '1.6.0'
|
||||
'ndk': '26.1.10909125',
|
||||
'androidx_core': '1.12.0',
|
||||
'androidx_annotations': '1.7.0'
|
||||
]
|
||||
|
||||
ext.deps = [
|
||||
@@ -104,7 +104,7 @@ buildscript {
|
||||
]
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.1.0'
|
||||
classpath 'com.android.tools.build:gradle:8.2.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
|
||||
}
|
||||
|
||||
|
||||
@@ -849,6 +849,10 @@ public class Texture {
|
||||
public static final int SAMPLEABLE = 0x10;
|
||||
/** Texture can be used as a subpass input */
|
||||
public static final int SUBPASS_INPUT = 0x20;
|
||||
/** Texture can be used the source of a blit() */
|
||||
public static final int BLIT_SRC = 0x40;
|
||||
/** Texture can be used the destination of a blit() */
|
||||
public static final int BLIT_DST = 0x80;
|
||||
/** by default textures are <code>UPLOADABLE</code> and <code>SAMPLEABLE</code>*/
|
||||
public static final int DEFAULT = UPLOADABLE | SAMPLEABLE;
|
||||
}
|
||||
|
||||
@@ -1093,7 +1093,7 @@ public class View {
|
||||
* @see #setStereoscopicOptions
|
||||
*/
|
||||
@NonNull
|
||||
public StereoscopicOptions getStereoscoopicOptions() {
|
||||
public StereoscopicOptions getStereoscopicOptions() {
|
||||
if (mStereoscopicOptions == null) {
|
||||
mStereoscopicOptions = new StereoscopicOptions();
|
||||
}
|
||||
@@ -1637,6 +1637,10 @@ public class View {
|
||||
* circle of confusion scale factor (amount of blur)
|
||||
*/
|
||||
public float cocScale = 1.0f;
|
||||
/**
|
||||
* width/height aspect ratio of the circle of confusion (simulate anamorphic lenses)
|
||||
*/
|
||||
public float cocAspectRatio = 1.0f;
|
||||
/**
|
||||
* maximum aperture diameter in meters (zero to disable rotation)
|
||||
*/
|
||||
@@ -1852,7 +1856,7 @@ public class View {
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for Temporal Multi-Sample Anti-aliasing (MSAA)
|
||||
* Options for Multi-Sample Anti-aliasing (MSAA)
|
||||
* @see setMultiSampleAntiAliasingOptions()
|
||||
*/
|
||||
public static class MultiSampleAntiAliasingOptions {
|
||||
@@ -1876,21 +1880,106 @@ public class View {
|
||||
|
||||
/**
|
||||
* Options for Temporal Anti-aliasing (TAA)
|
||||
* Most TAA parameters are extremely costly to change, as they will trigger the TAA post-process
|
||||
* shaders to be recompiled. These options should be changed or set during initialization.
|
||||
* `filterWidth`, `feedback` and `jitterPattern`, however, could be changed at any time.
|
||||
*
|
||||
* `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state.
|
||||
* see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information.
|
||||
*
|
||||
* @see setTemporalAntiAliasingOptions()
|
||||
*/
|
||||
public static class TemporalAntiAliasingOptions {
|
||||
public enum BoxType {
|
||||
/**
|
||||
* use an AABB neighborhood
|
||||
*/
|
||||
AABB,
|
||||
/**
|
||||
* use the variance of the neighborhood (not recommended)
|
||||
*/
|
||||
VARIANCE,
|
||||
/**
|
||||
* use both AABB and variance
|
||||
*/
|
||||
AABB_VARIANCE,
|
||||
}
|
||||
|
||||
public enum BoxClipping {
|
||||
/**
|
||||
* Accurate box clipping
|
||||
*/
|
||||
ACCURATE,
|
||||
/**
|
||||
* clamping
|
||||
*/
|
||||
CLAMP,
|
||||
/**
|
||||
* no rejections (use for debugging)
|
||||
*/
|
||||
NONE,
|
||||
}
|
||||
|
||||
public enum JitterPattern {
|
||||
RGSS_X4,
|
||||
UNIFORM_HELIX_X4,
|
||||
HALTON_23_X8,
|
||||
HALTON_23_X16,
|
||||
}
|
||||
|
||||
/**
|
||||
* reconstruction filter width typically between 0 (sharper, aliased) and 1 (smoother)
|
||||
* reconstruction filter width typically between 0.2 (sharper, aliased) and 1.5 (smoother)
|
||||
*/
|
||||
public float filterWidth = 1.0f;
|
||||
/**
|
||||
* history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA).
|
||||
*/
|
||||
public float feedback = 0.04f;
|
||||
public float feedback = 0.12f;
|
||||
/**
|
||||
* texturing lod bias (typically -1 or -2)
|
||||
*/
|
||||
public float lodBias = -1.0f;
|
||||
/**
|
||||
* enables or disables temporal anti-aliasing
|
||||
*/
|
||||
public boolean enabled = false;
|
||||
/**
|
||||
* 4x TAA upscaling. Disables Dynamic Resolution. [BETA]
|
||||
*/
|
||||
public boolean upscaling = false;
|
||||
/**
|
||||
* whether to filter the history buffer
|
||||
*/
|
||||
public boolean filterHistory = true;
|
||||
/**
|
||||
* whether to apply the reconstruction filter to the input
|
||||
*/
|
||||
public boolean filterInput = true;
|
||||
/**
|
||||
* whether to use the YcoCg color-space for history rejection
|
||||
*/
|
||||
public boolean useYCoCg = false;
|
||||
/**
|
||||
* type of color gamut box
|
||||
*/
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.BoxType boxType = TemporalAntiAliasingOptions.BoxType.AABB;
|
||||
/**
|
||||
* clipping algorithm
|
||||
*/
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.BoxClipping boxClipping = TemporalAntiAliasingOptions.BoxClipping.ACCURATE;
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.JitterPattern jitterPattern = TemporalAntiAliasingOptions.JitterPattern.HALTON_23_X16;
|
||||
public float varianceGamma = 1.0f;
|
||||
/**
|
||||
* adjust the feedback dynamically to reduce flickering
|
||||
*/
|
||||
public boolean preventFlickering = false;
|
||||
/**
|
||||
* whether to apply history reprojection (debug option)
|
||||
*/
|
||||
public boolean historyReprojection = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+8
-4
@@ -329,7 +329,6 @@ public class UiHelper {
|
||||
@Override
|
||||
public void detach() {
|
||||
mTextureView.setSurfaceTextureListener(null);
|
||||
setSurface(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -382,6 +381,7 @@ public class UiHelper {
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()");
|
||||
setSurface(null);
|
||||
destroySwapChain();
|
||||
return true;
|
||||
}
|
||||
@@ -445,6 +445,9 @@ public class UiHelper {
|
||||
* {@link #attachTo(TextureView)}, or {@link #attachTo(SurfaceHolder)}.
|
||||
*/
|
||||
public void detach() {
|
||||
if (mRenderSurface != null) {
|
||||
mRenderSurface.detach();
|
||||
}
|
||||
destroySwapChain();
|
||||
mNativeWindow = null;
|
||||
mRenderSurface = null;
|
||||
@@ -590,6 +593,10 @@ public class UiHelper {
|
||||
// nothing to do
|
||||
return false;
|
||||
}
|
||||
if (mRenderSurface != null) {
|
||||
mRenderSurface.detach();
|
||||
mRenderSurface = null;
|
||||
}
|
||||
destroySwapChain();
|
||||
}
|
||||
mNativeWindow = nativeWindow;
|
||||
@@ -604,9 +611,6 @@ public class UiHelper {
|
||||
}
|
||||
|
||||
private void destroySwapChain() {
|
||||
if (mRenderSurface != null) {
|
||||
mRenderSurface.detach();
|
||||
}
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onDetachedFromSurface();
|
||||
}
|
||||
|
||||
+1
-1
@@ -144,7 +144,7 @@ public class FilamentInstance {
|
||||
*
|
||||
* Ignored if variantIndex is out of bounds.
|
||||
*/
|
||||
void applyMaterialVariant(@IntRange(from = 0) int variantIndex) {
|
||||
public void applyMaterialVariant(@IntRange(from = 0) int variantIndex) {
|
||||
nApplyMaterialVariant(mNativeObject, variantIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.49.0
|
||||
VERSION_NAME=1.49.3
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#Wed Nov 17 10:40:18 PST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
+18
-7
File diff suppressed because one or more lines are too long
Binary file not shown.
+14
-1
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -587,6 +587,7 @@
|
||||
<li><em>overrides</em> Dictionary with one or more of the following properties: mapSize, shadowCascades, constantBias, normalBias, shadowFar, shadowNearHint, shadowFarHint, stable, polygonOffsetConstant, polygonOffsetSlope, \</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>engine.setStereoscopicOptions()</strong></li>
|
||||
<li><strong>engine.setTemporalAntiAliasingOptions()</strong></li>
|
||||
<li><strong>engine.setVignetteOptions()</strong></li>
|
||||
</ul>
|
||||
@@ -1462,6 +1463,8 @@ This defines the following functions:</p>
|
||||
<li>STENCIL_ATTACHMENT</li>
|
||||
<li>UPLOADABLE</li>
|
||||
<li>SAMPLEABLE</li>
|
||||
<li>BLIT_SRC</li>
|
||||
<li>BLIT_DST</li>
|
||||
<li>SUBPASS_INPUT</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+20
-19
@@ -209,10 +209,13 @@ set(PRIVATE_HDRS
|
||||
)
|
||||
|
||||
set(MATERIAL_SRCS
|
||||
src/materials/fsr/fsr_easu.mat
|
||||
src/materials/fsr/fsr_easu_mobile.mat
|
||||
src/materials/fsr/fsr_easu_mobileF.mat
|
||||
src/materials/fsr/fsr_rcas.mat
|
||||
src/materials/antiAliasing/fxaa.mat
|
||||
src/materials/antiAliasing/taa.mat
|
||||
src/materials/blitLow.mat
|
||||
src/materials/bloom/bloomDownsample.mat
|
||||
src/materials/bloom/bloomDownsample2x.mat
|
||||
src/materials/bloom/bloomDownsample9.mat
|
||||
src/materials/bloom/bloomUpsample.mat
|
||||
src/materials/colorGrading/colorGrading.mat
|
||||
src/materials/colorGrading/colorGradingAsSubpass.mat
|
||||
src/materials/colorGrading/customResolveAsSubpass.mat
|
||||
@@ -220,29 +223,27 @@ set(MATERIAL_SRCS
|
||||
src/materials/defaultMaterial.mat
|
||||
src/materials/dof/dof.mat
|
||||
src/materials/dof/dofCoc.mat
|
||||
src/materials/dof/dofDownsample.mat
|
||||
src/materials/dof/dofCombine.mat
|
||||
src/materials/dof/dofDilate.mat
|
||||
src/materials/dof/dofDownsample.mat
|
||||
src/materials/dof/dofMedian.mat
|
||||
src/materials/dof/dofMipmap.mat
|
||||
src/materials/dof/dofTiles.mat
|
||||
src/materials/dof/dofTilesSwizzle.mat
|
||||
src/materials/dof/dofDilate.mat
|
||||
src/materials/dof/dofMipmap.mat
|
||||
src/materials/dof/dofMedian.mat
|
||||
src/materials/flare/flare.mat
|
||||
src/materials/bloom/bloomDownsample.mat
|
||||
src/materials/bloom/bloomDownsample2x.mat
|
||||
src/materials/bloom/bloomDownsample9.mat
|
||||
src/materials/bloom/bloomUpsample.mat
|
||||
src/materials/fsr/fsr_easu.mat
|
||||
src/materials/fsr/fsr_easu_mobile.mat
|
||||
src/materials/fsr/fsr_easu_mobileF.mat
|
||||
src/materials/fsr/fsr_rcas.mat
|
||||
src/materials/resolveDepth.mat
|
||||
src/materials/separableGaussianBlur.mat
|
||||
src/materials/skybox.mat
|
||||
src/materials/ssao/bilateralBlur.mat
|
||||
src/materials/ssao/bilateralBlurBentNormals.mat
|
||||
src/materials/ssao/mipmapDepth.mat
|
||||
src/materials/skybox.mat
|
||||
src/materials/ssao/sao.mat
|
||||
src/materials/ssao/saoBentNormals.mat
|
||||
src/materials/separableGaussianBlur.mat
|
||||
src/materials/antiAliasing/fxaa.mat
|
||||
src/materials/antiAliasing/taa.mat
|
||||
src/materials/vsmMipmap.mat
|
||||
src/materials/blitLow.mat
|
||||
)
|
||||
|
||||
set(MATERIAL_FL0_SRCS
|
||||
@@ -320,7 +321,7 @@ foreach (mat_src ${MATERIAL_SRCS})
|
||||
list(APPEND MATERIAL_BINS ${output_path})
|
||||
endforeach()
|
||||
|
||||
if (FILAMENT_ENABLE_FEATURE_LEVEL_0 AND FILAMENT_SUPPORTS_OPENGL)
|
||||
if (FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
foreach (mat_src ${MATERIAL_FL0_SRCS})
|
||||
get_filename_component(localname "${mat_src}" NAME_WE)
|
||||
get_filename_component(fullname "${mat_src}" ABSOLUTE)
|
||||
@@ -328,7 +329,7 @@ if (FILAMENT_ENABLE_FEATURE_LEVEL_0 AND FILAMENT_SUPPORTS_OPENGL)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${output_path}
|
||||
COMMAND matc -a opengl -p ${MATC_TARGET} ${MATC_OPT_FLAGS} -o ${output_path} ${fullname}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname}
|
||||
MAIN_DEPENDENCY ${fullname}
|
||||
DEPENDS matc
|
||||
COMMENT "Compiling material ${mat_src} to ${output_path}"
|
||||
|
||||
@@ -227,6 +227,7 @@ struct Viewport {
|
||||
int32_t top() const noexcept { return bottom + int32_t(height); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the mapping of the near and far clipping plane to window coordinates.
|
||||
*/
|
||||
@@ -658,13 +659,15 @@ enum class TextureFormat : uint16_t {
|
||||
|
||||
//! Bitmask describing the intended Texture Usage
|
||||
enum class TextureUsage : uint8_t {
|
||||
NONE = 0x0,
|
||||
COLOR_ATTACHMENT = 0x1, //!< Texture can be used as a color attachment
|
||||
DEPTH_ATTACHMENT = 0x2, //!< Texture can be used as a depth attachment
|
||||
STENCIL_ATTACHMENT = 0x4, //!< Texture can be used as a stencil attachment
|
||||
UPLOADABLE = 0x8, //!< Data can be uploaded into this texture (default)
|
||||
NONE = 0x00,
|
||||
COLOR_ATTACHMENT = 0x01, //!< Texture can be used as a color attachment
|
||||
DEPTH_ATTACHMENT = 0x02, //!< Texture can be used as a depth attachment
|
||||
STENCIL_ATTACHMENT = 0x04, //!< Texture can be used as a stencil attachment
|
||||
UPLOADABLE = 0x08, //!< Data can be uploaded into this texture (default)
|
||||
SAMPLEABLE = 0x10, //!< Texture can be sampled (default)
|
||||
SUBPASS_INPUT = 0x20, //!< Texture can be used as a subpass input
|
||||
BLIT_SRC = 0x40, //!< Texture can be used the source of a blit()
|
||||
BLIT_DST = 0x80, //!< Texture can be used the destination of a blit()
|
||||
DEFAULT = UPLOADABLE | SAMPLEABLE //!< Default texture usage
|
||||
};
|
||||
|
||||
@@ -692,6 +695,17 @@ static constexpr bool isDepthFormat(TextureFormat format) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr bool isStencilFormat(TextureFormat format) noexcept {
|
||||
switch (format) {
|
||||
case TextureFormat::STENCIL8:
|
||||
case TextureFormat::DEPTH24_STENCIL8:
|
||||
case TextureFormat::DEPTH32F_STENCIL8:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr bool isUnsignedIntFormat(TextureFormat format) {
|
||||
switch (format) {
|
||||
case TextureFormat::R8UI:
|
||||
|
||||
@@ -300,10 +300,10 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isStereoSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, canGenerateMipmaps)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage, void*, image)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isWorkaroundNeeded, backend::Workaround, workaround)
|
||||
@@ -468,7 +468,7 @@ DECL_DRIVER_API_N(readBufferSubData,
|
||||
* --------------------
|
||||
*/
|
||||
|
||||
DECL_DRIVER_API_N(blit,
|
||||
DECL_DRIVER_API_N(blitDEPRECATED,
|
||||
backend::TargetBufferFlags, buffers,
|
||||
backend::RenderTargetHandle, dst,
|
||||
backend::Viewport, dstRect,
|
||||
@@ -476,6 +476,21 @@ DECL_DRIVER_API_N(blit,
|
||||
backend::Viewport, srcRect,
|
||||
backend::SamplerMagFilter, filter)
|
||||
|
||||
DECL_DRIVER_API_N(resolve,
|
||||
backend::TextureHandle, dst,
|
||||
uint8_t, dstLevel, uint8_t, dstLayer,
|
||||
backend::TextureHandle, src,
|
||||
uint8_t, srcLevel, uint8_t, srcLayer)
|
||||
|
||||
DECL_DRIVER_API_N(blit,
|
||||
backend::TextureHandle, dst,
|
||||
uint8_t, dstLevel, uint8_t, dstLayer,
|
||||
math::uint2, dstOrigin,
|
||||
backend::TextureHandle, src,
|
||||
uint8_t, srcLevel, uint8_t, srcLayer,
|
||||
math::uint2, srcOrigin,
|
||||
math::uint2, size)
|
||||
|
||||
DECL_DRIVER_API_N(draw,
|
||||
backend::PipelineState, state,
|
||||
backend::RenderPrimitiveHandle, rph,
|
||||
|
||||
@@ -24,8 +24,7 @@
|
||||
#include <tsl/robin_map.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
namespace filament::backend {
|
||||
|
||||
struct MetalContext;
|
||||
|
||||
@@ -37,8 +36,7 @@ public:
|
||||
|
||||
struct BlitArgs {
|
||||
struct Attachment {
|
||||
id<MTLTexture> color = nil;
|
||||
id<MTLTexture> depth = nil;
|
||||
id<MTLTexture> texture = nil;
|
||||
MTLRegion region = {};
|
||||
uint8_t level = 0;
|
||||
uint32_t slice = 0; // must be 0 on source attachment
|
||||
@@ -46,25 +44,13 @@ public:
|
||||
|
||||
// Valid source formats: 2D, 2DArray, 2DMultisample, 3D
|
||||
// Valid destination formats: 2D, 2DArray, 3D, Cube
|
||||
Attachment source, destination;
|
||||
Attachment source;
|
||||
Attachment destination;
|
||||
SamplerMagFilter filter;
|
||||
|
||||
bool blitColor() const {
|
||||
return source.color != nil && destination.color != nil;
|
||||
}
|
||||
|
||||
bool blitDepth() const {
|
||||
return source.depth != nil && destination.depth != nil;
|
||||
}
|
||||
|
||||
bool colorDestinationIsFullAttachment() const {
|
||||
return destination.color.width == destination.region.size.width &&
|
||||
destination.color.height == destination.region.size.height;
|
||||
}
|
||||
|
||||
bool depthDestinationIsFullAttachment() const {
|
||||
return destination.depth.width == destination.region.size.width &&
|
||||
destination.depth.height == destination.region.size.height;
|
||||
bool destinationIsFullAttachment() const {
|
||||
return destination.texture.width == destination.region.size.width &&
|
||||
destination.texture.height == destination.region.size.height;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,48 +64,36 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
static void setupColorAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor,
|
||||
uint32_t depthPlane);
|
||||
static void setupDepthAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor,
|
||||
uint32_t depthPlane);
|
||||
static void setupAttachment(MTLRenderPassAttachmentDescriptor* descriptor,
|
||||
const BlitArgs& args, uint32_t depthPlane);
|
||||
|
||||
struct BlitFunctionKey {
|
||||
bool blitColor;
|
||||
bool blitDepth;
|
||||
bool msaaColorSource;
|
||||
bool msaaDepthSource;
|
||||
bool sources3D;
|
||||
|
||||
char padding[3];
|
||||
bool msaaColorSource{};
|
||||
bool sources3D{};
|
||||
char padding[2]{};
|
||||
|
||||
bool isValid() const noexcept {
|
||||
// MSAA 3D textures do not exist.
|
||||
bool hasMsaa = msaaColorSource || msaaDepthSource;
|
||||
bool const hasMsaa = msaaColorSource;
|
||||
return !(hasMsaa && sources3D);
|
||||
}
|
||||
|
||||
bool operator==(const BlitFunctionKey& rhs) const noexcept {
|
||||
return blitColor == rhs.blitColor &&
|
||||
blitDepth == rhs.blitDepth &&
|
||||
msaaColorSource == rhs.msaaColorSource &&
|
||||
msaaDepthSource == rhs.msaaDepthSource &&
|
||||
return msaaColorSource == rhs.msaaColorSource &&
|
||||
sources3D == rhs.sources3D;
|
||||
}
|
||||
|
||||
BlitFunctionKey() {
|
||||
std::memset(this, 0, sizeof(BlitFunctionKey));
|
||||
}
|
||||
};
|
||||
|
||||
void blitFastPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
static bool blitFastPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label);
|
||||
void blitSlowPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
|
||||
void blitSlowPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label);
|
||||
void blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor, bool blitDepth,
|
||||
const BlitArgs& args, uint32_t depthPlaneSource, uint32_t depthPlaneDest,
|
||||
const char* label);
|
||||
id<MTLTexture> createIntermediateTexture(id<MTLTexture> t, MTLSize size);
|
||||
id<MTLFunction> compileFragmentFunction(BlitFunctionKey key);
|
||||
|
||||
void blitDepthPlane(id <MTLCommandBuffer> cmdBuffer, const BlitArgs& args,
|
||||
uint32_t depthPlaneSource, uint32_t depthPlaneDest, const char* label);
|
||||
|
||||
id<MTLFunction> compileFragmentFunction(BlitFunctionKey key) const;
|
||||
id<MTLFunction> getBlitVertexFunction();
|
||||
id<MTLFunction> getBlitFragmentFunction(BlitFunctionKey key);
|
||||
|
||||
@@ -130,10 +104,9 @@ private:
|
||||
tsl::robin_map<BlitFunctionKey, Function, HashFn> mBlitFunctions;
|
||||
|
||||
id<MTLFunction> mVertexFunction = nil;
|
||||
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
#endif //TNT_METALBLITTER_H
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
namespace filament::backend {
|
||||
|
||||
static const char* functionLibrary = R"(
|
||||
#include <metal_stdlib>
|
||||
@@ -37,13 +36,7 @@ struct VertexOut
|
||||
|
||||
struct FragmentOut
|
||||
{
|
||||
#ifdef BLIT_COLOR
|
||||
float4 color [[color(0)]];
|
||||
#endif
|
||||
|
||||
#ifdef BLIT_DEPTH
|
||||
float depth [[depth(any)]];
|
||||
#endif
|
||||
};
|
||||
|
||||
vertex VertexOut
|
||||
@@ -67,7 +60,6 @@ fragment FragmentOut
|
||||
blitterFrag(VertexOut in [[stage_in]],
|
||||
sampler sourceSampler [[sampler(0)]],
|
||||
|
||||
#ifdef BLIT_COLOR
|
||||
#ifdef MSAA_COLOR_SOURCE
|
||||
texture2d_ms<float, access::read> sourceColor [[texture(0)]],
|
||||
#elif SOURCES_3D
|
||||
@@ -75,32 +67,18 @@ blitterFrag(VertexOut in [[stage_in]],
|
||||
#else
|
||||
texture2d<float, access::sample> sourceColor [[texture(0)]],
|
||||
#endif // MSAA_COLOR_SOURCE
|
||||
#endif // BLIT_COLOR
|
||||
|
||||
#ifdef BLIT_DEPTH
|
||||
#ifdef MSAA_DEPTH_SOURCE
|
||||
texture2d_ms<float, access::read> sourceDepth [[texture(1)]],
|
||||
#elif SOURCES_3D
|
||||
texture3d<float, access::sample> sourceDepth [[texture(1)]],
|
||||
#else
|
||||
texture2d<float, access::sample> sourceDepth [[texture(1)]],
|
||||
#endif // MSAA_DEPTH_SOURCE
|
||||
#endif // BLIT_DEPTH
|
||||
|
||||
constant FragmentArgs* args [[buffer(0)]])
|
||||
{
|
||||
FragmentOut out = {};
|
||||
|
||||
#if defined(BLIT_COLOR) || defined(BLIT_DEPTH)
|
||||
// These coordinates match the Vulkan vkCmdBlitImage spec:
|
||||
// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkCmdBlitImage.html
|
||||
float2 uvbase = in.position.xy; // unnormalized coordinates at center of texel: (1.5, 2.5, etc)
|
||||
float2 uvoffset = uvbase - args->dstOffset;
|
||||
float2 uvscaled = uvoffset * args->scale;
|
||||
float2 uv = uvscaled + args->srcOffset;
|
||||
#endif
|
||||
|
||||
#ifdef BLIT_COLOR
|
||||
#ifdef MSAA_COLOR_SOURCE
|
||||
out.color = float4(0.0);
|
||||
for (uint s = 0; s < sourceColor.get_num_samples(); s++) {
|
||||
@@ -116,201 +94,106 @@ blitterFrag(VertexOut in [[stage_in]],
|
||||
float2 uvnorm = uv / float2(sourceColor.get_width(args->lod), sourceColor.get_height(args->lod));
|
||||
out.color += sourceColor.sample(sourceSampler, uvnorm, level(args->lod));
|
||||
#endif // MSAA_COLOR_SOURCE
|
||||
#endif // BLIT_COLOR
|
||||
|
||||
#ifdef BLIT_DEPTH
|
||||
#ifdef MSAA_DEPTH_SOURCE
|
||||
out.depth = 0.0;
|
||||
for (uint s = 0; s < sourceDepth.get_num_samples(); s++) {
|
||||
out.depth += sourceDepth.read(static_cast<uint2>(uv), s).r;
|
||||
}
|
||||
out.depth /= sourceDepth.get_num_samples();
|
||||
#elif SOURCES_3D
|
||||
float2 uvnormd = uv / float2(sourceDepth.get_width(args->lod), sourceDepth.get_height(args->lod));
|
||||
float3 coords = float3(uvnormd, (static_cast<float>(args->depthPlane) + 0.5) /
|
||||
sourceDepth.get_depth(args->lod));
|
||||
out.depth = sourceDepth.sample(sourceSampler, coords, level(args->lod)).r;
|
||||
#else
|
||||
float2 uvnormd = uv / float2(sourceDepth.get_width(args->lod), sourceDepth.get_height(args->lod));
|
||||
out.depth = sourceDepth.sample(sourceSampler, uvnormd, level(args->lod)).r;
|
||||
#endif // MSAA_DEPTH_SOURCE
|
||||
#endif // BLIT_DEPTH
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
|
||||
|
||||
template<typename T>
|
||||
inline bool MTLSizeEqual(T a, T b) noexcept {
|
||||
return (a.width == b.width && a.height == b.height && a.depth == b.depth);
|
||||
}
|
||||
|
||||
MetalBlitter::MetalBlitter(MetalContext& context) noexcept : mContext(context) { }
|
||||
|
||||
#define MTLSizeEqual(a, b) (a.width == b.width && a.height == b.height && a.depth == b.depth)
|
||||
|
||||
void MetalBlitter::blit(id<MTLCommandBuffer> cmdBuffer, const BlitArgs& args, const char* label) {
|
||||
bool blitColor = args.blitColor();
|
||||
bool blitDepth = args.blitDepth();
|
||||
|
||||
ASSERT_PRECONDITION(args.source.region.size.depth == args.destination.region.size.depth,
|
||||
"Blitting requires the source and destination regions to have the same depth.");
|
||||
|
||||
if (args.source.color && args.source.depth) {
|
||||
MTLTextureType colorType = args.source.color.textureType;
|
||||
MTLTextureType depthType = args.source.depth.textureType;
|
||||
|
||||
if (colorType == MTLTextureType2DMultisample) colorType = MTLTextureType2D;
|
||||
if (depthType == MTLTextureType2DMultisample) depthType = MTLTextureType2D;
|
||||
|
||||
ASSERT_PRECONDITION(colorType == depthType,
|
||||
"Blitting requires color and depth sources to be the same texture type.");
|
||||
}
|
||||
|
||||
// Determine if the blit for color or depth are eligible to use a MTLBlitCommandEncoder.
|
||||
// blitColor and / or blitDepth are set to false upon success, to indicate that no more work is
|
||||
// necessary for that attachment.
|
||||
blitFastPath(cmdBuffer, blitColor, blitDepth, args, label);
|
||||
|
||||
if (!blitColor && !blitDepth) {
|
||||
// blitFastPath returns true upon success.
|
||||
// blitFastPath fails if either the format or sampleCount don't match (i.e. resolve)
|
||||
if (blitFastPath(cmdBuffer, args, label)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the destination is MSAA and we weren't able to use the fast path, report an error, as
|
||||
// blitting to a MSAA texture isn't supported through the "slow path" yet.
|
||||
const bool colorDestinationIsMultisample =
|
||||
blitColor && args.destination.color.textureType == MTLTextureType2DMultisample;
|
||||
const bool depthDestinationIsMultisample =
|
||||
blitDepth && args.destination.depth.textureType == MTLTextureType2DMultisample;
|
||||
ASSERT_PRECONDITION(!colorDestinationIsMultisample && !depthDestinationIsMultisample,
|
||||
"Blitting between MSAA render targets with differing pixel formats and/or regions is not supported.");
|
||||
// If we end-up here, it means that either:
|
||||
// - we're resolving a color texture
|
||||
// - src/dest formats didn't match, or we're scaling -- this can only happen with the legacy
|
||||
// blit() path and implies that the format is not depth.
|
||||
// note: in the future we will support a fast-path resolve
|
||||
|
||||
// If the destination texture doesn't have the MTLTextureUsageRenderTarget flag, we have to blit
|
||||
// to an intermediate texture first to perform the format conversion. Then, we can perform a
|
||||
// "fast blit" to the final destination texture.
|
||||
UTILS_UNUSED_IN_RELEASE
|
||||
const bool destinationIsMultisample =
|
||||
args.destination.texture.textureType == MTLTextureType2DMultisample;
|
||||
|
||||
id<MTLTexture> intermediateColor = nil;
|
||||
id<MTLTexture> intermediateDepth = nil;
|
||||
BlitArgs slowBlit = args;
|
||||
BlitArgs finalBlit = args;
|
||||
|
||||
MTLRegion sourceRegionNoOffset = MTLRegionMake3D(0, 0, 0,
|
||||
args.source.region.size.width, args.source.region.size.height,
|
||||
args.source.region.size.depth);
|
||||
|
||||
if (blitColor && !(args.destination.color.usage & MTLTextureUsageRenderTarget)) {
|
||||
intermediateColor = createIntermediateTexture(args.destination.color, args.source.region.size);
|
||||
slowBlit.destination.color = finalBlit.source.color = intermediateColor;
|
||||
slowBlit.destination.level = finalBlit.source.level = 0;
|
||||
slowBlit.destination.slice = finalBlit.source.slice = 0;
|
||||
slowBlit.destination.region = finalBlit.source.region = sourceRegionNoOffset;
|
||||
}
|
||||
if (blitDepth && !(args.destination.depth.usage & MTLTextureUsageRenderTarget)) {
|
||||
intermediateDepth = createIntermediateTexture(args.destination.depth, args.source.region.size);
|
||||
slowBlit.destination.depth = finalBlit.source.depth = intermediateDepth;
|
||||
slowBlit.destination.level = finalBlit.source.level = 0;
|
||||
slowBlit.destination.slice = finalBlit.source.slice = 0;
|
||||
slowBlit.destination.region = finalBlit.source.region = sourceRegionNoOffset;
|
||||
}
|
||||
|
||||
blitSlowPath(cmdBuffer, blitColor, blitDepth, slowBlit, label);
|
||||
|
||||
bool finalBlitColor = intermediateColor != nil;
|
||||
bool finalBlitDepth = intermediateDepth != nil;
|
||||
blitFastPath(cmdBuffer, finalBlitColor, finalBlitDepth, finalBlit, label);
|
||||
assert_invariant(!destinationIsMultisample);
|
||||
assert_invariant((args.destination.texture.usage & MTLTextureUsageRenderTarget));
|
||||
blitSlowPath(cmdBuffer, args, label);
|
||||
}
|
||||
|
||||
void MetalBlitter::blitFastPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
bool MetalBlitter::blitFastPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label) {
|
||||
if (blitColor) {
|
||||
if (args.source.color.sampleCount == args.destination.color.sampleCount &&
|
||||
args.source.color.pixelFormat == args.destination.color.pixelFormat &&
|
||||
MTLSizeEqual(args.source.region.size, args.destination.region.size)) {
|
||||
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @(label);
|
||||
[blitEncoder copyFromTexture:args.source.color
|
||||
sourceSlice:args.source.slice
|
||||
sourceLevel:args.source.level
|
||||
sourceOrigin:args.source.region.origin
|
||||
sourceSize:args.source.region.size
|
||||
toTexture:args.destination.color
|
||||
destinationSlice:args.destination.slice
|
||||
destinationLevel:args.destination.level
|
||||
destinationOrigin:args.destination.region.origin];
|
||||
[blitEncoder endEncoding];
|
||||
if (args.source.texture.sampleCount == args.destination.texture.sampleCount &&
|
||||
args.source.texture.pixelFormat == args.destination.texture.pixelFormat &&
|
||||
MTLSizeEqual(args.source.region.size, args.destination.region.size)) {
|
||||
|
||||
blitColor = false;
|
||||
}
|
||||
}
|
||||
id<MTLBlitCommandEncoder> const blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @(label);
|
||||
[blitEncoder copyFromTexture:args.source.texture
|
||||
sourceSlice:args.source.slice
|
||||
sourceLevel:args.source.level
|
||||
sourceOrigin:args.source.region.origin
|
||||
sourceSize:args.source.region.size
|
||||
toTexture:args.destination.texture
|
||||
destinationSlice:args.destination.slice
|
||||
destinationLevel:args.destination.level
|
||||
destinationOrigin:args.destination.region.origin];
|
||||
[blitEncoder endEncoding];
|
||||
|
||||
if (blitDepth) {
|
||||
if (args.source.depth.sampleCount == args.destination.depth.sampleCount &&
|
||||
args.source.depth.pixelFormat == args.destination.depth.pixelFormat &&
|
||||
MTLSizeEqual(args.source.region.size, args.destination.region.size)) {
|
||||
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @(label);
|
||||
[blitEncoder copyFromTexture:args.source.depth
|
||||
sourceSlice:args.source.slice
|
||||
sourceLevel:args.source.level
|
||||
sourceOrigin:args.source.region.origin
|
||||
sourceSize:args.source.region.size
|
||||
toTexture:args.destination.depth
|
||||
destinationSlice:args.destination.slice
|
||||
destinationLevel:args.destination.level
|
||||
destinationOrigin:args.destination.region.origin];
|
||||
[blitEncoder endEncoding];
|
||||
|
||||
blitDepth = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MetalBlitter::blitSlowPath(id<MTLCommandBuffer> cmdBuffer, bool& blitColor, bool& blitDepth,
|
||||
void MetalBlitter::blitSlowPath(id<MTLCommandBuffer> cmdBuffer,
|
||||
const BlitArgs& args, const char* label) {
|
||||
|
||||
// scaling in any dimension is not allowed
|
||||
assert_invariant(args.source.region.size.depth == args.destination.region.size.depth);
|
||||
// we're always blitting a single plane
|
||||
uint32_t depthPlaneSource = args.source.region.origin.z;
|
||||
uint32_t depthPlaneDest = args.destination.region.origin.z;
|
||||
|
||||
assert_invariant(args.source.region.size.depth == args.destination.region.size.depth);
|
||||
uint32_t depthPlaneCount = args.source.region.size.depth;
|
||||
for (NSUInteger d = 0; d < depthPlaneCount; d++) {
|
||||
blitDepthPlane(cmdBuffer, blitColor, blitDepth, args, depthPlaneSource++, depthPlaneDest++,
|
||||
label);
|
||||
for (NSUInteger d = 0; d < args.source.region.size.depth; d++) {
|
||||
blitDepthPlane(cmdBuffer, args,
|
||||
depthPlaneSource++, depthPlaneDest++, label);
|
||||
}
|
||||
|
||||
blitColor = false;
|
||||
blitDepth = false;
|
||||
}
|
||||
|
||||
void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor, bool blitDepth,
|
||||
const BlitArgs& args, uint32_t depthPlaneSource, uint32_t depthPlaneDest,
|
||||
const char* label) {
|
||||
MTLRenderPassDescriptor* descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, const BlitArgs& args,
|
||||
uint32_t depthPlaneSource, uint32_t depthPlaneDest, const char* label) {
|
||||
|
||||
if (blitColor) {
|
||||
setupColorAttachment(args, descriptor, depthPlaneDest);
|
||||
}
|
||||
MTLRenderPassDescriptor* const descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
|
||||
|
||||
if (blitDepth) {
|
||||
setupDepthAttachment(args, descriptor, depthPlaneDest);
|
||||
}
|
||||
setupAttachment(descriptor.colorAttachments[0], args, depthPlaneDest);
|
||||
|
||||
id<MTLRenderCommandEncoder> encoder = [cmdBuffer renderCommandEncoderWithDescriptor:descriptor];
|
||||
id<MTLRenderCommandEncoder> const encoder =
|
||||
[cmdBuffer renderCommandEncoderWithDescriptor:descriptor];
|
||||
encoder.label = @(label);
|
||||
|
||||
BlitFunctionKey key;
|
||||
key.blitColor = blitColor;
|
||||
key.blitDepth = blitDepth;
|
||||
key.msaaColorSource = blitColor && args.source.color.textureType == MTLTextureType2DMultisample;
|
||||
key.msaaDepthSource = blitDepth && args.source.depth.textureType == MTLTextureType2DMultisample;
|
||||
key.sources3D = blitColor && args.source.color.textureType == MTLTextureType3D;
|
||||
if (key.sources3D && blitDepth) {
|
||||
assert_invariant(args.source.depth.textureType == MTLTextureType3D);
|
||||
}
|
||||
id<MTLFunction> fragmentFunction = getBlitFragmentFunction(key);
|
||||
key.msaaColorSource = args.source.texture.textureType == MTLTextureType2DMultisample;
|
||||
key.sources3D = args.source.texture.textureType == MTLTextureType3D;
|
||||
id<MTLFunction> const fragmentFunction = getBlitFragmentFunction(key);
|
||||
|
||||
MetalPipelineState pipelineState {
|
||||
MetalPipelineState const pipelineState {
|
||||
.vertexFunction = getBlitVertexFunction(),
|
||||
.fragmentFunction = fragmentFunction,
|
||||
.vertexDescription = {},
|
||||
.colorAttachmentPixelFormat = {
|
||||
blitColor ? args.destination.color.pixelFormat : MTLPixelFormatInvalid,
|
||||
args.destination.texture.pixelFormat,
|
||||
MTLPixelFormatInvalid,
|
||||
MTLPixelFormatInvalid,
|
||||
MTLPixelFormatInvalid,
|
||||
@@ -319,31 +202,21 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
MTLPixelFormatInvalid,
|
||||
MTLPixelFormatInvalid
|
||||
},
|
||||
.depthAttachmentPixelFormat =
|
||||
blitDepth ? args.destination.depth.pixelFormat : MTLPixelFormatInvalid,
|
||||
.depthAttachmentPixelFormat = MTLPixelFormatInvalid,
|
||||
.sampleCount = 1,
|
||||
.blendState = {}
|
||||
};
|
||||
id<MTLRenderPipelineState> pipeline = mContext.pipelineStateCache.getOrCreateState(pipelineState);
|
||||
id<MTLRenderPipelineState> const pipeline =
|
||||
mContext.pipelineStateCache.getOrCreateState(pipelineState);
|
||||
[encoder setRenderPipelineState:pipeline];
|
||||
|
||||
// For texture arrays, create a view of the texture at the given slice (layer).
|
||||
id<MTLTexture> srcTextureColor = args.source.color;
|
||||
|
||||
id<MTLTexture> srcTextureColor = args.source.texture;
|
||||
if (srcTextureColor && srcTextureColor.textureType == MTLTextureType2DArray) {
|
||||
srcTextureColor = createTextureViewWithSingleSlice(srcTextureColor, args.source.slice);
|
||||
}
|
||||
id<MTLTexture> srcTextureDepth = args.source.depth;
|
||||
if (srcTextureDepth && srcTextureDepth.textureType == MTLTextureType2DArray) {
|
||||
srcTextureDepth = createTextureViewWithSingleSlice(srcTextureDepth, args.source.slice);
|
||||
}
|
||||
|
||||
if (blitColor) {
|
||||
[encoder setFragmentTexture:srcTextureColor atIndex:0];
|
||||
}
|
||||
|
||||
if (blitDepth) {
|
||||
[encoder setFragmentTexture:srcTextureDepth atIndex:1];
|
||||
}
|
||||
[encoder setFragmentTexture:srcTextureColor atIndex:0];
|
||||
|
||||
SamplerMinFilter filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST;
|
||||
if (args.filter == SamplerMagFilter::NEAREST) {
|
||||
@@ -352,13 +225,13 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
|
||||
}
|
||||
|
||||
SamplerState s {
|
||||
SamplerState const s {
|
||||
.samplerParams = {
|
||||
.filterMag = args.filter,
|
||||
.filterMin = filterMin
|
||||
}
|
||||
};
|
||||
id<MTLSamplerState> sampler = mContext.samplerStateCache.getOrCreateState(s);
|
||||
id<MTLSamplerState> const sampler = mContext.samplerStateCache.getOrCreateState(s);
|
||||
[encoder setFragmentSamplerState:sampler atIndex:0];
|
||||
|
||||
MTLViewport viewport;
|
||||
@@ -370,11 +243,11 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
viewport.zfar = 1.0;
|
||||
[encoder setViewport:viewport];
|
||||
|
||||
DepthStencilState depthStencilState {
|
||||
DepthStencilState const depthStencilState {
|
||||
.depthCompare = MTLCompareFunctionAlways,
|
||||
.depthWriteEnabled = blitDepth
|
||||
.depthWriteEnabled = false
|
||||
};
|
||||
id<MTLDepthStencilState> depthStencil =
|
||||
id<MTLDepthStencilState> const depthStencil =
|
||||
mContext.depthStencilStateCache.getOrCreateState(depthStencilState);
|
||||
[encoder setDepthStencilState:depthStencil];
|
||||
|
||||
@@ -410,80 +283,42 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, bool blitColor
|
||||
[encoder endEncoding];
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalBlitter::createIntermediateTexture(id<MTLTexture> t, MTLSize size) {
|
||||
MTLTextureDescriptor* descriptor = [MTLTextureDescriptor new];
|
||||
descriptor.textureType = size.depth == 1 ? MTLTextureType2D : MTLTextureType3D;
|
||||
descriptor.pixelFormat = t.pixelFormat;
|
||||
descriptor.width = size.width;
|
||||
descriptor.height = size.height;
|
||||
descriptor.depth = size.depth;
|
||||
descriptor.usage = t.usage & MTLTextureUsageRenderTarget;
|
||||
return [mContext.device newTextureWithDescriptor:descriptor];
|
||||
}
|
||||
|
||||
void MetalBlitter::shutdown() noexcept {
|
||||
mBlitFunctions.clear();
|
||||
mVertexFunction = nil;
|
||||
}
|
||||
|
||||
void MetalBlitter::setupColorAttachment(const BlitArgs& args,
|
||||
MTLRenderPassDescriptor* descriptor, uint32_t depthPlane) {
|
||||
descriptor.colorAttachments[0].texture = args.destination.color;
|
||||
descriptor.colorAttachments[0].level = args.destination.level;
|
||||
descriptor.colorAttachments[0].slice = args.destination.slice;
|
||||
descriptor.colorAttachments[0].depthPlane = depthPlane;
|
||||
|
||||
descriptor.colorAttachments[0].loadAction = MTLLoadActionLoad;
|
||||
void MetalBlitter::setupAttachment(MTLRenderPassAttachmentDescriptor* descriptor,
|
||||
const BlitArgs& args, uint32_t depthPlane) {
|
||||
descriptor.texture = args.destination.texture;
|
||||
descriptor.level = args.destination.level;
|
||||
descriptor.slice = args.destination.slice;
|
||||
descriptor.depthPlane = depthPlane;
|
||||
descriptor.loadAction = MTLLoadActionLoad;
|
||||
// We don't need to load the contents of the attachment if we're blitting over all of it.
|
||||
if (args.colorDestinationIsFullAttachment()) {
|
||||
descriptor.colorAttachments[0].loadAction = MTLLoadActionDontCare;
|
||||
if (args.destinationIsFullAttachment()) {
|
||||
descriptor.loadAction = MTLLoadActionDontCare;
|
||||
}
|
||||
|
||||
descriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
|
||||
descriptor.storeAction = MTLStoreActionStore;
|
||||
}
|
||||
|
||||
void MetalBlitter::setupDepthAttachment(const BlitArgs& args, MTLRenderPassDescriptor* descriptor,
|
||||
uint32_t depthPlane) {
|
||||
descriptor.depthAttachment.texture = args.destination.depth;
|
||||
descriptor.depthAttachment.level = args.destination.level;
|
||||
descriptor.depthAttachment.slice = args.destination.slice;
|
||||
descriptor.depthAttachment.depthPlane = depthPlane;
|
||||
|
||||
descriptor.depthAttachment.loadAction = MTLLoadActionLoad;
|
||||
// We don't need to load the contents of the attachment if we're blitting over all of it.
|
||||
if (args.depthDestinationIsFullAttachment()) {
|
||||
descriptor.depthAttachment.loadAction = MTLLoadActionDontCare;
|
||||
}
|
||||
|
||||
descriptor.depthAttachment.storeAction = MTLStoreActionStore;
|
||||
}
|
||||
|
||||
id<MTLFunction> MetalBlitter::compileFragmentFunction(BlitFunctionKey key) {
|
||||
MTLCompileOptions* options = [MTLCompileOptions new];
|
||||
NSMutableDictionary* macros = [NSMutableDictionary dictionary];
|
||||
if (key.blitColor) {
|
||||
macros[@"BLIT_COLOR"] = @"1";
|
||||
}
|
||||
if (key.blitDepth) {
|
||||
macros[@"BLIT_DEPTH"] = @"1";
|
||||
}
|
||||
id<MTLFunction> MetalBlitter::compileFragmentFunction(BlitFunctionKey key) const {
|
||||
MTLCompileOptions* const options = [MTLCompileOptions new];
|
||||
NSMutableDictionary* const macros = [NSMutableDictionary dictionary];
|
||||
if (key.msaaColorSource) {
|
||||
macros[@"MSAA_COLOR_SOURCE"] = @"1";
|
||||
}
|
||||
if (key.msaaDepthSource) {
|
||||
macros[@"MSAA_DEPTH_SOURCE"] = @"1";
|
||||
}
|
||||
if (key.sources3D) {
|
||||
macros[@"SOURCES_3D"] = @"1";
|
||||
}
|
||||
options.preprocessorMacros = macros;
|
||||
NSString* objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSString* const objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSError* error = nil;
|
||||
id<MTLLibrary> library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:options
|
||||
error:&error];
|
||||
id<MTLFunction> function = [library newFunctionWithName:@"blitterFrag"];
|
||||
id <MTLLibrary> const library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:options
|
||||
error:&error];
|
||||
id <MTLFunction> const function = [library newFunctionWithName:@"blitterFrag"];
|
||||
|
||||
if (!library || !function) {
|
||||
if (error) {
|
||||
@@ -501,13 +336,14 @@ id<MTLFunction> MetalBlitter::getBlitVertexFunction() {
|
||||
return mVertexFunction;
|
||||
}
|
||||
|
||||
NSString* objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSString* const objcSource = [NSString stringWithCString:functionLibrary
|
||||
encoding:NSUTF8StringEncoding];
|
||||
NSError* error = nil;
|
||||
id<MTLLibrary> library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
error:&error];
|
||||
id<MTLFunction> function = [library newFunctionWithName:@"blitterVertex"];
|
||||
id <MTLLibrary> const library = [mContext.device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
error:&error];
|
||||
|
||||
id<MTLFunction> const function = [library newFunctionWithName:@"blitterVertex"];
|
||||
|
||||
if (!library || !function) {
|
||||
if (error) {
|
||||
@@ -535,5 +371,5 @@ id<MTLFunction> MetalBlitter::getBlitFragmentFunction(BlitFunctionKey key) {
|
||||
return function;
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
@@ -73,7 +73,10 @@ private:
|
||||
std::unordered_set<MetalBufferPoolEntry const*> mUsedStages;
|
||||
|
||||
// Store the current "time" (really just a frame count) and LRU eviction parameters.
|
||||
uint64_t mCurrentFrame = 0;
|
||||
// An atomic is necessary as mCurrentFrame is incremented in gc() (called on
|
||||
// the driver thread) and read from acquireBuffer() and releaseBuffer(),
|
||||
// which may be called on non-driver threads.
|
||||
std::atomic<uint64_t> mCurrentFrame = 0;
|
||||
static constexpr uint32_t TIME_BEFORE_EVICTION = 10;
|
||||
};
|
||||
|
||||
|
||||
@@ -142,7 +142,10 @@ struct MetalContext {
|
||||
// Fences, only supported on macOS 10.14 and iOS 12 and above.
|
||||
API_AVAILABLE(macos(10.14), ios(12.0))
|
||||
MTLSharedEventListener* eventListener = nil;
|
||||
uint64_t signalId = 1;
|
||||
// signalId is incremented in the MetalFence constructor, which is called on
|
||||
// both the driver (MetalTimerQueryFence::beginTimeElapsedQuery) and main
|
||||
// threads (in createFenceS), so an atomic is necessary.
|
||||
std::atomic<uint64_t> signalId = 1;
|
||||
|
||||
MetalTimerQueryInterface* timerQueryImpl;
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
#include <utils/Log.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
@@ -52,13 +56,13 @@ class MetalDriver final : public DriverBase {
|
||||
|
||||
public:
|
||||
static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig);
|
||||
void runAtNextTick(const std::function<void()>& fn) noexcept;
|
||||
|
||||
private:
|
||||
|
||||
friend class MetalSwapChain;
|
||||
|
||||
MetalPlatform& mPlatform;
|
||||
|
||||
MetalContext* mContext;
|
||||
|
||||
ShaderModel getShaderModel() const noexcept final;
|
||||
@@ -66,6 +70,13 @@ private:
|
||||
// Overrides the default implementation by wrapping the call to fn in an @autoreleasepool block.
|
||||
void execute(std::function<void(void)> const& fn) noexcept final;
|
||||
|
||||
/*
|
||||
* Tasks run regularly on the driver thread.
|
||||
*/
|
||||
void executeTickOps() noexcept;
|
||||
std::vector<std::function<void()>> mTickOps;
|
||||
std::mutex mTickOpsLock;
|
||||
|
||||
/*
|
||||
* Driver interface
|
||||
*/
|
||||
|
||||
@@ -166,6 +166,7 @@ MetalDriver::~MetalDriver() noexcept {
|
||||
}
|
||||
|
||||
void MetalDriver::tick(int) {
|
||||
executeTickOps();
|
||||
}
|
||||
|
||||
void MetalDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
||||
@@ -583,6 +584,8 @@ void MetalDriver::terminate() {
|
||||
// This must be done before calling bufferPool->reset() to ensure no buffers are in flight.
|
||||
finish();
|
||||
|
||||
executeTickOps();
|
||||
|
||||
mContext->bufferPool->reset();
|
||||
mContext->commandQueue = nil;
|
||||
|
||||
@@ -727,6 +730,10 @@ bool MetalDriver::isParallelShaderCompileSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isDepthStencilResolveSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -865,10 +872,6 @@ void MetalDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
tex->generateMipmaps();
|
||||
}
|
||||
|
||||
bool MetalDriver::canGenerateMipmaps() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescriptor&& data) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"updateSamplerGroup must be called outside of a render pass.");
|
||||
@@ -1256,14 +1259,14 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
|
||||
textureDescriptor.usage = MTLTextureUsageShaderWrite | MTLTextureUsageRenderTarget;
|
||||
id<MTLTexture> readPixelsTexture = [mContext->device newTextureWithDescriptor:textureDescriptor];
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = SamplerMagFilter::NEAREST;
|
||||
args.source.level = miplevel;
|
||||
args.source.region = MTLRegionMake2D(0, 0, srcTexture.width >> miplevel, srcTexture.height >> miplevel);
|
||||
args.source.texture = srcTexture;
|
||||
args.destination.level = 0;
|
||||
args.destination.region = MTLRegionMake2D(0, 0, readPixelsTexture.width, readPixelsTexture.height);
|
||||
args.source.color = srcTexture;
|
||||
args.destination.color = readPixelsTexture;
|
||||
args.destination.texture = readPixelsTexture;
|
||||
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "readPixels blit");
|
||||
|
||||
@@ -1299,24 +1302,123 @@ void MetalDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void MetalDriver::blit(TargetBufferFlags buffers,
|
||||
void MetalDriver::resolve(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
auto* const srcTexture = handle_cast<MetalTexture>(src);
|
||||
auto* const dstTexture = handle_cast<MetalTexture>(dst);
|
||||
assert_invariant(srcTexture);
|
||||
assert_invariant(dstTexture);
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"resolve() cannot be invoked inside a render pass.");
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height,
|
||||
"invalid resolve: src and dst sizes don't match");
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1,
|
||||
"invalid resolve: src.samples=%u, dst.samples=%u",
|
||||
+srcTexture->samples, +dstTexture->samples);
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
ASSERT_PRECONDITION(!isDepthFormat(srcTexture->format),
|
||||
"can't resolve depth formats");
|
||||
|
||||
ASSERT_PRECONDITION(!isStencilFormat(srcTexture->format),
|
||||
"can't resolve stencil formats");
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
// FIXME: on metal the blit() call below always take the slow path (using a shader)
|
||||
|
||||
blit( dst, dstLevel, dstLayer, {},
|
||||
src, srcLevel, srcLayer, {},
|
||||
{ dstTexture->width, dstTexture->height });
|
||||
}
|
||||
|
||||
void MetalDriver::blit(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin,
|
||||
math::uint2 size) {
|
||||
|
||||
|
||||
auto isBlitableTextureType = [](MTLTextureType t) -> bool {
|
||||
return t == MTLTextureType2D || t == MTLTextureType2DMultisample ||
|
||||
t == MTLTextureType2DArray;
|
||||
};
|
||||
|
||||
auto* const srcTexture = handle_cast<MetalTexture>(src);
|
||||
auto* const dstTexture = handle_cast<MetalTexture>(dst);
|
||||
assert_invariant(srcTexture);
|
||||
assert_invariant(dstTexture);
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"blit() cannot be invoked inside a render pass.");
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcTexture->getMtlTextureForRead().textureType) &&
|
||||
isBlitableTextureType(dstTexture->getMtlTextureForWrite().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = SamplerMagFilter::NEAREST;
|
||||
args.source.region = MTLRegionMake2D(
|
||||
(NSUInteger)srcOrigin.x,
|
||||
std::max(srcTexture->height - (int64_t)srcOrigin.y - size.y, (int64_t)0),
|
||||
size.x, size.y);
|
||||
args.destination.region = MTLRegionMake2D(
|
||||
(NSUInteger)dstOrigin.x,
|
||||
std::max(dstTexture->height - (int64_t)dstOrigin.y - size.y, (int64_t)0),
|
||||
size.x, size.y);
|
||||
|
||||
// FIXME: we shouldn't need to know the type here. This is an artifact of using the old API.
|
||||
|
||||
args.source.texture = srcTexture->getMtlTextureForRead();
|
||||
args.destination.texture = dstTexture->getMtlTextureForWrite();
|
||||
args.source.level = srcLevel;
|
||||
args.source.slice = srcLayer;
|
||||
args.destination.level = dstLevel;
|
||||
args.destination.slice = dstLayer;
|
||||
|
||||
// TODO: The blit() call below always take the fast path.
|
||||
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blit/resolve");
|
||||
|
||||
dstTexture->extendLodRangeTo(dstLevel);
|
||||
}
|
||||
|
||||
void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
// If we're the in middle of a render pass, finish it.
|
||||
// This condition should only occur during copyFrame. It's okay to end the render pass because
|
||||
// we don't issue any other rendering commands.
|
||||
if (mContext->currentRenderPassEncoder) {
|
||||
[mContext->currentRenderPassEncoder endEncoding];
|
||||
mContext->currentRenderPassEncoder = nil;
|
||||
}
|
||||
|
||||
// Note: blitDEPRECATED is only used by Renderer::copyFrame
|
||||
// It is called between beginFrame and endFrame, but should never be called in the middle of
|
||||
// a render pass.
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"blitDEPRECATED() cannot be invoked inside a render pass.");
|
||||
|
||||
auto srcTarget = handle_cast<MetalRenderTarget>(src);
|
||||
auto dstTarget = handle_cast<MetalRenderTarget>(dst);
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
!(buffers & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)),
|
||||
"Blitting only supports COLOR0");
|
||||
ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0,
|
||||
"blitDEPRECATED only supports COLOR0");
|
||||
|
||||
ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 &&
|
||||
dstRect.left >= 0 && dstRect.bottom >= 0,
|
||||
@@ -1327,54 +1429,28 @@ void MetalDriver::blit(TargetBufferFlags buffers,
|
||||
t == MTLTextureType2DArray;
|
||||
};
|
||||
|
||||
// MetalBlitter supports blitting color and depth simultaneously, but for simplicitly we'll blit
|
||||
// them separately. In practice, Filament only ever blits a single buffer at a time anyway.
|
||||
// We always blit from/to the COLOR0 attachment.
|
||||
MetalRenderTarget::Attachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
|
||||
MetalRenderTarget::Attachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
|
||||
|
||||
if (any(buffers & TargetBufferFlags::COLOR_ALL)) {
|
||||
// We always blit from/to the COLOR0 attachment.
|
||||
MetalRenderTarget::Attachment srcColorAttachment = srcTarget->getReadColorAttachment(0);
|
||||
MetalRenderTarget::Attachment dstColorAttachment = dstTarget->getDrawColorAttachment(0);
|
||||
if (srcColorAttachment && dstColorAttachment) {
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcColorAttachment.getTexture().textureType) &&
|
||||
isBlitableTextureType(dstColorAttachment.getTexture().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
|
||||
if (srcColorAttachment && dstColorAttachment) {
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcColorAttachment.getTexture().textureType) &&
|
||||
isBlitableTextureType(dstColorAttachment.getTexture().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = filter;
|
||||
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
|
||||
args.source.texture = srcColorAttachment.getTexture();
|
||||
args.source.level = srcColorAttachment.level;
|
||||
args.source.slice = srcColorAttachment.layer;
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
args.filter = filter;
|
||||
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
|
||||
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
|
||||
args.source.color = srcColorAttachment.getTexture();
|
||||
args.destination.color = dstColorAttachment.getTexture();
|
||||
args.source.level = srcColorAttachment.level;
|
||||
args.destination.level = dstColorAttachment.level;
|
||||
args.source.slice = srcColorAttachment.layer;
|
||||
args.destination.slice = dstColorAttachment.layer;
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "Color blit");
|
||||
}
|
||||
}
|
||||
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
|
||||
args.destination.texture = dstColorAttachment.getTexture();
|
||||
args.destination.level = dstColorAttachment.level;
|
||||
args.destination.slice = dstColorAttachment.layer;
|
||||
|
||||
if (any(buffers & TargetBufferFlags::DEPTH)) {
|
||||
MetalRenderTarget::Attachment srcDepthAttachment = srcTarget->getDepthAttachment();
|
||||
MetalRenderTarget::Attachment dstDepthAttachment = dstTarget->getDepthAttachment();
|
||||
|
||||
if (srcDepthAttachment && dstDepthAttachment) {
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcDepthAttachment.getTexture().textureType) &&
|
||||
isBlitableTextureType(dstDepthAttachment.getTexture().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
args.filter = filter;
|
||||
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
|
||||
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
|
||||
args.source.depth = srcDepthAttachment.getTexture();
|
||||
args.destination.depth = dstDepthAttachment.getTexture();
|
||||
args.source.level = srcDepthAttachment.level;
|
||||
args.destination.level = dstDepthAttachment.level;
|
||||
args.source.slice = srcDepthAttachment.layer;
|
||||
args.destination.slice = dstDepthAttachment.layer;
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "Depth blit");
|
||||
}
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blitDEPRECATED");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1512,7 +1588,7 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
stencilPixelFormat = stencilAttachment.getPixelFormat();
|
||||
assert_invariant(isMetalFormatStencil(stencilPixelFormat));
|
||||
}
|
||||
MetalPipelineState pipelineState {
|
||||
MetalPipelineState const pipelineState {
|
||||
.vertexFunction = vertex,
|
||||
.fragmentFunction = fragment,
|
||||
.vertexDescription = primitive->vertexDescription,
|
||||
@@ -1530,13 +1606,13 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t
|
||||
.stencilAttachmentPixelFormat = stencilPixelFormat,
|
||||
.sampleCount = mContext->currentRenderTarget->getSamples(),
|
||||
.blendState = BlendState {
|
||||
.blendingEnabled = rs.hasBlending(),
|
||||
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),
|
||||
.alphaBlendOperation = getMetalBlendOperation(rs.blendEquationAlpha),
|
||||
.sourceRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcRGB),
|
||||
.sourceAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcAlpha),
|
||||
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),
|
||||
.destinationAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionDstAlpha),
|
||||
.destinationRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionDstRGB),
|
||||
.destinationAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionDstAlpha)
|
||||
.sourceAlphaBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcAlpha),
|
||||
.sourceRGBBlendFactor = getMetalBlendFactor(rs.blendFunctionSrcRGB),
|
||||
.blendingEnabled = rs.hasBlending(),
|
||||
},
|
||||
.colorWrite = rs.colorWrite
|
||||
};
|
||||
@@ -1835,6 +1911,21 @@ void MetalDriver::enumerateBoundBuffers(BufferObjectBinding bindingType,
|
||||
void MetalDriver::resetState(int) {
|
||||
}
|
||||
|
||||
void MetalDriver::runAtNextTick(const std::function<void()>& fn) noexcept {
|
||||
std::lock_guard<std::mutex> const lock(mTickOpsLock);
|
||||
mTickOps.push_back(fn);
|
||||
}
|
||||
|
||||
void MetalDriver::executeTickOps() noexcept {
|
||||
std::vector<std::function<void()>> ops;
|
||||
mTickOpsLock.lock();
|
||||
std::swap(ops, mTickOps);
|
||||
mTickOpsLock.unlock();
|
||||
for (const auto& f : ops) {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
// explicit instantiation of the Dispatcher
|
||||
template class ConcreteDispatcher<MetalDriver>;
|
||||
|
||||
|
||||
@@ -39,7 +39,15 @@ namespace filament {
|
||||
namespace backend {
|
||||
|
||||
static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) {
|
||||
NSUInteger u = 0;
|
||||
NSUInteger u = MTLTextureUsageUnknown;
|
||||
|
||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
||||
u |= MTLTextureUsageShaderRead;
|
||||
}
|
||||
if (any(usage & TextureUsage::UPLOADABLE)) {
|
||||
// This is only needed because of the slowpath is MetalBlitter
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
@@ -49,9 +57,13 @@ static inline MTLTextureUsage getMetalTextureUsage(TextureUsage usage) {
|
||||
if (any(usage & TextureUsage::STENCIL_ATTACHMENT)) {
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
|
||||
// All textures can be blitted from, so they must have the UsageShaderRead flag.
|
||||
u |= MTLTextureUsageShaderRead;
|
||||
if (any(usage & TextureUsage::BLIT_DST)) {
|
||||
// This is only needed because of the slowpath is MetalBlitter
|
||||
u |= MTLTextureUsageRenderTarget;
|
||||
}
|
||||
if (any(usage & TextureUsage::BLIT_SRC)) {
|
||||
u |= MTLTextureUsageShaderRead;
|
||||
}
|
||||
|
||||
return MTLTextureUsage(u);
|
||||
}
|
||||
@@ -233,13 +245,30 @@ void MetalSwapChain::present() {
|
||||
}
|
||||
}
|
||||
|
||||
struct PresentDrawableData {
|
||||
void* drawable = nullptr;
|
||||
MetalDriver* driver = nullptr;
|
||||
};
|
||||
|
||||
void presentDrawable(bool presentFrame, void* user) {
|
||||
// CFBridgingRelease here is used to balance the CFBridgingRetain inside of acquireDrawable.
|
||||
id<CAMetalDrawable> drawable = (id<CAMetalDrawable>) CFBridgingRelease(user);
|
||||
auto* presentDrawableData = static_cast<PresentDrawableData*>(user);
|
||||
|
||||
// CFBridgingRelease here is used to balance the CFBridgingRetain inside acquireDrawable.
|
||||
id<CAMetalDrawable> drawable =
|
||||
(id<CAMetalDrawable>)CFBridgingRelease(presentDrawableData->drawable);
|
||||
if (presentFrame) {
|
||||
[drawable present];
|
||||
}
|
||||
// The drawable will be released here when the "drawable" variable goes out of scope.
|
||||
|
||||
// Schedule the drawable destruction on the driver thread.
|
||||
void* voidDrawable = (void*) CFBridgingRetain(drawable);
|
||||
MetalDriver* driver = presentDrawableData->driver;
|
||||
driver->runAtNextTick([voidDrawable]() {
|
||||
// The drawable is released here.
|
||||
CFBridgingRelease(voidDrawable);
|
||||
});
|
||||
|
||||
delete presentDrawableData;
|
||||
}
|
||||
|
||||
void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
@@ -254,13 +283,15 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
// capture the _this_ pointer (MetalSwapChain*) instead of the drawable.
|
||||
id<CAMetalDrawable> d = drawable;
|
||||
void* userData = frameScheduledUserData;
|
||||
MetalDriver* driver = context.driver;
|
||||
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
|
||||
// CFBridgingRetain is used here to give the drawable a +1 retain count before
|
||||
// casting it to a void*.
|
||||
PresentCallable callable(presentDrawable, (void*) CFBridgingRetain(d));
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
callback(callable, userData);
|
||||
});
|
||||
auto* presentDrawableData = new PresentDrawableData;
|
||||
presentDrawableData->drawable = (void*) CFBridgingRetain(d);
|
||||
presentDrawableData->driver = driver;
|
||||
PresentCallable callable(presentDrawable, (void*) presentDrawableData);
|
||||
callback(callable, userData);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -756,6 +787,7 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
|
||||
#endif
|
||||
|
||||
id<MTLTexture> stagingTexture = [context.device newTextureWithDescriptor:descriptor];
|
||||
// FIXME? Why is this not just `MTLRegion sourceRegion = region;` ?
|
||||
MTLRegion sourceRegion = MTLRegionMake3D(0, 0, 0,
|
||||
region.size.width, region.size.height, region.size.depth);
|
||||
[stagingTexture replaceRegion:sourceRegion
|
||||
@@ -782,16 +814,16 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
|
||||
slices:NSMakeRange(0, slices)];
|
||||
}
|
||||
|
||||
MetalBlitter::BlitArgs args;
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = SamplerMagFilter::NEAREST;
|
||||
args.source.level = 0;
|
||||
args.source.slice = 0;
|
||||
args.source.region = sourceRegion;
|
||||
args.source.texture = stagingTexture;
|
||||
args.destination.level = level;
|
||||
args.destination.slice = slice;
|
||||
args.destination.region = region;
|
||||
args.source.color = stagingTexture;
|
||||
args.destination.color = destinationTexture;
|
||||
args.destination.texture = destinationTexture;
|
||||
context.blitter->blit(getPendingCommandBuffer(&context), args, "Texture upload blit");
|
||||
}
|
||||
|
||||
|
||||
@@ -103,10 +103,19 @@ void MetalShaderCompiler::terminate() noexcept {
|
||||
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
|
||||
length:source.size() - 1
|
||||
encoding:NSUTF8StringEncoding];
|
||||
|
||||
// By default, Metal uses the most recent language version.
|
||||
MTLCompileOptions* options = [MTLCompileOptions new];
|
||||
|
||||
// Disable Fast Math optimizations.
|
||||
// This ensures that operations adhere to IEEE standards for floating-point arithmetic,
|
||||
// which is crucial for half precision floats in scenarios where fast math optimizations
|
||||
// lead to inaccuracies, such as in handling special values like NaN or Infinity.
|
||||
options.fastMathEnabled = NO;
|
||||
|
||||
NSError* error = nil;
|
||||
// When options is nil, Metal uses the most recent language version available.
|
||||
id<MTLLibrary> library = [device newLibraryWithSource:objcSource
|
||||
options:nil
|
||||
options:options
|
||||
error:&error];
|
||||
if (library == nil) {
|
||||
if (error) {
|
||||
|
||||
@@ -182,6 +182,10 @@ bool NoopDriver::isParallelShaderCompileSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isDepthStencilResolveSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isWorkaroundNeeded(Workaround) {
|
||||
return false;
|
||||
}
|
||||
@@ -252,10 +256,6 @@ void NoopDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
|
||||
|
||||
void NoopDriver::generateMipmaps(Handle<HwTexture> th) { }
|
||||
|
||||
bool NoopDriver::canGenerateMipmaps() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void NoopDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||
BufferDescriptor&& data) {
|
||||
scheduleDestroy(std::move(data));
|
||||
@@ -322,12 +322,23 @@ void NoopDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void NoopDriver::blit(TargetBufferFlags buffers,
|
||||
void NoopDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
}
|
||||
|
||||
void NoopDriver::resolve(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
}
|
||||
|
||||
void NoopDriver::blit(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin,
|
||||
math::uint2 size) {
|
||||
}
|
||||
|
||||
void NoopDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t instanceCount) {
|
||||
}
|
||||
|
||||
@@ -274,6 +274,7 @@ void OpenGLContext::setDefaultState() noexcept {
|
||||
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);
|
||||
@@ -281,6 +282,7 @@ void OpenGLContext::setDefaultState() noexcept {
|
||||
glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ext.EXT_clip_cull_distance) {
|
||||
glEnable(GL_CLIP_DISTANCE0);
|
||||
@@ -309,7 +311,9 @@ void OpenGLContext::initProcs(Procs* procs,
|
||||
# ifdef BACKEND_OPENGL_VERSION_GL
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64v; // only core in GL
|
||||
# elif defined(GL_EXT_disjoint_timer_query)
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
||||
# ifndef __EMSCRIPTEN__
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
||||
# endif
|
||||
# endif // BACKEND_OPENGL_VERSION_GL
|
||||
|
||||
// core in ES 3.0 and GL 4.3
|
||||
@@ -321,6 +325,7 @@ void OpenGLContext::initProcs(Procs* procs,
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
# ifndef IOS // IOS is guaranteed to have ES3.x
|
||||
# ifndef __EMSCRIPTEN__
|
||||
if (UTILS_UNLIKELY(major == 2)) {
|
||||
// Runtime OpenGL version is ES 2.x
|
||||
if (UTILS_LIKELY(ext.OES_vertex_array_object)) {
|
||||
@@ -348,6 +353,7 @@ void OpenGLContext::initProcs(Procs* procs,
|
||||
|
||||
procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsKHR;
|
||||
}
|
||||
# endif // __EMSCRIPTEN__
|
||||
# endif // IOS
|
||||
#else
|
||||
procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsARB;
|
||||
@@ -476,7 +482,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
||||
// (that should be regardless of ANGLE, but we should double-check)
|
||||
bugs->split_easu = true;
|
||||
}
|
||||
// TODO: see if we could use `bugs.allow_read_only_ancillary_feedback_loop = true`
|
||||
}
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
@@ -581,17 +586,23 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
|
||||
// figure out and initialize the extensions we need
|
||||
using namespace std::literals;
|
||||
ext->APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_clip_control = exts.has("GL_EXT_clip_control"sv);
|
||||
#endif
|
||||
ext->EXT_clip_cull_distance = exts.has("GL_EXT_clip_cull_distance"sv);
|
||||
ext->EXT_color_buffer_float = exts.has("GL_EXT_color_buffer_float"sv);
|
||||
ext->EXT_color_buffer_half_float = exts.has("GL_EXT_color_buffer_half_float"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
#endif
|
||||
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
|
||||
ext->EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv);
|
||||
ext->EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv);
|
||||
#endif
|
||||
ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_texture_compression_etc2 = true;
|
||||
#endif
|
||||
ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
|
||||
|
||||
@@ -537,7 +537,7 @@ void OpenGLDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
|
||||
|
||||
if (UTILS_UNLIKELY(mContext.isES2())) {
|
||||
// Here we patch the specification constants to enable or not the rec709 output
|
||||
// Here we patch the specialization constants to enable or not the rec709 output
|
||||
// color space emulation in this program. Obviously, the backend shouldn't know about
|
||||
// specific spec-constants, so we need to handle failures gracefully. This cannot be
|
||||
// done at Material creation time because only the backend has access to
|
||||
@@ -1064,6 +1064,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
}
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
} else
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_multisampled_render_to_texture
|
||||
// EXT_multisampled_render_to_texture only support GL_COLOR_ATTACHMENT0
|
||||
if (!attachmentTypeNotSupportedByMSRTT && (t->depth <= 1)
|
||||
@@ -1076,7 +1077,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
// This extension only exists on OpenGL ES.
|
||||
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
|
||||
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
|
||||
glext::glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER,
|
||||
glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER,
|
||||
attachment, target, t->gl.id, binfo.level, rt->gl.samples);
|
||||
} else {
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment,
|
||||
@@ -1084,7 +1085,8 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
}
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
} else
|
||||
#endif
|
||||
#endif // GL_EXT_multisampled_render_to_texture
|
||||
#endif // __EMSCRIPTEN__
|
||||
if (!any(t->usage & TextureUsage::SAMPLEABLE) && t->samples > 1) {
|
||||
assert_invariant(rt->gl.samples > 1);
|
||||
assert_invariant(glIsRenderbuffer(t->gl.id));
|
||||
@@ -1183,6 +1185,7 @@ void OpenGLDriver::renderBufferStorage(GLuint rbo, GLenum internalformat, uint32
|
||||
uint32_t height, uint8_t samples) const noexcept {
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
|
||||
if (samples > 1) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_multisampled_render_to_texture
|
||||
auto& gl = mContext;
|
||||
if (gl.ext.EXT_multisampled_render_to_texture ||
|
||||
@@ -1190,7 +1193,8 @@ void OpenGLDriver::renderBufferStorage(GLuint rbo, GLenum internalformat, uint32
|
||||
glext::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER,
|
||||
samples, internalformat, width, height);
|
||||
} else
|
||||
#endif
|
||||
#endif // GL_EXT_multisampled_render_to_texture
|
||||
#endif // __EMSCRIPTEN__
|
||||
{
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glRenderbufferStorageMultisample(GL_RENDERBUFFER,
|
||||
@@ -1906,6 +1910,10 @@ bool OpenGLDriver::isParallelShaderCompileSupported() {
|
||||
return mShaderCompilerService.isParallelShaderCompileSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isDepthStencilResolveSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -2227,8 +2235,7 @@ void OpenGLDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint
|
||||
t->gl.baseLevel = (int8_t)minLevel;
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_BASE_LEVEL, t->gl.baseLevel);
|
||||
|
||||
t->gl
|
||||
.maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000
|
||||
t->gl.maxLevel = (int8_t)maxLevel; // NOTE: according to the GL spec, the default value of this 1000
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAX_LEVEL, t->gl.maxLevel);
|
||||
}
|
||||
#endif
|
||||
@@ -2278,16 +2285,13 @@ void OpenGLDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
bool OpenGLDriver::canGenerateMipmaps() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
PixelBufferDescriptor&& p) {
|
||||
auto& gl = mContext;
|
||||
|
||||
assert_invariant(t != nullptr);
|
||||
assert_invariant(xoffset + width <= std::max(1u, t->width >> level));
|
||||
assert_invariant(yoffset + height <= std::max(1u, t->height >> level));
|
||||
assert_invariant(t->samples <= 1);
|
||||
@@ -2986,16 +2990,18 @@ GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
|
||||
#endif
|
||||
|
||||
void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
auto& gl = mContext;
|
||||
if (gl.ext.EXT_debug_marker) {
|
||||
glInsertEventMarkerEXT(GLsizei(len ? len : strlen(string)), string);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
|
||||
@@ -3008,9 +3014,11 @@ void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_BEGIN(string);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::popGroupMarker(int) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
|
||||
@@ -3023,6 +3031,7 @@ void OpenGLDriver::popGroupMarker(int) {
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_END();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::startCapture(int) {
|
||||
@@ -3438,75 +3447,230 @@ void OpenGLDriver::clearWithRasterPipe(TargetBufferFlags clearFlags,
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::blit(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
void OpenGLDriver::resolve(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
DEBUG_MARKER()
|
||||
GLTexture const* const s = handle_cast<GLTexture*>(src);
|
||||
GLTexture const* const d = handle_cast<GLTexture*>(dst);
|
||||
assert_invariant(s);
|
||||
assert_invariant(d);
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
d->width == s->width && d->height == s->height,
|
||||
"invalid resolve: src and dst sizes don't match");
|
||||
|
||||
ASSERT_PRECONDITION(s->samples > 1 && d->samples == 1,
|
||||
"invalid resolve: src.samples=%u, dst.samples=%u",
|
||||
+s->samples, +d->samples);
|
||||
|
||||
blit( dst, dstLevel, dstLayer, {},
|
||||
src, srcLevel, srcLayer, {},
|
||||
{ d->width, d->height });
|
||||
}
|
||||
|
||||
void OpenGLDriver::blit(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, uint2 dstOrigin,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, uint2 srcOrigin,
|
||||
uint2 size) {
|
||||
DEBUG_MARKER()
|
||||
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
|
||||
assert_invariant(!gl.isES2());
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLbitfield const mask = getAttachmentBitfield(buffers);
|
||||
if (mask) {
|
||||
GLenum glFilterMode = (filter == SamplerMagFilter::NEAREST) ? GL_NEAREST : GL_LINEAR;
|
||||
if (mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) {
|
||||
// GL_INVALID_OPERATION is generated if mask contains any of the GL_DEPTH_BUFFER_BIT or
|
||||
// GL_STENCIL_BUFFER_BIT and filter is not GL_NEAREST.
|
||||
glFilterMode = GL_NEAREST;
|
||||
}
|
||||
|
||||
// note: for msaa RenderTargets with non-msaa attachments, we copy from the msaa sidecar
|
||||
// buffer -- this should produce the same output that if we copied from the resolved
|
||||
// texture. EXT_multisampled_render_to_texture seems to allow both behaviours, and this
|
||||
// is an emulation of that. We cannot use the resolved texture easily because it's not
|
||||
// actually attached to the RenderTarget. Another implementation would be to do a
|
||||
// reverse-resolve, but that wouldn't buy us anything.
|
||||
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
|
||||
GLRenderTarget const* d = handle_cast<GLRenderTarget const*>(dst);
|
||||
GLTexture* d = handle_cast<GLTexture*>(dst);
|
||||
GLTexture* s = handle_cast<GLTexture*>(src);
|
||||
assert_invariant(d);
|
||||
assert_invariant(s);
|
||||
|
||||
// blit operations are only supported from RenderTargets that have only COLOR0 (or
|
||||
// no color buffer at all)
|
||||
assert_invariant(
|
||||
!(s->targets & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)));
|
||||
ASSERT_PRECONDITION_NON_FATAL(any(d->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
assert_invariant(
|
||||
!(d->targets & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)));
|
||||
ASSERT_PRECONDITION_NON_FATAL(any(s->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
// With GLES 3.x, GL_INVALID_OPERATION is generated if the value of GL_SAMPLE_BUFFERS
|
||||
// for the draw buffer is greater than zero. This works with OpenGL, so we want to
|
||||
// make sure to catch this scenario.
|
||||
assert_invariant(d->gl.samples <= 1);
|
||||
ASSERT_PRECONDITION_NON_FATAL(s->format == d->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and the formats of draw and read buffers are not identical.
|
||||
// However, it's not well-defined in the spec what "format" means. So it's difficult
|
||||
// to have an assert here -- especially when dealing with the default framebuffer
|
||||
enum class AttachmentType : GLenum {
|
||||
COLOR = GL_COLOR_ATTACHMENT0,
|
||||
DEPTH = GL_DEPTH_ATTACHMENT,
|
||||
STENCIL = GL_STENCIL_ATTACHMENT,
|
||||
DEPTH_STENCIL = GL_DEPTH_STENCIL_ATTACHMENT,
|
||||
};
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and (...) the source and destination rectangles are not defined with the
|
||||
// same (X0, Y0) and (X1, Y1) bounds.
|
||||
auto getFormatType = [](TextureFormat format) -> AttachmentType {
|
||||
bool const depth = isDepthFormat(format);
|
||||
bool const stencil = isStencilFormat(format);
|
||||
if (depth && stencil) return AttachmentType::DEPTH_STENCIL;
|
||||
if (depth) return AttachmentType::DEPTH;
|
||||
if (stencil) return AttachmentType::STENCIL;
|
||||
return AttachmentType::COLOR;
|
||||
};
|
||||
|
||||
// Additionally, the EXT_multisampled_render_to_texture extension doesn't specify what
|
||||
// happens when blitting from an "implicit" resolve render target (does it work?), so
|
||||
// to ere on the safe side, we don't allow it.
|
||||
if (s->gl.samples > 1) {
|
||||
assert_invariant(!memcmp(&dstRect, &srcRect, sizeof(srcRect)));
|
||||
}
|
||||
AttachmentType const type = getFormatType(d->format);
|
||||
assert_invariant(type == getFormatType(s->format));
|
||||
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo);
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, d->gl.fbo);
|
||||
// GL_INVALID_OPERATION is generated if mask contains any of the GL_DEPTH_BUFFER_BIT or
|
||||
// GL_STENCIL_BUFFER_BIT and filter is not GL_NEAREST.
|
||||
GLbitfield mask = {};
|
||||
switch (type) {
|
||||
case AttachmentType::COLOR:
|
||||
mask = GL_COLOR_BUFFER_BIT;
|
||||
break;
|
||||
case AttachmentType::DEPTH:
|
||||
mask = GL_DEPTH_BUFFER_BIT;
|
||||
break;
|
||||
case AttachmentType::STENCIL:
|
||||
mask = GL_STENCIL_BUFFER_BIT;
|
||||
break;
|
||||
case AttachmentType::DEPTH_STENCIL:
|
||||
mask = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
|
||||
break;
|
||||
};
|
||||
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
|
||||
GLuint fbo[2] = {};
|
||||
glGenFramebuffers(2, fbo);
|
||||
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
glBlitFramebuffer(
|
||||
srcRect.left, srcRect.bottom, srcRect.right(), srcRect.top(),
|
||||
dstRect.left, dstRect.bottom, dstRect.right(), dstRect.top(),
|
||||
mask, glFilterMode);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[0]);
|
||||
switch (d->target) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
if (any(d->usage & TextureUsage::SAMPLEABLE)) {
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_2D, d->gl.id, dstLevel);
|
||||
} else {
|
||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
GL_RENDERBUFFER, d->gl.id);
|
||||
}
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + dstLayer, d->gl.id, dstLevel);
|
||||
break;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
case SamplerType::SAMPLER_3D:
|
||||
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GLenum(type),
|
||||
d->gl.id, dstLevel, dstLayer);
|
||||
break;
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
break;
|
||||
}
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
|
||||
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, fbo[1]);
|
||||
switch (s->target) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
if (any(s->usage & TextureUsage::SAMPLEABLE)) {
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_2D, s->gl.id, srcLevel);
|
||||
} else {
|
||||
glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
GL_RENDERBUFFER, s->gl.id);
|
||||
}
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + srcLayer, s->gl.id, srcLevel);
|
||||
break;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
case SamplerType::SAMPLER_3D:
|
||||
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GLenum(type),
|
||||
s->gl.id, srcLevel, srcLayer);
|
||||
break;
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
break;
|
||||
}
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
|
||||
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
glBlitFramebuffer(
|
||||
srcOrigin.x, srcOrigin.y, srcOrigin.x + size.x, srcOrigin.y + size.y,
|
||||
dstOrigin.x, dstOrigin.y, dstOrigin.x + size.x, dstOrigin.y + size.y,
|
||||
mask, GL_NEAREST);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(2, fbo);
|
||||
|
||||
if (any(d->usage & TextureUsage::SAMPLEABLE)) {
|
||||
// In a sense, blitting to a texture level is similar to calling setTextureData on it; in
|
||||
// both cases, we update the base/max LOD to give shaders access to levels as they become
|
||||
// available. Note that this can only expand the LOD range (never shrink it), and that
|
||||
// users can override this range by calling setMinMaxLevels().
|
||||
updateTextureLodRange(d, int8_t(dstLevel));
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
|
||||
// Note: blitDEPRECATED is only used by Renderer::copyFrame
|
||||
|
||||
DEBUG_MARKER()
|
||||
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
|
||||
assert_invariant(!gl.isES2());
|
||||
|
||||
ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0,
|
||||
"blitDEPRECATED only supports COLOR0");
|
||||
|
||||
ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 &&
|
||||
dstRect.left >= 0 && dstRect.bottom >= 0,
|
||||
"Source and destination rects must be positive.");
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
|
||||
GLenum const glFilterMode = (filter == SamplerMagFilter::NEAREST) ? GL_NEAREST : GL_LINEAR;
|
||||
|
||||
// note: for msaa RenderTargets with non-msaa attachments, we copy from the msaa sidecar
|
||||
// buffer -- this should produce the same output that if we copied from the resolved
|
||||
// texture. EXT_multisampled_render_to_texture seems to allow both behaviours, and this
|
||||
// is an emulation of that. We cannot use the resolved texture easily because it's not
|
||||
// actually attached to the RenderTarget. Another implementation would be to do a
|
||||
// reverse-resolve, but that wouldn't buy us anything.
|
||||
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
|
||||
GLRenderTarget const* d = handle_cast<GLRenderTarget const*>(dst);
|
||||
|
||||
// With GLES 3.x, GL_INVALID_OPERATION is generated if the value of GL_SAMPLE_BUFFERS
|
||||
// for the draw buffer is greater than zero. This works with OpenGL, so we want to
|
||||
// make sure to catch this scenario.
|
||||
assert_invariant(d->gl.samples <= 1);
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and the formats of draw and read buffers are not identical.
|
||||
// However, it's not well-defined in the spec what "format" means. So it's difficult
|
||||
// to have an assert here -- especially when dealing with the default framebuffer
|
||||
|
||||
// GL_INVALID_OPERATION is generated if GL_SAMPLE_BUFFERS for the read buffer is greater
|
||||
// than zero and (...) the source and destination rectangles are not defined with the
|
||||
// same (X0, Y0) and (X1, Y1) bounds.
|
||||
|
||||
// Additionally, the EXT_multisampled_render_to_texture extension doesn't specify what
|
||||
// happens when blitting from an "implicit" resolve render target (does it work?), so
|
||||
// to ere on the safe side, we don't allow it.
|
||||
if (s->gl.samples > 1) {
|
||||
assert_invariant(!memcmp(&dstRect, &srcRect, sizeof(srcRect)));
|
||||
}
|
||||
|
||||
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo);
|
||||
gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, d->gl.fbo);
|
||||
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_READ_FRAMEBUFFER)
|
||||
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_DRAW_FRAMEBUFFER)
|
||||
|
||||
gl.disable(GL_SCISSOR_TEST);
|
||||
glBlitFramebuffer(
|
||||
srcRect.left, srcRect.bottom, srcRect.right(), srcRect.top(),
|
||||
dstRect.left, dstRect.bottom, dstRect.right(), dstRect.top(),
|
||||
GL_COLOR_BUFFER_BIT, glFilterMode);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -140,20 +140,37 @@ void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept
|
||||
ShaderCompilerService::ShaderCompilerService(OpenGLDriver& driver)
|
||||
: mDriver(driver),
|
||||
mBlobCache(driver.getContext()),
|
||||
mCallbackManager(driver),
|
||||
KHR_parallel_shader_compile(driver.getContext().ext.KHR_parallel_shader_compile) {
|
||||
mCallbackManager(driver) {
|
||||
}
|
||||
|
||||
ShaderCompilerService::~ShaderCompilerService() noexcept = default;
|
||||
|
||||
bool ShaderCompilerService::isParallelShaderCompileSupported() const noexcept {
|
||||
return KHR_parallel_shader_compile || mShaderCompilerThreadCount;
|
||||
assert_invariant(mMode != Mode::UNDEFINED);
|
||||
return mMode != Mode::SYNCHRONOUS;
|
||||
}
|
||||
|
||||
void ShaderCompilerService::init() noexcept {
|
||||
// If we have KHR_parallel_shader_compile, we always use it, it should be more resource
|
||||
// friendly.
|
||||
if (!KHR_parallel_shader_compile) {
|
||||
// Here we decide which mode we'll be using. We always prefer our own thread-pool if
|
||||
// that mode is available because, we have no control on how the compilation queues are
|
||||
// handled if done by the driver (so at the very least we'd need to decode this per-driver).
|
||||
// In theory, using Mode::ASYNCHRONOUS (a.k.a. KHR_parallel_shader_compile) could be more
|
||||
// efficient, since it doesn't require shared contexts.
|
||||
// In practice, we already know that with ANGLE, Mode::ASYNCHRONOUS can cause very long
|
||||
// pauses at glDraw() time in the situation where glLinkProgram() has been emitted, but has
|
||||
// other programs ahead of it in ANGLE's queue.
|
||||
if (mDriver.mPlatform.isExtraContextSupported()) {
|
||||
// our thread-pool if possible
|
||||
mMode = Mode::THREAD_POOL;
|
||||
} else if (mDriver.getContext().ext.KHR_parallel_shader_compile) {
|
||||
// if not, async shader compilation and link if the driver supports it
|
||||
mMode = Mode::ASYNCHRONOUS;
|
||||
} else {
|
||||
// fallback to synchronous shader compilation
|
||||
mMode = Mode::SYNCHRONOUS;
|
||||
}
|
||||
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// - on Adreno there is a single compiler object. We can't use a pool > 1
|
||||
// also glProgramBinary blocks if other threads are compiling.
|
||||
// - on Mali shader compilation can be multi-threaded, but program linking happens on
|
||||
@@ -163,37 +180,42 @@ void ShaderCompilerService::init() noexcept {
|
||||
// - on macOS (M1 MacBook Pro/Ventura) there is global lock around all GL APIs when using
|
||||
// a shared context, so parallel shader compilation yields no benefit.
|
||||
// - on windows/linux we could use more threads, tbd.
|
||||
if (mDriver.mPlatform.isExtraContextSupported()) {
|
||||
// By default, we use one thread at the same priority as the gl thread. This is the
|
||||
// safest choice that avoids priority inversions.
|
||||
uint32_t poolSize = 1;
|
||||
JobSystem::Priority priority = JobSystem::Priority::DISPLAY;
|
||||
|
||||
auto const& renderer = mDriver.getContext().state.renderer;
|
||||
if (UTILS_UNLIKELY(strstr(renderer, "PowerVR"))) {
|
||||
// The PowerVR driver support parallel shader compilation well, so we use 2
|
||||
// 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
|
||||
// increase memory pressure significantly.
|
||||
poolSize = 2;
|
||||
priority = JobSystem::Priority::BACKGROUND;
|
||||
}
|
||||
// By default, we use one thread at the same priority as the gl thread. This is the
|
||||
// safest choice that avoids priority inversions.
|
||||
uint32_t poolSize = 1;
|
||||
JobSystem::Priority priority = JobSystem::Priority::DISPLAY;
|
||||
|
||||
mShaderCompilerThreadCount = poolSize;
|
||||
mCompilerThreadPool.init(mShaderCompilerThreadCount,
|
||||
[&platform = mDriver.mPlatform, priority]() {
|
||||
// give the thread a name
|
||||
JobSystem::setThreadName("CompilerThreadPool");
|
||||
// run at a slightly lower priority than other filament threads
|
||||
JobSystem::setThreadPriority(priority);
|
||||
// create a gl context current to this thread
|
||||
platform.createContext(true);
|
||||
},
|
||||
[&platform = mDriver.mPlatform]() {
|
||||
// release context and thread state
|
||||
platform.releaseContext();
|
||||
});
|
||||
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
|
||||
// increase memory pressure significantly.
|
||||
if (UTILS_UNLIKELY(strstr(renderer, "PowerVR"))) {
|
||||
// For PowerVR, it's unclear what the cost of extra shared contexts is, so we only
|
||||
// use 2 to be safe.
|
||||
poolSize = 2;
|
||||
priority = JobSystem::Priority::BACKGROUND;
|
||||
} else if (UTILS_UNLIKELY(strstr(renderer, "ANGLE"))) {
|
||||
// Angle shared contexts are not expensive once we have two.
|
||||
poolSize = (std::thread::hardware_concurrency() + 1) / 2;
|
||||
priority = JobSystem::Priority::BACKGROUND;
|
||||
}
|
||||
|
||||
mShaderCompilerThreadCount = poolSize;
|
||||
mCompilerThreadPool.init(mShaderCompilerThreadCount,
|
||||
[&platform = mDriver.mPlatform, priority]() {
|
||||
// give the thread a name
|
||||
JobSystem::setThreadName("CompilerThreadPool");
|
||||
// run at a slightly lower priority than other filament threads
|
||||
JobSystem::setThreadPriority(priority);
|
||||
// create a gl context current to this thread
|
||||
platform.createContext(true);
|
||||
},
|
||||
[&platform = mDriver.mPlatform]() {
|
||||
// release context and thread state
|
||||
platform.releaseContext();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +249,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
token->handle = mCallbackManager.get();
|
||||
|
||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||
if (mShaderCompilerThreadCount) {
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// queue a compile job
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, &gl, program = std::move(program), token]() mutable {
|
||||
@@ -276,7 +298,8 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
token->shaderSourceCode);
|
||||
|
||||
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
||||
if (KHR_parallel_shader_compile) {
|
||||
assert_invariant(mMode != Mode::THREAD_POOL);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// don't attempt to link this program if all shaders are not done compiling
|
||||
GLint status;
|
||||
if (token->gl.program) {
|
||||
@@ -300,7 +323,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
// link the program, this also cannot fail because status is checked later.
|
||||
token->gl.program = linkProgram(mDriver.getContext(),
|
||||
token->gl.shaders, token->attributes);
|
||||
if (KHR_parallel_shader_compile) {
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// wait until the link finishes...
|
||||
return false;
|
||||
}
|
||||
@@ -341,7 +364,7 @@ void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
|
||||
bool const canceled = token->compiler.cancelTickOp(token);
|
||||
|
||||
if (token->compiler.mShaderCompilerThreadCount) {
|
||||
if (token->compiler.mMode == Mode::THREAD_POOL) {
|
||||
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||
if (!job) {
|
||||
// The job is being executed right now. We need to wait for it to finish to avoid a
|
||||
@@ -375,7 +398,7 @@ void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
|
||||
void ShaderCompilerService::tick() {
|
||||
// we don't need to run executeTickOps() if we're using the thread-pool
|
||||
if (UTILS_UNLIKELY(!mShaderCompilerThreadCount)) {
|
||||
if (UTILS_UNLIKELY(mMode != Mode::THREAD_POOL)) {
|
||||
executeTickOps();
|
||||
}
|
||||
}
|
||||
@@ -400,7 +423,7 @@ void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) n
|
||||
GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
if (!token->gl.program) {
|
||||
if (mShaderCompilerThreadCount) {
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// we need this program right now, remove it from the queue
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
@@ -416,7 +439,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
// Block until we get the program from the pool. Generally this wouldn't block
|
||||
// because we just compiled the program above, when executing job.
|
||||
ShaderCompilerService::getProgramFromCompilerPool(token);
|
||||
} else if (KHR_parallel_shader_compile) {
|
||||
} else if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// we force the program link -- which might stall, either here or below in
|
||||
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
|
||||
token->compiler.cancelTickOp(token);
|
||||
@@ -524,7 +547,8 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
// add support for ARB_shading_language_packing if needed
|
||||
auto const packingFunctions = process_ARB_shading_language_packing(context);
|
||||
|
||||
// split shader source, so we can insert the specification constants and the packing functions
|
||||
// split shader source, so we can insert the specialization constants and the packing
|
||||
// functions
|
||||
auto const [prolog, body] = splitShaderSource(source);
|
||||
|
||||
const std::array<const char*, 4> sources = {
|
||||
|
||||
@@ -95,19 +95,42 @@ public:
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
||||
|
||||
private:
|
||||
struct Job {
|
||||
template<typename FUNC>
|
||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
|
||||
Job(std::function<bool(Job const& job)> fn,
|
||||
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
|
||||
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
|
||||
}
|
||||
std::function<bool(Job const& job)> fn;
|
||||
CallbackHandler* handler = nullptr;
|
||||
void* user = nullptr;
|
||||
CallbackHandler::Callback callback{};
|
||||
};
|
||||
|
||||
enum class Mode {
|
||||
UNDEFINED, // init() has not beed called yet.
|
||||
SYNCHRONOUS, // synchronous shader compilation
|
||||
THREAD_POOL, // asynchronous shader compilation using a thread-pool (most common)
|
||||
ASYNCHRONOUS // asynchronous shader compilation using KHR_parallel_shader_compile
|
||||
};
|
||||
|
||||
OpenGLDriver& mDriver;
|
||||
OpenGLBlobCache mBlobCache;
|
||||
CallbackManager mCallbackManager;
|
||||
CompilerThreadPool mCompilerThreadPool;
|
||||
|
||||
const bool KHR_parallel_shader_compile;
|
||||
uint32_t mShaderCompilerThreadCount = 0u;
|
||||
Mode mMode = Mode::UNDEFINED; // valid after init() is called
|
||||
|
||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||
std::vector<ContainerType> mRunAtNextTickOps;
|
||||
|
||||
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
|
||||
|
||||
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
|
||||
|
||||
static void compileShaders(
|
||||
static void compileShaders(
|
||||
OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
@@ -127,27 +150,11 @@ private:
|
||||
|
||||
static bool checkProgramStatus(program_token_t const& token) noexcept;
|
||||
|
||||
struct Job {
|
||||
template<typename FUNC>
|
||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
|
||||
Job(std::function<bool(Job const& job)> fn,
|
||||
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
|
||||
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
|
||||
}
|
||||
std::function<bool(Job const& job)> fn;
|
||||
CallbackHandler* handler = nullptr;
|
||||
void* user = nullptr;
|
||||
CallbackHandler::Callback callback{};
|
||||
};
|
||||
|
||||
void runAtNextTick(CompilerPriorityQueue priority,
|
||||
const program_token_t& token, Job job) noexcept;
|
||||
void executeTickOps() noexcept;
|
||||
bool cancelTickOp(program_token_t token) noexcept;
|
||||
// order of insertion is important
|
||||
|
||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||
std::vector<ContainerType> mRunAtNextTickOps;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -28,6 +28,7 @@ static void getProcAddress(T& pfn, const char* name) noexcept {
|
||||
}
|
||||
|
||||
namespace glext {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_OES_EGL_image
|
||||
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
|
||||
#endif
|
||||
@@ -72,10 +73,11 @@ PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR;
|
||||
// use getProcAddress for ES3.1 and above entry points.
|
||||
PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
|
||||
#endif
|
||||
|
||||
static std::once_flag sGlExtInitialized;
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
void importGLESExtensionsEntryPoints() {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
std::call_once(sGlExtInitialized, +[]() {
|
||||
#ifdef GL_OES_EGL_image
|
||||
getProcAddress(glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES");
|
||||
@@ -119,6 +121,7 @@ void importGLESExtensionsEntryPoints() {
|
||||
getProcAddress(glDispatchCompute, "glDispatchCompute");
|
||||
#endif
|
||||
});
|
||||
#endif // __EMSCRIPTEN__
|
||||
}
|
||||
|
||||
} // namespace glext
|
||||
|
||||
@@ -112,6 +112,7 @@ namespace glext {
|
||||
// it is currently called from PlatformEGL.
|
||||
void importGLESExtensionsEntryPoints();
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_OES_EGL_image
|
||||
extern PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
|
||||
#endif
|
||||
@@ -153,6 +154,7 @@ extern PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR;
|
||||
#if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
|
||||
extern PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
|
||||
#endif
|
||||
#endif // __EMSCRIPTEN__
|
||||
} // namespace glext
|
||||
|
||||
using namespace glext;
|
||||
|
||||
@@ -293,20 +293,6 @@ io::ostream& operator<<(io::ostream& out, TextureFormat format) {
|
||||
return out;
|
||||
}
|
||||
|
||||
io::ostream& operator<<(io::ostream& out, TextureUsage usage) {
|
||||
switch (usage) {
|
||||
CASE(TextureUsage, NONE)
|
||||
CASE(TextureUsage, DEFAULT)
|
||||
CASE(TextureUsage, COLOR_ATTACHMENT)
|
||||
CASE(TextureUsage, DEPTH_ATTACHMENT)
|
||||
CASE(TextureUsage, STENCIL_ATTACHMENT)
|
||||
CASE(TextureUsage, UPLOADABLE)
|
||||
CASE(TextureUsage, SAMPLEABLE)
|
||||
CASE(TextureUsage, SUBPASS_INPUT)
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
io::ostream& operator<<(io::ostream& out, TextureCubemapFace face) {
|
||||
switch (face) {
|
||||
CASE(TextureCubemapFace, NEGATIVE_X)
|
||||
@@ -469,7 +455,7 @@ io::ostream& operator<<(io::ostream& out, filament::backend::Viewport const& vie
|
||||
|
||||
io::ostream& operator<<(io::ostream& out, TargetBufferFlags flags) {
|
||||
// TODO: implement decoding of enum
|
||||
out << uint8_t(flags);
|
||||
out << uint32_t(flags);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,22 +38,15 @@ using ImgUtil = VulkanImageUtility;
|
||||
namespace {
|
||||
|
||||
inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect, VkFilter filter,
|
||||
const VkExtent2D srcExtent, VulkanAttachment src, VulkanAttachment dst,
|
||||
VulkanAttachment src, VulkanAttachment dst,
|
||||
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
|
||||
const VkImageBlit blitRegions[1] = {{
|
||||
.srcSubresource = {aspect, src.level, src.layer, 1},
|
||||
.srcOffsets = {srcRect[0], srcRect[1]},
|
||||
.dstSubresource = {aspect, dst.level, dst.layer, 1},
|
||||
.dstOffsets = {dstRect[0], dstRect[1]},
|
||||
}};
|
||||
|
||||
const VkImageResolve resolveRegions[1] = {{
|
||||
.srcSubresource = {aspect, src.level, src.layer, 1},
|
||||
.srcOffset = srcRect[0],
|
||||
.dstSubresource = {aspect, dst.level, dst.layer, 1},
|
||||
.dstOffset = dstRect[0],
|
||||
.extent = {srcExtent.width, srcExtent.height, 1},
|
||||
}};
|
||||
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
|
||||
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
|
||||
<< " layout=" << src.getLayout()
|
||||
<< " to=" << dst.texture->getVkImage() << ",level=" << (int) dst.level
|
||||
<< " layout=" << dst.getLayout() << utils::io::endl;
|
||||
}
|
||||
|
||||
const VkImageSubresourceRange srcRange = {
|
||||
.aspectMask = aspect,
|
||||
@@ -71,6 +64,36 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
.layerCount = 1,
|
||||
};
|
||||
|
||||
VulkanLayout oldSrcLayout = src.getLayout();
|
||||
VulkanLayout oldDstLayout = dst.getLayout();
|
||||
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST);
|
||||
|
||||
const VkImageBlit blitRegions[1] = {{
|
||||
.srcSubresource = { aspect, src.level, src.layer, 1 },
|
||||
.srcOffsets = { srcRect[0], srcRect[1] },
|
||||
.dstSubresource = { aspect, dst.level, dst.layer, 1 },
|
||||
.dstOffsets = { dstRect[0], dstRect[1] },
|
||||
}};
|
||||
vkCmdBlitImage(cmdbuffer,
|
||||
src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST),
|
||||
1, blitRegions, filter);
|
||||
|
||||
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
|
||||
oldSrcLayout = ImgUtil::getDefaultLayout(src.texture->usage);
|
||||
}
|
||||
if (oldDstLayout == VulkanLayout::UNDEFINED) {
|
||||
oldDstLayout = ImgUtil::getDefaultLayout(dst.texture->usage);
|
||||
}
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, oldSrcLayout);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, oldDstLayout);
|
||||
}
|
||||
|
||||
inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
VulkanAttachment src, VulkanAttachment dst) {
|
||||
|
||||
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
|
||||
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
|
||||
<< " layout=" << src.getLayout()
|
||||
@@ -78,25 +101,41 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
<< " layout=" << dst.getLayout() << utils::io::endl;
|
||||
}
|
||||
|
||||
const VkImageSubresourceRange srcRange = {
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = src.level,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = src.layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
|
||||
const VkImageSubresourceRange dstRange = {
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = dst.level,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = dst.layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
|
||||
VulkanLayout oldSrcLayout = src.getLayout();
|
||||
VulkanLayout oldDstLayout = dst.getLayout();
|
||||
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, VulkanLayout::TRANSFER_SRC);
|
||||
dst.texture->transitionLayout(cmdbuffer, dstRange, VulkanLayout::TRANSFER_DST);
|
||||
|
||||
if (src.texture->samples > 1 && dst.texture->samples == 1) {
|
||||
assert_invariant(
|
||||
aspect != VK_IMAGE_ASPECT_DEPTH_BIT && "Resolve with depth is not yet supported.");
|
||||
vkCmdResolveImage(cmdbuffer,
|
||||
src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST),
|
||||
1, resolveRegions);
|
||||
} else {
|
||||
vkCmdBlitImage(cmdbuffer,
|
||||
src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST),
|
||||
1, blitRegions, filter);
|
||||
}
|
||||
assert_invariant(
|
||||
aspect != VK_IMAGE_ASPECT_DEPTH_BIT && "Resolve with depth is not yet supported.");
|
||||
const VkImageResolve resolveRegions[1] = {{
|
||||
.srcSubresource = { aspect, src.level, src.layer, 1 },
|
||||
.srcOffset = { 0, 0 },
|
||||
.dstSubresource = { aspect, dst.level, dst.layer, 1 },
|
||||
.dstOffset = { 0, 0 },
|
||||
.extent = { src.getExtent2D().width, src.getExtent2D().height, 1 },
|
||||
}};
|
||||
vkCmdResolveImage(cmdbuffer,
|
||||
src.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_SRC),
|
||||
dst.getImage(), ImgUtil::getVkLayout(VulkanLayout::TRANSFER_DST),
|
||||
1, resolveRegions);
|
||||
|
||||
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
|
||||
oldSrcLayout = ImgUtil::getDefaultLayout(src.texture->usage);
|
||||
@@ -115,52 +154,22 @@ struct BlitterUniforms {
|
||||
|
||||
}// anonymous namespace
|
||||
|
||||
VulkanBlitter::VulkanBlitter(VulkanStagePool& stagePool, VulkanPipelineCache& pipelineCache,
|
||||
VulkanFboCache& fboCache, VulkanSamplerCache& samplerCache) noexcept
|
||||
: mStagePool(stagePool), mPipelineCache(pipelineCache), mFramebufferCache(fboCache),
|
||||
mSamplerCache(samplerCache) {}
|
||||
VulkanBlitter::VulkanBlitter() noexcept = default;
|
||||
|
||||
void VulkanBlitter::initialize(VkPhysicalDevice physicalDevice, VkDevice device,
|
||||
VmaAllocator allocator, VulkanCommands* commands, VulkanTexture* emptyTexture) noexcept {
|
||||
VmaAllocator allocator, VulkanCommands* commands) noexcept {
|
||||
mPhysicalDevice = physicalDevice;
|
||||
mDevice = device;
|
||||
mAllocator = allocator;
|
||||
mCommands = commands;
|
||||
mEmptyTexture = emptyTexture;
|
||||
}
|
||||
|
||||
void VulkanBlitter::blitColor(BlitArgs args) {
|
||||
const VulkanAttachment src = args.srcTarget->getColor(args.targetIndex);
|
||||
const VulkanAttachment dst = args.dstTarget->getColor(0);
|
||||
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
void VulkanBlitter::resolve(VulkanAttachment dst, VulkanAttachment src) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_BLIT_FORMAT)
|
||||
VkPhysicalDevice const gpu = mPhysicalDevice;
|
||||
VkFormatProperties info;
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
|
||||
"Source format is not blittable %d", src.getFormat())) {
|
||||
return;
|
||||
}
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
|
||||
"Destination format is not blittable %d", dst.getFormat())) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
// src and dst should have the same aspect here
|
||||
VkImageAspectFlags const aspect = src.texture->getImageAspect();
|
||||
|
||||
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst,
|
||||
args.srcRectPair, args.dstRectPair);
|
||||
}
|
||||
|
||||
void VulkanBlitter::blitDepth(BlitArgs args) {
|
||||
const VulkanAttachment src = args.srcTarget->getDepth();
|
||||
const VulkanAttachment dst = args.dstTarget->getDepth();
|
||||
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
assert_invariant(!(aspect & VK_IMAGE_ASPECT_DEPTH_BIT));
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_BLIT_FORMAT)
|
||||
VkPhysicalDevice const gpu = mPhysicalDevice;
|
||||
@@ -177,286 +186,40 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
|
||||
}
|
||||
#endif
|
||||
|
||||
assert_invariant(src.texture && dst.texture);
|
||||
|
||||
if (src.texture->samples > 1 && dst.texture->samples == 1) {
|
||||
blitSlowDepth(args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
|
||||
args.dstRectPair);
|
||||
return;
|
||||
}
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
|
||||
args.dstRectPair);
|
||||
resolveFast(cmdbuffer, aspect, src, dst);
|
||||
}
|
||||
|
||||
void VulkanBlitter::blit(VkFilter filter,
|
||||
VulkanAttachment dst, const VkOffset3D* dstRectPair,
|
||||
VulkanAttachment src, const VkOffset3D* srcRectPair) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_BLIT_FORMAT)
|
||||
VkPhysicalDevice const gpu = mPhysicalDevice;
|
||||
VkFormatProperties info;
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
|
||||
"Depth src format is not blittable %d", src.getFormat())) {
|
||||
return;
|
||||
}
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
|
||||
"Depth dst format is not blittable %d", dst.getFormat())) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// src and dst should have the same aspect here
|
||||
VkImageAspectFlags const aspect = src.texture->getImageAspect();
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
blitFast(cmdbuffer, aspect, filter, src, dst, srcRectPair, dstRectPair);
|
||||
}
|
||||
|
||||
void VulkanBlitter::terminate() noexcept {
|
||||
if (mDepthResolveProgram) {
|
||||
delete mDepthResolveProgram;
|
||||
mDepthResolveProgram = nullptr;
|
||||
}
|
||||
|
||||
if (mTriangleBuffer) {
|
||||
delete mTriangleBuffer;
|
||||
mTriangleBuffer = nullptr;
|
||||
}
|
||||
|
||||
if (mParamsBuffer) {
|
||||
delete mParamsBuffer;
|
||||
mParamsBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// If we created these shader modules in the constructor, the device might not be ready yet.
|
||||
// It is easier to do lazy initialization, which can also improve load time.
|
||||
void VulkanBlitter::lazyInit() noexcept {
|
||||
if (mDepthResolveProgram != nullptr) {
|
||||
return;
|
||||
}
|
||||
assert_invariant(mDevice);
|
||||
|
||||
auto decode = [device = mDevice](const uint8_t* compressed, int compressedSize) {
|
||||
const size_t spirvSize = smolv::GetDecodedBufferSize(compressed, compressedSize);
|
||||
FixedCapacityVector<uint8_t> spirv(spirvSize);
|
||||
smolv::Decode(compressed, compressedSize, spirv.data(), spirvSize);
|
||||
|
||||
VkShaderModuleCreateInfo moduleInfo = {};
|
||||
moduleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
moduleInfo.codeSize = spirvSize;
|
||||
moduleInfo.pCode = (uint32_t*) spirv.data();
|
||||
|
||||
VkShaderModule result = VK_NULL_HANDLE;
|
||||
vkCreateShaderModule(device, &moduleInfo, VKALLOC, &result);
|
||||
assert_invariant(result);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
VkShaderModule vertexShader = decode(VKSHADERS_BLITDEPTHVS_DATA, VKSHADERS_BLITDEPTHVS_SIZE);
|
||||
VkShaderModule fragmentShader = decode(VKSHADERS_BLITDEPTHFS_DATA, VKSHADERS_BLITDEPTHFS_SIZE);
|
||||
|
||||
// Allocate one anonymous sampler at slot 0.
|
||||
VulkanProgram::CustomSamplerInfoList samplers = {
|
||||
{0, 0, ShaderStageFlags::FRAGMENT},
|
||||
};
|
||||
mDepthResolveProgram = new VulkanProgram(mDevice, vertexShader, fragmentShader, samplers);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_BLITTER)
|
||||
utils::slog.d << "Created Shader Module for VulkanBlitter "
|
||||
<< "shaders = (" << vertexShader << ", " << fragmentShader << ")"
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
|
||||
static const float kTriangleVertices[] = {
|
||||
-1.0f, -1.0f,
|
||||
+1.0f, -1.0f,
|
||||
-1.0f, +1.0f,
|
||||
+1.0f, +1.0f,
|
||||
};
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.buffer();
|
||||
|
||||
mTriangleBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
sizeof(kTriangleVertices));
|
||||
|
||||
mTriangleBuffer->loadFromCpu(cmdbuffer, kTriangleVertices, 0, sizeof(kTriangleVertices));
|
||||
|
||||
mParamsBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
sizeof(BlitterUniforms));
|
||||
}
|
||||
|
||||
// At a high level, the procedure for resolving depth looks like this:
|
||||
// 1. Load uniforms
|
||||
// 2. Begin render pass
|
||||
// 3. Draw a big triangle
|
||||
// 4. End render pass.
|
||||
void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, VulkanAttachment src,
|
||||
VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
|
||||
lazyInit();
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
commands->acquire(src.texture);
|
||||
commands->acquire(dst.texture);
|
||||
|
||||
BlitterUniforms const uniforms = {
|
||||
.sampleCount = src.texture->samples,
|
||||
.inverseSampleCount = 1.0f / float(src.texture->samples),
|
||||
};
|
||||
mParamsBuffer->loadFromCpu(cmdbuffer, &uniforms, 0, sizeof(uniforms));
|
||||
|
||||
VkImageAspectFlags const aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
|
||||
// We will transition the src into sampler layout and also keep it in sampler layout for
|
||||
// consistency.
|
||||
VulkanLayout const samplerLayout = VulkanLayout::DEPTH_SAMPLER;
|
||||
|
||||
// BEGIN RENDER PASS
|
||||
// -----------------
|
||||
|
||||
const VulkanFboCache::RenderPassKey rpkey = {
|
||||
.initialColorLayoutMask = 0,
|
||||
.initialDepthLayout = VulkanLayout::UNDEFINED,
|
||||
.renderPassDepthLayout = samplerLayout,
|
||||
.finalDepthLayout = samplerLayout,
|
||||
.depthFormat = dst.getFormat(),
|
||||
.clear = {},
|
||||
.discardStart = TargetBufferFlags::DEPTH,
|
||||
.samples = 1,
|
||||
};
|
||||
|
||||
const VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey);
|
||||
mPipelineCache.bindRenderPass(renderPass, 0);
|
||||
|
||||
const VulkanFboCache::FboKey fbkey {
|
||||
.renderPass = renderPass,
|
||||
.width = (uint16_t) (dst.texture->width >> dst.level),
|
||||
.height = (uint16_t) (dst.texture->height >> dst.level),
|
||||
.layers = 1,
|
||||
.samples = rpkey.samples,
|
||||
.color = {},
|
||||
.resolve = {},
|
||||
.depth = dst.getImageView(aspect),
|
||||
};
|
||||
const VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
|
||||
|
||||
VkRenderPassBeginInfo renderPassInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
.renderPass = renderPass,
|
||||
.framebuffer = vkfb,
|
||||
};
|
||||
renderPassInfo.renderArea.offset.x = dstRect[0].x;
|
||||
renderPassInfo.renderArea.offset.y = dstRect[0].y;
|
||||
renderPassInfo.renderArea.extent.width = dstRect[1].x - dstRect[0].x;
|
||||
renderPassInfo.renderArea.extent.height = dstRect[1].y - dstRect[0].y;
|
||||
|
||||
// We need to transition the source into a sampler since it'll be sampled in the shader.
|
||||
const VkImageSubresourceRange srcRange = {
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = src.level,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = src.layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, samplerLayout);
|
||||
|
||||
vkCmdBeginRenderPass(cmdbuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = {
|
||||
.x = (float) dstRect[0].x,
|
||||
.y = (float) dstRect[0].y,
|
||||
.width = (float) renderPassInfo.renderArea.extent.width,
|
||||
.height = (float) renderPassInfo.renderArea.extent.height,
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f
|
||||
};
|
||||
|
||||
vkCmdSetViewport(cmdbuffer, 0, 1, &viewport);
|
||||
|
||||
// DRAW THE TRIANGLE
|
||||
// -----------------
|
||||
|
||||
mPipelineCache.bindProgram(mDepthResolveProgram);
|
||||
mPipelineCache.bindPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP);
|
||||
|
||||
auto vkraster = mPipelineCache.getCurrentRasterState();
|
||||
vkraster.depthWriteEnable = true;
|
||||
vkraster.depthCompareOp = SamplerCompareFunc::A;
|
||||
vkraster.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
|
||||
vkraster.alphaToCoverageEnable = false,
|
||||
vkraster.blendEnable = VK_FALSE,
|
||||
vkraster.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
|
||||
vkraster.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
|
||||
vkraster.colorBlendOp = BlendEquation::ADD,
|
||||
vkraster.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
|
||||
vkraster.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
|
||||
vkraster.alphaBlendOp = BlendEquation::ADD,
|
||||
vkraster.colorWriteMask = (VkColorComponentFlags) 0,
|
||||
vkraster.cullMode = VK_CULL_MODE_NONE;
|
||||
vkraster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
vkraster.depthBiasEnable = VK_FALSE;
|
||||
vkraster.colorTargetCount = 0;
|
||||
mPipelineCache.bindRasterState(vkraster);
|
||||
|
||||
VkBuffer buffers[1] = {};
|
||||
VkDeviceSize offsets[1] = {};
|
||||
buffers[0] = mTriangleBuffer->getGpuBuffer();
|
||||
VkVertexInputAttributeDescription attribDesc = {
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32_SFLOAT,
|
||||
};
|
||||
VkVertexInputBindingDescription bufferDesc = {
|
||||
.binding = 0,
|
||||
.stride = sizeof(float) * 2,
|
||||
};
|
||||
mPipelineCache.bindVertexArray(&attribDesc, &bufferDesc, 1);
|
||||
|
||||
// Select nearest filtering and clamp_to_edge.
|
||||
VkSampler vksampler = mSamplerCache.getSampler({});
|
||||
|
||||
VkDescriptorImageInfo samplers[VulkanPipelineCache::SAMPLER_BINDING_COUNT];
|
||||
VulkanTexture* textures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
|
||||
for (auto& sampler : samplers) {
|
||||
sampler = {
|
||||
.sampler = vksampler,
|
||||
.imageView = mEmptyTexture->getPrimaryImageView(),
|
||||
.imageLayout = ImgUtil::getVkLayout(VulkanLayout::READ_WRITE),
|
||||
};
|
||||
}
|
||||
|
||||
samplers[0] = {
|
||||
.sampler = vksampler,
|
||||
.imageView = src.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT),
|
||||
.imageLayout = ImgUtil::getVkLayout(samplerLayout),
|
||||
};
|
||||
textures[0] = src.texture;
|
||||
|
||||
mPipelineCache.bindSamplers(samplers, textures,
|
||||
VulkanPipelineCache::getUsageFlags(0, ShaderStageFlags::FRAGMENT));
|
||||
|
||||
auto previousUbo = mPipelineCache.getUniformBufferBinding(0);
|
||||
mPipelineCache.bindUniformBuffer(0, mParamsBuffer->getGpuBuffer());
|
||||
|
||||
if (!mPipelineCache.bindDescriptors(cmdbuffer)) {
|
||||
assert_invariant(false);
|
||||
}
|
||||
|
||||
const VkRect2D scissor = {
|
||||
.offset = renderPassInfo.renderArea.offset,
|
||||
.extent = renderPassInfo.renderArea.extent,
|
||||
};
|
||||
|
||||
mPipelineCache.bindScissor(cmdbuffer, scissor);
|
||||
|
||||
if (!mPipelineCache.bindPipeline(commands)) {
|
||||
assert_invariant(false);
|
||||
}
|
||||
|
||||
vkCmdBindVertexBuffers(cmdbuffer, 0, 1, buffers, offsets);
|
||||
vkCmdDraw(cmdbuffer, 4, 1, 0, 0);
|
||||
|
||||
// END RENDER PASS
|
||||
// ---------------
|
||||
|
||||
vkCmdEndRenderPass(cmdbuffer);
|
||||
|
||||
VkMemoryBarrier barrier {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
};
|
||||
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
vkCmdPipelineBarrier(cmdbuffer, srcStageMask,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | // <== For Mali
|
||||
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
0, 1, &barrier, 0, nullptr, 0, nullptr);
|
||||
|
||||
mPipelineCache.bindUniformBuffer(0, previousUbo.buffer, previousUbo.offset, previousUbo.size);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -32,46 +32,24 @@ struct VulkanProgram;
|
||||
|
||||
class VulkanBlitter {
|
||||
public:
|
||||
VulkanBlitter(VulkanStagePool& stagePool, VulkanPipelineCache& pipelineCache,
|
||||
VulkanFboCache& fboCache, VulkanSamplerCache& samplerCache) noexcept;
|
||||
VulkanBlitter() noexcept;
|
||||
|
||||
void initialize(VkPhysicalDevice physicalDevice, VkDevice device, VmaAllocator allocator,
|
||||
VulkanCommands* commands, VulkanTexture* emptyTexture) noexcept;
|
||||
void initialize(VkPhysicalDevice physicalDevice, VkDevice device,
|
||||
VmaAllocator allocator, VulkanCommands* commands) noexcept;
|
||||
|
||||
struct BlitArgs {
|
||||
const VulkanRenderTarget* dstTarget;
|
||||
const VkOffset3D* dstRectPair;
|
||||
const VulkanRenderTarget* srcTarget;
|
||||
const VkOffset3D* srcRectPair;
|
||||
VkFilter filter = VK_FILTER_NEAREST;
|
||||
int targetIndex = 0;
|
||||
};
|
||||
void blit(VkFilter filter,
|
||||
VulkanAttachment dst, const VkOffset3D* dstRectPair,
|
||||
VulkanAttachment src, const VkOffset3D* srcRectPair);
|
||||
|
||||
void blitColor(BlitArgs args);
|
||||
void blitDepth(BlitArgs args);
|
||||
void resolve(VulkanAttachment dst, VulkanAttachment src);
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
private:
|
||||
void lazyInit() noexcept;
|
||||
|
||||
void blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, VulkanAttachment src,
|
||||
VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]);
|
||||
|
||||
VulkanBuffer* mTriangleBuffer = nullptr;
|
||||
VulkanBuffer* mParamsBuffer = nullptr;
|
||||
VulkanProgram* mDepthResolveProgram = nullptr;
|
||||
|
||||
UTILS_UNUSED VkPhysicalDevice mPhysicalDevice;
|
||||
VkDevice mDevice;
|
||||
VmaAllocator mAllocator;
|
||||
VulkanCommands* mCommands;
|
||||
VulkanTexture* mEmptyTexture;
|
||||
|
||||
VulkanStagePool& mStagePool;
|
||||
VulkanPipelineCache& mPipelineCache;
|
||||
VulkanFboCache& mFramebufferCache;
|
||||
VulkanSamplerCache& mSamplerCache;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -156,7 +156,6 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mResourceManager(&mResourceAllocator),
|
||||
mThreadSafeResourceManager(&mResourceAllocator),
|
||||
mPipelineCache(&mResourceAllocator),
|
||||
mBlitter(mStagePool, mPipelineCache, mFramebufferCache, mSamplerCache),
|
||||
mReadPixels(mPlatform->getDevice()),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported) {
|
||||
|
||||
@@ -206,7 +205,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
|
||||
mPipelineCache.setDummyTexture(mEmptyTexture->getPrimaryImageView());
|
||||
mBlitter.initialize(mPlatform->getPhysicalDevice(), mPlatform->getDevice(), mAllocator,
|
||||
mCommands.get(), mEmptyTexture.get());
|
||||
mCommands.get());
|
||||
}
|
||||
|
||||
VulkanDriver::~VulkanDriver() noexcept = default;
|
||||
@@ -757,6 +756,7 @@ bool VulkanDriver::isFrameTimeSupported() {
|
||||
}
|
||||
|
||||
bool VulkanDriver::isAutoDepthResolveSupported() {
|
||||
// TODO: this could be supported with vk 1.2 or VK_KHR_depth_stencil_resolve
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -772,6 +772,11 @@ bool VulkanDriver::isParallelShaderCompileSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isDepthStencilResolveSupported() {
|
||||
// TODO: apparently it could be supported in core 1.2 and/or with VK_KHR_depth_stencil_resolve
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU: {
|
||||
@@ -955,10 +960,43 @@ void VulkanDriver::setExternalImagePlane(Handle<HwTexture> th, void* image, uint
|
||||
void VulkanDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
|
||||
}
|
||||
|
||||
void VulkanDriver::generateMipmaps(Handle<HwTexture> th) { }
|
||||
void VulkanDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
auto* const t = mResourceAllocator.handle_cast<VulkanTexture*>(th);
|
||||
assert_invariant(t);
|
||||
|
||||
bool VulkanDriver::canGenerateMipmaps() {
|
||||
return false;
|
||||
int32_t layerCount = int32_t(t->depth);
|
||||
if (t->target == SamplerType::SAMPLER_CUBEMAP_ARRAY ||
|
||||
t->target == SamplerType::SAMPLER_CUBEMAP) {
|
||||
layerCount *= 6;
|
||||
}
|
||||
|
||||
// FIXME: the loop below can perform many layout transitions and back. We should be
|
||||
// able to optimize that.
|
||||
|
||||
uint8_t level = 0;
|
||||
int32_t srcw = int32_t(t->width);
|
||||
int32_t srch = int32_t(t->height);
|
||||
do {
|
||||
int32_t const dstw = std::max(srcw >> 1, 1);
|
||||
int32_t const dsth = std::max(srch >> 1, 1);
|
||||
const VkOffset3D srcOffsets[2] = {{ 0, 0, 0 }, { srcw, srch, 1 }};
|
||||
const VkOffset3D dstOffsets[2] = {{ 0, 0, 0 }, { dstw, dsth, 1 }};
|
||||
|
||||
// TODO: there should be a way to do this using layerCount in vkBlitImage
|
||||
// TODO: vkBlitImage should be able to handle 3D textures too
|
||||
for (int32_t layer = 0; layer < layerCount; layer++) {
|
||||
mBlitter.blit(VK_FILTER_LINEAR,
|
||||
{ .texture = t, .level = uint8_t(level + 1), .layer = (uint16_t)layer },
|
||||
dstOffsets,
|
||||
{ .texture = t, .level = uint8_t(level ), .layer = (uint16_t)layer },
|
||||
srcOffsets);
|
||||
}
|
||||
|
||||
level++;
|
||||
srcw = dstw;
|
||||
srch = dsth;
|
||||
} while ((srcw > 1 || srch > 1) && level < t->levels);
|
||||
t->setPrimaryRange(0, t->levels - 1);
|
||||
}
|
||||
|
||||
void VulkanDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||
@@ -1439,50 +1477,138 @@ void VulkanDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect, SamplerMagFilter filter) {
|
||||
void VulkanDriver::resolve(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("resolve");
|
||||
|
||||
ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE,
|
||||
"resolve() cannot be invoked inside a render pass.");
|
||||
|
||||
auto* const srcTexture = mResourceAllocator.handle_cast<VulkanTexture*>(src);
|
||||
auto* const dstTexture = mResourceAllocator.handle_cast<VulkanTexture*>(dst);
|
||||
assert_invariant(srcTexture);
|
||||
assert_invariant(dstTexture);
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height,
|
||||
"invalid resolve: src and dst sizes don't match");
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1,
|
||||
"invalid resolve: src.samples=%u, dst.samples=%u",
|
||||
+srcTexture->samples, +dstTexture->samples);
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
ASSERT_PRECONDITION(!isDepthFormat(srcTexture->format),
|
||||
"can't resolve depth formats");
|
||||
|
||||
ASSERT_PRECONDITION(!isStencilFormat(srcTexture->format),
|
||||
"can't resolve stencil formats");
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
mBlitter.resolve(
|
||||
{ .texture = dstTexture, .level = dstLevel, .layer = dstLayer },
|
||||
{ .texture = srcTexture, .level = srcLevel, .layer = srcLayer });
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::blit(
|
||||
Handle<HwTexture> dst, uint8_t srcLevel, uint8_t srcLayer, math::uint2 dstOrigin,
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer, math::uint2 srcOrigin,
|
||||
math::uint2 size) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("blit");
|
||||
|
||||
assert_invariant(mCurrentRenderPass.renderPass == VK_NULL_HANDLE);
|
||||
ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE,
|
||||
"blit() cannot be invoked inside a render pass.");
|
||||
|
||||
// blit operation only support COLOR0 color buffer
|
||||
assert_invariant(
|
||||
!(buffers & (TargetBufferFlags::COLOR_ALL & ~TargetBufferFlags::COLOR0)));
|
||||
auto* const srcTexture = mResourceAllocator.handle_cast<VulkanTexture*>(src);
|
||||
auto* const dstTexture = mResourceAllocator.handle_cast<VulkanTexture*>(dst);
|
||||
|
||||
if (UTILS_UNLIKELY(mCurrentRenderPass.renderPass)) {
|
||||
utils::slog.e << "Blits cannot be invoked inside a render pass." << utils::io::endl;
|
||||
return;
|
||||
}
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
|
||||
// The Y inversion below makes it so that Vk matches GL and Metal.
|
||||
|
||||
auto const srcLeft = int32_t(srcOrigin.x);
|
||||
auto const dstLeft = int32_t(dstOrigin.x);
|
||||
auto const srcTop = int32_t(srcTexture->height - (srcOrigin.y + size.y));
|
||||
auto const dstTop = int32_t(dstTexture->height - (dstOrigin.y + size.y));
|
||||
auto const srcRight = int32_t(srcOrigin.x + size.x);
|
||||
auto const dstRight = int32_t(dstOrigin.x + size.x);
|
||||
auto const srcBottom = int32_t(srcTop + size.y);
|
||||
auto const dstBottom = int32_t(dstTop + size.y);
|
||||
VkOffset3D const srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }};
|
||||
VkOffset3D const dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }};
|
||||
|
||||
// no scaling guaranteed
|
||||
mBlitter.blit(VK_FILTER_NEAREST,
|
||||
{ .texture = dstTexture, .level = dstLevel, .layer = dstLayer }, dstOffsets,
|
||||
{ .texture = srcTexture, .level = srcLevel, .layer = srcLayer }, srcOffsets);
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> dst, Viewport dstRect,
|
||||
Handle<HwRenderTarget> src, Viewport srcRect,
|
||||
SamplerMagFilter filter) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("blitDEPRECATED");
|
||||
|
||||
// Note: blitDEPRECATED is only used for Renderer::copyFrame()
|
||||
|
||||
ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE,
|
||||
"blitDEPRECATED() cannot be invoked inside a render pass.");
|
||||
|
||||
ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0,
|
||||
"blitDEPRECATED only supports COLOR0");
|
||||
|
||||
ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 &&
|
||||
dstRect.left >= 0 && dstRect.bottom >= 0,
|
||||
"Source and destination rects must be positive.");
|
||||
|
||||
VulkanRenderTarget* dstTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(dst);
|
||||
VulkanRenderTarget* srcTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(src);
|
||||
|
||||
VkFilter vkfilter = filter == SamplerMagFilter::NEAREST ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
||||
VkFilter const vkfilter = (filter == SamplerMagFilter::NEAREST) ?
|
||||
VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
||||
|
||||
// The Y inversion below makes it so that Vk matches GL and Metal.
|
||||
|
||||
const VkExtent2D srcExtent = srcTarget->getExtent();
|
||||
const int32_t srcLeft = srcRect.left;
|
||||
const int32_t srcTop = srcExtent.height - srcRect.bottom - srcRect.height;
|
||||
const int32_t srcRight = srcRect.left + srcRect.width;
|
||||
const int32_t srcBottom = srcTop + srcRect.height;
|
||||
const VkOffset3D srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }};
|
||||
VkExtent2D const srcExtent = srcTarget->getExtent();
|
||||
VkExtent2D const dstExtent = dstTarget->getExtent();
|
||||
|
||||
const VkExtent2D dstExtent = dstTarget->getExtent();
|
||||
const int32_t dstLeft = dstRect.left;
|
||||
const int32_t dstTop = dstExtent.height - dstRect.bottom - dstRect.height;
|
||||
const int32_t dstRight = dstRect.left + dstRect.width;
|
||||
const int32_t dstBottom = dstTop + dstRect.height;
|
||||
const VkOffset3D dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }};
|
||||
auto const dstLeft = int32_t(dstRect.left);
|
||||
auto const srcLeft = int32_t(srcRect.left);
|
||||
auto const dstTop = int32_t(dstExtent.height - (dstRect.bottom + dstRect.height));
|
||||
auto const srcTop = int32_t(srcExtent.height - (srcRect.bottom + srcRect.height));
|
||||
auto const dstRight = int32_t(dstRect.left + dstRect.width);
|
||||
auto const srcRight = int32_t(srcRect.left + srcRect.width);
|
||||
auto const dstBottom = int32_t(dstTop + dstRect.height);
|
||||
auto const srcBottom = int32_t(srcTop + srcRect.height);
|
||||
VkOffset3D const srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }};
|
||||
VkOffset3D const dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }};
|
||||
|
||||
if (any(buffers & TargetBufferFlags::DEPTH) && srcTarget->hasDepth() && dstTarget->hasDepth()) {
|
||||
mBlitter.blitDepth({dstTarget, dstOffsets, srcTarget, srcOffsets});
|
||||
}
|
||||
mBlitter.blit(vkfilter,
|
||||
dstTarget->getColor(0), dstOffsets,
|
||||
srcTarget->getColor(0), srcOffsets);
|
||||
|
||||
if (any(buffers & TargetBufferFlags::COLOR0)) {
|
||||
mBlitter.blitColor({ dstTarget, dstOffsets, srcTarget, srcOffsets, vkfilter, int(0) });
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
#elif defined(WIN32)
|
||||
// No platform specific includes
|
||||
#else
|
||||
#error Not a supported Vulkan platform
|
||||
// Not a supported Vulkan platform
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -233,7 +233,7 @@ TEST_F(BackendTest, ColorMagnify) {
|
||||
|
||||
// Do a "magnify" blit from level 1 of the source RT to the level 0 of the destination RT.
|
||||
const int srcLevel = 1;
|
||||
api.blit(TargetBufferFlags::COLOR0, dstRenderTargets[0],
|
||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTargets[0],
|
||||
{0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTargets[srcLevel],
|
||||
{0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel}, SamplerMagFilter::LINEAR);
|
||||
|
||||
@@ -308,7 +308,8 @@ TEST_F(BackendTest, ColorMinify) {
|
||||
|
||||
// Do a "minify" blit from level 1 of the source RT to the level 0 of the destination RT.
|
||||
const int srcLevel = 1;
|
||||
api.blit(TargetBufferFlags::COLOR0,
|
||||
|
||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0,
|
||||
dstRenderTargets[0], {0, 0, kDstTexWidth, kDstTexHeight},
|
||||
srcRenderTargets[srcLevel], {0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel},
|
||||
SamplerMagFilter::LINEAR);
|
||||
@@ -334,147 +335,6 @@ TEST_F(BackendTest, ColorMinify) {
|
||||
executeCommands();
|
||||
}
|
||||
|
||||
TEST_F(BackendTest, DepthMinify) {
|
||||
auto& api = getDriverApi();
|
||||
|
||||
constexpr int kSrcTexWidth = 1024;
|
||||
constexpr int kSrcTexHeight = 1024;
|
||||
constexpr int kDstTexWidth = 256;
|
||||
constexpr int kDstTexHeight = 256;
|
||||
constexpr auto kColorTexFormat = TextureFormat::RGBA8;
|
||||
constexpr auto kDepthTexFormat = TextureFormat::DEPTH24;
|
||||
constexpr int kNumLevels = 3;
|
||||
|
||||
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
||||
auto swapChain = api.createSwapChainHeadless(256, 256, 0);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ProgramHandle program;
|
||||
{
|
||||
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
|
||||
Program prog = shaderGen.getProgram(api);
|
||||
prog.uniformBlockBindings({{"params", 1}});
|
||||
program = api.createProgram(std::move(prog));
|
||||
}
|
||||
|
||||
// Create a VertexBuffer, IndexBuffer, and RenderPrimitive.
|
||||
TrianglePrimitive* triangle = new TrianglePrimitive(api);
|
||||
|
||||
// Create textures for the first rendering pass.
|
||||
Handle<HwTexture> srcColorTexture = api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kColorTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT);
|
||||
|
||||
Handle<HwTexture> srcDepthTexture = api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kDepthTexFormat, 1, kSrcTexWidth, kSrcTexHeight,
|
||||
1, TextureUsage::DEPTH_ATTACHMENT);
|
||||
|
||||
// Create textures for the second rendering pass.
|
||||
Handle<HwTexture> dstColorTexture = api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT);
|
||||
|
||||
Handle<HwTexture> dstDepthTexture = api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kDepthTexFormat, 1, kDstTexWidth, kDstTexHeight,
|
||||
1, TextureUsage::DEPTH_ATTACHMENT);
|
||||
|
||||
// Create render targets.
|
||||
const int level = 0;
|
||||
|
||||
Handle<HwRenderTarget> srcRenderTarget = api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH,
|
||||
kSrcTexWidth >> level, kSrcTexHeight >> level, 1,
|
||||
{{ srcColorTexture, level }},
|
||||
{ srcDepthTexture, level }, {});
|
||||
|
||||
Handle<HwRenderTarget> dstRenderTarget = api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH,
|
||||
kDstTexWidth >> level, kDstTexHeight >> level, 1,
|
||||
{{ dstColorTexture, level }},
|
||||
{ dstDepthTexture, level }, {});
|
||||
|
||||
// Prep for rendering.
|
||||
RenderPassParams params = {};
|
||||
params.flags.clear = TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH;
|
||||
params.clearColor = float4(1, 1, 0, 1);
|
||||
params.clearDepth = 1.0;
|
||||
params.viewport.width = kSrcTexWidth;
|
||||
params.viewport.height = kSrcTexHeight;
|
||||
|
||||
PipelineState state = {};
|
||||
state.rasterState.colorWrite = true;
|
||||
state.rasterState.depthWrite = true;
|
||||
state.rasterState.depthFunc = RasterState::DepthFunc::L;
|
||||
state.rasterState.culling = RasterState::CullingMode::NONE;
|
||||
state.program = program;
|
||||
auto ubuffer = api.createBufferObject(sizeof(MaterialParams),
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0);
|
||||
api.bindUniformBuffer(0, ubuffer);
|
||||
|
||||
// Draw red triangle into srcRenderTarget.
|
||||
uploadUniforms(api, ubuffer, {
|
||||
.color = float4(1, 0, 0, 1),
|
||||
.scale = float4(1, 1, 0.5, 0),
|
||||
});
|
||||
api.beginRenderPass(srcRenderTarget, params);
|
||||
api.draw(state, triangle->getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
api.endFrame(0);
|
||||
|
||||
// Copy over the color buffer and the depth buffer.
|
||||
api.blit(TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, dstRenderTarget,
|
||||
{0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTarget,
|
||||
{0, 0, kSrcTexWidth, kSrcTexHeight}, SamplerMagFilter::LINEAR);
|
||||
|
||||
// Draw a larger blue triangle into dstRenderTarget. We carefully place it below the red
|
||||
// triangle in order to exercise depth testing.
|
||||
api.beginFrame(0, 0);
|
||||
params.viewport.width = kDstTexWidth;
|
||||
params.viewport.height = kDstTexHeight;
|
||||
params.flags.clear = TargetBufferFlags::NONE;
|
||||
uploadUniforms(api, ubuffer, {
|
||||
.color = float4(0, 0, 1, 1),
|
||||
.scale = float4(1.2, 1.2, 0.75, 0),
|
||||
});
|
||||
api.beginRenderPass(dstRenderTarget, params);
|
||||
api.draw(state, triangle->getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
api.endFrame(0);
|
||||
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams sparams { kDstTexWidth, kDstTexHeight, "DepthMinify.png" };
|
||||
api.beginFrame(0, 0);
|
||||
dumpScreenshot(api, dstRenderTarget, &sparams);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
api.finish();
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
|
||||
// Check if the image matches perfectly to our golden run.
|
||||
const uint32_t expected = 0x92b6f5c1;
|
||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sparams.pixelHashResult, expected);
|
||||
EXPECT_TRUE(sparams.pixelHashResult == expected);
|
||||
|
||||
// Cleanup.
|
||||
api.destroyBufferObject(ubuffer);
|
||||
api.destroyProgram(program);
|
||||
api.destroyTexture(srcColorTexture);
|
||||
api.destroyTexture(dstColorTexture);
|
||||
api.destroyTexture(srcDepthTexture);
|
||||
api.destroyTexture(dstDepthTexture);
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyRenderTarget(srcRenderTarget);
|
||||
api.destroyRenderTarget(dstRenderTarget);
|
||||
delete triangle;
|
||||
executeCommands();
|
||||
}
|
||||
|
||||
TEST_F(BackendTest, ColorResolve) {
|
||||
auto& api = getDriverApi();
|
||||
|
||||
@@ -550,7 +410,7 @@ TEST_F(BackendTest, ColorResolve) {
|
||||
api.endFrame(0);
|
||||
|
||||
// Resolve the MSAA render target into the single-sample render target.
|
||||
api.blit(TargetBufferFlags::COLOR,
|
||||
api.blitDEPRECATED(TargetBufferFlags::COLOR,
|
||||
dstRenderTarget, {0, 0, kDstTexWidth, kDstTexHeight},
|
||||
srcRenderTarget, {0, 0, kSrcTexWidth, kSrcTexHeight},
|
||||
SamplerMagFilter::NEAREST);
|
||||
@@ -577,142 +437,6 @@ TEST_F(BackendTest, ColorResolve) {
|
||||
flushAndWait();
|
||||
}
|
||||
|
||||
TEST_F(BackendTest, DepthResolve) {
|
||||
auto& api = getDriverApi();
|
||||
|
||||
constexpr int kSrcTexWidth = 256;
|
||||
constexpr int kSrcTexHeight = 256;
|
||||
constexpr int kDstTexWidth = 256;
|
||||
constexpr int kDstTexHeight = 256;
|
||||
constexpr auto kColorTexFormat = TextureFormat::RGBA8;
|
||||
constexpr auto kDepthTexFormat = TextureFormat::DEPTH24;
|
||||
constexpr int kSampleCount = 4;
|
||||
|
||||
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
|
||||
auto swapChain = api.createSwapChainHeadless(256, 256, 0);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ProgramHandle program;
|
||||
{
|
||||
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
|
||||
Program prog = shaderGen.getProgram(api);
|
||||
prog.uniformBlockBindings({{"params", 1}});
|
||||
program = api.createProgram(std::move(prog));
|
||||
}
|
||||
|
||||
// Create a VertexBuffer, IndexBuffer, and RenderPrimitive.
|
||||
TrianglePrimitive* triangle = new TrianglePrimitive(api);
|
||||
|
||||
// Create 4-sample textures.
|
||||
Handle<HwTexture> srcColorTexture = api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::COLOR_ATTACHMENT);
|
||||
|
||||
Handle<HwTexture> srcDepthTexture = api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kDepthTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::DEPTH_ATTACHMENT);
|
||||
|
||||
// Create 1-sample textures.
|
||||
Handle<HwTexture> dstColorTexture = api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT);
|
||||
|
||||
Handle<HwTexture> dstDepthTexture = api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kDepthTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::DEPTH_ATTACHMENT);
|
||||
|
||||
// Create a 4-sample render target with 4-sample textures.
|
||||
Handle<HwRenderTarget> srcRenderTarget = api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, kSrcTexWidth, kSrcTexHeight,
|
||||
kSampleCount, {{ srcColorTexture }}, { srcDepthTexture }, {});
|
||||
|
||||
// Create a 1-sample render target with 1-sample textures.
|
||||
Handle<HwRenderTarget> dstRenderTarget = api.createRenderTarget(
|
||||
TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, kDstTexWidth, kDstTexHeight,
|
||||
1, {{ dstColorTexture }}, { dstDepthTexture }, {});
|
||||
|
||||
// Prep for rendering.
|
||||
RenderPassParams params = {};
|
||||
params.flags.clear = TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH;
|
||||
params.clearColor = float4(1, 1, 0, 1);
|
||||
params.clearDepth = 1.0;
|
||||
params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
params.viewport.width = kSrcTexWidth;
|
||||
params.viewport.height = kSrcTexHeight;
|
||||
|
||||
PipelineState state = {};
|
||||
state.rasterState.colorWrite = true;
|
||||
state.rasterState.depthWrite = true;
|
||||
state.rasterState.depthFunc = RasterState::DepthFunc::L;
|
||||
state.rasterState.culling = RasterState::CullingMode::NONE;
|
||||
state.program = program;
|
||||
auto ubuffer = api.createBufferObject(sizeof(MaterialParams),
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::STATIC);
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0);
|
||||
api.bindUniformBuffer(0, ubuffer);
|
||||
|
||||
// Draw red triangle into srcRenderTarget.
|
||||
uploadUniforms(api, ubuffer, {
|
||||
.color = float4(1, 0, 0, 1),
|
||||
.scale = float4(1, 1, 0.5, 0),
|
||||
});
|
||||
api.beginRenderPass(srcRenderTarget, params);
|
||||
api.draw(state, triangle->getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
api.endFrame(0);
|
||||
|
||||
// Resolve the MSAA color and depth buffers into the single-sample render target.
|
||||
api.blit(TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, dstRenderTarget,
|
||||
{0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTarget,
|
||||
{0, 0, kSrcTexWidth, kSrcTexHeight}, SamplerMagFilter::LINEAR);
|
||||
|
||||
// Draw a larger blue triangle into dstRenderTarget. We carefully place it below the red
|
||||
// triangle in order to exercise depth testing.
|
||||
api.beginFrame(0, 0);
|
||||
params.viewport.width = kDstTexWidth;
|
||||
params.viewport.height = kDstTexHeight;
|
||||
params.flags.clear = TargetBufferFlags::NONE;
|
||||
uploadUniforms(api, ubuffer, {
|
||||
.color = float4(0, 0, 1, 1),
|
||||
.scale = float4(1.2, 1.2, 0.75, 0),
|
||||
});
|
||||
api.beginRenderPass(dstRenderTarget, params);
|
||||
api.draw(state, triangle->getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams sparams { kDstTexWidth, kDstTexHeight, "DepthResolve.png" };
|
||||
api.beginFrame(0, 0);
|
||||
dumpScreenshot(api, dstRenderTarget, &sparams);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
api.finish();
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
|
||||
// Check if the image matches perfectly to our golden run.
|
||||
const uint32_t expected = 0x5a38e6ba;
|
||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sparams.pixelHashResult, expected);
|
||||
EXPECT_TRUE(sparams.pixelHashResult == expected);
|
||||
|
||||
// Cleanup.
|
||||
api.destroyBufferObject(ubuffer);
|
||||
api.destroyProgram(program);
|
||||
api.destroyTexture(srcColorTexture);
|
||||
api.destroyTexture(dstColorTexture);
|
||||
api.destroyTexture(srcDepthTexture);
|
||||
api.destroyTexture(dstDepthTexture);
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyRenderTarget(srcRenderTarget);
|
||||
api.destroyRenderTarget(dstRenderTarget);
|
||||
delete triangle;
|
||||
executeCommands();
|
||||
}
|
||||
|
||||
TEST_F(BackendTest, Blit2DTextureArray) {
|
||||
auto& api = getDriverApi();
|
||||
|
||||
@@ -755,7 +479,7 @@ TEST_F(BackendTest, Blit2DTextureArray) {
|
||||
|
||||
// Do a blit from kSrcTexLayer of the source RT to kDstTexLayer of the destination RT.
|
||||
const int srcLevel = 0;
|
||||
api.blit(TargetBufferFlags::COLOR0, dstRenderTarget,
|
||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
|
||||
{0, 0, kDstTexWidth, kDstTexHeight}, srcRenderTarget,
|
||||
{0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel}, SamplerMagFilter::LINEAR);
|
||||
|
||||
@@ -846,7 +570,7 @@ TEST_F(BackendTest, BlitRegion) {
|
||||
api.createRenderTarget(TargetBufferFlags::COLOR, kDstTexWidth >> kDstLevel,
|
||||
kDstTexHeight >> kDstLevel, 1, {dstTexture, kDstLevel, 0}, {}, {});
|
||||
|
||||
api.blit(TargetBufferFlags::COLOR0, dstRenderTarget, dstRect, srcRenderTarget, srcRect,
|
||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget, dstRect, srcRenderTarget, srcRect,
|
||||
SamplerMagFilter::LINEAR);
|
||||
|
||||
// Push through an empty frame to allow the texture to upload and the blit to execute.
|
||||
@@ -933,7 +657,7 @@ TEST_F(BackendTest, BlitRegionToSwapChain) {
|
||||
|
||||
api.beginFrame(0, 0);
|
||||
|
||||
api.blit(TargetBufferFlags::COLOR0, dstRenderTarget,
|
||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
|
||||
dstRect, srcRenderTargets[srcLevel],
|
||||
srcRect, SamplerMagFilter::LINEAR);
|
||||
|
||||
|
||||
@@ -293,6 +293,7 @@ struct DepthOfFieldOptions {
|
||||
MEDIAN
|
||||
};
|
||||
float cocScale = 1.0f; //!< circle of confusion scale factor (amount of blur)
|
||||
float cocAspectRatio = 1.0f; //!< width/height aspect ratio of the circle of confusion (simulate anamorphic lenses)
|
||||
float maxApertureDiameter = 0.01f; //!< maximum aperture diameter in meters (zero to disable rotation)
|
||||
bool enabled = false; //!< enable or disable depth of field effect
|
||||
Filter filter = Filter::MEDIAN; //!< filter to use for filling gaps in the kernel
|
||||
@@ -401,7 +402,7 @@ struct AmbientOcclusionOptions {
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for Temporal Multi-Sample Anti-aliasing (MSAA)
|
||||
* Options for Multi-Sample Anti-aliasing (MSAA)
|
||||
* @see setMultiSampleAntiAliasingOptions()
|
||||
*/
|
||||
struct MultiSampleAntiAliasingOptions {
|
||||
@@ -424,12 +425,51 @@ struct MultiSampleAntiAliasingOptions {
|
||||
|
||||
/**
|
||||
* Options for Temporal Anti-aliasing (TAA)
|
||||
* Most TAA parameters are extremely costly to change, as they will trigger the TAA post-process
|
||||
* shaders to be recompiled. These options should be changed or set during initialization.
|
||||
* `filterWidth`, `feedback` and `jitterPattern`, however, can be changed at any time.
|
||||
*
|
||||
* `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state.
|
||||
* see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information.
|
||||
*
|
||||
* @see setTemporalAntiAliasingOptions()
|
||||
*/
|
||||
struct TemporalAntiAliasingOptions {
|
||||
float filterWidth = 1.0f; //!< reconstruction filter width typically between 0 (sharper, aliased) and 1 (smoother)
|
||||
float feedback = 0.04f; //!< history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA).
|
||||
float filterWidth = 1.0f; //!< reconstruction filter width typically between 0.2 (sharper, aliased) and 1.5 (smoother)
|
||||
float feedback = 0.12f; //!< history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA).
|
||||
float lodBias = -1.0f; //!< texturing lod bias (typically -1 or -2)
|
||||
bool enabled = false; //!< enables or disables temporal anti-aliasing
|
||||
bool upscaling = false; //!< 4x TAA upscaling. Disables Dynamic Resolution. [BETA]
|
||||
|
||||
enum class BoxType : uint8_t {
|
||||
AABB, //!< use an AABB neighborhood
|
||||
VARIANCE, //!< use the variance of the neighborhood (not recommended)
|
||||
AABB_VARIANCE //!< use both AABB and variance
|
||||
};
|
||||
|
||||
enum class BoxClipping : uint8_t {
|
||||
ACCURATE, //!< Accurate box clipping
|
||||
CLAMP, //!< clamping
|
||||
NONE //!< no rejections (use for debugging)
|
||||
};
|
||||
|
||||
enum class JitterPattern : uint8_t {
|
||||
RGSS_X4, //! 4-samples, rotated grid sampling
|
||||
UNIFORM_HELIX_X4, //! 4-samples, uniform grid in helix sequence
|
||||
HALTON_23_X8, //! 8-samples of halton 2,3
|
||||
HALTON_23_X16 //! 16-samples of halton 2,3
|
||||
};
|
||||
|
||||
bool filterHistory = true; //!< whether to filter the history buffer
|
||||
bool filterInput = true; //!< whether to apply the reconstruction filter to the input
|
||||
bool useYCoCg = false; //!< whether to use the YcoCg color-space for history rejection
|
||||
BoxType boxType = BoxType::AABB; //!< type of color gamut box
|
||||
BoxClipping boxClipping = BoxClipping::ACCURATE; //!< clipping algorithm
|
||||
JitterPattern jitterPattern = JitterPattern::HALTON_23_X16; //! Jitter Pattern
|
||||
float varianceGamma = 1.0f; //! High values increases ghosting artefact, lower values increases jittering, range [0.75, 1.25]
|
||||
|
||||
bool preventFlickering = false; //!< adjust the feedback dynamically to reduce flickering
|
||||
bool historyReprojection = true; //!< whether to apply history reprojection (debug option)
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -252,6 +252,12 @@ public:
|
||||
* automatically schedule itself for presentation. Instead, the application must call the
|
||||
* PresentCallable passed to the FrameScheduledCallback.
|
||||
*
|
||||
* If your application delays the call to the PresentCallable by, for example, calling it on a
|
||||
* separate thread, you must ensure all PresentCallables have been called before shutting down
|
||||
* the Filament Engine. You can do this by issuing an Engine::flushAndWait before calling
|
||||
* Engine::shutdown. This is necessary to ensure the Filament Engine has had a chance to clean
|
||||
* up all memory related to frame presentation.
|
||||
*
|
||||
* @param callback A callback, or nullptr to unset.
|
||||
* @param user An optional pointer to user data passed to the callback function.
|
||||
*
|
||||
|
||||
@@ -450,12 +450,13 @@ public:
|
||||
|
||||
/**
|
||||
* Generates all the mipmap levels automatically. This requires the texture to have a
|
||||
* color-renderable format.
|
||||
* color-renderable format and usage set to BLIT_SRC | BLIT_DST. If unspecified,
|
||||
* usage bits are set automatically.
|
||||
*
|
||||
* @param engine Engine this texture is associated to.
|
||||
*
|
||||
* @attention \p engine must be the instance passed to Builder::build()
|
||||
* @attention This Texture instance must NOT use Sampler::SAMPLER_CUBEMAP or it has no effect
|
||||
* @attention This Texture instance must NOT use SamplerType::SAMPLER_3D or it has no effect
|
||||
*/
|
||||
void generateMipmaps(Engine& engine) const noexcept;
|
||||
|
||||
|
||||
@@ -71,8 +71,7 @@ void PerShadowMapUniforms::prepareCamera(Transaction const& transaction,
|
||||
s.clipControl = engine.getDriverApi().getClipSpaceParams();
|
||||
}
|
||||
|
||||
void PerShadowMapUniforms::prepareLodBias(Transaction const& transaction,
|
||||
float bias) noexcept {
|
||||
void PerShadowMapUniforms::prepareLodBias(Transaction const& transaction, float bias) noexcept {
|
||||
auto& s = edit(transaction);
|
||||
s.lodBias = bias;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ PerViewUniforms::PerViewUniforms(FEngine& engine) noexcept
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC);
|
||||
|
||||
if (engine.getDFG().isValid()) {
|
||||
TextureSampler sampler(TextureSampler::MagFilter::LINEAR);
|
||||
TextureSampler const sampler(TextureSampler::MagFilter::LINEAR);
|
||||
mSamplers.setSampler(PerViewSib::IBL_DFG_LUT,
|
||||
{ engine.getDFG().getTexture(), sampler.getSamplerParams() });
|
||||
}
|
||||
@@ -96,9 +96,10 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n
|
||||
s.clipControl = engine.getDriverApi().getClipSpaceParams();
|
||||
}
|
||||
|
||||
void PerViewUniforms::prepareLodBias(float bias) noexcept {
|
||||
void PerViewUniforms::prepareLodBias(float bias, float2 derivativesScale) noexcept {
|
||||
auto& s = mUniforms.edit();
|
||||
s.lodBias = bias;
|
||||
s.derivativesScale = derivativesScale;
|
||||
}
|
||||
|
||||
void PerViewUniforms::prepareExposure(float ev100) noexcept {
|
||||
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
void terminate(backend::DriverApi& driver);
|
||||
|
||||
void prepareCamera(FEngine& engine, const CameraInfo& camera) noexcept;
|
||||
void prepareLodBias(float bias) noexcept;
|
||||
void prepareLodBias(float bias, math::float2 derivativesScale) noexcept;
|
||||
|
||||
/*
|
||||
* @param viewport viewport (should be same as RenderPassParams::viewport)
|
||||
|
||||
+323
-169
@@ -27,9 +27,12 @@
|
||||
#include "details/Engine.h"
|
||||
|
||||
#include "fg/FrameGraph.h"
|
||||
#include "fg/FrameGraphId.h"
|
||||
#include "fg/FrameGraphResources.h"
|
||||
#include "fg/FrameGraphTexture.h"
|
||||
|
||||
#include "fsr.h"
|
||||
#include "FrameHistory.h"
|
||||
#include "PerViewUniforms.h"
|
||||
#include "RenderPass.h"
|
||||
|
||||
@@ -41,13 +44,44 @@
|
||||
|
||||
#include "generated/resources/materials.h"
|
||||
|
||||
#include <filament/Material.h>
|
||||
#include <filament/MaterialEnums.h>
|
||||
#include <filament/Options.h>
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
#include <private/filament/EngineEnums.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/Handle.h>
|
||||
#include <backend/PipelineState.h>
|
||||
#include <backend/PixelBufferDescriptor.h>
|
||||
|
||||
#include <private/backend/BackendUtils.h>
|
||||
|
||||
#include <math/half.h>
|
||||
#include <math/mat2.h>
|
||||
#include <math/mat3.h>
|
||||
#include <math/mat4.h>
|
||||
#include <math/scalar.h>
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -167,7 +201,21 @@ FMaterialInstance* PostProcessManager::PostProcessMaterial::getMaterialInstance(
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
const math::float2 PostProcessManager::sHaltonSamples[16] = {
|
||||
const PostProcessManager::JitterSequence<4> PostProcessManager::sRGSS4 = {{
|
||||
{ 0.625f, 0.125f },
|
||||
{ 0.125f, 0.375f },
|
||||
{ 0.875f, 0.625f },
|
||||
{ 0.375f, 0.875f }
|
||||
}};
|
||||
|
||||
const PostProcessManager::JitterSequence<4> PostProcessManager::sUniformHelix4 = {{
|
||||
{ 0.25f, 0.25f },
|
||||
{ 0.75f, 0.75f },
|
||||
{ 0.25f, 0.75f },
|
||||
{ 0.75f, 0.25f },
|
||||
}};
|
||||
|
||||
const PostProcessManager::JitterSequence<16> PostProcessManager::sHaltonSamples = {{
|
||||
{ filament::halton( 0, 2), filament::halton( 0, 3) },
|
||||
{ filament::halton( 1, 2), filament::halton( 1, 3) },
|
||||
{ filament::halton( 2, 2), filament::halton( 2, 3) },
|
||||
@@ -184,7 +232,7 @@ const math::float2 PostProcessManager::sHaltonSamples[16] = {
|
||||
{ filament::halton(13, 2), filament::halton(13, 3) },
|
||||
{ filament::halton(14, 2), filament::halton(14, 3) },
|
||||
{ filament::halton(15, 2), filament::halton(15, 3) }
|
||||
};
|
||||
}};
|
||||
|
||||
PostProcessManager::PostProcessManager(FEngine& engine) noexcept
|
||||
: mEngine(engine),
|
||||
@@ -200,7 +248,8 @@ void PostProcessManager::registerPostProcessMaterial(std::string_view name, Mate
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
PostProcessManager::PostProcessMaterial& PostProcessManager::getPostProcessMaterial(std::string_view name) noexcept {
|
||||
PostProcessManager::PostProcessMaterial& PostProcessManager::getPostProcessMaterial(
|
||||
std::string_view name) noexcept {
|
||||
assert_invariant(mMaterialRegistry.find(name) != mMaterialRegistry.end());
|
||||
return mMaterialRegistry[name];
|
||||
}
|
||||
@@ -258,6 +307,7 @@ static const PostProcessManager::MaterialInfo sMaterialList[] = {
|
||||
{ "fsr_easu_mobileF", MATERIAL(FSR_EASU_MOBILEF) },
|
||||
{ "fsr_rcas", MATERIAL(FSR_RCAS) },
|
||||
{ "debugShadowCascades", MATERIAL(DEBUGSHADOWCASCADES) },
|
||||
{ "resolveDepth", MATERIAL(RESOLVEDEPTH) },
|
||||
};
|
||||
|
||||
void PostProcessManager::init() noexcept {
|
||||
@@ -676,32 +726,34 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
};
|
||||
|
||||
// Needed for Vulkan and GLES. Some GLES implementations don't need it. Never needed for Metal.
|
||||
auto& duplicateDepthPass = fg.addPass<DuplicateDepthPassData>("Duplicate Depth Pass",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
// read the depth as an attachment
|
||||
data.input = builder.read(depth,
|
||||
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
FrameGraphTexture::Usage::BLIT_SRC);
|
||||
|
||||
auto desc = builder.getDescriptor(data.input);
|
||||
desc.levels = 1; // only copy the base level
|
||||
|
||||
// create a new buffer for the copy
|
||||
data.output = builder.createTexture("Depth Texture Copy", desc);
|
||||
|
||||
// output is an attachment
|
||||
data.output = builder.write(data.output,
|
||||
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
builder.declareRenderPass("Depth Copy RenderTarget", {{ .depth = data.output }});
|
||||
FrameGraphTexture::Usage::BLIT_DST);
|
||||
},
|
||||
[=](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
auto const& desc = resources.getDescriptor(data.input);
|
||||
auto out = resources.getRenderPassInfo();
|
||||
// create a temporary render target for source, needed for the blit.
|
||||
auto inTarget = driver.createRenderTarget(TargetBufferFlags::DEPTH,
|
||||
desc.width, desc.height, desc.samples, {},
|
||||
{ resources.getTexture(data.input) }, {});
|
||||
driver.blit(TargetBufferFlags::DEPTH,
|
||||
out.target, out.params.viewport,
|
||||
inTarget, out.params.viewport,
|
||||
SamplerMagFilter::NEAREST);
|
||||
driver.destroyRenderTarget(inTarget);
|
||||
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
auto const& src = resources.getTexture(data.input);
|
||||
auto const& dst = resources.getTexture(data.output);
|
||||
auto const& srcSubDesc = resources.getSubResourceDescriptor(data.input);
|
||||
auto const& dstSubDesc = resources.getSubResourceDescriptor(data.output);
|
||||
auto const& desc = resources.getDescriptor(data.output);
|
||||
assert_invariant(desc.samples == resources.getDescriptor(data.input).samples);
|
||||
// here we can guarantee that src and dst format and size match, by construction.
|
||||
driver.blit(
|
||||
dst, dstSubDesc.level, dstSubDesc.layer, { 0, 0 },
|
||||
src, srcSubDesc.level, srcSubDesc.layer, { 0, 0 },
|
||||
{ desc.width, desc.height });
|
||||
});
|
||||
|
||||
auto& SSAOPass = fg.addPass<SSAOPassData>("SSAO Pass",
|
||||
@@ -752,7 +804,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
|
||||
auto ssao = resources.getRenderPassInfo();
|
||||
auto const& desc = resources.getDescriptor(data.depth);
|
||||
|
||||
// estimate of the size in pixel of a 1m tall/wide object viewed from 1m away (i.e. at z=-1)
|
||||
// Estimate of the size in pixel units of a 1m tall/wide object viewed from 1m away (i.e. at z=-1)
|
||||
const float projectionScale = std::min(
|
||||
0.5f * cameraInfo.projection[0].x * desc.width,
|
||||
0.5f * cameraInfo.projection[1].y * desc.height);
|
||||
@@ -909,7 +961,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
|
||||
auto const& desc = resources.getDescriptor(data.blurred);
|
||||
|
||||
// unnormalized gaussian half-kernel of a given standard deviation
|
||||
// returns number of samples stored in array (max 16)
|
||||
// returns number of samples stored in the array (max 16)
|
||||
constexpr size_t kernelArraySize = 16; // limited by bilateralBlur.mat
|
||||
auto gaussianKernel =
|
||||
[kernelArraySize](float* outKernel, size_t gaussianWidth, float stdDev) -> uint32_t {
|
||||
@@ -953,7 +1005,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::generateGaussianMipmap(Frame
|
||||
|
||||
auto const subResourceDesc = fg.getSubResourceDescriptor(input);
|
||||
|
||||
// create one subresource per level to be generated from the input. These will be our
|
||||
// Create one subresource per level to be generated from the input. These will be our
|
||||
// destinations.
|
||||
struct MipmapPassData {
|
||||
FixedCapacityVector<FrameGraphId<FrameGraphTexture>> out;
|
||||
@@ -1114,7 +1166,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
|
||||
size_t const m = computeGaussianCoefficients(kernel,
|
||||
std::min(sizeof(kernel) / sizeof(*kernel), kernelStorageSize));
|
||||
|
||||
std::string_view sourceParameterName = is2dArray ? "sourceArray"sv : "source"sv;
|
||||
std::string_view const sourceParameterName = is2dArray ? "sourceArray"sv : "source"sv;
|
||||
// horizontal pass
|
||||
mi->setParameter(sourceParameterName, hwIn, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
@@ -1160,7 +1212,7 @@ PostProcessManager::ScreenSpaceRefConfig PostProcessManager::prepareMipmapSSR(Fr
|
||||
|
||||
// The kernel-size was determined empirically so that we don't get too many artifacts
|
||||
// due to the down-sampling with a box filter (which happens implicitly).
|
||||
// requires only 6 stored coefficients and 11 tap/pass
|
||||
// Requires only 6 stored coefficients and 11 tap/pass
|
||||
// e.g.: size of 13 (4 stored coefficients)
|
||||
// +-------+-------+-------*===*-------+-------+-------+
|
||||
// ... | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | ...
|
||||
@@ -1324,10 +1376,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::generateMipmapSSR(
|
||||
FrameGraphId<FrameGraphTexture> output,
|
||||
bool needInputDuplication, ScreenSpaceRefConfig const& config) noexcept {
|
||||
|
||||
// descriptor of our actual input image (e.g. reflection buffer or refraction framebuffer)
|
||||
// Descriptor of our actual input image (e.g. reflection buffer or refraction framebuffer)
|
||||
auto const& desc = fg.getDescriptor(input);
|
||||
|
||||
// descriptor of the destination. output is a subresource (i.e. a layer of a 2D array)
|
||||
// Descriptor of the destination. `output` is a subresource (i.e. a layer of a 2D array)
|
||||
auto const& outDesc = fg.getDescriptor(output);
|
||||
|
||||
/*
|
||||
@@ -1343,14 +1395,18 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::generateMipmapSSR(
|
||||
if (desc.samples > 1 &&
|
||||
outDesc.width == desc.width && outDesc.height == desc.height &&
|
||||
desc.format == outDesc.format) {
|
||||
// resolve directly into the destination
|
||||
input = ppm.resolveBaseLevelNoCheck(fg, "ssr", input, outDesc);
|
||||
// Resolve directly into the destination. This guarantees a blit/resolve will be
|
||||
// performed (i.e.: the source is copied) and we also guarantee that format/scaling
|
||||
// is the same after the forwardResource call below.
|
||||
input = ppm.resolve(fg, "ssr", input, outDesc);
|
||||
} else {
|
||||
// first resolve (if needed)
|
||||
input = ppm.resolveBaseLevel(fg, "ssr", input);
|
||||
// then blit into an appropriate texture
|
||||
// this handles scaling, format conversion and mipmaping
|
||||
input = ppm.opaqueBlit(fg, input, { 0, 0, desc.width, desc.height }, outDesc);
|
||||
// First resolve (if needed), may be a no-op. Guarantees that format/size is unchanged
|
||||
// by construction.
|
||||
input = ppm.resolve(fg, "ssr", input, { .levels = 1 });
|
||||
// Then blit into an appropriate texture, this handles scaling and format conversion.
|
||||
// The input/output sizes may differ when non-homogenous DSR is enabled.
|
||||
input = ppm.blit(fg, false, input, { 0, 0, desc.width, desc.height }, outDesc,
|
||||
SamplerMagFilter::LINEAR, SamplerMinFilter::LINEAR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1372,7 +1428,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> depth,
|
||||
const CameraInfo& cameraInfo,
|
||||
bool translucent,
|
||||
float bokehAspectRatio,
|
||||
float2 bokehScale,
|
||||
const DepthOfFieldOptions& dofOptions) noexcept {
|
||||
|
||||
assert_invariant(depth);
|
||||
@@ -1383,7 +1439,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
const TextureFormat format = translucent ? TextureFormat::RGBA16F
|
||||
: TextureFormat::R11F_G11F_B10F;
|
||||
|
||||
// rotate the bokeh based on the aperture diameter (i.e. angle of the blades)
|
||||
// Rotate the bokeh based on the aperture diameter (i.e. angle of the blades)
|
||||
float bokehAngle = f::PI / 6.0f;
|
||||
if (dofOptions.maxApertureDiameter > 0.0f) {
|
||||
bokehAngle += f::PI_2 * saturate(cameraInfo.A / dofOptions.maxApertureDiameter);
|
||||
@@ -1762,8 +1818,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
mi->setParameter("tiles", tilesCocMinMax,
|
||||
{ .filterMin = SamplerMinFilter::NEAREST });
|
||||
mi->setParameter("cocToTexelScale", float2{
|
||||
bokehAspectRatio / (inputDesc.width * dofResolution),
|
||||
1.0 / (inputDesc.height * dofResolution)
|
||||
bokehScale.x / (inputDesc.width * dofResolution),
|
||||
bokehScale.y / (inputDesc.height * dofResolution)
|
||||
});
|
||||
mi->setParameter("cocToPixelScale", (1.0f / float(dofResolution)));
|
||||
mi->setParameter("ringCounts", float4{
|
||||
@@ -1869,14 +1925,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
return ppDoFCombine->output;
|
||||
}
|
||||
|
||||
PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
BloomOptions& inoutBloomOptions,
|
||||
backend::TextureFormat outFormat,
|
||||
math::float2 scale) noexcept {
|
||||
return bloomPass(fg, input, outFormat, inoutBloomOptions, scale);
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::downscalePass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
@@ -1908,9 +1956,11 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::downscalePass(FrameGraph& fg
|
||||
return downsamplePass->output;
|
||||
}
|
||||
|
||||
PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg,
|
||||
PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, TextureFormat outFormat,
|
||||
BloomOptions& inoutBloomOptions, float2 scale) noexcept {
|
||||
BloomOptions& inoutBloomOptions,
|
||||
TemporalAntiAliasingOptions const& taaOptions,
|
||||
float2 scale) noexcept {
|
||||
|
||||
// Figure out a good size for the bloom buffer. We must use a fixed bloom buffer size so
|
||||
// that the size/strength of the bloom doesn't vary much with the resolution, otherwise
|
||||
@@ -1952,6 +2002,9 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg
|
||||
|
||||
bool threshold = inoutBloomOptions.threshold;
|
||||
|
||||
// we don't need to do the fireflies reduction if we have TAA (it already does it)
|
||||
bool fireflies = threshold && !taaOptions.enabled;
|
||||
|
||||
while (2 * bloomWidth < float(desc.width) || 2 * bloomHeight < float(desc.height)) {
|
||||
if (inoutBloomOptions.quality == QualityLevel::LOW ||
|
||||
inoutBloomOptions.quality == QualityLevel::MEDIUM) {
|
||||
@@ -1960,8 +2013,9 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg
|
||||
.height = (desc.height = std::max(1u, desc.height / 2)),
|
||||
.format = outFormat
|
||||
},
|
||||
threshold, inoutBloomOptions.highlight, threshold);
|
||||
threshold, inoutBloomOptions.highlight, fireflies);
|
||||
threshold = false; // we do the thresholding only once during down sampling
|
||||
fireflies = false; // we do the fireflies reduction only once during down sampling
|
||||
} else if (inoutBloomOptions.quality == QualityLevel::HIGH ||
|
||||
inoutBloomOptions.quality == QualityLevel::ULTRA) {
|
||||
// In high quality mode, we increase the size of the bloom buffer such that the
|
||||
@@ -1982,7 +2036,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg
|
||||
|
||||
input = downscalePass(fg, input,
|
||||
{ .width = width, .height = height, .format = outFormat },
|
||||
threshold, inoutBloomOptions.highlight, threshold);
|
||||
threshold, inoutBloomOptions.highlight, fireflies);
|
||||
|
||||
struct BloomPassData {
|
||||
FrameGraphId<FrameGraphTexture> out;
|
||||
@@ -2491,7 +2545,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::fxaa(FrameGraph& fg,
|
||||
return ppFXAA->output;
|
||||
}
|
||||
|
||||
void PostProcessManager::prepareTaa(FrameGraph& fg, filament::Viewport const& svp,
|
||||
void PostProcessManager::prepareTaa(FrameGraph& fg,
|
||||
filament::Viewport const& svp,
|
||||
TemporalAntiAliasingOptions const& taaOptions,
|
||||
FrameHistory& frameHistory,
|
||||
FrameHistoryEntry::TemporalAA FrameHistoryEntry::*pTaa,
|
||||
CameraInfo* inoutCameraInfo,
|
||||
@@ -2503,9 +2559,33 @@ void PostProcessManager::prepareTaa(FrameGraph& fg, filament::Viewport const& sv
|
||||
current.projection = inoutCameraInfo->projection * inoutCameraInfo->getUserViewMatrix();
|
||||
current.frameId = previous.frameId + 1;
|
||||
|
||||
auto jitterPosition = [pattern = taaOptions.jitterPattern](size_t frameIndex){
|
||||
using JitterPattern = TemporalAntiAliasingOptions::JitterPattern;
|
||||
switch (pattern) {
|
||||
case JitterPattern::RGSS_X4:
|
||||
return sRGSS4(frameIndex);
|
||||
case JitterPattern::UNIFORM_HELIX_X4:
|
||||
return sUniformHelix4(frameIndex);
|
||||
case JitterPattern::HALTON_23_X8:
|
||||
return sHaltonSamples(frameIndex % 8);
|
||||
case JitterPattern::HALTON_23_X16:
|
||||
return sHaltonSamples(frameIndex);
|
||||
}
|
||||
};
|
||||
|
||||
// sample position within a pixel [-0.5, 0.5]
|
||||
float2 const jitter = halton(previous.frameId) - 0.5f;
|
||||
current.jitter = jitter;
|
||||
// for metal/vulkan we need to reverse the y-offset
|
||||
current.jitter = jitterPosition(previous.frameId);
|
||||
float2 jitter = current.jitter;
|
||||
switch (mEngine.getBackend()) {
|
||||
case Backend::VULKAN:
|
||||
case Backend::METAL:
|
||||
jitter.y = -jitter.y;
|
||||
UTILS_FALLTHROUGH;
|
||||
case Backend::OPENGL:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
float2 const jitterInClipSpace = jitter * (2.0f / float2{ svp.width, svp.height });
|
||||
|
||||
@@ -2522,6 +2602,37 @@ void PostProcessManager::prepareTaa(FrameGraph& fg, filament::Viewport const& sv
|
||||
});
|
||||
}
|
||||
|
||||
void PostProcessManager::configureTemporalAntiAliasingMaterial(
|
||||
TemporalAntiAliasingOptions const& taaOptions) noexcept {
|
||||
|
||||
FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine);
|
||||
bool dirty = false;
|
||||
|
||||
auto setConstantParameter =
|
||||
[&dirty](FMaterial* const ma, std::string_view name, auto value) noexcept {
|
||||
auto id = ma->getSpecializationConstantId(name);
|
||||
if (id.has_value()) {
|
||||
if (ma->setConstant(id.value(), value)) {
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setConstantParameter(ma, "historyReprojection", taaOptions.historyReprojection);
|
||||
setConstantParameter(ma, "filterHistory", taaOptions.filterHistory);
|
||||
setConstantParameter(ma, "filterInput", taaOptions.filterInput);
|
||||
setConstantParameter(ma, "useYCoCg", taaOptions.useYCoCg);
|
||||
setConstantParameter(ma, "preventFlickering", taaOptions.preventFlickering);
|
||||
setConstantParameter(ma, "boxType", (int32_t)taaOptions.boxType);
|
||||
setConstantParameter(ma, "boxClipping", (int32_t)taaOptions.boxClipping);
|
||||
setConstantParameter(ma, "varianceGamma", taaOptions.varianceGamma);
|
||||
if (dirty) {
|
||||
ma->invalidate();
|
||||
// TODO: call Material::compile(), we can't si that now because it works only
|
||||
// with surface materials
|
||||
}
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> depth,
|
||||
@@ -2554,6 +2665,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
auto& taaPass = fg.addPass<TAAData>("TAA",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
auto desc = fg.getDescriptor(input);
|
||||
if (taaOptions.upscaling) {
|
||||
desc.width *= 2;
|
||||
desc.height *= 2;
|
||||
}
|
||||
data.color = builder.sample(input);
|
||||
data.depth = builder.sample(depth);
|
||||
data.history = builder.sample(colorHistory);
|
||||
@@ -2588,18 +2703,25 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
{ -1.0f, 1.0f }, { 0.0f, 1.0f }, { 1.0f, 1.0f },
|
||||
};
|
||||
|
||||
float sum = 0.0;
|
||||
float weights[9];
|
||||
constexpr float2 subSampleOffsets[4] = {
|
||||
{ -0.25f, 0.25f }, { 0.25f, 0.25f }, { 0.25f, -0.25f }, { -0.25f, -0.25f }
|
||||
};
|
||||
|
||||
float4 sum = 0.0;
|
||||
float4 weights[9];
|
||||
|
||||
// this doesn't get vectorized (probably because of exp()), so don't bother
|
||||
// unrolling it.
|
||||
#pragma nounroll
|
||||
for (size_t i = 0; i < 9; i++) {
|
||||
float2 d = sampleOffsets[i] - current.jitter;
|
||||
d *= 1.0f / taaOptions.filterWidth;
|
||||
// this is a gaussian fit of a 3.3 Blackman Harris window
|
||||
// see: "High Quality Temporal Supersampling" by Brian Karis
|
||||
weights[i] = std::exp2(-3.3f * (d.x * d.x + d.y * d.y));
|
||||
float2 const o = sampleOffsets[i];
|
||||
for (size_t j = 0; j < 4; j++) {
|
||||
float2 const s = taaOptions.upscaling ? subSampleOffsets[j] : float2{ 0 };
|
||||
float2 const d = (o - current.jitter - s) / taaOptions.filterWidth;
|
||||
// This is a gaussian fit of a 3.3-wide Blackman-Harris window
|
||||
// see: "High Quality Temporal Supersampling" by Brian Karis
|
||||
weights[i][j] = std::exp(-2.29f * (d.x * d.x + d.y * d.y));
|
||||
}
|
||||
sum += weights[i];
|
||||
}
|
||||
for (auto& w : weights) {
|
||||
@@ -2610,8 +2732,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
auto color = resources.getTexture(data.color);
|
||||
auto depth = resources.getTexture(data.depth);
|
||||
auto history = resources.getTexture(data.history);
|
||||
|
||||
auto const& material = getPostProcessMaterial("taa");
|
||||
|
||||
FMaterialInstance* mi = material.getMaterialInstance(mEngine);
|
||||
mi->setParameter("color", color, {}); // nearest
|
||||
mi->setParameter("depth", depth, {}); // nearest
|
||||
@@ -2621,6 +2743,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
.filterMin = SamplerMinFilter::LINEAR
|
||||
});
|
||||
mi->setParameter("filterWeights", weights, 9);
|
||||
mi->setParameter("jitter", current.jitter);
|
||||
mi->setParameter("reprojection",
|
||||
mat4f{ historyProjection * inverse(current.projection) } *
|
||||
normalizedToClip);
|
||||
@@ -2664,61 +2787,11 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
return exportHistoryPass->color;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::opaqueBlit(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
SamplerMagFilter filter) noexcept {
|
||||
|
||||
struct PostProcessScaling {
|
||||
FrameGraphId<FrameGraphTexture> input;
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
};
|
||||
|
||||
auto& ppBlit = fg.addPass<PostProcessScaling>("opaque blit",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
|
||||
// we currently have no use for this case, so we just assert. This is better for now to trap
|
||||
// cases that we might not intend.
|
||||
assert_invariant(fg.getDescriptor(input).samples <= 1);
|
||||
|
||||
data.output = builder.declareRenderPass(
|
||||
builder.createTexture("opaque blit output", outDesc));
|
||||
|
||||
data.input = builder.read(input);
|
||||
|
||||
// We use a RenderPass for the source here, instead of just creating a render
|
||||
// target from data.input in the execute closure, because data.input may refer to
|
||||
// an imported render target and in this case data.input won't resolve to an actual
|
||||
// HwTexture handle. Using a RenderPass works because data.input will resolve
|
||||
// to the actual imported render target and will have the correct viewport.
|
||||
builder.declareRenderPass("opaque blit input", {
|
||||
.attachments = { .color = { data.input }},
|
||||
.viewport = vp
|
||||
});
|
||||
},
|
||||
[=](FrameGraphResources const& resources, auto const&, DriverApi& driver) {
|
||||
auto out = resources.getRenderPassInfo(0);
|
||||
auto in = resources.getRenderPassInfo(1);
|
||||
driver.blit(TargetBufferFlags::COLOR,
|
||||
out.target, out.params.viewport,
|
||||
in.target, in.params.viewport,
|
||||
filter);
|
||||
});
|
||||
|
||||
// we rely on automatic culling of unused render passes
|
||||
return ppBlit->output;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool translucent,
|
||||
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> input,
|
||||
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
|
||||
backend::SamplerMagFilter filter) noexcept {
|
||||
|
||||
if (UTILS_LIKELY(!translucent && dsrOptions.quality == QualityLevel::LOW &&
|
||||
mEngine.getDriverApi().getFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1)) {
|
||||
return opaqueBlit(fg, input, vp, outDesc, filter);
|
||||
}
|
||||
|
||||
// The code below cannot handle sub-resources
|
||||
assert_invariant(fg.getSubResourceDescriptor(input).layer == 0);
|
||||
assert_invariant(fg.getSubResourceDescriptor(input).level == 0);
|
||||
@@ -2916,83 +2989,164 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool translucent,
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
backend::SamplerMagFilter filter) noexcept {
|
||||
// for now, we implement this by calling upscale() with the low-quality setting.
|
||||
return upscale(fg, translucent, { .quality = QualityLevel::LOW }, input, vp, outDesc, filter);
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
|
||||
SamplerMagFilter filterMag,
|
||||
SamplerMinFilter filterMin) noexcept {
|
||||
|
||||
// TODO: add support for sub-resources
|
||||
assert_invariant(fg.getSubResourceDescriptor(input).layer == 0);
|
||||
assert_invariant(fg.getSubResourceDescriptor(input).level == 0);
|
||||
|
||||
struct QuadBlitData {
|
||||
FrameGraphId<FrameGraphTexture> input;
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
};
|
||||
|
||||
auto& ppQuadBlit = fg.addPass<QuadBlitData>("blitting",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.input = builder.sample(input);
|
||||
data.output = builder.createTexture("upscaled output", outDesc);
|
||||
data.output = builder.write(data.output,
|
||||
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
builder.declareRenderPass(builder.getName(data.output), {
|
||||
.attachments = { .color = { data.output }},
|
||||
.clearFlags = TargetBufferFlags::DEPTH });
|
||||
},
|
||||
[=](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
auto color = resources.getTexture(data.input);
|
||||
auto const& inputDesc = resources.getDescriptor(data.input);
|
||||
auto const& outputDesc = resources.getDescriptor(data.output);
|
||||
auto out = resources.getRenderPassInfo();
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// set uniforms
|
||||
|
||||
PostProcessMaterial const& material = getPostProcessMaterial("blitLow");
|
||||
auto* mi = material.getMaterialInstance(mEngine);
|
||||
mi->setParameter("color", color, {
|
||||
.filterMag = filterMag,
|
||||
.filterMin = filterMin
|
||||
});
|
||||
mi->setParameter("resolution",
|
||||
float4{ outputDesc.width, outputDesc.height,
|
||||
1.0f / outputDesc.width, 1.0f / outputDesc.height });
|
||||
mi->setParameter("viewport", float4{
|
||||
float(vp.left) / inputDesc.width,
|
||||
float(vp.bottom) / inputDesc.height,
|
||||
float(vp.width) / inputDesc.width,
|
||||
float(vp.height) / inputDesc.height
|
||||
});
|
||||
mi->commit(driver);
|
||||
mi->use(driver);
|
||||
|
||||
PipelineState pipeline(material.getPipelineState(mEngine));
|
||||
if (translucent) {
|
||||
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA;
|
||||
pipeline.rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA;
|
||||
}
|
||||
render(out, pipeline, driver);
|
||||
});
|
||||
|
||||
return ppQuadBlit->output;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::resolveBaseLevel(FrameGraph& fg,
|
||||
const char* outputBufferName, FrameGraphId<FrameGraphTexture> input) noexcept {
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
|
||||
const char* outputBufferName, FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor outDesc) noexcept {
|
||||
|
||||
// Don't do anything if we're not a MSAA buffer
|
||||
auto desc = fg.getDescriptor(input);
|
||||
if (desc.samples <= 1) {
|
||||
auto const& inDesc = fg.getDescriptor(input);
|
||||
if (inDesc.samples <= 1) {
|
||||
return input;
|
||||
}
|
||||
desc.samples = 0;
|
||||
desc.levels = 1;
|
||||
return resolveBaseLevelNoCheck(fg, outputBufferName, input, desc);
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::resolveBaseLevelNoCheck(FrameGraph& fg,
|
||||
const char* outputBufferName, FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor const& desc) noexcept {
|
||||
// we currently don't support stencil resolve
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
// The Metal / Vulkan backends currently don't support depth/stencil resolve.
|
||||
if (isDepthFormat(inDesc.format) && (!mEngine.getDriverApi().isDepthStencilResolveSupported())) {
|
||||
return resolveDepth(fg, outputBufferName, input, outDesc);
|
||||
}
|
||||
|
||||
outDesc.width = inDesc.width;
|
||||
outDesc.height = inDesc.height;
|
||||
outDesc.format = inDesc.format;
|
||||
outDesc.samples = 0;
|
||||
|
||||
struct ResolveData {
|
||||
FrameGraphId<FrameGraphTexture> input;
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
backend::TargetBufferFlags inFlags;
|
||||
FrameGraphTexture::Usage usage;
|
||||
};
|
||||
|
||||
auto& ppResolve = fg.addPass<ResolveData>("resolve",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
FrameGraphRenderPass::Descriptor rpDesc;
|
||||
|
||||
auto& rpDescAttachment = isDepthFormat(desc.format) ?
|
||||
rpDesc.attachments.depth :
|
||||
rpDesc.attachments.color[0];
|
||||
|
||||
data.usage = isDepthFormat(desc.format) ?
|
||||
FrameGraphTexture::Usage::DEPTH_ATTACHMENT :
|
||||
FrameGraphTexture::Usage::COLOR_ATTACHMENT;
|
||||
|
||||
data.inFlags = isDepthFormat(desc.format) ?
|
||||
backend::TargetBufferFlags::DEPTH :
|
||||
backend::TargetBufferFlags::COLOR0;
|
||||
|
||||
data.input = builder.read(input, data.usage);
|
||||
|
||||
rpDescAttachment = builder.createTexture(outputBufferName, desc);
|
||||
rpDescAttachment = builder.write(rpDescAttachment, data.usage);
|
||||
data.output = rpDescAttachment;
|
||||
builder.declareRenderPass("Resolve Pass", rpDesc);
|
||||
data.input = builder.read(input, FrameGraphTexture::Usage::BLIT_SRC);
|
||||
data.output = builder.createTexture(outputBufferName, outDesc);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::BLIT_DST);
|
||||
},
|
||||
[](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
auto inDesc = resources.getDescriptor(data.input);
|
||||
auto in = resources.getTexture(data.input);
|
||||
auto out = resources.getRenderPassInfo();
|
||||
auto const& src = resources.getTexture(data.input);
|
||||
auto const& dst = resources.getTexture(data.output);
|
||||
auto const& srcSubDesc = resources.getSubResourceDescriptor(data.input);
|
||||
auto const& dstSubDesc = resources.getSubResourceDescriptor(data.output);
|
||||
UTILS_UNUSED_IN_RELEASE auto const& srcDesc = resources.getDescriptor(data.input);
|
||||
UTILS_UNUSED_IN_RELEASE auto const& dstDesc = resources.getDescriptor(data.output);
|
||||
assert_invariant(src);
|
||||
assert_invariant(dst);
|
||||
assert_invariant(srcDesc.format == dstDesc.format);
|
||||
assert_invariant(srcDesc.width == dstDesc.width && srcDesc.height == dstDesc.height);
|
||||
driver.resolve(
|
||||
dst, dstSubDesc.level, dstSubDesc.layer,
|
||||
src, srcSubDesc.level, srcSubDesc.layer);
|
||||
});
|
||||
|
||||
assert_invariant(in);
|
||||
return ppResolve->output;
|
||||
}
|
||||
|
||||
Handle<HwRenderTarget> inRt;
|
||||
if (data.usage == FrameGraphTexture::Usage::COLOR_ATTACHMENT) {
|
||||
inRt = driver.createRenderTarget(data.inFlags,
|
||||
out.params.viewport.width, out.params.viewport.height,
|
||||
inDesc.samples, {{ in }}, {}, {});
|
||||
}
|
||||
if (data.usage == FrameGraphTexture::Usage::DEPTH_ATTACHMENT) {
|
||||
inRt = driver.createRenderTarget(data.inFlags,
|
||||
out.params.viewport.width, out.params.viewport.height,
|
||||
inDesc.samples, {}, { in }, {});
|
||||
}
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepth(FrameGraph& fg,
|
||||
const char* outputBufferName, FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor outDesc) noexcept {
|
||||
|
||||
driver.blit(data.inFlags,
|
||||
out.target, out.params.viewport, inRt, out.params.viewport,
|
||||
SamplerMagFilter::NEAREST);
|
||||
// Don't do anything if we're not a MSAA buffer
|
||||
auto const& inDesc = fg.getDescriptor(input);
|
||||
if (inDesc.samples <= 1) {
|
||||
return input;
|
||||
}
|
||||
|
||||
driver.destroyRenderTarget(inRt);
|
||||
UTILS_UNUSED_IN_RELEASE auto const& inSubDesc = fg.getSubResourceDescriptor(input);
|
||||
assert_invariant(isDepthFormat(inDesc.format));
|
||||
assert_invariant(inSubDesc.layer == 0);
|
||||
assert_invariant(inSubDesc.level == 0);
|
||||
|
||||
outDesc.width = inDesc.width;
|
||||
outDesc.height = inDesc.height;
|
||||
outDesc.format = inDesc.format;
|
||||
outDesc.samples = 0;
|
||||
|
||||
struct ResolveData {
|
||||
FrameGraphId<FrameGraphTexture> input;
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
};
|
||||
|
||||
auto& ppResolve = fg.addPass<ResolveData>("resolveDepth",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.input = builder.sample(input);
|
||||
data.output = builder.createTexture(outputBufferName, outDesc);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
builder.declareRenderPass(builder.getName(data.output), {
|
||||
.attachments = { .depth = { data.output }},
|
||||
.clearFlags = TargetBufferFlags::DEPTH });
|
||||
},
|
||||
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
auto const& input = resources.getTexture(data.input);
|
||||
auto const& material = getPostProcessMaterial("resolveDepth");
|
||||
auto* mi = material.getMaterialInstance(mEngine);
|
||||
mi->setParameter("depth", input, {}); // NEAREST
|
||||
commitAndRender(resources.getRenderPassInfo(), material, driver);
|
||||
});
|
||||
|
||||
return ppResolve->output;
|
||||
@@ -3037,7 +3191,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
|
||||
auto& material = getPostProcessMaterial("vsmMipmap");
|
||||
|
||||
// When generating shadow map mip levels, we want to preserve the 1 texel border.
|
||||
// (note clearing never respects the scissor in filament)
|
||||
// (note clearing never respects the scissor in Filament)
|
||||
PipelineState pipeline(material.getPipelineState(mEngine));
|
||||
pipeline.scissor = { 1u, 1u, dim - 2u, dim - 2u };
|
||||
|
||||
|
||||
@@ -86,6 +86,10 @@ public:
|
||||
void init() noexcept;
|
||||
void terminate(backend::DriverApi& driver) noexcept;
|
||||
|
||||
|
||||
void configureTemporalAntiAliasingMaterial(
|
||||
TemporalAntiAliasingOptions const& taaOptions) noexcept;
|
||||
|
||||
// methods below are ordered relative to their position in the pipeline (as much as possible)
|
||||
|
||||
// structure (depth) pass
|
||||
@@ -158,7 +162,7 @@ public:
|
||||
FrameGraphId<FrameGraphTexture> depth,
|
||||
const CameraInfo& cameraInfo,
|
||||
bool translucent,
|
||||
float bokehAspectRatio,
|
||||
math::float2 bokehScale,
|
||||
const DepthOfFieldOptions& dofOptions) noexcept;
|
||||
|
||||
// Bloom
|
||||
@@ -167,7 +171,9 @@ public:
|
||||
FrameGraphId<FrameGraphTexture> flare;
|
||||
};
|
||||
BloomPassOutput bloom(FrameGraph& fg, FrameGraphId<FrameGraphTexture> input,
|
||||
BloomOptions& inoutBloomOptions, backend::TextureFormat outFormat,
|
||||
backend::TextureFormat outFormat,
|
||||
BloomOptions& inoutBloomOptions,
|
||||
TemporalAntiAliasingOptions const& taaOptions,
|
||||
math::float2 scale) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> flarePass(FrameGraph& fg,
|
||||
@@ -207,7 +213,9 @@ public:
|
||||
backend::TextureFormat outFormat, bool translucent) noexcept;
|
||||
|
||||
// Temporal Anti-aliasing
|
||||
void prepareTaa(FrameGraph& fg, filament::Viewport const& svp,
|
||||
void prepareTaa(FrameGraph& fg,
|
||||
filament::Viewport const& svp,
|
||||
TemporalAntiAliasingOptions const& taaOptions,
|
||||
FrameHistory& frameHistory,
|
||||
FrameHistoryEntry::TemporalAA FrameHistoryEntry::*pTaa,
|
||||
CameraInfo* inoutCameraInfo,
|
||||
@@ -221,32 +229,36 @@ public:
|
||||
TemporalAntiAliasingOptions const& taaOptions,
|
||||
ColorGradingConfig const& colorGradingConfig) noexcept;
|
||||
|
||||
// Blit/rescaling/resolves
|
||||
FrameGraphId<FrameGraphTexture> opaqueBlit(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
backend::SamplerMagFilter filter = backend::SamplerMagFilter::LINEAR) noexcept;
|
||||
/*
|
||||
* Blit/rescaling/resolves
|
||||
*/
|
||||
|
||||
// high quality upscaler
|
||||
// - when translucent, reverts to LINEAR
|
||||
// - doens't handle sub-resouces
|
||||
FrameGraphId<FrameGraphTexture> upscale(FrameGraph& fg, bool translucent,
|
||||
DynamicResolutionOptions dsrOptions, FrameGraphId<FrameGraphTexture> input,
|
||||
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
|
||||
backend::SamplerMagFilter filter = backend::SamplerMagFilter::LINEAR) noexcept;
|
||||
backend::SamplerMagFilter filter) noexcept;
|
||||
|
||||
// upscale/downscale blitter using shaders
|
||||
FrameGraphId<FrameGraphTexture> blit(FrameGraph& fg, bool translucent,
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
backend::SamplerMagFilter filter = backend::SamplerMagFilter::LINEAR) noexcept;
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
|
||||
backend::SamplerMagFilter filterMag,
|
||||
backend::SamplerMinFilter filterMin) noexcept;
|
||||
|
||||
// resolve base level of input and outputs a 1-level texture
|
||||
FrameGraphId<FrameGraphTexture> resolveBaseLevel(FrameGraph& fg,
|
||||
const char* outputBufferName, FrameGraphId<FrameGraphTexture> input) noexcept;
|
||||
|
||||
// resolves base level of input and outputs a texture from outDesc. outDesc must
|
||||
// have the same dimensions and format as input, or this will fail.
|
||||
// outDesc can have mipmaps.
|
||||
FrameGraphId<FrameGraphTexture> resolveBaseLevelNoCheck(FrameGraph& fg,
|
||||
// Resolves base level of input and outputs a texture from outDesc.
|
||||
// outDesc with, height, format and samples will be overridden.
|
||||
FrameGraphId<FrameGraphTexture> resolve(FrameGraph& fg,
|
||||
const char* outputBufferName, FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor const& outDesc) noexcept;
|
||||
FrameGraphTexture::Descriptor outDesc) noexcept;
|
||||
|
||||
// Resolves base level of input and outputs a texture from outDesc.
|
||||
// outDesc with, height, format and samples will be overridden.
|
||||
FrameGraphId<FrameGraphTexture> resolveDepth(FrameGraph& fg,
|
||||
const char* outputBufferName, FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor outDesc) noexcept;
|
||||
|
||||
// VSM shadow mipmap pass
|
||||
FrameGraphId<FrameGraphTexture> vsmMipmapPass(FrameGraph& fg,
|
||||
@@ -267,48 +279,6 @@ public:
|
||||
backend::Handle<backend::HwTexture> getOneTextureArray() const;
|
||||
backend::Handle<backend::HwTexture> getZeroTextureArray() const;
|
||||
|
||||
math::float2 halton(size_t index) const noexcept {
|
||||
return mHaltonSamples[index & 0xFu];
|
||||
}
|
||||
|
||||
private:
|
||||
FEngine& mEngine;
|
||||
class PostProcessMaterial;
|
||||
|
||||
struct BilateralPassConfig {
|
||||
uint8_t kernelSize = 11;
|
||||
bool bentNormals = false;
|
||||
float standardDeviation = 1.0f;
|
||||
float bilateralThreshold = 0.0625f;
|
||||
float scale = 1.0f;
|
||||
};
|
||||
|
||||
FrameGraphId<FrameGraphTexture> bilateralBlurPass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, FrameGraphId<FrameGraphTexture> depth,
|
||||
math::int2 axis, float zf, backend::TextureFormat format,
|
||||
BilateralPassConfig const& config) noexcept;
|
||||
|
||||
BloomPassOutput bloomPass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, backend::TextureFormat outFormat,
|
||||
BloomOptions& inoutBloomOptions, math::float2 scale) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> downscalePass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
bool threshold, float highlight, bool fireflies) noexcept;
|
||||
|
||||
void commitAndRender(FrameGraphResources::RenderPassInfo const& out,
|
||||
PostProcessMaterial const& material, uint8_t variant,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
void commitAndRender(FrameGraphResources::RenderPassInfo const& out,
|
||||
PostProcessMaterial const& material,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
void render(FrameGraphResources::RenderPassInfo const& out,
|
||||
backend::PipelineState const& pipeline,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
class PostProcessMaterial {
|
||||
public:
|
||||
PostProcessMaterial() noexcept;
|
||||
@@ -342,21 +312,62 @@ private:
|
||||
utils::FixedCapacityVector<ConstantInfo> mConstants{};
|
||||
};
|
||||
|
||||
void registerPostProcessMaterial(std::string_view name, MaterialInfo const& info);
|
||||
|
||||
PostProcessMaterial& getPostProcessMaterial(std::string_view name) noexcept;
|
||||
|
||||
void commitAndRender(FrameGraphResources::RenderPassInfo const& out,
|
||||
PostProcessMaterial const& material, uint8_t variant,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
void commitAndRender(FrameGraphResources::RenderPassInfo const& out,
|
||||
PostProcessMaterial const& material,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
void render(FrameGraphResources::RenderPassInfo const& out,
|
||||
backend::PipelineState const& pipeline,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
private:
|
||||
FEngine& mEngine;
|
||||
|
||||
struct BilateralPassConfig {
|
||||
uint8_t kernelSize = 11;
|
||||
bool bentNormals = false;
|
||||
float standardDeviation = 1.0f;
|
||||
float bilateralThreshold = 0.0625f;
|
||||
float scale = 1.0f;
|
||||
};
|
||||
|
||||
FrameGraphId<FrameGraphTexture> bilateralBlurPass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, FrameGraphId<FrameGraphTexture> depth,
|
||||
math::int2 axis, float zf, backend::TextureFormat format,
|
||||
BilateralPassConfig const& config) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> downscalePass(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
bool threshold, float highlight, bool fireflies) noexcept;
|
||||
|
||||
using MaterialRegistryMap = tsl::robin_map<
|
||||
std::string_view,
|
||||
PostProcessMaterial>;
|
||||
|
||||
MaterialRegistryMap mMaterialRegistry;
|
||||
|
||||
void registerPostProcessMaterial(std::string_view name, MaterialInfo const& info);
|
||||
PostProcessMaterial& getPostProcessMaterial(std::string_view name) noexcept;
|
||||
|
||||
backend::Handle<backend::HwTexture> mStarburstTexture;
|
||||
|
||||
std::uniform_real_distribution<float> mUniformDistribution{0.0f, 1.0f};
|
||||
|
||||
static const math::float2 sHaltonSamples[16];
|
||||
math::float2 const* mHaltonSamples = sHaltonSamples;
|
||||
template<size_t SIZE>
|
||||
struct JitterSequence {
|
||||
auto operator()(size_t i) const noexcept { return positions[i % SIZE] - 0.5f; }
|
||||
const math::float2 positions[SIZE];
|
||||
};
|
||||
|
||||
static const JitterSequence<4> sRGSS4;
|
||||
static const JitterSequence<4> sUniformHelix4;
|
||||
static const JitterSequence<16> sHaltonSamples;
|
||||
|
||||
bool mWorkaroundSplitEasu : 1;
|
||||
bool mWorkaroundAllowReadOnlyAncillaryFeedbackLoop : 1;
|
||||
|
||||
@@ -89,7 +89,7 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
|
||||
data.color = builder.createTexture("Color Buffer", colorBufferDesc);
|
||||
}
|
||||
|
||||
const bool canResolveDepth = engine.getDriverApi().isAutoDepthResolveSupported();
|
||||
const bool canAutoResolveDepth = engine.getDriverApi().isAutoDepthResolveSupported();
|
||||
|
||||
if (!data.depth) {
|
||||
// clear newly allocated depth/stencil buffers, regardless of given clear flags
|
||||
@@ -114,13 +114,17 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
|
||||
data.depth = builder.createTexture(name, {
|
||||
.width = colorBufferDesc.width,
|
||||
.height = colorBufferDesc.height,
|
||||
// If the color attachment requested MS, we assume this means the MS buffer
|
||||
// must be kept, and for that reason we allocate the depth buffer with MS
|
||||
// as well. On the other hand, if the color attachment was allocated without
|
||||
// MS, no need to allocate the depth buffer with MS, if the RT is MS,
|
||||
// the tile depth buffer will be MS, but it'll be resolved to single
|
||||
// sample automatically -- which is what we want.
|
||||
.samples = canResolveDepth ? colorBufferDesc.samples : uint8_t(config.msaa),
|
||||
// If the color attachment requested MS, we assume this means the MS
|
||||
// buffer must be kept, and for that reason we allocate the depth
|
||||
// buffer with MS as well.
|
||||
// On the other hand, if the color attachment was allocated without
|
||||
// MS, no need to allocate the depth buffer with MS; Either it's not
|
||||
// multi-sampled or it is auto-resolved.
|
||||
// One complication above is that some backends don't support
|
||||
// depth auto-resolve, in which case we must allocate the depth
|
||||
// buffer with MS and manually resolve it (see "Resolved Depth Buffer"
|
||||
// pass).
|
||||
.samples = canAutoResolveDepth ? colorBufferDesc.samples : uint8_t(config.msaa),
|
||||
.format = format,
|
||||
});
|
||||
if (config.enabledStencilBuffer) {
|
||||
@@ -282,7 +286,7 @@ std::pair<FrameGraphId<FrameGraphTexture>, bool> RendererUtils::refractionPass(
|
||||
|
||||
input = RendererUtils::colorPass(fg, "Color Pass (opaque)", engine, view, {
|
||||
// When rendering the opaques, we need to conserve the sample buffer,
|
||||
// so create config that specifies the sample count.
|
||||
// so create a config that specifies the sample count.
|
||||
.width = config.physicalViewport.width,
|
||||
.height = config.physicalViewport.height,
|
||||
.samples = config.msaa,
|
||||
@@ -312,11 +316,11 @@ std::pair<FrameGraphId<FrameGraphTexture>, bool> RendererUtils::refractionPass(
|
||||
config, colorGradingConfig, pass.getExecutor(refraction, pass.end()));
|
||||
|
||||
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
|
||||
// We need to do a resolve here because later passes (such as color grading or DoF) will need
|
||||
// to sample from 'output'. However, because we have MSAA, we know we're not sampleable.
|
||||
// And this is because in the SSR case, we had to use a renderbuffer to conserve the
|
||||
// multi-sample buffer.
|
||||
output = ppm.resolveBaseLevel(fg, "Resolved Color Buffer", output);
|
||||
// We need to do a resolve here because later passes (such as color grading or DoF) will
|
||||
// need to sample from 'output'. However, because we have MSAA, we know we're not
|
||||
// sampleable. And this is because in the SSR case, we had to use a renderbuffer to
|
||||
// conserve the multi-sample buffer.
|
||||
output = ppm.resolve(fg, "Resolved Color Buffer", output, { .levels = 1 });
|
||||
}
|
||||
} else {
|
||||
output = input;
|
||||
|
||||
@@ -247,7 +247,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
|
||||
|
||||
// cameraInfo only valid after calling update
|
||||
const CameraInfo cameraInfo{ shadowMap.getCamera() };
|
||||
const CameraInfo cameraInfo{ shadowMap.getCamera(), mainCameraInfo };
|
||||
|
||||
auto transaction = ShadowMap::open(driver);
|
||||
ShadowMap::prepareCamera(transaction, engine, cameraInfo);
|
||||
@@ -844,7 +844,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine
|
||||
FScene::LightSoa const& lightData) noexcept {
|
||||
|
||||
// The const_cast here is a little ugly, but conceptually lightData should be const,
|
||||
// it's just that we're using it to store some temporary data. with SoA we can't have
|
||||
// it's just that we're using it to store some temporary data. With SoA we can't have
|
||||
// a `mutable` element, so that's a workaround.
|
||||
FScene::ShadowInfo* const shadowInfo = const_cast<FScene::ShadowInfo*>(
|
||||
lightData.data<FScene::SHADOW_INFO>());
|
||||
@@ -855,7 +855,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine
|
||||
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
|
||||
for (ShadowMap const& shadowMap : spotShadowMaps) {
|
||||
const size_t lightIndex = shadowMap.getLightIndex();
|
||||
// gather the per-light (not per shadow map) information. For point lights we will
|
||||
// Gather the per-light (not per shadow map) information. For point lights we will
|
||||
// "see" 6 shadowmaps (one per face), we must use the first face one, the shader
|
||||
// knows how to find the entry for other faces (they're guaranteed to be sequential).
|
||||
if (shadowMap.getFace() == 0) {
|
||||
|
||||
@@ -265,24 +265,22 @@ uint8_t FCamera::getStereoscopicEyeCount() const noexcept {
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
CameraInfo::CameraInfo(FCamera const& camera) noexcept {
|
||||
for (int i = 0; i < camera.getStereoscopicEyeCount(); i++) {
|
||||
eyeProjection[i] = mat4f{ camera.getProjectionMatrix(i) };
|
||||
}
|
||||
cullingProjection = mat4f{ camera.getCullingProjectionMatrix() };
|
||||
model = mat4f{ camera.getModelMatrix() };
|
||||
view = mat4f{ camera.getViewMatrix() };
|
||||
zn = (float)camera.getNear();
|
||||
zf = (float)camera.getCullingFar();
|
||||
ev100 = Exposure::ev100(camera);
|
||||
f = (float)camera.getFocalLength();
|
||||
A = f / camera.getAperture();
|
||||
d = std::max(zn, camera.getFocusDistance());
|
||||
CameraInfo::CameraInfo(FCamera const& camera) noexcept
|
||||
: CameraInfo(camera, {}, camera.getModelMatrix()) {
|
||||
}
|
||||
|
||||
CameraInfo::CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform) noexcept {
|
||||
const mat4 modelMatrix{ inWorldTransform * camera.getModelMatrix() };
|
||||
for (int i = 0; i < camera.getStereoscopicEyeCount(); i++) {
|
||||
CameraInfo::CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform) noexcept
|
||||
: CameraInfo(camera, inWorldTransform, inWorldTransform * camera.getModelMatrix()) {
|
||||
}
|
||||
|
||||
CameraInfo::CameraInfo(FCamera const& camera, CameraInfo const& mainCameraInfo) noexcept
|
||||
: CameraInfo(camera, mainCameraInfo.worldTransform, camera.getModelMatrix()) {
|
||||
}
|
||||
|
||||
CameraInfo::CameraInfo(FCamera const& camera,
|
||||
math::mat4 const& inWorldTransform,
|
||||
math::mat4 const& modelMatrix) noexcept {
|
||||
for (size_t i = 0; i < camera.getStereoscopicEyeCount(); i++) {
|
||||
eyeProjection[i] = mat4f{ camera.getProjectionMatrix(i) };
|
||||
eyeFromView[i] = mat4f{ camera.getEyeFromViewMatrix(i) };
|
||||
}
|
||||
|
||||
@@ -220,9 +220,19 @@ private:
|
||||
|
||||
struct CameraInfo {
|
||||
CameraInfo() noexcept {}
|
||||
explicit CameraInfo(FCamera const& camera) noexcept;
|
||||
|
||||
// Creates a CameraInfo relative to inWorldTransform (i.e. it's model matrix is
|
||||
// transformed by inWorldTransform and inWorldTransform is recorded).
|
||||
// This is typically used for the color pass camera.
|
||||
CameraInfo(FCamera const& camera, math::mat4 const& inWorldTransform) noexcept;
|
||||
|
||||
// Creates a CameraInfo from a camera that is relative to mainCameraInfo.
|
||||
// This is typically used for the shadow pass cameras.
|
||||
CameraInfo(FCamera const& camera, CameraInfo const& mainCameraInfo) noexcept;
|
||||
|
||||
// Creates a CameraInfo from the FCamera
|
||||
explicit CameraInfo(FCamera const& camera) noexcept;
|
||||
|
||||
union {
|
||||
// projection matrix for drawing (infinite zfar)
|
||||
// for monoscopic rendering
|
||||
@@ -249,6 +259,11 @@ struct CameraInfo {
|
||||
math::float3 const& getPosition() const noexcept { return model[3].xyz; }
|
||||
math::float3 getForwardVector() const noexcept { return normalize(-model[2].xyz); }
|
||||
math::mat4 getUserViewMatrix() const noexcept { return view * worldTransform; }
|
||||
|
||||
private:
|
||||
CameraInfo(FCamera const& camera,
|
||||
math::mat4 const& inWorldTransform,
|
||||
math::mat4 const& modelMatrix) noexcept;
|
||||
};
|
||||
|
||||
FILAMENT_DOWNCAST(Camera)
|
||||
|
||||
@@ -373,8 +373,13 @@ void FEngine::init() {
|
||||
|
||||
mDebugRegistry.registerProperty("d.shadowmap.debug_directional_shadowmap",
|
||||
&debug.shadowmap.debug_directional_shadowmap, [this]() {
|
||||
mMaterials.forEach([](FMaterial* material) {
|
||||
mMaterials.forEach([this](FMaterial* material) {
|
||||
if (material->getMaterialDomain() == MaterialDomain::SURFACE) {
|
||||
|
||||
material->setConstant(
|
||||
+ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP,
|
||||
debug.shadowmap.debug_directional_shadowmap);
|
||||
|
||||
material->invalidate(
|
||||
Variant::DIR | Variant::SRE | Variant::DEP,
|
||||
Variant::DIR | Variant::SRE);
|
||||
@@ -384,8 +389,13 @@ void FEngine::init() {
|
||||
|
||||
mDebugRegistry.registerProperty("d.lighting.debug_froxel_visualization",
|
||||
&debug.lighting.debug_froxel_visualization, [this]() {
|
||||
mMaterials.forEach([](FMaterial* material) {
|
||||
mMaterials.forEach([this](FMaterial* material) {
|
||||
if (material->getMaterialDomain() == MaterialDomain::SURFACE) {
|
||||
|
||||
material->setConstant(
|
||||
+ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION,
|
||||
debug.lighting.debug_froxel_visualization);
|
||||
|
||||
material->invalidate(
|
||||
Variant::DYN | Variant::DEP,
|
||||
Variant::DYN);
|
||||
|
||||
@@ -79,7 +79,10 @@ struct Material::BuilderDetails {
|
||||
size_t mSize = 0;
|
||||
MaterialParser* mMaterialParser = nullptr;
|
||||
bool mDefaultMaterial = false;
|
||||
std::unordered_map<std::string, std::variant<int32_t, float, bool>> mConstantSpecializations;
|
||||
std::unordered_map<
|
||||
utils::CString,
|
||||
std::variant<int32_t, float, bool>,
|
||||
CString::Hasher> mConstantSpecializations;
|
||||
};
|
||||
|
||||
FMaterial::DefaultMaterialBuilder::DefaultMaterialBuilder() : Material::Builder() {
|
||||
@@ -216,9 +219,14 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
|
||||
mSubpassInfo.isValid = false;
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<MaterialConstant> constants;
|
||||
// Older materials won't have a constants chunk, but that's okay.
|
||||
parser->getConstants(&constants);
|
||||
parser->getConstants(&mMaterialConstants);
|
||||
for (size_t i = 0, c = mMaterialConstants.size(); i < c; i++) {
|
||||
auto& item = mMaterialConstants[i];
|
||||
// the key can be a string_view because mMaterialConstant owns the CString
|
||||
std::string_view const key{ item.name.data(), item.name.size() };
|
||||
mSpecializationConstantsNameToIndex[key] = i;
|
||||
}
|
||||
|
||||
// Verify that all the constant specializations exist in the material and that their types match.
|
||||
// The first specialization constants are defined internally by Filament.
|
||||
@@ -238,7 +246,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
|
||||
bool const powerVrShaderWorkarounds =
|
||||
engine.getDriverApi().isWorkaroundNeeded(Workaround::POWER_VR_SHADER_WORKAROUNDS);
|
||||
|
||||
mSpecializationConstants.reserve(constants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
|
||||
mSpecializationConstants.reserve(mMaterialConstants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
|
||||
mSpecializationConstants.push_back({
|
||||
+ReservedSpecializationConstants::BACKEND_FEATURE_LEVEL,
|
||||
(int)engine.getSupportedFeatureLevel() });
|
||||
@@ -250,16 +258,16 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
|
||||
(int)maxFroxelBufferHeight });
|
||||
mSpecializationConstants.push_back({
|
||||
+ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP,
|
||||
(bool)engine.debug.shadowmap.debug_directional_shadowmap });
|
||||
engine.debug.shadowmap.debug_directional_shadowmap });
|
||||
mSpecializationConstants.push_back({
|
||||
+ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION,
|
||||
(bool)engine.debug.lighting.debug_froxel_visualization });
|
||||
engine.debug.lighting.debug_froxel_visualization });
|
||||
mSpecializationConstants.push_back({
|
||||
+ReservedSpecializationConstants::CONFIG_STATIC_TEXTURE_TARGET_WORKAROUND,
|
||||
(bool)staticTextureWorkaround });
|
||||
staticTextureWorkaround });
|
||||
mSpecializationConstants.push_back({
|
||||
+ReservedSpecializationConstants::CONFIG_POWER_VR_SHADER_WORKAROUNDS,
|
||||
(bool)powerVrShaderWorkarounds });
|
||||
powerVrShaderWorkarounds });
|
||||
mSpecializationConstants.push_back({
|
||||
+ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT,
|
||||
(int)engine.getConfig().stereoscopicEyeCount });
|
||||
@@ -270,19 +278,18 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
|
||||
false});
|
||||
}
|
||||
|
||||
for (const auto& [name, value] : builder->mConstantSpecializations) {
|
||||
auto found = std::find_if(
|
||||
constants.begin(), constants.end(), [name = name](const auto& constant) {
|
||||
return strncmp(constant.name.data(), name.data(), name.length()) == 0;
|
||||
});
|
||||
ASSERT_PRECONDITION(found != constants.end(),
|
||||
for (auto const& [name, value] : builder->mConstantSpecializations) {
|
||||
std::string_view const key{ name.data(), name.size() };
|
||||
auto pos = mSpecializationConstantsNameToIndex.find(key);
|
||||
ASSERT_PRECONDITION(pos != mSpecializationConstantsNameToIndex.end(),
|
||||
"The material %s does not have a constant parameter named %s.",
|
||||
mName.c_str_safe(), name.c_str());
|
||||
const char* const types[3] = {"an int", "a float", "a bool"};
|
||||
const char* const errorMessage =
|
||||
"The constant parameter %s on material %s is of type %s, but %s was "
|
||||
"provided.";
|
||||
switch (found->type) {
|
||||
auto& constant = mMaterialConstants[pos->second];
|
||||
switch (constant.type) {
|
||||
case ConstantType::INT:
|
||||
ASSERT_PRECONDITION(std::holds_alternative<int32_t>(value), errorMessage,
|
||||
name.c_str(), mName.c_str_safe(), "int", types[value.index()]);
|
||||
@@ -296,8 +303,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
|
||||
name.c_str(), mName.c_str_safe(), "bool", types[value.index()]);
|
||||
break;
|
||||
}
|
||||
uint32_t const index =
|
||||
std::distance(constants.begin(), found) + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
|
||||
uint32_t const index = pos->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
|
||||
mSpecializationConstants.push_back({ index, value });
|
||||
}
|
||||
|
||||
@@ -405,6 +411,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
|
||||
mIsDefaultMaterial = builder->mDefaultMaterial;
|
||||
|
||||
if (UTILS_UNLIKELY(mIsDefaultMaterial)) {
|
||||
assert_invariant(mMaterialDomain == MaterialDomain::SURFACE);
|
||||
filaflat::MaterialChunk const& materialChunk{ mMaterialParser->getMaterialChunk() };
|
||||
auto variants = FixedCapacityVector<Variant>::with_capacity(materialChunk.getShaderCount());
|
||||
materialChunk.visitShaders([&variants](
|
||||
@@ -420,12 +427,14 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
|
||||
std::swap(mDepthVariants, variants);
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) {
|
||||
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
|
||||
auto& cachedPrograms = mCachedPrograms;
|
||||
for (Variant const variant : pDefaultMaterial->mDepthVariants) {
|
||||
pDefaultMaterial->prepareProgram(variant);
|
||||
cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant);
|
||||
if (mMaterialDomain == MaterialDomain::SURFACE) {
|
||||
if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) {
|
||||
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
|
||||
auto& cachedPrograms = mCachedPrograms;
|
||||
for (Variant const variant: pDefaultMaterial->mDepthVariants) {
|
||||
pDefaultMaterial->prepareProgram(variant);
|
||||
cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,44 +468,47 @@ FMaterial::~FMaterial() noexcept {
|
||||
}
|
||||
|
||||
void FMaterial::invalidate(Variant::type_t variantMask, Variant::type_t variantValue) noexcept {
|
||||
// update the spec constants that can change
|
||||
// TODO: should we just always update all of them?
|
||||
for (auto& item : mSpecializationConstants) {
|
||||
if (item.id == ReservedSpecializationConstants::CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP) {
|
||||
item.value = mEngine.debug.shadowmap.debug_directional_shadowmap;
|
||||
} else if (item.id == ReservedSpecializationConstants::CONFIG_DEBUG_FROXEL_VISUALIZATION) {
|
||||
item.value = mEngine.debug.lighting.debug_froxel_visualization;
|
||||
if (mMaterialDomain == MaterialDomain::SURFACE) {
|
||||
DriverApi& driverApi = mEngine.getDriverApi();
|
||||
auto& cachedPrograms = mCachedPrograms;
|
||||
for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) {
|
||||
Variant const variant(k);
|
||||
if ((k & variantMask) == variantValue) {
|
||||
if (UTILS_LIKELY(!mIsDefaultMaterial)) {
|
||||
// The depth variants may be shared with the default material, in which case
|
||||
// we should not free it now.
|
||||
bool const isSharedVariant =
|
||||
Variant::isValidDepthVariant(variant) && !mHasCustomDepthShader;
|
||||
if (isSharedVariant) {
|
||||
// we don't own this variant, skip.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
driverApi.destroyProgram(cachedPrograms[k]);
|
||||
cachedPrograms[k].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DriverApi& driverApi = mEngine.getDriverApi();
|
||||
auto& cachedPrograms = mCachedPrograms;
|
||||
for (size_t k = 0, n = VARIANT_COUNT; k < n; ++k) {
|
||||
Variant const variant(k);
|
||||
if ((k & variantMask) == variantValue) {
|
||||
if (UTILS_LIKELY(!mIsDefaultMaterial)) {
|
||||
// The depth variants may be shared with the default material, in which case
|
||||
// we should not free it now.
|
||||
bool const isSharedVariant =
|
||||
Variant::isValidDepthVariant(variant) && !mHasCustomDepthShader;
|
||||
if (isSharedVariant) {
|
||||
// we don't own this variant, skip.
|
||||
continue;
|
||||
if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) {
|
||||
FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial();
|
||||
for (Variant const variant: pDefaultMaterial->mDepthVariants) {
|
||||
pDefaultMaterial->prepareProgram(variant);
|
||||
if (!cachedPrograms[variant.key]) {
|
||||
cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant);
|
||||
}
|
||||
}
|
||||
driverApi.destroyProgram(cachedPrograms[k]);
|
||||
cachedPrograms[k].clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) {
|
||||
FMaterial const* const pDefaultMaterial = mEngine.getDefaultMaterial();
|
||||
for (Variant const variant: pDefaultMaterial->mDepthVariants) {
|
||||
pDefaultMaterial->prepareProgram(variant);
|
||||
if (!cachedPrograms[variant.key]) {
|
||||
cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant);
|
||||
} else if (mMaterialDomain == MaterialDomain::POST_PROCESS) {
|
||||
DriverApi& driverApi = mEngine.getDriverApi();
|
||||
auto& cachedPrograms = mCachedPrograms;
|
||||
for (size_t k = 0, n = POST_PROCESS_VARIANT_COUNT; k < n; ++k) {
|
||||
if ((k & variantMask) == variantValue) {
|
||||
driverApi.destroyProgram(cachedPrograms[k]);
|
||||
cachedPrograms[k].clear();
|
||||
}
|
||||
}
|
||||
} else if (mMaterialDomain == MaterialDomain::COMPUTE) {
|
||||
// TODO: handle compute variants if any
|
||||
}
|
||||
}
|
||||
|
||||
@@ -825,4 +837,55 @@ void FMaterial::destroyPrograms(FEngine& engine) {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<uint32_t> FMaterial::getSpecializationConstantId(std::string_view name) const noexcept {
|
||||
auto pos = mSpecializationConstantsNameToIndex.find(name);
|
||||
if (pos != mSpecializationConstantsNameToIndex.end()) {
|
||||
return pos->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename T, typename>
|
||||
bool FMaterial::setConstant(uint32_t id, T value) noexcept {
|
||||
size_t const maxId = mMaterialConstants.size() + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
|
||||
if (UTILS_LIKELY(id < maxId)) {
|
||||
if (id >= CONFIG_MAX_RESERVED_SPEC_CONSTANTS) {
|
||||
// Constant from the material itself (as opposed to the reserved ones)
|
||||
auto& constant = mMaterialConstants[id - CONFIG_MAX_RESERVED_SPEC_CONSTANTS];
|
||||
using ConstantType = backend::ConstantType;
|
||||
switch (constant.type) {
|
||||
case ConstantType::INT:
|
||||
if (!std::is_same_v<T, int32_t>) return false;
|
||||
break;
|
||||
case ConstantType::FLOAT:
|
||||
if (!std::is_same_v<T, float>) return false;
|
||||
break;
|
||||
case ConstantType::BOOL:
|
||||
if (!std::is_same_v<T, bool>) return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto pos = std::find_if(
|
||||
mSpecializationConstants.begin(), mSpecializationConstants.end(),
|
||||
[id](backend::Program::SpecializationConstant const& specializationConstant) {
|
||||
return specializationConstant.id == id;
|
||||
});
|
||||
if (pos != mSpecializationConstants.end()) {
|
||||
if (std::get<T>(pos->value) != value) {
|
||||
pos->value = value;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
mSpecializationConstants.push_back({ id, value });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template bool FMaterial::setConstant<int32_t>(uint32_t id, int32_t value) noexcept;
|
||||
template bool FMaterial::setConstant<float>(uint32_t id, float value) noexcept;
|
||||
template bool FMaterial::setConstant<bool>(uint32_t id, bool value) noexcept;
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -165,6 +165,14 @@ public:
|
||||
|
||||
void destroyPrograms(FEngine& engine);
|
||||
|
||||
// return the id of a specialization constant specified by name for this material
|
||||
std::optional<uint32_t> getSpecializationConstantId(std::string_view name) const noexcept ;
|
||||
|
||||
// Sets a specialization constant by id. call is no-op if the id is invalid.
|
||||
// Return true is the value was changed.
|
||||
template<typename T, typename = Builder::is_supported_constant_parameter_t<T>>
|
||||
bool setConstant(uint32_t id, T value) noexcept;
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
void applyPendingEdits() noexcept;
|
||||
|
||||
@@ -261,6 +269,11 @@ private:
|
||||
|
||||
SamplerGroupBindingInfoList mSamplerGroupBindingInfoList;
|
||||
SamplerBindingToNameMap mSamplerBindingToNameMap;
|
||||
// Constants defined by this Material
|
||||
utils::FixedCapacityVector<MaterialConstant> mMaterialConstants;
|
||||
// A map from the Constant name to the mMaterialConstant index
|
||||
std::unordered_map<std::string_view, uint32_t> mSpecializationConstantsNameToIndex;
|
||||
// current specialization constants for the HwProgram
|
||||
utils::FixedCapacityVector<backend::Program::SpecializationConstant> mSpecializationConstants;
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
|
||||
@@ -117,7 +117,7 @@ void FMaterialInstance::initDefaultInstance(FEngine& engine, FMaterial const* ma
|
||||
if (!material->getSamplerInterfaceBlock().isEmpty()) {
|
||||
mSamplers = SamplerGroup(material->getSamplerInterfaceBlock().getSize());
|
||||
mSbHandle = driver.createSamplerGroup(
|
||||
mSamplers.getSize(), utils::FixedSizeString<32>("Default material"));
|
||||
mSamplers.getSize(), utils::FixedSizeString<32>(mMaterial->getName().c_str_safe()));
|
||||
}
|
||||
|
||||
const RasterState& rasterState = material->getRasterState();
|
||||
|
||||
@@ -365,19 +365,19 @@ void FRenderer::copyFrame(FSwapChain* dstSwapChain, filament::Viewport const& ds
|
||||
params.viewport.bottom = 0;
|
||||
params.viewport.width = std::numeric_limits<uint32_t>::max();
|
||||
params.viewport.height = std::numeric_limits<uint32_t>::max();
|
||||
driver.beginRenderPass(mRenderTargetHandle, params);
|
||||
driver.endRenderPass();
|
||||
}
|
||||
driver.beginRenderPass(mRenderTargetHandle, params);
|
||||
|
||||
// Verify that the source swap chain is readable.
|
||||
assert_invariant(mSwapChain->isReadable());
|
||||
driver.blit(TargetBufferFlags::COLOR,
|
||||
mRenderTargetHandle, dstViewport, mRenderTargetHandle, srcViewport, SamplerMagFilter::LINEAR);
|
||||
driver.blitDEPRECATED(TargetBufferFlags::COLOR, mRenderTargetHandle,
|
||||
dstViewport, mRenderTargetHandle, srcViewport, SamplerMagFilter::LINEAR);
|
||||
|
||||
if (flags & SET_PRESENTATION_TIME) {
|
||||
// TODO: Implement this properly, see https://github.com/google/filament/issues/633
|
||||
}
|
||||
|
||||
driver.endRenderPass();
|
||||
|
||||
if (flags & COMMIT) {
|
||||
dstSwapChain->commit(driver);
|
||||
}
|
||||
@@ -491,6 +491,19 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
|
||||
hasDithering = false;
|
||||
hasFXAA = false;
|
||||
scale = 1.0f;
|
||||
} else {
|
||||
// This configures post-process materials by setting constant parameters
|
||||
if (taaOptions.enabled) {
|
||||
ppm.configureTemporalAntiAliasingMaterial(taaOptions);
|
||||
if (taaOptions.upscaling) {
|
||||
// for now TAA upscaling is incompatible with regular dsr
|
||||
dsrOptions.enabled = false;
|
||||
// also, upscaling doesn't work well with quater-resolution SSAO
|
||||
aoOptions.resolution = 1.0;
|
||||
// Currently we only support a fixed TAA upscaling ratio
|
||||
scale = 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool blendModeTranslucent = view.getBlendMode() == BlendMode::TRANSLUCENT;
|
||||
@@ -520,7 +533,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
|
||||
};
|
||||
|
||||
// whether we're scaled at all
|
||||
const bool scaled = any(notEqual(scale, float2(1.0f)));
|
||||
bool scaled = any(notEqual(scale, float2(1.0f)));
|
||||
|
||||
// vp is the user defined viewport within the View
|
||||
filament::Viewport const& vp = view.getViewport();
|
||||
@@ -609,7 +622,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
|
||||
|
||||
view.prepare(engine, driver, arena, svp, cameraInfo, getShaderUserTime(), needsAlphaChannel);
|
||||
|
||||
view.prepareUpscaler(scale);
|
||||
view.prepareUpscaler(scale, taaOptions, dsrOptions);
|
||||
|
||||
/*
|
||||
* Allocate command buffer
|
||||
@@ -813,14 +826,14 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
|
||||
[=, &view](FrameGraphResources const& resources,
|
||||
auto const&, DriverApi& driver) mutable {
|
||||
auto out = resources.getRenderPassInfo();
|
||||
view.executePickingQueries(driver, out.target, aoOptions.resolution);
|
||||
view.executePickingQueries(driver, out.target, scale * aoOptions.resolution);
|
||||
});
|
||||
}
|
||||
|
||||
// Store this frame's camera projection in the frame history.
|
||||
if (UTILS_UNLIKELY(taaOptions.enabled)) {
|
||||
// Apply the TAA jitter to everything after the structure pass, starting with the color pass.
|
||||
ppm.prepareTaa(fg, svp, view.getFrameHistory(), &FrameHistoryEntry::taa,
|
||||
ppm.prepareTaa(fg, svp, taaOptions, view.getFrameHistory(), &FrameHistoryEntry::taa,
|
||||
&cameraInfo, view.getPerViewUniforms());
|
||||
}
|
||||
|
||||
@@ -984,10 +997,12 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
|
||||
view.commitUniforms(driver);
|
||||
});
|
||||
|
||||
// resolve depth -- which might be needed because of TAA or DoF. This pass will be culled
|
||||
// if the depth is not used below.
|
||||
auto const depth = ppm.resolveBaseLevel(fg, "Resolved Depth Buffer",
|
||||
blackboard.get<FrameGraphTexture>("depth"));
|
||||
// Resolve depth -- which might be needed because of TAA or DoF. This pass will be culled
|
||||
// if the depth is not used below or if the depth is not MS (e.g. it could have been
|
||||
// auto-resolved).
|
||||
// In practice, this is used on Vulkan and older Metal devices.
|
||||
auto depth = blackboard.get<FrameGraphTexture>("depth");
|
||||
depth = ppm.resolve(fg, "Resolved Depth Buffer", depth, { .levels = 1 });
|
||||
|
||||
// Debug: CSM visualisation
|
||||
if (UTILS_UNLIKELY(engine.debug.shadowmap.visualize_cascades &&
|
||||
@@ -1004,6 +1019,15 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
|
||||
if (taaOptions.enabled) {
|
||||
input = ppm.taa(fg, input, depth, view.getFrameHistory(), &FrameHistoryEntry::taa,
|
||||
taaOptions, colorGradingConfig);
|
||||
if (taaOptions.upscaling) {
|
||||
scale = 1.0f;
|
||||
scaled = false;
|
||||
UTILS_UNUSED_IN_RELEASE auto const& inputDesc = fg.getDescriptor(input);
|
||||
svp.width = inputDesc.width;
|
||||
svp.height = inputDesc.height;
|
||||
xvp.width *= 2;
|
||||
xvp.height *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
@@ -1019,16 +1043,21 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
|
||||
// The bokeh height is always correct regardless of the dynamic resolution scaling.
|
||||
// (because the CoC is calculated w.r.t. the height), so we only need to adjust
|
||||
// the width.
|
||||
float const bokehAspectRatio = scale.x / scale.y;
|
||||
float const aspect = (scale.x / scale.y) * dofOptions.cocAspectRatio;
|
||||
float2 const bokehScale{
|
||||
aspect < 1.0f ? aspect : 1.0f,
|
||||
aspect > 1.0f ? 1.0f / aspect : 1.0f
|
||||
};
|
||||
input = ppm.dof(fg, input, depth, cameraInfo, needsAlphaChannel,
|
||||
bokehAspectRatio, dofOptions);
|
||||
bokehScale, dofOptions);
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> bloom, flare;
|
||||
if (bloomOptions.enabled) {
|
||||
// generate the bloom buffer, which is stored in the blackboard as "bloom". This is
|
||||
// Generate the bloom buffer, which is stored in the blackboard as "bloom". This is
|
||||
// consumed by the colorGrading pass and will be culled if colorGrading is disabled.
|
||||
auto [bloom_, flare_] = ppm.bloom(fg, input, bloomOptions, TextureFormat::R11F_G11F_B10F, scale);
|
||||
auto [bloom_, flare_] = ppm.bloom(fg, input, TextureFormat::R11F_G11F_B10F,
|
||||
bloomOptions, taaOptions, scale);
|
||||
bloom = bloom_;
|
||||
flare = flare_;
|
||||
}
|
||||
@@ -1057,7 +1086,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
|
||||
auto viewport = DEBUG_DYNAMIC_SCALING ? xvp : vp;
|
||||
input = ppm.upscale(fg, needsAlphaChannel, dsrOptions, input, xvp, {
|
||||
.width = viewport.width, .height = viewport.height,
|
||||
.format = colorGradingConfig.ldrFormat });
|
||||
.format = colorGradingConfig.ldrFormat }, SamplerMagFilter::LINEAR);
|
||||
xvp.left = xvp.bottom = 0;
|
||||
svp = xvp;
|
||||
}
|
||||
@@ -1080,7 +1109,8 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
|
||||
// TODO: in that specific scenario it would be better to just not use xvp
|
||||
// The intermediate buffer is accomplished with a "fake" opaqueBlit (i.e. blit) operation.
|
||||
|
||||
const bool outputIsSwapChain = (input == colorPassOutput) && (viewRenderTarget == mRenderTargetHandle);
|
||||
const bool outputIsSwapChain =
|
||||
(input == colorPassOutput) && (viewRenderTarget == mRenderTargetHandle);
|
||||
if (mightNeedFinalBlit) {
|
||||
if (blendModeTranslucent ||
|
||||
xvp != svp ||
|
||||
@@ -1091,8 +1121,9 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
|
||||
ssReflectionsOptions.enabled))) {
|
||||
assert_invariant(!scaled);
|
||||
input = ppm.blit(fg, blendModeTranslucent, input, xvp, {
|
||||
.width = vp.width, .height = vp.height,
|
||||
.format = colorGradingConfig.ldrFormat }, SamplerMagFilter::NEAREST);
|
||||
.width = vp.width, .height = vp.height,
|
||||
.format = colorGradingConfig.ldrFormat },
|
||||
SamplerMagFilter::NEAREST, SamplerMinFilter::NEAREST);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ struct Texture::BuilderDetails {
|
||||
uint8_t mLevels = 1;
|
||||
Sampler mTarget = Sampler::SAMPLER_2D;
|
||||
InternalFormat mFormat = InternalFormat::RGBA8;
|
||||
Usage mUsage = Usage::DEFAULT;
|
||||
Usage mUsage = Usage::NONE;
|
||||
bool mTextureIsSwizzled = false;
|
||||
std::array<Swizzle, 4> mSwizzle = {
|
||||
Swizzle::CHANNEL_0, Swizzle::CHANNEL_1,
|
||||
@@ -125,6 +125,36 @@ Texture* Texture::Builder::build(Engine& engine) {
|
||||
ASSERT_PRECONDITION(Texture::isTextureFormatSupported(engine, mImpl->mFormat),
|
||||
"Texture format %u not supported on this platform", mImpl->mFormat);
|
||||
|
||||
uint8_t maxLevelCount;
|
||||
switch (mImpl->mTarget) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
maxLevelCount = FTexture::maxLevelCount(mImpl->mWidth, mImpl->mHeight);
|
||||
break;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
maxLevelCount = FTexture::maxLevelCount(std::max(
|
||||
{ mImpl->mWidth, mImpl->mHeight, mImpl->mDepth }));
|
||||
break;
|
||||
}
|
||||
mImpl->mLevels = std::min(mImpl->mLevels, maxLevelCount);
|
||||
|
||||
if (mImpl->mUsage == TextureUsage::NONE) {
|
||||
mImpl->mUsage = TextureUsage::DEFAULT;
|
||||
if (mImpl->mLevels > 1 &&
|
||||
(mImpl->mWidth > 1 || mImpl->mHeight > 1) &&
|
||||
mImpl->mTarget != SamplerType::SAMPLER_EXTERNAL) {
|
||||
const bool formatMipmappable =
|
||||
downcast(engine).getDriverApi().isTextureFormatMipmappable(mImpl->mFormat);
|
||||
if (formatMipmappable) {
|
||||
// by default mipmappable textures have the BLIT usage bits set
|
||||
mImpl->mUsage |= TextureUsage::BLIT_SRC | TextureUsage::BLIT_DST;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool sampleable = bool(mImpl->mUsage & TextureUsage::SAMPLEABLE);
|
||||
const bool swizzled = mImpl->mTextureIsSwizzled;
|
||||
const bool imported = mImpl->mImportedId;
|
||||
@@ -163,30 +193,15 @@ Texture* Texture::Builder::build(Engine& engine) {
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
FTexture::FTexture(FEngine& engine, const Builder& builder) {
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
mWidth = static_cast<uint32_t>(builder->mWidth);
|
||||
mHeight = static_cast<uint32_t>(builder->mHeight);
|
||||
mDepth = static_cast<uint32_t>(builder->mDepth);
|
||||
mFormat = builder->mFormat;
|
||||
mUsage = builder->mUsage;
|
||||
mTarget = builder->mTarget;
|
||||
mLevelCount = builder->mLevels;
|
||||
|
||||
uint8_t maxLevelCount;
|
||||
switch (builder->mTarget) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
maxLevelCount = FTexture::maxLevelCount(mWidth, mHeight);
|
||||
break;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
maxLevelCount = FTexture::maxLevelCount(std::max(mWidth, std::max(mHeight, mDepth)));
|
||||
break;
|
||||
}
|
||||
|
||||
mLevelCount = std::min(builder->mLevels, maxLevelCount);
|
||||
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
if (UTILS_LIKELY(builder->mImportedId == 0)) {
|
||||
if (UTILS_LIKELY(!builder->mTextureIsSwizzled)) {
|
||||
mHandle = driver.createTexture(
|
||||
@@ -391,6 +406,9 @@ void FTexture::generateMipmaps(FEngine& engine) const noexcept {
|
||||
ASSERT_PRECONDITION(mTarget != SamplerType::SAMPLER_EXTERNAL,
|
||||
"External Textures are not mipmappable.");
|
||||
|
||||
ASSERT_PRECONDITION(mTarget != SamplerType::SAMPLER_3D,
|
||||
"3D Textures are not mipmappable.");
|
||||
|
||||
const bool formatMipmappable = engine.getDriverApi().isTextureFormatMipmappable(mFormat);
|
||||
ASSERT_PRECONDITION(formatMipmappable,
|
||||
"Texture format %u is not mipmappable.", (unsigned)mFormat);
|
||||
@@ -399,74 +417,7 @@ void FTexture::generateMipmaps(FEngine& engine) const noexcept {
|
||||
return;
|
||||
}
|
||||
|
||||
if (engine.getDriverApi().canGenerateMipmaps()) {
|
||||
engine.getDriverApi().generateMipmaps(mHandle);
|
||||
return;
|
||||
}
|
||||
|
||||
auto generateMipsForLayer = [this, &engine](TargetBufferInfo proto) {
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
// Wrap miplevel 0 in a render target so that we can use it as a blit source.
|
||||
uint8_t level = 0;
|
||||
uint32_t srcw = mWidth;
|
||||
uint32_t srch = mHeight;
|
||||
proto.handle = mHandle;
|
||||
proto.level = level++;
|
||||
backend::Handle<backend::HwRenderTarget> srcrth = driver.createRenderTarget(
|
||||
TargetBufferFlags::COLOR, srcw, srch, mSampleCount, proto, {}, {});
|
||||
|
||||
// Perform a blit for all miplevels down to 1x1.
|
||||
backend::Handle<backend::HwRenderTarget> dstrth;
|
||||
do {
|
||||
uint32_t const dstw = std::max(srcw >> 1u, 1u);
|
||||
uint32_t const dsth = std::max(srch >> 1u, 1u);
|
||||
proto.level = level++;
|
||||
dstrth = driver.createRenderTarget(
|
||||
TargetBufferFlags::COLOR, dstw, dsth, mSampleCount, proto, {}, {});
|
||||
driver.blit(TargetBufferFlags::COLOR,
|
||||
dstrth, { 0, 0, dstw, dsth },
|
||||
srcrth, { 0, 0, srcw, srch },
|
||||
SamplerMagFilter::LINEAR);
|
||||
driver.destroyRenderTarget(srcrth);
|
||||
srcrth = dstrth;
|
||||
srcw = dstw;
|
||||
srch = dsth;
|
||||
} while ((srcw > 1 || srch > 1) && level < mLevelCount);
|
||||
driver.destroyRenderTarget(dstrth);
|
||||
};
|
||||
|
||||
switch (mTarget) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
generateMipsForLayer({});
|
||||
break;
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
UTILS_NOUNROLL
|
||||
for (uint16_t layer = 0, c = mDepth; layer < c; ++layer) {
|
||||
generateMipsForLayer({ .layer = layer });
|
||||
}
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
UTILS_NOUNROLL
|
||||
for (uint16_t layer = 0, c = mDepth * 6; layer < c; ++layer) {
|
||||
generateMipsForLayer({ .layer = layer });
|
||||
}
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
UTILS_NOUNROLL
|
||||
for (uint8_t face = 0; face < 6; ++face) {
|
||||
generateMipsForLayer({ .layer = face });
|
||||
}
|
||||
break;
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
// not mipmapable
|
||||
break;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
// TODO: handle SAMPLER_3D -- this can't be done with a 2D blit, this would require
|
||||
// a fragment shader
|
||||
slog.w << "Texture::generateMipmap does not support SAMPLER_3D yet on this h/w." << io::endl;
|
||||
break;
|
||||
}
|
||||
engine.getDriverApi().generateMipmaps(mHandle);
|
||||
}
|
||||
|
||||
bool FTexture::isTextureFormatSupported(FEngine& engine, InternalFormat format) noexcept {
|
||||
|
||||
@@ -313,6 +313,10 @@ void FView::prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableD
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(!lcm.isShadowCaster(li))) {
|
||||
// Because we early exit here, we need to make sure we mark the light as non-casting.
|
||||
// See `ShadowMapManager::updateSpotShadowMaps` for const_cast<> justification.
|
||||
const_cast<FScene::ShadowInfo&>(
|
||||
lightData.elementAt<FScene::SHADOW_INFO>(l)).castsShadows = false;
|
||||
continue; // doesn't cast shadows
|
||||
}
|
||||
|
||||
@@ -721,11 +725,22 @@ UTILS_NOINLINE
|
||||
});
|
||||
}
|
||||
|
||||
void FView::prepareUpscaler(float2 scale) const noexcept {
|
||||
void FView::prepareUpscaler(float2 scale,
|
||||
TemporalAntiAliasingOptions const& taaOptions,
|
||||
DynamicResolutionOptions const& dsrOptions) const noexcept {
|
||||
SYSTRACE_CALL();
|
||||
const float bias = (mDynamicResolution.quality >= QualityLevel::HIGH) ?
|
||||
std::log2(std::min(scale.x, scale.y)) : 0.0f;
|
||||
mPerViewUniforms.prepareLodBias(bias);
|
||||
float bias = 0.0f;
|
||||
float2 derivativesScale{ 1.0f };
|
||||
if (dsrOptions.enabled && dsrOptions.quality >= QualityLevel::HIGH) {
|
||||
bias = std::log2(std::min(scale.x, scale.y));
|
||||
}
|
||||
if (taaOptions.enabled) {
|
||||
bias += taaOptions.lodBias;
|
||||
if (taaOptions.upscaling) {
|
||||
derivativesScale = 0.5f;
|
||||
}
|
||||
}
|
||||
mPerViewUniforms.prepareLodBias(bias, derivativesScale);
|
||||
}
|
||||
|
||||
void FView::prepareCamera(FEngine& engine, const CameraInfo& cameraInfo) const noexcept {
|
||||
@@ -981,15 +996,15 @@ void FView::drainFrameHistory(FEngine& engine) noexcept {
|
||||
}
|
||||
|
||||
void FView::executePickingQueries(backend::DriverApi& driver,
|
||||
backend::RenderTargetHandle handle, float scale) noexcept {
|
||||
backend::RenderTargetHandle handle, float2 scale) noexcept {
|
||||
|
||||
while (mActivePickingQueriesList) {
|
||||
FPickingQuery* const pQuery = mActivePickingQueriesList;
|
||||
mActivePickingQueriesList = pQuery->next;
|
||||
|
||||
// adjust for dynamic resolution and structure buffer scale
|
||||
const uint32_t x = uint32_t(float(pQuery->x) * (scale * mScale.x));
|
||||
const uint32_t y = uint32_t(float(pQuery->y) * (scale * mScale.y));
|
||||
const uint32_t x = uint32_t(float(pQuery->x) * scale.x);
|
||||
const uint32_t y = uint32_t(float(pQuery->y) * scale.y);
|
||||
|
||||
if (UTILS_UNLIKELY(driver.getFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)) {
|
||||
driver.readPixels(handle, x, y, 1, 1, {
|
||||
|
||||
@@ -133,7 +133,9 @@ public:
|
||||
return mName.c_str_safe();
|
||||
}
|
||||
|
||||
void prepareUpscaler(math::float2 scale) const noexcept;
|
||||
void prepareUpscaler(math::float2 scale,
|
||||
TemporalAntiAliasingOptions const& taaOptions,
|
||||
DynamicResolutionOptions const& dsrOptions) const noexcept;
|
||||
void prepareCamera(FEngine& engine, const CameraInfo& cameraInfo) const noexcept;
|
||||
|
||||
void prepareViewport(
|
||||
@@ -417,7 +419,7 @@ public:
|
||||
View::PickingQueryResultCallback callback) noexcept;
|
||||
|
||||
void executePickingQueries(backend::DriverApi& driver,
|
||||
backend::RenderTargetHandle handle, float scale) noexcept;
|
||||
backend::RenderTargetHandle handle, math::float2 scale) noexcept;
|
||||
|
||||
void setMaterialGlobal(uint32_t index, math::float4 const& value);
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ void FrameGraph::execute(backend::DriverApi& driver) noexcept {
|
||||
}
|
||||
|
||||
// call execute
|
||||
FrameGraphResources resources(*this, *node);
|
||||
FrameGraphResources const resources(*this, *node);
|
||||
node->execute(resources, driver);
|
||||
|
||||
// destroy concrete resources
|
||||
|
||||
@@ -229,6 +229,11 @@ void RenderPassNode::resolve() noexcept {
|
||||
rt.descriptor.samples = pImportedRenderTarget->importedDesc.samples;
|
||||
rt.backend.target = pImportedRenderTarget->target;
|
||||
|
||||
// We could end-up here more than once, for instance if the rendertarget is used
|
||||
// by multiple passes (this would imply a read-back, btw). In this case, we don't want
|
||||
// to clear it the 2nd time, so we clear the imported pass's clear flags.
|
||||
pImportedRenderTarget->importedDesc.clearFlags = TargetBufferFlags::NONE;
|
||||
|
||||
// but don't discard attachments the imported target tells us to keep
|
||||
rt.backend.params.flags.discardStart &= ~pImportedRenderTarget->importedDesc.keepOverrideStart;
|
||||
rt.backend.params.flags.discardEnd &= ~pImportedRenderTarget->importedDesc.keepOverrideEnd;
|
||||
|
||||
@@ -24,10 +24,57 @@ material {
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float[9],
|
||||
type : float2,
|
||||
name : jitter,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float4[9],
|
||||
name : filterWeights
|
||||
}
|
||||
],
|
||||
constants : [
|
||||
{
|
||||
type : bool,
|
||||
name : historyReprojection,
|
||||
default : true
|
||||
},
|
||||
{
|
||||
type : bool,
|
||||
name : filterHistory,
|
||||
default : true
|
||||
},
|
||||
{
|
||||
type : bool,
|
||||
name : filterInput,
|
||||
default : true
|
||||
},
|
||||
{
|
||||
type : int,
|
||||
name : boxClipping,
|
||||
default : 0
|
||||
},
|
||||
{
|
||||
type : int,
|
||||
name : boxType,
|
||||
default : 1
|
||||
},
|
||||
{
|
||||
type : bool,
|
||||
name : useYCoCg,
|
||||
default : false
|
||||
},
|
||||
{
|
||||
type : bool,
|
||||
name : preventFlickering,
|
||||
default : false
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : varianceGamma,
|
||||
default : 1.0
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
vertex
|
||||
],
|
||||
@@ -44,14 +91,6 @@ vertex {
|
||||
|
||||
fragment {
|
||||
|
||||
#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT)
|
||||
#define TEXTURE_SPACE_UP -1
|
||||
#define TEXTURE_SPACE_DN 1
|
||||
#else
|
||||
#define TEXTURE_SPACE_UP 1
|
||||
#define TEXTURE_SPACE_DN -1
|
||||
#endif
|
||||
|
||||
/* Clipping box type */
|
||||
|
||||
// min/max neighborhood
|
||||
@@ -61,9 +100,6 @@ fragment {
|
||||
// uses both min/max and variance
|
||||
#define BOX_TYPE_AABB_VARIANCE 2
|
||||
|
||||
// High VARIANCE_GAMMA [0.75, 1.25] increases ghosting artefact, lower values increases jittering
|
||||
#define VARIANCE_GAMMA 1.0
|
||||
|
||||
/* Clipping algorithm */
|
||||
|
||||
// accurate box clipping
|
||||
@@ -73,34 +109,12 @@ fragment {
|
||||
// no clipping (for debugging only)
|
||||
#define BOX_CLIPPING_NONE 2
|
||||
|
||||
/* Configuration mobile/desktop */
|
||||
|
||||
#if FILAMENT_QUALITY < FILAMENT_QUALITY_HIGH
|
||||
#define BOX_CLIPPING BOX_CLIPPING_ACCURATE
|
||||
#define BOX_TYPE BOX_TYPE_VARIANCE
|
||||
#define USE_YCoCg 0
|
||||
#define FILTER_INPUT 1
|
||||
#define FILTER_HISTORY 0
|
||||
#else
|
||||
#define BOX_CLIPPING BOX_CLIPPING_ACCURATE
|
||||
#define BOX_TYPE BOX_TYPE_AABB_VARIANCE
|
||||
#define USE_YCoCg 1
|
||||
#define FILTER_INPUT 1
|
||||
#define FILTER_HISTORY 1
|
||||
#endif
|
||||
|
||||
/* debugging helper */
|
||||
|
||||
#define HISTORY_REPROJECTION 1
|
||||
#define PREVENT_FLICKERING 0 // FIXME: thin lines disapear
|
||||
|
||||
|
||||
float luma(const vec3 color) {
|
||||
#if USE_YCoCg
|
||||
return color.x;
|
||||
#else
|
||||
return luminance(color);
|
||||
#endif
|
||||
if (materialConstants_useYCoCg) {
|
||||
return color.x;
|
||||
} else {
|
||||
return luminance(color);
|
||||
}
|
||||
}
|
||||
|
||||
vec3 RGB_YCoCg(const vec3 c) {
|
||||
@@ -194,108 +208,136 @@ vec4 sampleTextureCatmullRom(const sampler2D tex, const highp vec2 uv, const hig
|
||||
|
||||
result *= 1.0 / (k0 + k1 + k2 + k3 + k4);
|
||||
|
||||
// we could end-up with negative values
|
||||
result = max(vec4(0), result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
highp vec4 uv = variable_vertex.xyxy; // interpolated to pixel center
|
||||
|
||||
// read the depth buffer center sample for reprojection
|
||||
float depth = textureLod(materialParams_depth, uv.xy, 0.0).r;
|
||||
|
||||
#if HISTORY_REPROJECTION
|
||||
uv.zw = uvToRenderTargetUV(uv.zw);
|
||||
// reproject history to current frame
|
||||
highp vec4 q = materialParams.reprojection * vec4(uv.zw, depth, 1.0);
|
||||
uv.zw = (q.xy * (1.0 / q.w)) * 0.5 + 0.5;
|
||||
uv.zw = uvToRenderTargetUV(uv.zw);
|
||||
#endif
|
||||
if (materialConstants_historyReprojection) {
|
||||
// read the depth buffer center sample for reprojection
|
||||
highp float depth = textureLod(materialParams_depth, uv.xy, 0.0).r;
|
||||
// reproject history to current frame
|
||||
uv.zw = uvToRenderTargetUV(uv.zw);
|
||||
highp vec4 q = materialParams.reprojection * vec4(uv.zw, depth, 1.0);
|
||||
uv.zw = (q.xy * (1.0 / q.w)) * 0.5 + 0.5;
|
||||
uv.zw = uvToRenderTargetUV(uv.zw);
|
||||
}
|
||||
|
||||
// read center color and history samples
|
||||
vec4 color = textureLod(materialParams_color, uv.xy, 0.0);
|
||||
#if FILTER_HISTORY
|
||||
vec4 history = sampleTextureCatmullRom(materialParams_history, uv.zw,
|
||||
vec2(textureSize(materialParams_history, 0)));
|
||||
#else
|
||||
vec4 history = textureLod(materialParams_history, uv.zw, 0.0);
|
||||
#endif
|
||||
vec4 history;
|
||||
if (materialConstants_filterHistory) {
|
||||
history = sampleTextureCatmullRom(materialParams_history, uv.zw,
|
||||
vec2(textureSize(materialParams_history, 0)));
|
||||
} else {
|
||||
history = textureLod(materialParams_history, uv.zw, 0.0);
|
||||
}
|
||||
|
||||
#if USE_YCoCg
|
||||
history.rgb = RGB_YCoCg(history.rgb);
|
||||
#endif
|
||||
if (materialConstants_useYCoCg) {
|
||||
history.rgb = RGB_YCoCg(history.rgb);
|
||||
}
|
||||
|
||||
highp vec2 size = vec2(textureSize(materialParams_color, 0));
|
||||
highp vec2 p = (floor(uv.xy * size) + 0.5) / size;
|
||||
vec4 color = textureLod(materialParams_color, p, 0.0);
|
||||
|
||||
vec3 s[9];
|
||||
if (materialConstants_filterInput || materialConstants_boxClipping != BOX_CLIPPING_NONE) {
|
||||
s[0] = textureLodOffset(materialParams_color, p, 0.0, ivec2(-1, -1)).rgb;
|
||||
s[1] = textureLodOffset(materialParams_color, p, 0.0, ivec2( 0, -1)).rgb;
|
||||
s[2] = textureLodOffset(materialParams_color, p, 0.0, ivec2( 1, -1)).rgb;
|
||||
s[3] = textureLodOffset(materialParams_color, p, 0.0, ivec2(-1, 0)).rgb;
|
||||
s[4] = color.rgb;
|
||||
s[5] = textureLodOffset(materialParams_color, p, 0.0, ivec2( 1, 0)).rgb;
|
||||
s[6] = textureLodOffset(materialParams_color, p, 0.0, ivec2(-1, 1)).rgb;
|
||||
s[7] = textureLodOffset(materialParams_color, p, 0.0, ivec2( 0, 1)).rgb;
|
||||
s[8] = textureLodOffset(materialParams_color, p, 0.0, ivec2( 1, 1)).rgb;
|
||||
}
|
||||
|
||||
if (materialConstants_useYCoCg) {
|
||||
color.rgb = RGB_YCoCg(color.rgb);
|
||||
if (materialConstants_filterInput || materialConstants_boxClipping != BOX_CLIPPING_NONE) {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
s[i] = RGB_YCoCg(s[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vec2 subPixelOffset = p - uv.xy; // +/- [0.25, 0.25]
|
||||
float confidence = 0.0;
|
||||
|
||||
vec4 filtered = color;
|
||||
if (materialConstants_filterInput) {
|
||||
// unjitter/filter input
|
||||
// figure out which set of coeficients to use
|
||||
int jxp = subPixelOffset.y > 0.0 ? 3 : 0;
|
||||
int jxn = subPixelOffset.y > 0.0 ? 2 : 1;
|
||||
int j = subPixelOffset.x > 0.0 ? jxp : jxn;
|
||||
filtered = vec4(0, 0, 0, color.a);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
float w = materialParams.filterWeights[i][j];
|
||||
filtered.rgb += s[i] * w;
|
||||
confidence = max(confidence, w);
|
||||
}
|
||||
} else {
|
||||
confidence = float(materialParams.jitter.x * subPixelOffset.x > 0.0 &&
|
||||
materialParams.jitter.y * subPixelOffset.y > 0.0);
|
||||
}
|
||||
|
||||
// build the history clamping box
|
||||
vec3 s[9];
|
||||
s[0] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2(-1, TEXTURE_SPACE_DN)).rgb;
|
||||
s[1] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2( 0, TEXTURE_SPACE_DN)).rgb;
|
||||
s[2] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2( 1, TEXTURE_SPACE_DN)).rgb;
|
||||
s[3] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2(-1, 0)).rgb;
|
||||
s[4] = color.rgb;
|
||||
s[5] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2( 1, 0)).rgb;
|
||||
s[6] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2(-1, TEXTURE_SPACE_UP)).rgb;
|
||||
s[7] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2( 0, TEXTURE_SPACE_UP)).rgb;
|
||||
s[8] = textureLodOffset(materialParams_color, uv.xy, 0.0, ivec2( 1, TEXTURE_SPACE_UP)).rgb;
|
||||
if (materialConstants_boxClipping != BOX_CLIPPING_NONE) {
|
||||
vec3 boxmin;
|
||||
vec3 boxmax;
|
||||
if (materialConstants_boxType == BOX_TYPE_AABB ||
|
||||
materialConstants_boxType == BOX_TYPE_AABB_VARIANCE) {
|
||||
boxmin = min(s[4], min(min(s[1], s[3]), min(s[5], s[7])));
|
||||
boxmax = max(s[4], max(max(s[1], s[3]), max(s[5], s[7])));
|
||||
vec3 box9min = min(boxmin, min(min(s[0], s[2]), min(s[6], s[8])));
|
||||
vec3 box9max = max(boxmax, max(max(s[0], s[2]), max(s[6], s[8])));
|
||||
// round the corners of the 3x3 box
|
||||
boxmin = (boxmin + box9min) * 0.5;
|
||||
boxmax = (boxmax + box9max) * 0.5;
|
||||
}
|
||||
|
||||
#if USE_YCoCg
|
||||
for (int i = 0 ; i < 9 ; i++) {
|
||||
s[i] = RGB_YCoCg(s[i]);
|
||||
if (materialConstants_boxType == BOX_TYPE_VARIANCE ||
|
||||
materialConstants_boxType == BOX_TYPE_AABB_VARIANCE) {
|
||||
// "An Excursion in Temporal Supersampling" by Marco Salvi
|
||||
highp vec3 m0 = s[4];// conversion to highp
|
||||
highp vec3 m1 = m0 * m0;
|
||||
// we use only 5 samples instead of all 9
|
||||
for (int i = 1; i < 9; i+=2) {
|
||||
highp vec3 c = s[i];// conversion to highp
|
||||
m0 += c;
|
||||
m1 += c * c;
|
||||
}
|
||||
highp vec3 a0 = m0 * (1.0 / 5.0);
|
||||
highp vec3 a1 = m1 * (1.0 / 5.0);
|
||||
highp vec3 sigma = sqrt(a1 - a0 * a0);
|
||||
if (materialConstants_boxType == BOX_TYPE_VARIANCE) {
|
||||
boxmin = a0 - materialConstants_varianceGamma * sigma;
|
||||
boxmax = a0 + materialConstants_varianceGamma * sigma;
|
||||
} else {
|
||||
// intersect both bounding boxes
|
||||
boxmin = max(boxmin, a0 - materialConstants_varianceGamma * sigma);
|
||||
boxmax = min(boxmax, a0 + materialConstants_varianceGamma * sigma);
|
||||
}
|
||||
}
|
||||
// history clamping
|
||||
history = clipToBox(materialConstants_boxClipping, boxmin, boxmax, filtered, history);
|
||||
}
|
||||
color.rgb = s[4].rgb;
|
||||
#endif
|
||||
|
||||
#if FILTER_INPUT
|
||||
// unjitter/filter input
|
||||
vec4 filtered = vec4(0, 0, 0, color.a);
|
||||
for (int i = 0 ; i < 9 ; i++) {
|
||||
filtered.rgb += s[i] * materialParams.filterWeights[i];
|
||||
}
|
||||
#else
|
||||
vec4 filtered = color;
|
||||
#endif
|
||||
|
||||
#if BOX_TYPE == BOX_TYPE_AABB || BOX_TYPE == BOX_TYPE_AABB_VARIANCE
|
||||
vec3 boxmin = min(s[4], min(min(s[1], s[3]), min(s[5], s[7])));
|
||||
vec3 boxmax = max(s[4], max(max(s[1], s[3]), max(s[5], s[7])));
|
||||
vec3 box9min = min(boxmin, min(min(s[0], s[2]), min(s[6], s[8])));
|
||||
vec3 box9max = max(boxmax, max(max(s[0], s[2]), max(s[6], s[8])));
|
||||
// round the corners of the 3x3 box
|
||||
boxmin = (boxmin + box9min) * 0.5;
|
||||
boxmax = (boxmax + box9max) * 0.5;
|
||||
#endif
|
||||
|
||||
#if BOX_TYPE == BOX_TYPE_VARIANCE || BOX_TYPE == BOX_TYPE_AABB_VARIANCE
|
||||
// "An Excursion in Temporal Supersampling" by Marco Salvi
|
||||
vec3 m0 = s[4];
|
||||
vec3 m1 = s[4] * s[4];
|
||||
// we use only 5 samples instead of all 9
|
||||
for (int i = 1 ; i < 9 ; i+=2) {
|
||||
m0 += s[i];
|
||||
m1 += s[i] * s[i];
|
||||
}
|
||||
vec3 a0 = m0 * (1.0 / 5.0);
|
||||
vec3 a1 = m1 * (1.0 / 5.0);
|
||||
vec3 sigma = sqrt(a1 - a0 * a0);
|
||||
#if BOX_TYPE == BOX_TYPE_VARIANCE
|
||||
vec3 boxmin = a0 - VARIANCE_GAMMA * sigma;
|
||||
vec3 boxmax = a0 + VARIANCE_GAMMA * sigma;
|
||||
#else
|
||||
boxmin = min(boxmin, a0 - VARIANCE_GAMMA * sigma);
|
||||
boxmax = max(boxmax, a0 + VARIANCE_GAMMA * sigma);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// history clamping
|
||||
history = clipToBox(BOX_CLIPPING, boxmin, boxmax, filtered, history);
|
||||
|
||||
float lumaColor = luma(filtered.rgb);
|
||||
float lumaHistory = luma(history.rgb);
|
||||
|
||||
float alpha = materialParams.alpha;
|
||||
#if PREVENT_FLICKERING
|
||||
// [Lottes] prevents flickering by modulating the blend weight by the difference in luma
|
||||
float diff = 1.0 - abs(lumaColor - lumaHistory) / (0.001 + max(lumaColor, lumaHistory));
|
||||
alpha *= diff * diff;
|
||||
#endif
|
||||
float alpha = materialParams.alpha * confidence;
|
||||
if (materialConstants_preventFlickering) {
|
||||
// [Lottes] prevents flickering by modulating the blend weight by the difference in luma
|
||||
float diff = 1.0 - abs(lumaColor - lumaHistory) / (0.001 + max(lumaColor, lumaHistory));
|
||||
alpha *= diff * diff;
|
||||
}
|
||||
|
||||
// tonemapping for handling HDR
|
||||
filtered.rgb *= 1.0 / (1.0 + lumaColor);
|
||||
@@ -307,20 +349,16 @@ void postProcess(inout PostProcessInputs postProcess) {
|
||||
// untonemap result
|
||||
result.rgb *= 1.0 / (1.0 - luma(result.rgb));
|
||||
|
||||
#if USE_YCoCg
|
||||
result.rgb = YCoCg_RGB(result.rgb);
|
||||
#endif
|
||||
|
||||
// store result (which will becomes new history)
|
||||
// we could end-up with negative values due to the bicubic filter, which never recover on
|
||||
// their own.
|
||||
result = max(vec4(0), result);
|
||||
if (materialConstants_useYCoCg) {
|
||||
result.rgb = YCoCg_RGB(result.rgb);
|
||||
}
|
||||
|
||||
#if POST_PROCESS_OPAQUE
|
||||
// kill the work performed above
|
||||
result.a = 1.0;
|
||||
#endif
|
||||
|
||||
// store result (which will becomes new history)
|
||||
postProcess.color = result;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
material {
|
||||
name : resolveDepth,
|
||||
parameters : [
|
||||
{
|
||||
type : sampler2d,
|
||||
name : depth,
|
||||
precision: high,
|
||||
multisample : true
|
||||
}
|
||||
],
|
||||
outputs : [
|
||||
{
|
||||
name : depth,
|
||||
target : depth,
|
||||
type : float
|
||||
}
|
||||
],
|
||||
domain : postprocess,
|
||||
depthWrite : true,
|
||||
depthCulling : false,
|
||||
culling: none
|
||||
}
|
||||
|
||||
fragment {
|
||||
// resolve depth by keeping the first sample. This is similar to what glBlitFramebuffer() does
|
||||
// this material is currently only used on Metal and Vulkan.
|
||||
// Technically it should be feature-level 2 material, but that would restrict when we can use
|
||||
// it because Vulkan can return feature-level 1 currently (we should fix that).
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
highp ivec2 icoord = ivec2(gl_FragCoord.xy);
|
||||
postProcess.depth = texelFetch(materialParams_depth, icoord, 0).r;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.49.0"
|
||||
spec.version = "1.49.3"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.49.0/filament-v1.49.0-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.49.3/filament-v1.49.3-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#include <utils/Entity.h>
|
||||
|
||||
namespace filament {
|
||||
class Engine;
|
||||
class Scene;
|
||||
@@ -76,6 +78,17 @@ typedef NSData* _Nonnull (^ResourceCallback)(NSString* _Nonnull);
|
||||
|
||||
- (void)destroyModel;
|
||||
|
||||
/**
|
||||
* Issues a pick query at the given view coordinates.
|
||||
* The coordinates should be in UIKit's coordinate system, with the origin at
|
||||
* the top-left.
|
||||
* The callback is triggered with entity of the picked object.
|
||||
*/
|
||||
typedef void (^PickCallback)(utils::Entity);
|
||||
- (void)issuePickQuery:(CGPoint)point callback:(PickCallback)callback;
|
||||
|
||||
- (NSString* _Nullable)getEntityName:(utils::Entity)entity;
|
||||
|
||||
/**
|
||||
* Sets up a root transform on the current model to make it fit into a unit cube.
|
||||
*/
|
||||
|
||||
@@ -127,13 +127,12 @@ const float kSensitivity = 100.0f;
|
||||
|
||||
_swapChain = _engine->createSwapChain((__bridge void*)self.layer);
|
||||
|
||||
_materialProvider = createUbershaderProvider(_engine,
|
||||
UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE);
|
||||
_materialProvider =
|
||||
createUbershaderProvider(_engine, UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE);
|
||||
EntityManager& em = EntityManager::get();
|
||||
NameComponentManager* ncm = new NameComponentManager(em);
|
||||
_assetLoader = AssetLoader::create({_engine, _materialProvider, ncm, &em});
|
||||
_resourceLoader = new ResourceLoader(
|
||||
{.engine = _engine, .normalizeSkinningWeights = true});
|
||||
_resourceLoader = new ResourceLoader({.engine = _engine, .normalizeSkinningWeights = true});
|
||||
_stbDecoder = createStbProvider(_engine);
|
||||
_ktxDecoder = createKtx2Provider(_engine);
|
||||
_resourceLoader->addTextureProvider("image/png", _stbDecoder);
|
||||
@@ -182,6 +181,26 @@ const float kSensitivity = 100.0f;
|
||||
_animator = nullptr;
|
||||
}
|
||||
|
||||
- (void)issuePickQuery:(CGPoint)point callback:(PickCallback)callback {
|
||||
CGPoint pointOriginBottomLeft = CGPointMake(point.x, self.bounds.size.height - point.y);
|
||||
CGPoint pointScaled = CGPointMake(pointOriginBottomLeft.x * self.contentScaleFactor,
|
||||
pointOriginBottomLeft.y * self.contentScaleFactor);
|
||||
_view->pick(pointScaled.x, pointScaled.y,
|
||||
[callback](View::PickingQueryResult const& result) { callback(result.renderable); });
|
||||
}
|
||||
|
||||
- (NSString* _Nullable)getEntityName:(utils::Entity)entity {
|
||||
if (!_asset) {
|
||||
return nil;
|
||||
}
|
||||
NameComponentManager* ncm = _assetLoader->getNames();
|
||||
NameComponentManager::Instance instance = ncm->getInstance(entity);
|
||||
if (instance) {
|
||||
return [NSString stringWithUTF8String:ncm->getName(ncm->getInstance(entity))];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)transformToUnitCube {
|
||||
if (!_asset) {
|
||||
return;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#import "FILViewController.h"
|
||||
#include <UIKit/UIKit.h>
|
||||
|
||||
#import "FILModelView.h"
|
||||
|
||||
@@ -35,6 +36,9 @@ using namespace filament;
|
||||
using namespace utils;
|
||||
using namespace ktxreader;
|
||||
|
||||
const float kToastAnimationDuration = 0.25f;
|
||||
const float kToastDelayDuration = 2.0f;
|
||||
|
||||
@interface FILViewController ()
|
||||
|
||||
- (void)startDisplayLink;
|
||||
@@ -43,6 +47,9 @@ using namespace ktxreader;
|
||||
- (void)createRenderables;
|
||||
- (void)createLights;
|
||||
|
||||
- (void)appWillResignActive:(NSNotification*)notification;
|
||||
- (void)appDidBecomeActive:(NSNotification*)notification;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FILViewController {
|
||||
@@ -50,6 +57,7 @@ using namespace ktxreader;
|
||||
CFTimeInterval _startTime;
|
||||
viewer::RemoteServer* _server;
|
||||
viewer::AutomationEngine* _automation;
|
||||
UILabel* _toastLabel;
|
||||
|
||||
Texture* _skyboxTexture;
|
||||
Skybox* _skybox;
|
||||
@@ -57,6 +65,7 @@ using namespace ktxreader;
|
||||
IndirectLight* _indirectLight;
|
||||
Entity _sun;
|
||||
|
||||
UITapGestureRecognizer* _singleTapRecognizer;
|
||||
UITapGestureRecognizer* _doubleTapRecognizer;
|
||||
}
|
||||
|
||||
@@ -67,6 +76,16 @@ using namespace ktxreader;
|
||||
|
||||
self.title = @"https://google.github.io/filament/remote";
|
||||
|
||||
// Observe lifecycle notifications to prevent us from rendering in the background.
|
||||
[NSNotificationCenter.defaultCenter addObserver:self
|
||||
selector:@selector(appWillResignActive:)
|
||||
name:UIApplicationWillResignActiveNotification
|
||||
object:nil];
|
||||
[NSNotificationCenter.defaultCenter addObserver:self
|
||||
selector:@selector(appDidBecomeActive:)
|
||||
name:UIApplicationDidBecomeActiveNotification
|
||||
object:nil];
|
||||
|
||||
// Arguments:
|
||||
// --model <path>
|
||||
// path to glb or gltf file to load from documents directory
|
||||
@@ -94,9 +113,37 @@ using namespace ktxreader;
|
||||
_server = new viewer::RemoteServer();
|
||||
_automation = viewer::AutomationEngine::createDefault();
|
||||
|
||||
_doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(reloadModel)];
|
||||
_doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(reloadModel)];
|
||||
_doubleTapRecognizer.numberOfTapsRequired = 2;
|
||||
[self.modelView addGestureRecognizer:_doubleTapRecognizer];
|
||||
_singleTapRecognizer =
|
||||
[[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(issuePickingQuery)];
|
||||
_singleTapRecognizer.numberOfTapsRequired = 1;
|
||||
[self.modelView addGestureRecognizer:_singleTapRecognizer];
|
||||
|
||||
// Create a label at the top of the screen to toast messages to the user.
|
||||
CGRect labelRect = self.view.bounds;
|
||||
labelRect.size.height = 50;
|
||||
_toastLabel = [[UILabel alloc] initWithFrame:labelRect];
|
||||
_toastLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||
_toastLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_toastLabel.textColor = [UIColor whiteColor];
|
||||
_toastLabel.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.5f];
|
||||
_toastLabel.numberOfLines = 0;
|
||||
_toastLabel.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
_toastLabel.text = @"";
|
||||
_toastLabel.alpha = 0.0f;
|
||||
[self.view addSubview:_toastLabel];
|
||||
}
|
||||
|
||||
- (void)appWillResignActive:(NSNotification*)notification {
|
||||
[self stopDisplayLink];
|
||||
}
|
||||
|
||||
- (void)appDidBecomeActive:(NSNotification*)notification {
|
||||
[self startDisplayLink];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
@@ -223,7 +270,8 @@ using namespace ktxreader;
|
||||
.indirectLight = _indirectLight,
|
||||
.sunlight = _sun,
|
||||
};
|
||||
_automation->applySettings(self.modelView.engine, message->buffer, message->bufferByteCount, content);
|
||||
_automation->applySettings(
|
||||
self.modelView.engine, message->buffer, message->bufferByteCount, content);
|
||||
ColorGrading* const colorGrading = _automation->getColorGrading(self.modelView.engine);
|
||||
self.modelView.view->setColorGrading(colorGrading);
|
||||
self.modelView.cameraFocalLength = _automation->getViewerOptions().cameraFocalLength;
|
||||
@@ -268,7 +316,42 @@ using namespace ktxreader;
|
||||
[self createDefaultRenderables];
|
||||
}
|
||||
|
||||
- (void)issuePickingQuery {
|
||||
CGPoint tapLocation = [_singleTapRecognizer locationInView:self.modelView];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.modelView issuePickQuery:tapLocation
|
||||
callback:^(utils::Entity entity) {
|
||||
NSString* name = [self.modelView getEntityName:entity];
|
||||
if (!name) {
|
||||
name = @"<unnamed>";
|
||||
}
|
||||
NSString* message = [NSString
|
||||
stringWithFormat:@"Picked entity %d (%@) at (%d,%d)",
|
||||
entity.getId(), name, int(tapLocation.x), int(tapLocation.y)];
|
||||
[weakSelf toastMessage:message];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)toastMessage:(NSString*)message {
|
||||
_toastLabel.text = message;
|
||||
_toastLabel.alpha = 0.0f;
|
||||
[UIView animateWithDuration:kToastAnimationDuration
|
||||
animations:^{
|
||||
_toastLabel.alpha = 1.0f;
|
||||
}
|
||||
completion:^(BOOL finished) {
|
||||
[UIView animateWithDuration:kToastAnimationDuration
|
||||
delay:kToastDelayDuration
|
||||
options:UIViewAnimationOptionCurveEaseInOut
|
||||
animations:^{
|
||||
_toastLabel.alpha = 0.0f;
|
||||
}
|
||||
completion:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[NSNotificationCenter.defaultCenter removeObserver:self];
|
||||
delete _server;
|
||||
delete _automation;
|
||||
self.modelView.engine->destroy(_indirectLight);
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
|
||||
namespace filament {
|
||||
|
||||
static constexpr size_t POST_PROCESS_VARIANT_COUNT = 2;
|
||||
static constexpr size_t POST_PROCESS_VARIANT_BITS = 1;
|
||||
static constexpr size_t POST_PROCESS_VARIANT_COUNT = (1u << POST_PROCESS_VARIANT_BITS);
|
||||
static constexpr size_t POST_PROCESS_VARIANT_MASK = POST_PROCESS_VARIANT_COUNT - 1;
|
||||
enum class PostProcessVariant : uint8_t {
|
||||
OPAQUE,
|
||||
TRANSLUCENT
|
||||
@@ -135,11 +137,15 @@ template<>
|
||||
struct utils::EnableIntegerOperators<filament::SamplerBindingPoints> : public std::true_type {};
|
||||
template<>
|
||||
struct utils::EnableIntegerOperators<filament::ReservedSpecializationConstants> : public std::true_type {};
|
||||
template<>
|
||||
struct utils::EnableIntegerOperators<filament::PostProcessVariant> : public std::true_type {};
|
||||
|
||||
template<>
|
||||
inline constexpr size_t utils::Enum::count<filament::UniformBindingPoints>() { return 9; }
|
||||
template<>
|
||||
inline constexpr size_t utils::Enum::count<filament::SamplerBindingPoints>() { return 4; }
|
||||
template<>
|
||||
inline constexpr size_t utils::Enum::count<filament::PostProcessVariant>() { return filament::POST_PROCESS_VARIANT_COUNT; }
|
||||
|
||||
static_assert(utils::Enum::count<filament::UniformBindingPoints>() <= filament::backend::CONFIG_UNIFORM_BINDING_COUNT);
|
||||
static_assert(utils::Enum::count<filament::SamplerBindingPoints>() <= filament::backend::CONFIG_SAMPLER_BINDING_COUNT);
|
||||
|
||||
@@ -99,6 +99,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
|
||||
float lodBias; // load bias to apply to user materials
|
||||
float refractionLodOffset;
|
||||
math::float2 derivativesScale;
|
||||
|
||||
// camera position in view space (when camera_at_origin is enabled), i.e. it's (0,0,0).
|
||||
float oneOverFarMinusNear; // 1 / (f-n), always positive
|
||||
@@ -111,8 +112,6 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
// AO
|
||||
float aoSamplingQualityAndEdgeDistance; // <0: no AO, 0: bilinear, !0: bilateral edge distance
|
||||
float aoBentNormals; // 0: no AO bent normal, >0.0 AO bent normals
|
||||
float aoReserved0;
|
||||
float aoReserved1;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Dynamic Lighting [variant: DYN]
|
||||
|
||||
@@ -311,12 +311,8 @@ public:
|
||||
*/
|
||||
MaterialBuilder& parameter(const char* name, SamplerType samplerType,
|
||||
SamplerFormat format = SamplerFormat::FLOAT,
|
||||
ParameterPrecision precision = ParameterPrecision::DEFAULT) noexcept;
|
||||
|
||||
/// @copydoc parameter(SamplerType, SamplerFormat, ParameterPrecision, const char*)
|
||||
MaterialBuilder& parameter(const char* name, SamplerType samplerType,
|
||||
ParameterPrecision precision) noexcept;
|
||||
|
||||
ParameterPrecision precision = ParameterPrecision::DEFAULT,
|
||||
bool multisample = false) noexcept;
|
||||
|
||||
MaterialBuilder& buffer(filament::BufferInterfaceBlock bib) noexcept;
|
||||
|
||||
@@ -622,8 +618,8 @@ public:
|
||||
Parameter() noexcept: parameterType(INVALID) {}
|
||||
|
||||
// Sampler
|
||||
Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p)
|
||||
: name(paramName), size(1), precision(p), samplerType(t), format(f), parameterType(SAMPLER) { }
|
||||
Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p, bool ms)
|
||||
: name(paramName), size(1), precision(p), samplerType(t), format(f), parameterType(SAMPLER), multisample(ms) { }
|
||||
|
||||
// Uniform
|
||||
Parameter(const char* paramName, UniformType t, size_t typeSize, ParameterPrecision p)
|
||||
@@ -640,6 +636,7 @@ public:
|
||||
SamplerType samplerType;
|
||||
SubpassType subpassType;
|
||||
SamplerFormat format;
|
||||
bool multisample;
|
||||
enum {
|
||||
INVALID,
|
||||
UNIFORM,
|
||||
|
||||
@@ -234,7 +234,7 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob *spirv, std::string *outMsl,
|
||||
for (const auto& info: infoList) {
|
||||
const std::string name = info.uniformName.c_str();
|
||||
argBufferBuilder
|
||||
.texture(info.offset * 2, name, info.type, info.format)
|
||||
.texture(info.offset * 2, name, info.type, info.format, info.multisample)
|
||||
.sampler(info.offset * 2 + 1, name + "Smplr");
|
||||
}
|
||||
|
||||
|
||||
@@ -250,15 +250,18 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType type,
|
||||
|
||||
|
||||
MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType samplerType,
|
||||
SamplerFormat format, ParameterPrecision precision) noexcept {
|
||||
ASSERT_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT, "Too many parameters");
|
||||
mParameters[mParameterCount++] = { name, samplerType, format, precision };
|
||||
return *this;
|
||||
}
|
||||
SamplerFormat format, ParameterPrecision precision, bool multisample) noexcept {
|
||||
|
||||
MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType samplerType,
|
||||
ParameterPrecision precision) noexcept {
|
||||
return parameter(name, samplerType, SamplerFormat::FLOAT, precision);
|
||||
ASSERT_PRECONDITION(
|
||||
!multisample || (format != SamplerFormat::SHADOW && (
|
||||
samplerType == SamplerType::SAMPLER_2D
|
||||
|| samplerType == SamplerType::SAMPLER_2D_ARRAY)),
|
||||
"multisample samplers only possible with SAMPLER_2D or SAMPLER_2D_ARRAY,"
|
||||
" as long as type is not SHADOW");
|
||||
|
||||
ASSERT_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT, "Too many parameters");
|
||||
mParameters[mParameterCount++] = { name, samplerType, format, precision, multisample };
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, typename>
|
||||
@@ -556,7 +559,7 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
|
||||
assert_invariant(!param.isSubpass());
|
||||
if (param.isSampler()) {
|
||||
sbb.add({ param.name.data(), param.name.size() },
|
||||
param.samplerType, param.format, param.precision);
|
||||
param.samplerType, param.format, param.precision, param.multisample);
|
||||
} else if (param.isUniform()) {
|
||||
ibb.add({{{ param.name.data(), param.name.size() },
|
||||
uint32_t(param.size == 1u ? 0u : param.size), param.uniformType,
|
||||
@@ -1165,7 +1168,6 @@ error:
|
||||
mVariantFilter |= uint32_t(UserVariantFilterBit::SHADOW_RECEIVER);
|
||||
mVariantFilter |= uint32_t(UserVariantFilterBit::VSM);
|
||||
mVariantFilter |= uint32_t(UserVariantFilterBit::SSR);
|
||||
mVariantFilter |= uint32_t(UserVariantFilterBit::STE);
|
||||
}
|
||||
|
||||
// Create chunk tree.
|
||||
|
||||
@@ -29,11 +29,20 @@ MetalArgumentBuffer::Builder& filamat::MetalArgumentBuffer::Builder::name(
|
||||
|
||||
MetalArgumentBuffer::Builder& MetalArgumentBuffer::Builder::texture(size_t index,
|
||||
const std::string& name, filament::backend::SamplerType type,
|
||||
filament::backend::SamplerFormat format) noexcept {
|
||||
filament::backend::SamplerFormat format,
|
||||
bool multisample) noexcept {
|
||||
|
||||
using namespace filament::backend;
|
||||
|
||||
// All combinations of SamplerType and SamplerFormat are valid except for SAMPLER_3D / SHADOW.
|
||||
assert_invariant(type != filament::backend::SamplerType::SAMPLER_3D ||
|
||||
format != filament::backend::SamplerFormat::SHADOW);
|
||||
mArguments.emplace_back(TextureArgument { name, index, type, format });
|
||||
assert_invariant(type != SamplerType::SAMPLER_3D || format != SamplerFormat::SHADOW);
|
||||
|
||||
// multisample textures have restrictions too
|
||||
assert_invariant(!multisample || (
|
||||
format != SamplerFormat::SHADOW && (
|
||||
type == SamplerType::SAMPLER_2D || type == SamplerType::SAMPLER_2D_ARRAY)));
|
||||
|
||||
mArguments.emplace_back(TextureArgument { name, index, type, format, multisample });
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -79,6 +88,10 @@ std::ostream& MetalArgumentBuffer::Builder::TextureArgument::write(std::ostream&
|
||||
break;
|
||||
}
|
||||
|
||||
if (multisample) {
|
||||
os << "_ms";
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case filament::backend::SamplerFormat::INT:
|
||||
os << "<int>";
|
||||
|
||||
@@ -48,7 +48,8 @@ public:
|
||||
*/
|
||||
Builder& texture(size_t index, const std::string& name,
|
||||
filament::backend::SamplerType type,
|
||||
filament::backend::SamplerFormat format) noexcept;
|
||||
filament::backend::SamplerFormat format,
|
||||
bool multisample) noexcept;
|
||||
|
||||
/**
|
||||
* Add a sampler argument to the argument buffer structure.
|
||||
@@ -69,6 +70,7 @@ public:
|
||||
size_t index;
|
||||
filament::backend::SamplerType type;
|
||||
filament::backend::SamplerFormat format;
|
||||
bool multisample;
|
||||
|
||||
std::ostream& write(std::ostream& os) const;
|
||||
};
|
||||
|
||||
@@ -176,7 +176,7 @@ bool GLSLTools::analyzeComputeShader(const std::string& shaderCode,
|
||||
bool const ok = tShader.parse(&DefaultTBuiltInResource, version, false, msg);
|
||||
if (!ok) {
|
||||
utils::slog.e << "ERROR: Unable to parse compute shader:" << utils::io::endl;
|
||||
utils::slog.e << tShader.getInfoLog() << utils::io::flush;
|
||||
utils::slog.e << tShader.getInfoLog() << utils::io::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ std::optional<GLSLTools::FragmentShaderInfo> GLSLTools::analyzeFragmentShader(
|
||||
bool const ok = tShader.parse(&DefaultTBuiltInResource, version, false, msg);
|
||||
if (!ok) {
|
||||
utils::slog.e << "ERROR: Unable to parse fragment shader:" << utils::io::endl;
|
||||
utils::slog.e << tShader.getInfoLog() << utils::io::flush;
|
||||
utils::slog.e << tShader.getInfoLog() << utils::io::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ bool GLSLTools::analyzeVertexShader(const std::string& shaderCode,
|
||||
bool const ok = tShader.parse(&DefaultTBuiltInResource, version, false, msg);
|
||||
if (!ok) {
|
||||
utils::slog.e << "ERROR: Unable to parse vertex shader" << utils::io::endl;
|
||||
utils::slog.e << tShader.getInfoLog() << utils::io::flush;
|
||||
utils::slog.e << tShader.getInfoLog() << utils::io::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1150,13 +1150,12 @@ char const* CodeGenerator::getOutputTypeName(MaterialBuilder::OutputType type) n
|
||||
|
||||
char const* CodeGenerator::getSamplerTypeName(SamplerType type, SamplerFormat format,
|
||||
bool multisample) const noexcept {
|
||||
assert(!multisample); // multisample samplers not yet supported.
|
||||
switch (type) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
switch (format) {
|
||||
case SamplerFormat::INT: return "isampler2D";
|
||||
case SamplerFormat::UINT: return "usampler2D";
|
||||
case SamplerFormat::FLOAT: return "sampler2D";
|
||||
case SamplerFormat::INT: return multisample ? "isampler2DMS" : "isampler2D";
|
||||
case SamplerFormat::UINT: return multisample ? "usampler2DMS" : "usampler2D";
|
||||
case SamplerFormat::FLOAT: return multisample ? "sampler2DMS" : "sampler2D";
|
||||
case SamplerFormat::SHADOW: return "sampler2DShadow";
|
||||
}
|
||||
case SamplerType::SAMPLER_3D:
|
||||
@@ -1169,9 +1168,9 @@ char const* CodeGenerator::getSamplerTypeName(SamplerType type, SamplerFormat fo
|
||||
}
|
||||
case SamplerType::SAMPLER_2D_ARRAY:
|
||||
switch (format) {
|
||||
case SamplerFormat::INT: return "isampler2DArray";
|
||||
case SamplerFormat::UINT: return "usampler2DArray";
|
||||
case SamplerFormat::FLOAT: return "sampler2DArray";
|
||||
case SamplerFormat::INT: return multisample ? "isampler2DMSArray": "isampler2DArray";
|
||||
case SamplerFormat::UINT: return multisample ? "usampler2DMSArray": "usampler2DArray";
|
||||
case SamplerFormat::FLOAT: return multisample ? "sampler2DMSArray": "sampler2DArray";
|
||||
case SamplerFormat::SHADOW: return "sampler2DArrayShadow";
|
||||
}
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
|
||||
@@ -52,7 +52,7 @@ void ShaderGenerator::generateSurfaceMaterialVariantDefines(utils::io::sstream&
|
||||
CodeGenerator::generateDefine(out, "VARIANT_HAS_VSM",
|
||||
filament::Variant::isVSMVariant(variant));
|
||||
CodeGenerator::generateDefine(out, "VARIANT_HAS_INSTANCED_STEREO",
|
||||
filament::Variant::isStereoVariant(variant));
|
||||
hasInstancedStereo(variant, featureLevel));
|
||||
|
||||
switch (stage) {
|
||||
case ShaderStage::VERTEX:
|
||||
@@ -758,4 +758,12 @@ bool ShaderGenerator::hasSkinningOrMorphing(
|
||||
&& featureLevel > MaterialBuilder::FeatureLevel::FEATURE_LEVEL_0;
|
||||
}
|
||||
|
||||
bool ShaderGenerator::hasInstancedStereo(
|
||||
filament::Variant variant, MaterialBuilder::FeatureLevel featureLevel) noexcept {
|
||||
return variant.hasInstancedStereo()
|
||||
// HACK(exv): Ignore stereo variant when targeting ESSL 1.0. We should properly build a
|
||||
// system in matc which allows the set of included variants to differ per-feature level.
|
||||
&& featureLevel > MaterialBuilder::FeatureLevel::FEATURE_LEVEL_0;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -115,6 +115,10 @@ private:
|
||||
filament::Variant variant,
|
||||
MaterialBuilder::FeatureLevel featureLevel) noexcept;
|
||||
|
||||
static bool hasInstancedStereo(
|
||||
filament::Variant variant,
|
||||
MaterialBuilder::FeatureLevel featureLevel) noexcept;
|
||||
|
||||
MaterialBuilder::PropertyList mProperties;
|
||||
MaterialBuilder::VariableList mVariables;
|
||||
MaterialBuilder::OutputList mOutputs;
|
||||
|
||||
@@ -60,6 +60,7 @@ BufferInterfaceBlock const& UibGenerator::getPerViewUib() noexcept {
|
||||
|
||||
{ "lodBias", 0, Type::FLOAT, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 },
|
||||
{ "refractionLodOffset", 0, Type::FLOAT, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 },
|
||||
{ "derivativesScale", 0, Type::FLOAT2 },
|
||||
|
||||
{ "oneOverFarMinusNear", 0, Type::FLOAT, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 },
|
||||
{ "nearOverFarMinusNear", 0, Type::FLOAT, Precision::HIGH, FeatureLevel::FEATURE_LEVEL_0 },
|
||||
@@ -71,8 +72,6 @@ BufferInterfaceBlock const& UibGenerator::getPerViewUib() noexcept {
|
||||
// AO
|
||||
{ "aoSamplingQualityAndEdgeDistance", 0, Type::FLOAT },
|
||||
{ "aoBentNormals", 0, Type::FLOAT },
|
||||
{ "aoReserved0", 0, Type::FLOAT },
|
||||
{ "aoReserved1", 0, Type::FLOAT },
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Dynamic Lighting [variant: DYN]
|
||||
|
||||
@@ -72,7 +72,7 @@ TEST(ArgBufferFixup, TextureAndSampler) {
|
||||
auto argBuffer =
|
||||
MetalArgumentBuffer::Builder()
|
||||
.name("myArgumentBuffer")
|
||||
.texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT)
|
||||
.texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, false)
|
||||
.sampler(1, "samplerA")
|
||||
.build();
|
||||
auto argBufferStr = argBuffer->getMsl();
|
||||
@@ -88,13 +88,33 @@ TEST(ArgBufferFixup, TextureAndSampler) {
|
||||
MetalArgumentBuffer::destroy(&argBuffer);
|
||||
}
|
||||
|
||||
TEST(ArgBufferFixup, TextureAndSamplerMS) {
|
||||
auto argBuffer =
|
||||
MetalArgumentBuffer::Builder()
|
||||
.name("myArgumentBuffer")
|
||||
.texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, true)
|
||||
.sampler(1, "samplerA")
|
||||
.build();
|
||||
auto argBufferStr = argBuffer->getMsl();
|
||||
|
||||
const std::string expected =
|
||||
"struct myArgumentBuffer {\n"
|
||||
"texture2d_ms<float> textureA [[id(0)]];\n"
|
||||
"sampler samplerA [[id(1)]];\n"
|
||||
"}";
|
||||
|
||||
EXPECT_EQ(argBuffer->getMsl(), expected);
|
||||
|
||||
MetalArgumentBuffer::destroy(&argBuffer);
|
||||
}
|
||||
|
||||
TEST(ArgBufferFixup, Sorted) {
|
||||
auto argBuffer =
|
||||
MetalArgumentBuffer::Builder()
|
||||
.name("myArgumentBuffer")
|
||||
.sampler(3, "samplerB")
|
||||
.texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT)
|
||||
.texture(2, "textureB", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT)
|
||||
.texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, false)
|
||||
.texture(2, "textureB", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, false)
|
||||
.sampler(1, "samplerA")
|
||||
.build();
|
||||
auto argBufferStr = argBuffer->getMsl();
|
||||
@@ -116,12 +136,12 @@ TEST(ArgBufferFixup, TextureTypes) {
|
||||
auto argBuffer =
|
||||
MetalArgumentBuffer::Builder()
|
||||
.name("myArgumentBuffer")
|
||||
.texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::INT)
|
||||
.texture(1, "textureB", SamplerType::SAMPLER_2D_ARRAY, SamplerFormat::UINT)
|
||||
.texture(2, "textureC", SamplerType::SAMPLER_CUBEMAP, SamplerFormat::FLOAT)
|
||||
.texture(3, "textureD", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT)
|
||||
.texture(4, "textureE", SamplerType::SAMPLER_3D, SamplerFormat::FLOAT)
|
||||
.texture(5, "textureF", SamplerType::SAMPLER_CUBEMAP_ARRAY, SamplerFormat::SHADOW)
|
||||
.texture(0, "textureA", SamplerType::SAMPLER_2D, SamplerFormat::INT, false)
|
||||
.texture(1, "textureB", SamplerType::SAMPLER_2D_ARRAY, SamplerFormat::UINT, false)
|
||||
.texture(2, "textureC", SamplerType::SAMPLER_CUBEMAP, SamplerFormat::FLOAT, false)
|
||||
.texture(3, "textureD", SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT, false)
|
||||
.texture(4, "textureE", SamplerType::SAMPLER_3D, SamplerFormat::FLOAT, false)
|
||||
.texture(5, "textureF", SamplerType::SAMPLER_CUBEMAP_ARRAY, SamplerFormat::SHADOW, false)
|
||||
.build();
|
||||
auto argBufferStr = argBuffer->getMsl();
|
||||
|
||||
@@ -147,7 +167,7 @@ TEST(ArgBufferFixup, InvalidType) {
|
||||
auto argBuffer =
|
||||
MetalArgumentBuffer::Builder()
|
||||
.name("myArgumentBuffer")
|
||||
.texture(0, "textureA", SamplerType::SAMPLER_3D, SamplerFormat::SHADOW)
|
||||
.texture(0, "textureA", SamplerType::SAMPLER_3D, SamplerFormat::SHADOW, false)
|
||||
.build();
|
||||
MetalArgumentBuffer::destroy(&argBuffer);
|
||||
}, "failed assertion");
|
||||
|
||||
@@ -215,7 +215,7 @@ bool ApiHandler::handleGetApiShader(struct mg_connection* conn,
|
||||
}
|
||||
|
||||
void ApiHandler::addMaterial(MaterialRecord const* material) {
|
||||
std::unique_lock<utils::Mutex> lock(mStatusMutex);
|
||||
std::unique_lock const lock(mStatusMutex);
|
||||
snprintf(statusMaterialId, sizeof(statusMaterialId), "%8.8x", material->key);
|
||||
mStatusCondition.notify_all();
|
||||
}
|
||||
@@ -229,7 +229,7 @@ bool ApiHandler::handleGetStatus(struct mg_connection* conn,
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_lock<utils::Mutex> lock(mStatusMutex);
|
||||
std::unique_lock<std::mutex> lock(mStatusMutex);
|
||||
uint64_t const currentStatusCount = mCurrentStatus;
|
||||
if (mStatusCondition.wait_for(lock, 10s,
|
||||
[this, currentStatusCount] { return currentStatusCount < mCurrentStatus; })) {
|
||||
@@ -258,13 +258,13 @@ bool ApiHandler::handlePost(CivetServer* server, struct mg_connection* conn) {
|
||||
// [material id] [api index] [shader index] [shader source....]
|
||||
if (uri == "/api/edit") {
|
||||
struct mg_request_info const* req_info = mg_get_request_info(conn);
|
||||
size_t msgLen = req_info->content_length;
|
||||
size_t const msgLen = req_info->content_length;
|
||||
|
||||
char buf[1024];
|
||||
size_t readLen = 0;
|
||||
std::stringstream sstream;
|
||||
while (readLen < msgLen) {
|
||||
int res = mg_read(conn, buf, sizeof(buf));
|
||||
int const res = mg_read(conn, buf, sizeof(buf));
|
||||
if (res < 0) {
|
||||
utils::slog.e << "civet error parsing /api/edit body: " << res << utils::io::endl;
|
||||
break;
|
||||
@@ -279,7 +279,7 @@ bool ApiHandler::handlePost(CivetServer* server, struct mg_connection* conn) {
|
||||
int api;
|
||||
int shaderIndex;
|
||||
sstream >> std::hex >> matid >> std::dec >> api >> shaderIndex;
|
||||
std::string shader = sstream.str().substr(sstream.tellg());
|
||||
std::string const shader = sstream.str().substr(sstream.tellg());
|
||||
|
||||
mServer->handleEditCommand(matid, backend::Backend(api), shaderIndex, shader.c_str(),
|
||||
shader.size());
|
||||
@@ -298,7 +298,7 @@ bool ApiHandler::handleGet(CivetServer* server, struct mg_connection* conn) {
|
||||
mServer->updateActiveVariants();
|
||||
|
||||
// Careful not to lock the above line.
|
||||
std::unique_lock<utils::Mutex> lock(mServer->mMaterialRecordsMutex);
|
||||
std::unique_lock const lock(mServer->mMaterialRecordsMutex);
|
||||
mg_printf(conn, kSuccessHeader.data(), "application/json");
|
||||
mg_printf(conn, "{");
|
||||
|
||||
@@ -329,7 +329,7 @@ bool ApiHandler::handleGet(CivetServer* server, struct mg_connection* conn) {
|
||||
}
|
||||
|
||||
if (uri == "/api/matids") {
|
||||
std::unique_lock<utils::Mutex> lock(mServer->mMaterialRecordsMutex);
|
||||
std::unique_lock const lock(mServer->mMaterialRecordsMutex);
|
||||
mg_printf(conn, kSuccessHeader.data(), "application/json");
|
||||
mg_printf(conn, "[");
|
||||
int index = 0;
|
||||
@@ -342,7 +342,7 @@ bool ApiHandler::handleGet(CivetServer* server, struct mg_connection* conn) {
|
||||
}
|
||||
|
||||
if (uri == "/api/materials") {
|
||||
std::unique_lock<utils::Mutex> lock(mServer->mMaterialRecordsMutex);
|
||||
std::unique_lock const lock(mServer->mMaterialRecordsMutex);
|
||||
mg_printf(conn, kSuccessHeader.data(), "application/json");
|
||||
mg_printf(conn, "[");
|
||||
int index = 0;
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
#ifndef MATDBG_APIHANDLER_H
|
||||
#define MATDBG_APIHANDLER_H
|
||||
|
||||
#include <utils/Mutex.h>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <CivetServer.h>
|
||||
|
||||
@@ -53,7 +54,7 @@ private:
|
||||
|
||||
DebugServer* mServer;
|
||||
|
||||
utils::Mutex mStatusMutex;
|
||||
std::mutex mStatusMutex;
|
||||
std::condition_variable mStatusCondition;
|
||||
char statusMaterialId[9] = {};
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
margin: 0;
|
||||
font-family: "Open Sans";
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: codicon;
|
||||
src: url(https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/vs/base/browser/ui/codicons/codicon/codicon.ttf);
|
||||
}
|
||||
</style>
|
||||
<script src="api.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/vs/loader.js"></script>
|
||||
|
||||
@@ -35,7 +35,7 @@ struct hashCStrings {
|
||||
typedef size_t result_type;
|
||||
result_type operator()(argument_type cstr) const noexcept {
|
||||
size_t hash = 5381;
|
||||
while (int c = *cstr++) {
|
||||
while (int const c = *cstr++) {
|
||||
hash = (hash * 33u) ^ size_t(c);
|
||||
}
|
||||
return hash;
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace utils::io {
|
||||
|
||||
struct ostream_;
|
||||
|
||||
class UTILS_PUBLIC ostream : protected utils::PrivateImplementation<ostream_> {
|
||||
class UTILS_PUBLIC ostream : protected utils::PrivateImplementation<ostream_> {
|
||||
friend struct ostream_;
|
||||
|
||||
public:
|
||||
@@ -69,6 +69,13 @@ public:
|
||||
ostream& dec() noexcept;
|
||||
ostream& hex() noexcept;
|
||||
|
||||
/*! @cond PRIVATE */
|
||||
// Sets a consumer of the log. The consumer is invoked on flush() and replaces the default.
|
||||
// Thread safe and reentrant.
|
||||
using ConsumerCallback = void(*)(void*, char const*);
|
||||
void setConsumer(ConsumerCallback consumer, void* user) noexcept;
|
||||
/*! @endcond */
|
||||
|
||||
protected:
|
||||
ostream& print(const char* format, ...) noexcept;
|
||||
|
||||
@@ -104,7 +111,7 @@ private:
|
||||
friend ostream& hex(ostream& s) noexcept;
|
||||
friend ostream& dec(ostream& s) noexcept;
|
||||
friend ostream& endl(ostream& s) noexcept;
|
||||
friend ostream& flush(ostream& s) noexcept;
|
||||
UTILS_PUBLIC friend ostream& flush(ostream& s) noexcept;
|
||||
|
||||
enum type {
|
||||
SHORT, USHORT, CHAR, UCHAR, INT, UINT, LONG, ULONG, LONG_LONG, ULONG_LONG, FLOAT, DOUBLE,
|
||||
@@ -132,8 +139,7 @@ inline ostream& operator<<(ostream& stream, const VECTOR<T>& v) {
|
||||
|
||||
inline ostream& hex(ostream& s) noexcept { return s.hex(); }
|
||||
inline ostream& dec(ostream& s) noexcept { return s.dec(); }
|
||||
inline ostream& endl(ostream& s) noexcept { s << '\n'; return s.flush(); }
|
||||
inline ostream& flush(ostream& s) noexcept { return s.flush(); }
|
||||
inline ostream& endl(ostream& s) noexcept { return flush(s << '\n'); }
|
||||
|
||||
} // namespace utils::io
|
||||
|
||||
|
||||
+25
-25
@@ -31,6 +31,8 @@
|
||||
#include <emscripten/console.h>
|
||||
#endif
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace utils {
|
||||
namespace io {
|
||||
|
||||
@@ -46,43 +48,41 @@ public:
|
||||
ostream& flush() noexcept override;
|
||||
|
||||
private:
|
||||
Priority mPriority;
|
||||
const Priority mPriority;
|
||||
};
|
||||
|
||||
ostream& LogStream::flush() noexcept {
|
||||
std::lock_guard lock(mImpl->mLock);
|
||||
std::lock_guard const lock(mImpl->mLock);
|
||||
Buffer& buf = getBuffer();
|
||||
#ifdef __ANDROID__
|
||||
switch (mPriority) {
|
||||
case LOG_DEBUG:
|
||||
__android_log_write(ANDROID_LOG_DEBUG, UTILS_LOG_TAG, buf.get());
|
||||
break;
|
||||
case LOG_ERROR:
|
||||
__android_log_write(ANDROID_LOG_ERROR, UTILS_LOG_TAG, buf.get());
|
||||
break;
|
||||
case LOG_WARNING:
|
||||
__android_log_write(ANDROID_LOG_WARN, UTILS_LOG_TAG, buf.get());
|
||||
break;
|
||||
case LOG_INFO:
|
||||
__android_log_write(ANDROID_LOG_INFO, UTILS_LOG_TAG, buf.get());
|
||||
break;
|
||||
case LOG_VERBOSE:
|
||||
__android_log_write(ANDROID_LOG_VERBOSE, UTILS_LOG_TAG, buf.get());
|
||||
break;
|
||||
char const* const str = buf.get();
|
||||
if (UTILS_UNLIKELY(!str)) {
|
||||
// this can happen if the log hasn't been written ever
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef __ANDROID__
|
||||
int prio = ANDROID_LOG_UNKNOWN;
|
||||
switch (mPriority) {
|
||||
case LOG_DEBUG: prio = ANDROID_LOG_DEBUG; break;
|
||||
case LOG_ERROR: prio = ANDROID_LOG_ERROR; break;
|
||||
case LOG_WARNING: prio = ANDROID_LOG_WARN; break;
|
||||
case LOG_INFO: prio = ANDROID_LOG_INFO; break;
|
||||
case LOG_VERBOSE: prio = ANDROID_LOG_VERBOSE; break;
|
||||
}
|
||||
__android_log_write(prio, UTILS_LOG_TAG, str);
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
switch (mPriority) {
|
||||
case LOG_DEBUG:
|
||||
case LOG_WARNING:
|
||||
case LOG_INFO:
|
||||
_emscripten_out(buf.get());
|
||||
_emscripten_out(str);
|
||||
break;
|
||||
case LOG_ERROR:
|
||||
_emscripten_err(buf.get());
|
||||
_emscripten_err(str);
|
||||
break;
|
||||
case LOG_VERBOSE:
|
||||
#ifndef NFIL_DEBUG
|
||||
_emscripten_out(buf.get());
|
||||
_emscripten_out(str);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -91,14 +91,14 @@ ostream& LogStream::flush() noexcept {
|
||||
case LOG_DEBUG:
|
||||
case LOG_WARNING:
|
||||
case LOG_INFO:
|
||||
fprintf(stdout, "%s", buf.get());
|
||||
fprintf(stdout, "%s", str);
|
||||
break;
|
||||
case LOG_ERROR:
|
||||
fprintf(stderr, "%s", buf.get());
|
||||
fprintf(stderr, "%s", str);
|
||||
break;
|
||||
case LOG_VERBOSE:
|
||||
#ifndef NDEBUG
|
||||
fprintf(stdout, "%s", buf.get());
|
||||
fprintf(stdout, "%s", str);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -24,10 +24,14 @@
|
||||
#include <utils/PrivateImplementation-impl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
template class utils::PrivateImplementation<utils::io::ostream_>;
|
||||
|
||||
@@ -35,6 +39,34 @@ namespace utils::io {
|
||||
|
||||
ostream::~ostream() = default;
|
||||
|
||||
void ostream::setConsumer(ConsumerCallback consumer, void* user) noexcept {
|
||||
auto* const pImpl = mImpl;
|
||||
std::lock_guard const lock(pImpl->mLock);
|
||||
pImpl->mConsumer = { consumer, user };
|
||||
}
|
||||
|
||||
ostream& flush(ostream& s) noexcept {
|
||||
auto* const pImpl = s.mImpl;
|
||||
pImpl->mLock.lock();
|
||||
auto const callback = pImpl->mConsumer;
|
||||
if (UTILS_UNLIKELY(callback.first)) {
|
||||
auto& buf = s.getBuffer();
|
||||
char const* const data = buf.get();
|
||||
if (UTILS_LIKELY(data)) {
|
||||
char* const str = strdup(data);
|
||||
buf.reset();
|
||||
pImpl->mLock.unlock();
|
||||
// call ConsumerCallback without lock held
|
||||
callback.first(callback.second, str);
|
||||
::free(str);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
pImpl->mLock.unlock();
|
||||
|
||||
return s.flush();
|
||||
}
|
||||
|
||||
ostream::Buffer& ostream::getBuffer() noexcept {
|
||||
return mImpl->mData;
|
||||
}
|
||||
@@ -69,12 +101,12 @@ ostream& ostream::print(const char* format, ...) noexcept {
|
||||
// figure out how much size to we need
|
||||
va_start(args0, format);
|
||||
va_copy(args1, args0);
|
||||
ssize_t s = vsnprintf(nullptr, 0, format, args0);
|
||||
ssize_t const s = vsnprintf(nullptr, 0, format, args0);
|
||||
va_end(args0);
|
||||
|
||||
|
||||
{ // scope for the lock
|
||||
std::lock_guard lock(mImpl->mLock);
|
||||
std::lock_guard const lock(mImpl->mLock);
|
||||
|
||||
Buffer& buf = getBuffer();
|
||||
|
||||
@@ -205,14 +237,14 @@ ostream::Buffer::~Buffer() noexcept {
|
||||
|
||||
void ostream::Buffer::advance(ssize_t n) noexcept {
|
||||
if (n > 0) {
|
||||
size_t written = n < size ? size_t(n) : size;
|
||||
size_t const written = n < size ? size_t(n) : size;
|
||||
curr += written;
|
||||
size -= written;
|
||||
}
|
||||
}
|
||||
|
||||
void ostream::Buffer::reserve(size_t newSize) noexcept {
|
||||
size_t offset = curr - buffer;
|
||||
size_t const offset = curr - buffer;
|
||||
if (buffer == nullptr) {
|
||||
buffer = (char*)malloc(newSize);
|
||||
} else {
|
||||
@@ -237,8 +269,8 @@ void ostream::Buffer::reset() noexcept {
|
||||
|
||||
std::pair<char*, size_t> ostream::Buffer::grow(size_t s) noexcept {
|
||||
if (UTILS_UNLIKELY(size < s)) {
|
||||
size_t used = curr - buffer;
|
||||
size_t newCapacity = std::max(size_t(32), used + (s * 3 + 1) / 2); // 32 bytes minimum
|
||||
size_t const used = curr - buffer;
|
||||
size_t const newCapacity = std::max(size_t(32), used + (s * 3 + 1) / 2); // 32 bytes minimum
|
||||
reserve(newCapacity);
|
||||
assert(size >= s);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace utils::io {
|
||||
struct ostream_ {
|
||||
std::mutex mLock;
|
||||
ostream::Buffer mData;
|
||||
std::pair<ostream::ConsumerCallback, void*> mConsumer{};
|
||||
bool mShowHex = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -18,8 +18,33 @@
|
||||
|
||||
#include <utils/sstream.h>
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
using namespace utils;
|
||||
using namespace utils::io;
|
||||
|
||||
TEST(ostream, setConsumer) {
|
||||
slog.d.setConsumer(+[](void*, char const*) {
|
||||
GTEST_FAIL();
|
||||
}, nullptr);
|
||||
// we test that we don't crash if the log is empty and that we don't call the consumer.
|
||||
flush(slog.d);
|
||||
|
||||
slog.d.setConsumer(nullptr, nullptr);
|
||||
slog.d << "hello world";
|
||||
// we test that after resetting the consumer, it's not called on flush.
|
||||
flush(slog.d);
|
||||
|
||||
const char* str = "hello world!";
|
||||
slog.d.setConsumer(+[](void* user, char const* str) {
|
||||
ASSERT_STREQ(str, (const char*)user);
|
||||
}, (void*)str);
|
||||
slog.d << str;
|
||||
// we test that the comsumer is called with the right string
|
||||
flush(slog.d);
|
||||
slog.d.setConsumer(nullptr, nullptr);
|
||||
}
|
||||
|
||||
TEST(sstream, EmptyString) {
|
||||
sstream ss;
|
||||
EXPECT_STREQ("", ss.c_str());
|
||||
|
||||
@@ -234,6 +234,7 @@ struct ViewerOptions {
|
||||
float cameraNear = 0.1f;
|
||||
float cameraFar = 100.0f;
|
||||
float cameraEyeOcularDistance = 0.0f;
|
||||
float cameraEyeToeIn = 0.0f;
|
||||
float groundShadowStrength = 0.75f;
|
||||
bool groundPlaneEnabled = false;
|
||||
bool skyboxEnabled = true;
|
||||
|
||||
@@ -504,6 +504,8 @@ static int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, ViewerOp
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraFar);
|
||||
} else if (compare(tok, jsonChunk, "cameraEyeOcularDistance") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraEyeOcularDistance);
|
||||
} else if (compare(tok, jsonChunk, "cameraEyeToeIn") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cameraEyeToeIn);
|
||||
} else if (compare(tok, jsonChunk, "groundShadowStrength") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->groundShadowStrength);
|
||||
} else if (compare(tok, jsonChunk, "groundPlaneEnabled") == 0) {
|
||||
@@ -658,9 +660,11 @@ void applySettings(Engine* engine, const ViewerOptions& settings, Camera* camera
|
||||
// "cross-eyed" stereo.
|
||||
// For cross-eyed stereo, Eye 0 is really the RIGHT eye, while Eye 1 is the LEFT eye.
|
||||
const auto od = settings.cameraEyeOcularDistance;
|
||||
const auto toeIn = settings.cameraEyeToeIn;
|
||||
const auto eyeCount = engine->getConfig().stereoscopicEyeCount;
|
||||
const mat4 rightEye = mat4::translation(double3{ od, 0.0, 0.0}); // right eye
|
||||
const mat4 leftEye = mat4::translation(double3{-od, 0.0, 0.0}); // left eye
|
||||
const double3 up = double3(0.0, 1.0, 0.0);
|
||||
const mat4 rightEye = mat4::translation(double3{ od, 0.0, 0.0}) * mat4::rotation( toeIn, up);
|
||||
const mat4 leftEye = mat4::translation(double3{-od, 0.0, 0.0}) * mat4::rotation(-toeIn, up);
|
||||
const mat4 modelMatrices[2] = { rightEye, leftEye };
|
||||
for (int i = 0; i < eyeCount; i++) {
|
||||
camera->setEyeModelMatrix(i, modelMatrices[i % 2]);
|
||||
@@ -877,6 +881,7 @@ static std::ostream& operator<<(std::ostream& out, const ViewerOptions& in) {
|
||||
<< "\"cameraNear\": " << (in.cameraNear) << ",\n"
|
||||
<< "\"cameraFar\": " << (in.cameraFar) << ",\n"
|
||||
<< "\"cameraEyeOcularDistance\": " << (in.cameraEyeOcularDistance) << ",\n"
|
||||
<< "\"cameraEyeToeIn\": " << (in.cameraEyeToeIn) << ",\n"
|
||||
<< "\"groundShadowStrength\": " << (in.groundShadowStrength) << ",\n"
|
||||
<< "\"groundPlaneEnabled\": " << to_string(in.groundPlaneEnabled) << ",\n"
|
||||
<< "\"skyboxEnabled\": " << to_string(in.skyboxEnabled) << ",\n"
|
||||
|
||||
@@ -391,6 +391,8 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, DepthOfFieldOpt
|
||||
CHECK_KEY(tok);
|
||||
if (compare(tok, jsonChunk, "cocScale") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cocScale);
|
||||
} else if (compare(tok, jsonChunk, "cocAspectRatio") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->cocAspectRatio);
|
||||
} else if (compare(tok, jsonChunk, "maxApertureDiameter") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->maxApertureDiameter);
|
||||
} else if (compare(tok, jsonChunk, "enabled") == 0) {
|
||||
@@ -424,6 +426,7 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, DepthOfFieldOpt
|
||||
std::ostream& operator<<(std::ostream& out, const DepthOfFieldOptions& in) {
|
||||
return out << "{\n"
|
||||
<< "\"cocScale\": " << (in.cocScale) << ",\n"
|
||||
<< "\"cocAspectRatio\": " << (in.cocAspectRatio) << ",\n"
|
||||
<< "\"maxApertureDiameter\": " << (in.maxApertureDiameter) << ",\n"
|
||||
<< "\"enabled\": " << to_string(in.enabled) << ",\n"
|
||||
<< "\"filter\": " << (in.filter) << ",\n"
|
||||
@@ -647,6 +650,65 @@ std::ostream& operator<<(std::ostream& out, const MultiSampleAntiAliasingOptions
|
||||
<< "}";
|
||||
}
|
||||
|
||||
int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::BoxType* out) {
|
||||
if (0 == compare(tokens[i], jsonChunk, "AABB")) { *out = TemporalAntiAliasingOptions::BoxType::AABB; }
|
||||
else if (0 == compare(tokens[i], jsonChunk, "VARIANCE")) { *out = TemporalAntiAliasingOptions::BoxType::VARIANCE; }
|
||||
else if (0 == compare(tokens[i], jsonChunk, "AABB_VARIANCE")) { *out = TemporalAntiAliasingOptions::BoxType::AABB_VARIANCE; }
|
||||
else {
|
||||
slog.w << "Invalid TemporalAntiAliasingOptions::BoxType: '" << STR(tokens[i], jsonChunk) << "'" << io::endl;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::BoxType in) {
|
||||
switch (in) {
|
||||
case TemporalAntiAliasingOptions::BoxType::AABB: return out << "\"AABB\"";
|
||||
case TemporalAntiAliasingOptions::BoxType::VARIANCE: return out << "\"VARIANCE\"";
|
||||
case TemporalAntiAliasingOptions::BoxType::AABB_VARIANCE: return out << "\"AABB_VARIANCE\"";
|
||||
}
|
||||
return out << "\"INVALID\"";
|
||||
}
|
||||
|
||||
int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::BoxClipping* out) {
|
||||
if (0 == compare(tokens[i], jsonChunk, "ACCURATE")) { *out = TemporalAntiAliasingOptions::BoxClipping::ACCURATE; }
|
||||
else if (0 == compare(tokens[i], jsonChunk, "CLAMP")) { *out = TemporalAntiAliasingOptions::BoxClipping::CLAMP; }
|
||||
else if (0 == compare(tokens[i], jsonChunk, "NONE")) { *out = TemporalAntiAliasingOptions::BoxClipping::NONE; }
|
||||
else {
|
||||
slog.w << "Invalid TemporalAntiAliasingOptions::BoxClipping: '" << STR(tokens[i], jsonChunk) << "'" << io::endl;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::BoxClipping in) {
|
||||
switch (in) {
|
||||
case TemporalAntiAliasingOptions::BoxClipping::ACCURATE: return out << "\"ACCURATE\"";
|
||||
case TemporalAntiAliasingOptions::BoxClipping::CLAMP: return out << "\"CLAMP\"";
|
||||
case TemporalAntiAliasingOptions::BoxClipping::NONE: return out << "\"NONE\"";
|
||||
}
|
||||
return out << "\"INVALID\"";
|
||||
}
|
||||
|
||||
int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::JitterPattern* out) {
|
||||
if (0 == compare(tokens[i], jsonChunk, "RGSS_X4")) { *out = TemporalAntiAliasingOptions::JitterPattern::RGSS_X4; }
|
||||
else if (0 == compare(tokens[i], jsonChunk, "UNIFORM_HELIX_X4")) { *out = TemporalAntiAliasingOptions::JitterPattern::UNIFORM_HELIX_X4; }
|
||||
else if (0 == compare(tokens[i], jsonChunk, "HALTON_23_X8")) { *out = TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X8; }
|
||||
else if (0 == compare(tokens[i], jsonChunk, "HALTON_23_X16")) { *out = TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X16; }
|
||||
else {
|
||||
slog.w << "Invalid TemporalAntiAliasingOptions::JitterPattern: '" << STR(tokens[i], jsonChunk) << "'" << io::endl;
|
||||
}
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::JitterPattern in) {
|
||||
switch (in) {
|
||||
case TemporalAntiAliasingOptions::JitterPattern::RGSS_X4: return out << "\"RGSS_X4\"";
|
||||
case TemporalAntiAliasingOptions::JitterPattern::UNIFORM_HELIX_X4: return out << "\"UNIFORM_HELIX_X4\"";
|
||||
case TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X8: return out << "\"HALTON_23_X8\"";
|
||||
case TemporalAntiAliasingOptions::JitterPattern::HALTON_23_X16: return out << "\"HALTON_23_X16\"";
|
||||
}
|
||||
return out << "\"INVALID\"";
|
||||
}
|
||||
|
||||
int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions* out) {
|
||||
CHECK_TOKTYPE(tokens[i], JSMN_OBJECT);
|
||||
int size = tokens[i++].size;
|
||||
@@ -657,8 +719,30 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAli
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->filterWidth);
|
||||
} else if (compare(tok, jsonChunk, "feedback") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->feedback);
|
||||
} else if (compare(tok, jsonChunk, "lodBias") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->lodBias);
|
||||
} else if (compare(tok, jsonChunk, "enabled") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->enabled);
|
||||
} else if (compare(tok, jsonChunk, "upscaling") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->upscaling);
|
||||
} else if (compare(tok, jsonChunk, "filterHistory") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->filterHistory);
|
||||
} else if (compare(tok, jsonChunk, "filterInput") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->filterInput);
|
||||
} else if (compare(tok, jsonChunk, "useYCoCg") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->useYCoCg);
|
||||
} else if (compare(tok, jsonChunk, "boxType") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->boxType);
|
||||
} else if (compare(tok, jsonChunk, "boxClipping") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->boxClipping);
|
||||
} else if (compare(tok, jsonChunk, "jitterPattern") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->jitterPattern);
|
||||
} else if (compare(tok, jsonChunk, "varianceGamma") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->varianceGamma);
|
||||
} else if (compare(tok, jsonChunk, "preventFlickering") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->preventFlickering);
|
||||
} else if (compare(tok, jsonChunk, "historyReprojection") == 0) {
|
||||
i = parse(tokens, i + 1, jsonChunk, &out->historyReprojection);
|
||||
} else {
|
||||
slog.w << "Invalid TemporalAntiAliasingOptions key: '" << STR(tok, jsonChunk) << "'" << io::endl;
|
||||
i = parse(tokens, i + 1);
|
||||
@@ -675,7 +759,18 @@ std::ostream& operator<<(std::ostream& out, const TemporalAntiAliasingOptions& i
|
||||
return out << "{\n"
|
||||
<< "\"filterWidth\": " << (in.filterWidth) << ",\n"
|
||||
<< "\"feedback\": " << (in.feedback) << ",\n"
|
||||
<< "\"enabled\": " << to_string(in.enabled) << "\n"
|
||||
<< "\"lodBias\": " << (in.lodBias) << ",\n"
|
||||
<< "\"enabled\": " << to_string(in.enabled) << ",\n"
|
||||
<< "\"upscaling\": " << to_string(in.upscaling) << ",\n"
|
||||
<< "\"filterHistory\": " << to_string(in.filterHistory) << ",\n"
|
||||
<< "\"filterInput\": " << to_string(in.filterInput) << ",\n"
|
||||
<< "\"useYCoCg\": " << to_string(in.useYCoCg) << ",\n"
|
||||
<< "\"boxType\": " << (in.boxType) << ",\n"
|
||||
<< "\"boxClipping\": " << (in.boxClipping) << ",\n"
|
||||
<< "\"jitterPattern\": " << (in.jitterPattern) << ",\n"
|
||||
<< "\"varianceGamma\": " << (in.varianceGamma) << ",\n"
|
||||
<< "\"preventFlickering\": " << to_string(in.preventFlickering) << ",\n"
|
||||
<< "\"historyReprojection\": " << to_string(in.historyReprojection) << "\n"
|
||||
<< "}";
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,15 @@ std::ostream& operator<<(std::ostream& out, const AmbientOcclusionOptions& in);
|
||||
int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, MultiSampleAntiAliasingOptions* out);
|
||||
std::ostream& operator<<(std::ostream& out, const MultiSampleAntiAliasingOptions& in);
|
||||
|
||||
int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::BoxType* out);
|
||||
std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::BoxType in);
|
||||
|
||||
int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::BoxClipping* out);
|
||||
std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::BoxClipping in);
|
||||
|
||||
int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions::JitterPattern* out);
|
||||
std::ostream& operator<<(std::ostream& out, TemporalAntiAliasingOptions::JitterPattern in);
|
||||
|
||||
int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, TemporalAntiAliasingOptions* out);
|
||||
std::ostream& operator<<(std::ostream& out, const TemporalAntiAliasingOptions& in);
|
||||
|
||||
|
||||
@@ -792,6 +792,28 @@ void ViewerGui::updateUserInterface() {
|
||||
ImGui::Checkbox("Lens Flare", &mSettings.view.bloom.lensFlare);
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("TAA Options")) {
|
||||
ImGui::Checkbox("Upscaling", &mSettings.view.taa.upscaling);
|
||||
ImGui::Checkbox("History Reprojection", &mSettings.view.taa.historyReprojection);
|
||||
ImGui::SliderFloat("Feedback", &mSettings.view.taa.feedback, 0.0f, 1.0f);
|
||||
ImGui::Checkbox("Filter History", &mSettings.view.taa.filterHistory);
|
||||
ImGui::Checkbox("Filter Input", &mSettings.view.taa.filterInput);
|
||||
ImGui::SliderFloat("FilterWidth", &mSettings.view.taa.filterWidth, 0.2f, 2.0f);
|
||||
ImGui::SliderFloat("LOD bias", &mSettings.view.taa.lodBias, -8.0f, 0.0f);
|
||||
ImGui::Checkbox("Use YCoCg", &mSettings.view.taa.useYCoCg);
|
||||
ImGui::Checkbox("Prevent Flickering", &mSettings.view.taa.preventFlickering);
|
||||
int jitterSequence = (int)mSettings.view.taa.jitterPattern;
|
||||
int boxClipping = (int)mSettings.view.taa.boxClipping;
|
||||
int boxType = (int)mSettings.view.taa.boxType;
|
||||
ImGui::Combo("Jitter Pattern", &jitterSequence, "RGSS x4\0Uniform Helix x4\0Halton x8\0Halton x16\0\0");
|
||||
ImGui::Combo("Box Clipping", &boxClipping, "Accurate\0Clamp\0None\0\0");
|
||||
ImGui::Combo("Box Type", &boxType, "AABB\0Variance\0Both\0\0");
|
||||
ImGui::SliderFloat("Variance Gamma", &mSettings.view.taa.varianceGamma, 0.75f, 1.25f);
|
||||
mSettings.view.taa.boxClipping = (TemporalAntiAliasingOptions::BoxClipping)boxClipping;
|
||||
mSettings.view.taa.boxType = (TemporalAntiAliasingOptions::BoxType)boxType;
|
||||
mSettings.view.taa.jitterPattern = (TemporalAntiAliasingOptions::JitterPattern)jitterSequence;
|
||||
}
|
||||
|
||||
if (ImGui::CollapsingHeader("SSAO Options")) {
|
||||
auto& ssao = mSettings.view.ssao;
|
||||
|
||||
@@ -1014,6 +1036,7 @@ void ViewerGui::updateUserInterface() {
|
||||
ImGui::Checkbox("Enabled##dofEnabled", &mSettings.view.dof.enabled);
|
||||
ImGui::SliderFloat("Focus distance", &mSettings.viewer.cameraFocusDistance, 0.0f, 30.0f);
|
||||
ImGui::SliderFloat("Blur scale", &mSettings.view.dof.cocScale, 0.1f, 10.0f);
|
||||
ImGui::SliderFloat("CoC aspect-ratio", &mSettings.view.dof.cocAspectRatio, 0.25f, 4.0f);
|
||||
ImGui::SliderInt("Ring count", &dofRingCount, 1, 17);
|
||||
ImGui::SliderInt("Max CoC", &dofMaxCoC, 1, 32);
|
||||
ImGui::Checkbox("Native Resolution", &mSettings.view.dof.nativeResolution);
|
||||
@@ -1071,6 +1094,10 @@ void ViewerGui::updateUserInterface() {
|
||||
ImGui::SliderFloat(
|
||||
"Ocular distance", &mSettings.viewer.cameraEyeOcularDistance, 0.0f, 1.0f);
|
||||
|
||||
float toeInDegrees = mSettings.viewer.cameraEyeToeIn / f::PI * 180.0f;
|
||||
ImGui::SliderFloat("Toe in", &toeInDegrees, 0.0f, 30.0, "%.3f°");
|
||||
mSettings.viewer.cameraEyeToeIn = toeInDegrees / 180.0f * f::PI;
|
||||
|
||||
ImGui::Unindent();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,22 +5,27 @@ void main() {
|
||||
|
||||
inputs.normalizedUV = position.xy * 0.5 + 0.5;
|
||||
|
||||
vec4 position = getPosition();
|
||||
inputs.position = getPosition();
|
||||
|
||||
// GL convention to inverted DX convention
|
||||
position.z = position.z * -0.5 + 0.5;
|
||||
// Invoke user code
|
||||
postProcessVertex(inputs);
|
||||
|
||||
// (vertex domain) GL convention to inverted DX convention
|
||||
inputs.position.z = inputs.position.z * -0.5 + 0.5;
|
||||
|
||||
#if defined(TARGET_VULKAN_ENVIRONMENT)
|
||||
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
|
||||
inputs.position.y = -inputs.position.y;
|
||||
#endif
|
||||
|
||||
// Adjust clip-space
|
||||
#if !defined(TARGET_VULKAN_ENVIRONMENT) && !defined(TARGET_METAL_ENVIRONMENT)
|
||||
// This is not needed in Vulkan or Metal because clipControl is always (1, 0)
|
||||
// (We don't use a dot() here because it workaround a spirv-opt optimization that in turn
|
||||
// (We don't use a dot() here because it works around a spirv-opt optimization that in turn
|
||||
// causes a crash on PowerVR, see #5118)
|
||||
position.z = position.z * frameUniforms.clipControl.x + position.w * frameUniforms.clipControl.y;
|
||||
inputs.position.z = inputs.position.z * frameUniforms.clipControl.x + inputs.position.w * frameUniforms.clipControl.y;
|
||||
#endif
|
||||
|
||||
// Invoke user code
|
||||
postProcessVertex(inputs);
|
||||
|
||||
// Handle user-defined interpolated attributes
|
||||
#if defined(VARIABLE_CUSTOM0)
|
||||
VARIABLE_CUSTOM_AT0 = inputs.VARIABLE_CUSTOM0;
|
||||
@@ -36,5 +41,5 @@ void main() {
|
||||
#endif
|
||||
|
||||
// some PowerVR drivers crash when gl_Position is written more than once
|
||||
gl_Position = position;
|
||||
gl_Position = inputs.position;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user