Compare commits

..
Author SHA1 Message Date
Eliza Velasquez ea9fdbeb5c wip: shader compilation benchmark 2024-01-16 17:03:41 -08:00
Ben Doherty 5fd7a4e153 Don't render in background (#7486) 2024-01-12 10:17:00 -08:00
Sungun Park 20ff230b92 Fix a json parsing bug (#7490)
When parsing a lexeme, we use one less byte than it's intended to be for
comparing the current string.

This results in a success in cases like:
- true and truX
- false and falsX
- null and nulX
where X means an arbitrary character.

Fix this by the full intended length.
2024-01-11 12:47:11 -08:00
Ben Doherty 44ff79ad34 Metal: disable fast math (#7485) 2024-01-10 15:31:23 -08:00
Mathias AgopianandPowei Feng 102d2db008 Bokeh aspect ratio (#7482)
* Bokeh aspect ratio

new DoF option to set the bokeh aspect ratio, this can be used to
simulate anamorphic lenses

* Update android/filament-android/src/main/java/com/google/android/filament/View.java

Co-authored-by: Powei Feng <powei@google.com>

* Update web/filament-js/filament.d.ts

Co-authored-by: Powei Feng <powei@google.com>

---------

Co-authored-by: Powei Feng <powei@google.com>
2024-01-10 15:25:28 -08:00
Powei Feng 5c9039e650 Release Filament 1.49.3 2024-01-10 13:55:33 -08:00
Eliza Velasquez 1203c24f06 Generate dummy stereo variants for FL0 mats
See #7415 for a more detailed description of why this change is necessary.

The remaining variants which are filtered from FL0 materials are all related to
lighting, so further hacks like this won't be necessary.

Future work involves properly supporting differing sets of variants based on
shader language.
2024-01-10 13:50:53 -08:00
Mathias Agopian 9704d27aeb TAA upscaling
This feature is still work in progress. TAA can now optionally
upscale by 4x (this disables dynamic resolution scaling).
2024-01-09 14:07:18 -08:00
Mathias Agopian c6f2c3fc1c bloom: disable fireflies reduction when using TAA
TAA already does a fireflies reduction pass, so it's not needed when
applying bloom.
2024-01-09 14:04:01 -08:00
Mathias Agopian 2faf868341 fix missing includes (new CLion warnings) 2024-01-09 14:04:01 -08:00
Powei Feng 81f6260843 Fix typo oin MaterialCompiler (#7477) 2024-01-08 15:03:07 -08:00
Ben Doherty 1a9063d53a Metal: Always report material name in use-after-free detector (#7473) 2024-01-04 11:55:28 -08:00
Ben Doherty 6062b3c8c6 Fix ostream linking error when compiling Linux DSO (#7470) 2024-01-03 12:06:44 -08:00
Ben Doherty d9186c44ba matdbg: Load codicon font (#7469) 2024-01-03 10:41:18 -08:00
Benjamin Doherty 6d06e00934 Release Filament 1.49.2 2024-01-02 14:52:58 -08:00
Ben Doherty b4021a1fbc Add picking queries to gltf-viewer iOS sample (#7464) 2024-01-02 14:48:59 -08:00
Benjamin Doherty 2c9559f9b8 Add string.h header 2024-01-02 13:24:08 -08:00
Ben Doherty e2da13f817 Fix a couple race conditions in Metal backend (#7457) 2023-12-26 15:33:37 -07:00
Mathias Agopian addae314b4 on vulkan and metal resolve depth manuall with a shader
both vk and metal don't support depth resolves, and are currently
implemented in the backend. vk is buggy and they don't resolve the
same way that gl does.
2023-12-21 21:32:22 -08:00
Mathias Agopian 5e3c51822d add basic support for multisampled samplers
we don't document this feature yet because it's still WIP and 
intended to be used internally only at this point
2023-12-21 21:32:22 -08:00
Mathias Agopian 9d817a0cc9 TAA improvements (#7441)
* TAA improvements

- fix variance + AABB history clipping (we were computing the union
  of both AABB instead of the intersection).
- added a setting for the variance parameter
- added a setting for the jitter pattern
- improved some default values
- fixed a few comments
- smaller code tweaks to facilitate future improvements
2023-12-21 16:51:28 -08:00
princeku07 95ecc3d1e4 Make applyMaterialVariant public 2023-12-19 10:09:42 -08:00
Mathias Agopian cabaa4323e add a way to set a log consumer
this is a private API to capture filament's logs.
2023-12-18 16:28:31 -08:00
Mathias Agopian d6b2ec49b0 cleanup 2023-12-18 16:28:31 -08:00
Mathias Agopian 93004aa5bc add -fno-limit-debug-info to android debug builds
This is what the NDK does, it's needed to keep debug infos for the 
STL symbols (because the STL is provided by the platform and doesn't
have debug infos).
2023-12-18 16:28:06 -08:00
Mathias Agopian 27fa8ab46d don't use eglGetProcAddress on emscripten
In practice none of the extensions we would use eglGetProcAddress for
are supported. And we had a case where an extension was reported
supported but eglGetProcAddress didn't return the corresponding
entry point.

update web demos remote ui.

FIXES=[315033914]
2023-12-18 16:27:33 -08:00
Benjamin Doherty 614990f413 Release Filament 1.49.1 2023-12-18 13:35:28 -08:00
Benjamin Doherty da058028fd Fix VulkanPlatformAndroidLinuxWindows for G3 2023-12-18 09:48:20 -08:00
selcott 55c75453df Assert non-null texture in setTextureData 2023-12-15 09:22:01 -08:00
Mathias Agopian f5eff10532 fix TAA on mobile devices.
The history neighborhood variance needed to be computed in highp.

fixes #6504
2023-12-14 13:44:15 -08:00
Ben Doherty 56a0364534 Metal: call frame scheduled callback on driver thread (#7414) 2023-12-14 11:28:29 -08:00
Mathias Agopian c8aae3cdaa fix android build 2023-12-13 16:32:22 -08:00
Mathias Agopian 4f94c575c9 fix UiHelper
We shouldn't remove the listener callbacks when a surface is destroyed
by the system (e.g. screen off/on) because then we won't know when
it comes back.

But we still need to do this when the user calls UiHelper.detach() or
when we are attaching to a new surface.

Fixes #7424
2023-12-13 15:21:04 -08:00
Mathias Agopian 1a29f020c9 fix getUserWorldFromWorldMatrix() during the shadow passes
getUserWorldFromWorldMatrix() was always set to identity during the
shadow pass. It needs to be set the the same value as the main
camera.

FIXES=[315504607]
2023-12-13 10:58:20 -08:00
Mathias Agopian d5847a3c6a fix per-light shadow "caster" flag update
The per-light shadow caster flag wasn't updated when a light was
toggled from casting to non-casting. This resulted in an out of date or
incorrect shadowmap to be used.

FIXES=[315859790]
2023-12-12 19:31:03 -08:00
Mathias Agopian 3dbae482d3 make a few PostProcessManager methods "package" public
we make PostProcessMaterial, getPostProcessMaterial() as well as
render(), commitAndRender() public (as in public to filament internals).

These methods have no reason to be private to PostProcessManager.
2023-12-12 11:18:52 -08:00
Mathias Agopian 27572e3be6 fix framegraph clear flags for imported targets
Imported targets always use the imported flags, not the flags that
where specified when the target was created. The clear flags
should be cleared after they've been used once in case that rendertarget
is reused by multiple passes.

For e.g. if the clear flag is set, the target will be cleared the first
time it is used, but if it's used again, we don't want to clear again, 
in that case we'll use the "local" flags used when the target was
created (as opposed to the imported flags).
2023-12-12 11:18:36 -08:00
Mathias Agopian dedf276e26 Allow post-process materials to update the vertex position
we add a `position` field to PostProcessVertexInputs which the user
code can modify if needed.
2023-12-12 11:18:18 -08:00
Romain Guy 111ad96134 Update dependencies to latest (#7423) 2023-12-11 21:11:54 -08:00
Mathias Agopian 2a67152dda Dynamic "Material constants" support
Material constants (a.k.a: specialization constants) can only be set
during Material creation through Material::Builder.
This change somewhat relaxes that limitation by allowing constants to
be set at runtime on Material directly.

Currently this new API is still private and only supported on FMaterial.

This feature works by invalidating the HwProgram cache of the concerned
Material, causing a shader recompile per variant; so this API is costly
and should be used only for debugging or during app/game configuration.

The TAA material is modified to use constants instead of #defines for
various settings and those are exposed in TaaOptions as well is in 
ViewerGui. So with this change all aspects of the TAA material can
be changed at runtime.
2023-12-11 16:20:47 -08:00
Maximilien 2f62978e4c Changed the level 0 materials custom commands so that materials for all APIs are built.
This allows to turn off the OpenGL backend (using -DFILAMENT_SUPPORTS_OPENGL=OFF).
Previously, disabling the OpenGL backend was leading to a compilation error related to missing level 0 materials.
2023-12-11 10:40:34 -08:00
Benjamin Doherty fdb90f2f49 Bump MATERIAL_VERSION to 49 2023-12-11 00:16:13 -08:00
Benjamin Doherty 5418b8c4cc Release Filament 1.49.0 2023-12-11 00:12:52 -08:00
Benjamin Doherty 724cab3623 Update NEW_RELEASE_NOTES.md to reflect cherry-pick 2023-12-11 00:10:29 -08:00
Benjamin Doherty d9faea264a Fix bump-version script on Darwin 2023-12-10 19:53:12 -08:00
Powei Feng 6116c2f9eb vk: workaround swiftshader spirv no-op issue (#7417)
Swiftshader runs spirv validation before compilation. However,
the validation does not like having Nop (no-op) in the input.
So we skip instructions instead of writing no-op for the
output of `workaroundSpecConstant`.

Also, fix issue to keep the value in the original shader if a
specialization wasn't provided.
2023-12-08 10:32:18 -08:00
Ben Doherty 95fdba0c5d Add camera toe-in setting for stereoscopic testing (#7411) 2023-12-08 10:03:57 -08:00
mdagois 9c56517370 Added an option to shorten compile time in Visual Studio (#7408) 2023-12-08 09:18:31 -08:00
Powei Feng 08f06f4298 spirv-headers: fix failed headers check (#7416)
Due to a cmake misconfiguration, we are installing spirv as part
of the release, public headers. It's now corrected.
2023-12-07 17:21:25 -08:00
Eliza Velasquez b1794a82ca Generate dummy skinning variants for FL0 mats
Even if skinning is not fully implemented on FL0, we have clients which depend
on materials with skinning variants that otherwise could easily be converted to
FL0 materials. There are two proper ways to deal with this:

1. Support skinning/morphing in Feature Level 0.

2. Allow ESSL 1.0 code and ESSL 3.0 code to support different sets of variants.

However, the simplest solution is to just include skinning/morphing variants,
but disable codegen for ESSL 1.0 code, making them identical to the base
variants. This shouldn't increase the file size much due to the dictionary
deflation. Of course, skinning will not work correctly on FL0, but this has
always been the case. Future work here would be to properly implement one of the
two solutions described above.
2023-12-07 15:37:23 -08:00
Powei Feng 56bd2871ec vk: spec const workaround through runtime spirv modification (#7399)
To avoid driver edge cases with spec const, we manually change the
spec const into a regular constant in the spirv.

FIXES=310603393
2023-12-07 14:49:12 -08:00
Mathias Agopian 5a9c47bcb5 Improve blitting APIs
- deprecate blit(), renamed to blitDEPRECATED. It's only used in one
  place in copyFrame() now. We can't void it because we don't have
  access to the texture from the RenderTarget.

- add a new blit() api that works with textures instead of render
  targets.

- add a new resolve() api that works with textures instead of render
  targets. doesn't support scaling.

- always use a shader when scaling in the framegraph
  (there was only one place where we used blit)

- use the new blit() for:
	- for mipmap generation on vk (fixme)
	- copying the depth buffer to avoid ssao feedback loop

- use the new resolve() for:
    - manually resolving MSAA color/depth

- Simplify the resolve APIs on the filament side

- implement generateMipmaps for the vulkan backend

simplify MetalBlitter

- remove metal blit workarounds
- don't issue a blit from a renderpass, this only affect Renderer::copyFrame
- We only handle a single texture now (instead of color+depth), so we
  can simplify the code a lot.
  Didn't touch the "slowpath" much, but it now assumes it blits color or
  depth, not both.
2023-12-06 11:22:53 -08:00
Ben Doherty 51c2fa2728 Fix typo in getStereoscopicOptions Java (#7410) 2023-12-06 11:03:51 -08:00
Mathias Agopian 334c71de52 material depth variant caching should only happen for surface materials (#7407) 2023-12-05 14:28:32 -08:00
Mathias AgopianandBen Doherty 001098098b on ANGLE we now use a thread pool for parallel shader compilation (#7405)
* on ANGLE we now use a thread pool for parallel shader compilation

in general we now prefer using a thread pool instead of the KHR
extension, because we have less control over how the queue is
managed by the driver.

ANGLE supports many threads very well, so we use cpu_threads/2 for the
pool size, at background priority.

* Update filament/backend/src/opengl/ShaderCompilerService.h

Co-authored-by: Ben Doherty <bendoherty@google.com>

* Update filament/backend/src/opengl/ShaderCompilerService.h

Co-authored-by: Ben Doherty <bendoherty@google.com>

---------

Co-authored-by: Ben Doherty <bendoherty@google.com>
2023-12-05 14:28:11 -08:00
115 changed files with 3019 additions and 2027 deletions
+24 -1
View File
@@ -41,6 +41,8 @@ option(FILAMENT_LINUX_IS_MOBILE "Treat Linux as Mobile" OFF)
option(FILAMENT_ENABLE_ASAN_UBSAN "Enable Address and Undefined Behavior Sanitizers" OFF)
option(FILAMENT_ENABLE_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()
+1
View File
@@ -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.
+2 -2
View File
@@ -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
View File
@@ -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
+8 -8
View File
@@ -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;
}
/**
@@ -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();
}
@@ -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 -1
View File
@@ -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
View File
@@ -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
File diff suppressed because one or more lines are too long
Binary file not shown.
+14 -1
View File
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
View File
@@ -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
View File
@@ -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}"
+19 -5
View File
@@ -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 -51
View File
@@ -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
+96 -260
View File
@@ -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
+4 -1
View File
@@ -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;
};
+4 -1
View File
@@ -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;
+12 -1
View File
@@ -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
*/
+159 -68
View File
@@ -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>;
+46 -14
View File
@@ -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) {
+16 -5
View File
@@ -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) {
}
+14 -3
View File
@@ -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);
+228 -64
View File
@@ -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
+4 -1
View File
@@ -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
+2
View File
@@ -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;
+1 -15
View File
@@ -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;
}
+99 -336
View File
@@ -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
+7 -29
View File
@@ -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
+160 -34
View File
@@ -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;
+7 -283
View File
@@ -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);
+43 -3
View File
@@ -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)
};
/**
+6
View File
@@ -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.
*
+3 -2
View File
@@ -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;
+1 -2
View File
@@ -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;
}
+3 -2
View File
@@ -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 {
+1 -1
View File
@@ -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
View File
@@ -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 };
+79 -68
View File
@@ -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;
+18 -14
View File
@@ -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;
+3 -3
View File
@@ -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) {
+14 -16
View File
@@ -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) };
}
+16 -1
View File
@@ -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)
+12 -2
View File
@@ -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);
+117 -54
View File
@@ -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
+13
View File
@@ -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
+1 -1
View File
@@ -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();
+52 -21
View File
@@ -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);
}
}
+37 -86
View File
@@ -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 {
+22 -7
View File
@@ -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, {
+4 -2
View File
@@ -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);
+1 -1
View File
@@ -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
+5
View File
@@ -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;
+170 -132
View File
@@ -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;
}
+33
View File
@@ -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;
}
}
+2 -2
View File
@@ -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,
+1 -1
View File
@@ -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");
}
+12 -10
View File
@@ -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.
+17 -4
View File
@@ -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>";
+3 -1
View File
@@ -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;
};
+3 -3
View File
@@ -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;
}
+6 -7
View File
@@ -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:
+9 -1
View File
@@ -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;
+1 -2
View File
@@ -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]
+30 -10
View File
@@ -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");
+8 -8
View File
@@ -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;
+3 -2
View File
@@ -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] = {};
+5
View File
@@ -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>
+1 -1
View File
@@ -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;
+10 -4
View File
@@ -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
View File
@@ -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;
}
+38 -6
View File
@@ -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);
}
+1
View File
@@ -25,6 +25,7 @@ namespace utils::io {
struct ostream_ {
std::mutex mLock;
ostream::Buffer mData;
std::pair<ostream::ConsumerCallback, void*> mConsumer{};
bool mShowHex = false;
};
+25
View File
@@ -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());
+1
View File
@@ -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;
+7 -2
View File
@@ -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"
+96 -1
View File
@@ -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"
<< "}";
}
+9
View File
@@ -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);
+27
View File
@@ -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();
}
+14 -9
View File
@@ -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