Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0404be5127 | ||
|
|
a784fb7d8b | ||
|
|
8459cbc91c | ||
|
|
344c04a2d8 | ||
|
|
55e2691f3f |
@@ -129,7 +129,7 @@ jobs:
|
||||
- name: Sign sample-gltf-viewer
|
||||
run: |
|
||||
echo "${APK_KEYSTORE_BASE64}" > filament.jks.base64
|
||||
base64 --decode -i filament.jks.base64 > filament.jks
|
||||
base64 --decode filament.jks.base64 > filament.jks
|
||||
BUILD_TOOLS_VERSION=$(ls ${ANDROID_HOME}/build-tools | sort -V | tail -n 1)
|
||||
APKSIGNER=${ANDROID_HOME}/build-tools/${BUILD_TOOLS_VERSION}/apksigner
|
||||
IN_FILE="out/sample-gltf-viewer-release.apk"
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
run: |
|
||||
build\windows\build-github.bat release
|
||||
move out\filament-windows.tgz out\filament-%TAG%-windows.tgz
|
||||
move out\filament-windows.tgz out\filament-$Env:TAG-windows.tgz
|
||||
shell: cmd
|
||||
- uses: actions/github-script@v6
|
||||
env:
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.51.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.50.2'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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.51.0'
|
||||
pod 'Filament', '~> 1.50.2'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -7,26 +7,6 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.51.0
|
||||
|
||||
- materials: add support for post-lighting mix factor (b/328498606) [⚠️ **New Material Version**]
|
||||
|
||||
## v1.50.6
|
||||
|
||||
- Add new API `SwapChain::getFrameScheduledCallback`
|
||||
- vulkan: fixed validation error VUID-vkAcquireNextImageKHR-semaphore-01779
|
||||
- opengl: Add support for protected content swapchains and contexts
|
||||
|
||||
## v1.50.5
|
||||
|
||||
- android: NDK 26.1.10909125 is used by default
|
||||
- android: Minimum API level on Android is now API 21 instead of API 19. This allows the use of OpenGL ES 3.1
|
||||
- rendering: New PBR Neutral tone mapper, designed to preserve materials color appearance
|
||||
- android: Change default frameRateOptions.interval to 1.0
|
||||
|
||||
## v1.50.4
|
||||
|
||||
|
||||
## v1.50.3
|
||||
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ buildscript {
|
||||
|
||||
ext.versions = [
|
||||
'jdk': 17,
|
||||
'minSdk': 21,
|
||||
'minSdk': 19,
|
||||
'targetSdk': 34,
|
||||
'compileSdk': 34,
|
||||
'kotlin': '1.9.21',
|
||||
|
||||
@@ -484,9 +484,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBuilderConfig(JNIEnv*,
|
||||
jclass, jlong nativeBuilder, jlong commandBufferSizeMB, jlong perRenderPassArenaSizeMB,
|
||||
jlong driverHandleArenaSizeMB, jlong minCommandBufferSizeMB, jlong perFrameCommandsSizeMB,
|
||||
jlong jobSystemThreadCount,
|
||||
jlong textureUseAfterFreePoolSize, jboolean disableParallelShaderCompile,
|
||||
jint stereoscopicType, jlong stereoscopicEyeCount,
|
||||
jlong jobSystemThreadCount, jlong stereoscopicEyeCount,
|
||||
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
Engine::Config config = {
|
||||
@@ -496,9 +494,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
.minCommandBufferSizeMB = (uint32_t) minCommandBufferSizeMB,
|
||||
.perFrameCommandsSizeMB = (uint32_t) perFrameCommandsSizeMB,
|
||||
.jobSystemThreadCount = (uint32_t) jobSystemThreadCount,
|
||||
.textureUseAfterFreePoolSize = (uint32_t) textureUseAfterFreePoolSize,
|
||||
.disableParallelShaderCompile = (bool) disableParallelShaderCompile,
|
||||
.stereoscopicType = (Engine::StereoscopicType) stereoscopicType,
|
||||
.stereoscopicEyeCount = (uint8_t) stereoscopicEyeCount,
|
||||
.resourceAllocatorCacheSizeMB = (uint32_t) resourceAllocatorCacheSizeMB,
|
||||
.resourceAllocatorCacheMaxAge = (uint8_t) resourceAllocatorCacheMaxAge,
|
||||
|
||||
@@ -34,15 +34,7 @@ Java_com_google_android_filament_SwapChain_nSetFrameCompletedCallback(JNIEnv* en
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_SwapChain_nIsSRGBSwapChainSupported(
|
||||
JNIEnv *, jclass, jlong nativeEngine) {
|
||||
Java_com_google_android_filament_SwapChain_nIsSRGBSwapChainSupported(JNIEnv *, jclass, jlong nativeEngine) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
return (jboolean)SwapChain::isSRGBSwapChainSupported(*engine);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_SwapChain_nIsProtectedContentSupported(
|
||||
JNIEnv *, jclass, jlong nativeEngine) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
return (jboolean)SwapChain::isProtectedContentSupported(*engine);
|
||||
return (bool)SwapChain::isSRGBSwapChainSupported(*engine);
|
||||
}
|
||||
|
||||
@@ -47,11 +47,6 @@ Java_com_google_android_filament_ToneMapper_nCreateFilmicToneMapper(JNIEnv*, jcl
|
||||
return (jlong) new FilmicToneMapper();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_ToneMapper_nCreatePBRNeutralToneMapper(JNIEnv*, jclass) {
|
||||
return (jlong) new PBRNeutralToneMapper();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_ToneMapper_nCreateAgxToneMapper(JNIEnv*, jclass, jint look) {
|
||||
return (jlong) new AgxToneMapper(AgxToneMapper::AgxLook(look));
|
||||
|
||||
@@ -158,16 +158,6 @@ public class Engine {
|
||||
FEATURE_LEVEL_3,
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of technique for stereoscopic rendering
|
||||
*/
|
||||
public enum StereoscopicType {
|
||||
/** Stereoscopic rendering is performed using instanced rendering technique. */
|
||||
INSTANCED,
|
||||
/** Stereoscopic rendering is performed using the multiview feature from the graphics backend. */
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs <code>Engine</code> objects using a builder pattern.
|
||||
*/
|
||||
@@ -221,9 +211,7 @@ public class Engine {
|
||||
nSetBuilderConfig(mNativeBuilder, config.commandBufferSizeMB,
|
||||
config.perRenderPassArenaSizeMB, config.driverHandleArenaSizeMB,
|
||||
config.minCommandBufferSizeMB, config.perFrameCommandsSizeMB,
|
||||
config.jobSystemThreadCount,
|
||||
config.textureUseAfterFreePoolSize, config.disableParallelShaderCompile,
|
||||
config.stereoscopicType.ordinal(), config.stereoscopicEyeCount,
|
||||
config.jobSystemThreadCount, config.stereoscopicEyeCount,
|
||||
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge);
|
||||
return this;
|
||||
}
|
||||
@@ -361,35 +349,6 @@ public class Engine {
|
||||
*/
|
||||
public long jobSystemThreadCount = 0;
|
||||
|
||||
/**
|
||||
* Number of most-recently destroyed textures to track for use-after-free.
|
||||
*
|
||||
* This will cause the backend to throw an exception when a texture is freed but still bound
|
||||
* to a SamplerGroup and used in a draw call. 0 disables completely.
|
||||
*
|
||||
* Currently only respected by the Metal backend.
|
||||
*/
|
||||
public long textureUseAfterFreePoolSize = 0;
|
||||
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL backend.
|
||||
*/
|
||||
public boolean disableParallelShaderCompile = false;
|
||||
|
||||
/**
|
||||
* The type of technique for stereoscopic rendering.
|
||||
*
|
||||
* This setting determines the algorithm used when stereoscopic rendering is enabled. This
|
||||
* decision applies to the entire Engine for the lifetime of the Engine. E.g., multiple
|
||||
* Views created from the Engine must use the same stereoscopic type.
|
||||
*
|
||||
* Each view can enable stereoscopic rendering via the StereoscopicOptions::enable flag.
|
||||
*
|
||||
* @see View#setStereoscopicOptions
|
||||
*/
|
||||
public StereoscopicType stereoscopicType = StereoscopicType.INSTANCED;
|
||||
|
||||
/**
|
||||
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
|
||||
* between 1 and Engine#getMaxStereoscopicEyes() (inclusive).
|
||||
@@ -400,14 +359,16 @@ public class Engine {
|
||||
public long stereoscopicEyeCount = 2;
|
||||
|
||||
/*
|
||||
* @Deprecated This value is no longer used.
|
||||
* Size in MiB of the frame graph texture cache. This should be adjusted based on the
|
||||
* size of used render targets (typically the screen).
|
||||
*/
|
||||
public long resourceAllocatorCacheSizeMB = 64;
|
||||
|
||||
/*
|
||||
* This value determines for how many frames are texture entries kept in the cache.
|
||||
* The default value of 30 corresponds to about half a second at 60 fps.
|
||||
*/
|
||||
public long resourceAllocatorCacheMaxAge = 2;
|
||||
public long resourceAllocatorCacheMaxAge = 30;
|
||||
}
|
||||
|
||||
private Engine(long nativeEngine, Config config) {
|
||||
@@ -654,7 +615,7 @@ public class Engine {
|
||||
*/
|
||||
@NonNull
|
||||
public SwapChain createSwapChain(@NonNull Object surface) {
|
||||
return createSwapChain(surface, SwapChainFlags.CONFIG_DEFAULT);
|
||||
return createSwapChain(surface, SwapChain.CONFIG_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -662,15 +623,15 @@ public class Engine {
|
||||
*
|
||||
* @param surface on Android, <b>must be</b> an instance of {@link android.view.Surface}
|
||||
*
|
||||
* @param flags configuration flags, see {@link SwapChainFlags}
|
||||
* @param flags configuration flags, see {@link SwapChain}
|
||||
*
|
||||
* @return a newly created {@link SwapChain} object
|
||||
*
|
||||
* @exception IllegalStateException can be thrown if the SwapChain couldn't be created
|
||||
*
|
||||
* @see SwapChainFlags#CONFIG_DEFAULT
|
||||
* @see SwapChainFlags#CONFIG_TRANSPARENT
|
||||
* @see SwapChainFlags#CONFIG_READABLE
|
||||
* @see SwapChain#CONFIG_DEFAULT
|
||||
* @see SwapChain#CONFIG_TRANSPARENT
|
||||
* @see SwapChain#CONFIG_READABLE
|
||||
*
|
||||
*/
|
||||
@NonNull
|
||||
@@ -688,22 +649,21 @@ public class Engine {
|
||||
*
|
||||
* @param width width of the rendering buffer
|
||||
* @param height height of the rendering buffer
|
||||
* @param flags configuration flags, see {@link SwapChainFlags}
|
||||
* @param flags configuration flags, see {@link SwapChain}
|
||||
*
|
||||
* @return a newly created {@link SwapChain} object
|
||||
*
|
||||
* @exception IllegalStateException can be thrown if the SwapChain couldn't be created
|
||||
*
|
||||
* @see SwapChainFlags#CONFIG_DEFAULT
|
||||
* @see SwapChainFlags#CONFIG_TRANSPARENT
|
||||
* @see SwapChainFlags#CONFIG_READABLE
|
||||
* @see SwapChain#CONFIG_DEFAULT
|
||||
* @see SwapChain#CONFIG_TRANSPARENT
|
||||
* @see SwapChain#CONFIG_READABLE
|
||||
*
|
||||
*/
|
||||
@NonNull
|
||||
public SwapChain createSwapChain(int width, int height, long flags) {
|
||||
if (width >= 0 && height >= 0) {
|
||||
long nativeSwapChain =
|
||||
nCreateSwapChainHeadless(getNativeObject(), width, height, flags);
|
||||
long nativeSwapChain = nCreateSwapChainHeadless(getNativeObject(), width, height, flags);
|
||||
if (nativeSwapChain == 0) throw new IllegalStateException("Couldn't create SwapChain");
|
||||
return new SwapChain(nativeSwapChain, null);
|
||||
}
|
||||
@@ -715,12 +675,11 @@ public class Engine {
|
||||
*
|
||||
* @param surface a properly initialized {@link NativeSurface}
|
||||
*
|
||||
* @param flags configuration flags, see {@link SwapChainFlags}
|
||||
* @param flags configuration flags, see {@link SwapChain}
|
||||
*
|
||||
* @return a newly created {@link SwapChain} object
|
||||
*
|
||||
* @exception IllegalStateException can be thrown if the {@link SwapChainFlags} couldn't be
|
||||
* created
|
||||
* @exception IllegalStateException can be thrown if the {@link SwapChain} couldn't be created
|
||||
*/
|
||||
@NonNull
|
||||
public SwapChain createSwapChainFromNativeSurface(@NonNull NativeSurface surface, long flags) {
|
||||
@@ -1281,8 +1240,7 @@ public class Engine {
|
||||
private static native void nSetBuilderConfig(long nativeBuilder, long commandBufferSizeMB,
|
||||
long perRenderPassArenaSizeMB, long driverHandleArenaSizeMB,
|
||||
long minCommandBufferSizeMB, long perFrameCommandsSizeMB, long jobSystemThreadCount,
|
||||
long textureUseAfterFreePoolSize, boolean disableParallelShaderCompile,
|
||||
int stereoscopicType, long stereoscopicEyeCount,
|
||||
long stereoscopicEyeCount,
|
||||
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge);
|
||||
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
|
||||
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
|
||||
|
||||
@@ -101,7 +101,7 @@ public class Renderer {
|
||||
/**
|
||||
* Desired frame interval in unit of 1 / DisplayInfo.refreshRate.
|
||||
*/
|
||||
public float interval = 1.0f;
|
||||
public float interval = 1.0f / 60.0f;
|
||||
|
||||
/**
|
||||
* Additional headroom for the GPU as a ratio of the targetFrameTime.
|
||||
|
||||
@@ -68,30 +68,77 @@ public class SwapChain {
|
||||
private final Object mSurface;
|
||||
private long mNativeObject;
|
||||
|
||||
public static final long CONFIG_DEFAULT = 0x0;
|
||||
|
||||
/**
|
||||
* This flag indicates that the <code>SwapChain</code> must be allocated with an
|
||||
* alpha-channel.
|
||||
*/
|
||||
public static final long CONFIG_TRANSPARENT = 0x1;
|
||||
|
||||
/**
|
||||
* This flag indicates that the <code>SwapChain</code> may be used as a source surface
|
||||
* for reading back render results. This config must be set when creating
|
||||
* any <code>SwapChain</code> that will be used as the source for a blit operation.
|
||||
*
|
||||
* @see Renderer#copyFrame
|
||||
*/
|
||||
public static final long CONFIG_READABLE = 0x2;
|
||||
|
||||
/**
|
||||
* Indicates that the native X11 window is an XCB window rather than an XLIB window.
|
||||
* This is ignored on non-Linux platforms and in builds that support only one X11 API.
|
||||
*/
|
||||
public static final long CONFIG_ENABLE_XCB = 0x4;
|
||||
|
||||
/**
|
||||
* Indicates that the SwapChain must automatically perform linear to sRGB encoding.
|
||||
*
|
||||
* This flag is ignored if isSRGBSwapChainSupported() is false.
|
||||
*
|
||||
* When using this flag, post-processing should be disabled.
|
||||
*
|
||||
* @see SwapChain#isSRGBSwapChainSupported
|
||||
* @see View#setPostProcessingEnabled
|
||||
*/
|
||||
public static final long CONFIG_SRGB_COLORSPACE = 0x10;
|
||||
|
||||
/**
|
||||
* Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer.
|
||||
*
|
||||
* This flag is necessary when using View::setStencilBufferEnabled and rendering directly into
|
||||
* the SwapChain (when post-processing is disabled).
|
||||
*
|
||||
* The specific format of the stencil buffer depends on platform support. The following pixel
|
||||
* formats are tried, in order of preference:
|
||||
*
|
||||
* Depth only (without CONFIG_HAS_STENCIL_BUFFER):
|
||||
* - DEPTH32F
|
||||
* - DEPTH24
|
||||
*
|
||||
* Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER):
|
||||
* - DEPTH32F_STENCIL8
|
||||
* - DEPTH24F_STENCIL8
|
||||
*
|
||||
* Note that enabling the stencil buffer may hinder depth precision and should only be used if
|
||||
* necessary.
|
||||
*
|
||||
* @see View#setStencilBufferEnabled
|
||||
* @see View#setPostProcessingEnabled
|
||||
*/
|
||||
public static final long CONFIG_HAS_STENCIL_BUFFER = 0x20;
|
||||
|
||||
SwapChain(long nativeSwapChain, Object surface) {
|
||||
mNativeObject = nativeSwapChain;
|
||||
mSurface = surface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether createSwapChain supports the CONFIG_PROTECTED_CONTENT flag.
|
||||
* Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag.
|
||||
* The default implementation returns false.
|
||||
*
|
||||
* @param engine A reference to the filament Engine
|
||||
* @return true if CONFIG_PROTECTED_CONTENT is supported, false otherwise.
|
||||
* @see SwapChainFlags#CONFIG_PROTECTED_CONTENT
|
||||
*/
|
||||
public static boolean isProtectedContentSupported(@NonNull Engine engine) {
|
||||
return nIsProtectedContentSupported(engine.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether createSwapChain supports the CONFIG_SRGB_COLORSPACE flag.
|
||||
* The default implementation returns false.
|
||||
*
|
||||
* @param engine A reference to the filament Engine
|
||||
* @return true if CONFIG_SRGB_COLORSPACE is supported, false otherwise.
|
||||
* @see SwapChainFlags#CONFIG_SRGB_COLORSPACE
|
||||
* @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise.
|
||||
*/
|
||||
public static boolean isSRGBSwapChainSupported(@NonNull Engine engine) {
|
||||
return nIsSRGBSwapChainSupported(engine.getNativeObject());
|
||||
@@ -139,5 +186,4 @@ public class SwapChain {
|
||||
|
||||
private static native void nSetFrameCompletedCallback(long nativeSwapChain, Object handler, Runnable callback);
|
||||
private static native boolean nIsSRGBSwapChainSupported(long nativeEngine);
|
||||
private static native boolean nIsProtectedContentSupported(long nativeEngine);
|
||||
}
|
||||
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament;
|
||||
|
||||
// Note: SwapChainFlags is kept separate from SwapChain so that UiHelper does not need to depend
|
||||
// on SwapChain. This allows clients to use UiHelper without requiring all of Filament's Java
|
||||
// classes.
|
||||
|
||||
/**
|
||||
* Flags that a <code>SwapChain</code> can be created with to control behavior.
|
||||
*
|
||||
* @see Engine#createSwapChain
|
||||
* @see Engine#createSwapChainFromNativeSurface
|
||||
*/
|
||||
public final class SwapChainFlags {
|
||||
|
||||
public static final long CONFIG_DEFAULT = 0x0;
|
||||
|
||||
/**
|
||||
* This flag indicates that the <code>SwapChain</code> must be allocated with an
|
||||
* alpha-channel.
|
||||
*/
|
||||
public static final long CONFIG_TRANSPARENT = 0x1;
|
||||
|
||||
/**
|
||||
* This flag indicates that the <code>SwapChain</code> may be used as a source surface
|
||||
* for reading back render results. This config must be set when creating
|
||||
* any <code>SwapChain</code> that will be used as the source for a blit operation.
|
||||
*
|
||||
* @see Renderer#copyFrame
|
||||
*/
|
||||
public static final long CONFIG_READABLE = 0x2;
|
||||
|
||||
/**
|
||||
* Indicates that the native X11 window is an XCB window rather than an XLIB window.
|
||||
* This is ignored on non-Linux platforms and in builds that support only one X11 API.
|
||||
*/
|
||||
public static final long CONFIG_ENABLE_XCB = 0x4;
|
||||
|
||||
/**
|
||||
* Indicates that the SwapChain must automatically perform linear to sRGB encoding.
|
||||
*
|
||||
* This flag is ignored if isSRGBSwapChainSupported() is false.
|
||||
*
|
||||
* When using this flag, post-processing should be disabled.
|
||||
*
|
||||
* @see SwapChain#isSRGBSwapChainSupported
|
||||
* @see View#setPostProcessingEnabled
|
||||
*/
|
||||
public static final long CONFIG_SRGB_COLORSPACE = 0x10;
|
||||
|
||||
/**
|
||||
* Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer.
|
||||
*
|
||||
* This flag is necessary when using View::setStencilBufferEnabled and rendering directly into
|
||||
* the SwapChain (when post-processing is disabled).
|
||||
*
|
||||
* The specific format of the stencil buffer depends on platform support. The following pixel
|
||||
* formats are tried, in order of preference:
|
||||
*
|
||||
* Depth only (without CONFIG_HAS_STENCIL_BUFFER):
|
||||
* - DEPTH32F
|
||||
* - DEPTH24
|
||||
*
|
||||
* Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER):
|
||||
* - DEPTH32F_STENCIL8
|
||||
* - DEPTH24F_STENCIL8
|
||||
*
|
||||
* Note that enabling the stencil buffer may hinder depth precision and should only be used if
|
||||
* necessary.
|
||||
*
|
||||
* @see View#setStencilBufferEnabled
|
||||
* @see View#setPostProcessingEnabled
|
||||
*/
|
||||
public static final long CONFIG_HAS_STENCIL_BUFFER = 0x20;
|
||||
|
||||
/**
|
||||
* The SwapChain contains protected content. Only supported when isProtectedContentSupported()
|
||||
* is true.
|
||||
*/
|
||||
public static final long CONFIG_PROTECTED_CONTENT = 0x40;
|
||||
}
|
||||
|
||||
@@ -16,14 +16,12 @@ package com.google.android.filament;
|
||||
* <li>Configurable tone mapping operators</li>
|
||||
* <ul>
|
||||
* <li>GenericToneMapper</li>
|
||||
* <li>AgXToneMapper</li>
|
||||
* </ul>
|
||||
* <li>Fixed-aesthetic tone mapping operators</li>
|
||||
* <ul>
|
||||
* <li>ACESToneMapper</li>
|
||||
* <li>ACESLegacyToneMapper</li>
|
||||
* <li>FilmicToneMapper</li>
|
||||
* <li>PBRNeutralToneMapper</li>
|
||||
* </ul>
|
||||
* <li>Debug/validation tone mapping operators</li>
|
||||
* <ul>
|
||||
@@ -102,21 +100,11 @@ public class ToneMapper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Khronos PBR Neutral tone mapping operator. This tone mapper was designed
|
||||
* to preserve the appearance of materials across lighting conditions while
|
||||
* avoiding artifacts in the highlights in high dynamic range conditions.
|
||||
*/
|
||||
public static class PBRNeutralToneMapper extends ToneMapper {
|
||||
public PBRNeutralToneMapper() {
|
||||
super(nCreatePBRNeutralToneMapper());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AgX tone mapping operator.
|
||||
*/
|
||||
public static class Agx extends ToneMapper {
|
||||
|
||||
public enum AgxLook {
|
||||
/**
|
||||
* Base contrast with no look applied
|
||||
@@ -245,7 +233,6 @@ public class ToneMapper {
|
||||
private static native long nCreateACESToneMapper();
|
||||
private static native long nCreateACESLegacyToneMapper();
|
||||
private static native long nCreateFilmicToneMapper();
|
||||
private static native long nCreatePBRNeutralToneMapper();
|
||||
private static native long nCreateAgxToneMapper(int look);
|
||||
private static native long nCreateGenericToneMapper(
|
||||
float contrast, float midGrayIn, float midGrayOut, float hdrMax);
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
|
||||
import com.google.android.filament.SwapChainFlags;
|
||||
import com.google.android.filament.SwapChain;
|
||||
|
||||
/**
|
||||
* UiHelper is a simple class that can manage either a SurfaceView, TextureView, or a SurfaceHolder
|
||||
@@ -538,7 +538,7 @@ public class UiHelper {
|
||||
* the options set on this UiHelper.
|
||||
*/
|
||||
public long getSwapChainFlags() {
|
||||
return isOpaque() ? SwapChainFlags.CONFIG_DEFAULT : SwapChainFlags.CONFIG_TRANSPARENT;
|
||||
return isOpaque() ? SwapChain.CONFIG_DEFAULT : SwapChain.CONFIG_TRANSPARENT;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.51.0
|
||||
VERSION_NAME=1.50.2
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
defaultConfig {
|
||||
applicationId "com.google.android.filament.gltf"
|
||||
minSdkVersion versions.minSdk
|
||||
minSdkVersion 19
|
||||
targetSdkVersion versions.targetSdk
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
26.1.10909125
|
||||
25.1.8937393
|
||||
@@ -21,7 +21,7 @@ set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_VERSION 1)
|
||||
|
||||
# android
|
||||
set(API_LEVEL 21)
|
||||
set(API_LEVEL 19)
|
||||
|
||||
# architecture
|
||||
set(ARCH armv7a-linux-androideabi)
|
||||
|
||||
@@ -21,7 +21,7 @@ set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_VERSION 1)
|
||||
|
||||
# android
|
||||
set(API_LEVEL 21)
|
||||
set(API_LEVEL 19)
|
||||
|
||||
# architecture
|
||||
set(ARCH i686-linux-android)
|
||||
|
||||
+22
-24
@@ -66,7 +66,6 @@ set(SRCS
|
||||
src/Froxelizer.cpp
|
||||
src/Frustum.cpp
|
||||
src/HwRenderPrimitiveFactory.cpp
|
||||
src/HwVertexBufferInfoFactory.cpp
|
||||
src/IndexBuffer.cpp
|
||||
src/IndirectLight.cpp
|
||||
src/InstanceBuffer.cpp
|
||||
@@ -138,7 +137,6 @@ set(SRCS
|
||||
|
||||
set(PRIVATE_HDRS
|
||||
src/Allocators.h
|
||||
src/Bimap.h
|
||||
src/BufferPoolAllocator.h
|
||||
src/ColorSpaceUtils.h
|
||||
src/Culler.h
|
||||
@@ -149,7 +147,6 @@ set(PRIVATE_HDRS
|
||||
src/FrameSkipper.h
|
||||
src/Froxelizer.h
|
||||
src/HwRenderPrimitiveFactory.h
|
||||
src/HwVertexBufferInfoFactory.h
|
||||
src/Intersections.h
|
||||
src/MaterialParser.h
|
||||
src/PerViewUniforms.h
|
||||
@@ -215,7 +212,6 @@ set(MATERIAL_SRCS
|
||||
src/materials/antiAliasing/fxaa.mat
|
||||
src/materials/antiAliasing/taa.mat
|
||||
src/materials/blitLow.mat
|
||||
src/materials/blitArray.mat
|
||||
src/materials/bloom/bloomDownsample.mat
|
||||
src/materials/bloom/bloomDownsample2x.mat
|
||||
src/materials/bloom/bloomDownsample9.mat
|
||||
@@ -252,8 +248,8 @@ set(MATERIAL_SRCS
|
||||
)
|
||||
|
||||
set(MATERIAL_FL0_SRCS
|
||||
src/materials/defaultMaterial.mat
|
||||
src/materials/skybox.mat
|
||||
src/materials/defaultMaterial0.mat
|
||||
src/materials/skybox0.mat
|
||||
)
|
||||
|
||||
# Embed the binary resource blob for materials.
|
||||
@@ -316,30 +312,32 @@ foreach (mat_src ${MATERIAL_SRCS})
|
||||
get_filename_component(fullname "${mat_src}" ABSOLUTE)
|
||||
set(output_path "${MATERIAL_DIR}/${localname}.filamat")
|
||||
|
||||
list(FIND MATERIAL_FL0_SRCS ${mat_src} index)
|
||||
if (${index} GREATER -1 AND FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
string(REGEX REPLACE "[.]filamat$" "0.filamat" output_path0 ${output_path})
|
||||
add_custom_command(
|
||||
OUTPUT ${output_path} ${output_path0}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -PfeatureLevel=0 -o ${output_path0} ${fullname}
|
||||
MAIN_DEPENDENCY ${fullname}
|
||||
DEPENDS matc
|
||||
COMMENT "Compiling material ${fullname} to ${output_path} and ${output_path0}"
|
||||
)
|
||||
list(APPEND MATERIAL_BINS ${output_path0})
|
||||
else ()
|
||||
add_custom_command(
|
||||
OUTPUT ${output_path}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname}
|
||||
MAIN_DEPENDENCY ${fullname}
|
||||
DEPENDS matc
|
||||
COMMENT "Compiling material ${mat_src} to ${output_path}"
|
||||
)
|
||||
list(APPEND MATERIAL_BINS ${output_path})
|
||||
endforeach()
|
||||
|
||||
if (FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
foreach (mat_src ${MATERIAL_FL0_SRCS})
|
||||
get_filename_component(localname "${mat_src}" NAME_WE)
|
||||
get_filename_component(fullname "${mat_src}" ABSOLUTE)
|
||||
set(output_path "${MATERIAL_DIR}/${localname}.filamat")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${output_path}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname}
|
||||
MAIN_DEPENDENCY ${fullname}
|
||||
DEPENDS matc
|
||||
COMMENT "Compiling material ${fullname} to ${output_path}"
|
||||
COMMENT "Compiling material ${mat_src} to ${output_path}"
|
||||
)
|
||||
endif ()
|
||||
|
||||
list(APPEND MATERIAL_BINS ${output_path})
|
||||
endforeach()
|
||||
list(APPEND MATERIAL_BINS ${output_path})
|
||||
endforeach ()
|
||||
endif ()
|
||||
|
||||
# Additional dependencies on included files for materials
|
||||
|
||||
|
||||
@@ -166,8 +166,6 @@ endif()
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatform.h
|
||||
src/vulkan/caching/VulkanDescriptorSet.cpp
|
||||
src/vulkan/caching/VulkanDescriptorSet.h
|
||||
src/vulkan/platform/VulkanPlatform.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
|
||||
|
||||
@@ -81,14 +81,7 @@ static constexpr uint64_t SWAP_CHAIN_CONFIG_SRGB_COLORSPACE = 0x10;
|
||||
/**
|
||||
* Indicates that the SwapChain should also contain a stencil component.
|
||||
*/
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_HAS_STENCIL_BUFFER = 0x20;
|
||||
static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = SWAP_CHAIN_CONFIG_HAS_STENCIL_BUFFER;
|
||||
|
||||
/**
|
||||
* The SwapChain contains protected content. Currently only supported by OpenGLPlatform and
|
||||
* only when OpenGLPlatform::isProtectedContextSupported() is true.
|
||||
*/
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_PROTECTED_CONTENT = 0x40;
|
||||
static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = 0x20;
|
||||
|
||||
|
||||
static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
@@ -136,12 +129,6 @@ enum class Backend : uint8_t {
|
||||
NOOP = 4, //!< Selects the no-op driver for testing purposes.
|
||||
};
|
||||
|
||||
enum class TimerQueryResult : int8_t {
|
||||
ERROR = -1, // an error occurred, result won't be available
|
||||
NOT_READY = 0, // result to ready yet
|
||||
AVAILABLE = 1, // result is available
|
||||
};
|
||||
|
||||
static constexpr const char* backendToString(Backend backend) {
|
||||
switch (backend) {
|
||||
case Backend::NOOP:
|
||||
@@ -672,17 +659,16 @@ enum class TextureFormat : uint16_t {
|
||||
};
|
||||
|
||||
//! Bitmask describing the intended Texture Usage
|
||||
enum class TextureUsage : uint16_t {
|
||||
NONE = 0x0000,
|
||||
COLOR_ATTACHMENT = 0x0001, //!< Texture can be used as a color attachment
|
||||
DEPTH_ATTACHMENT = 0x0002, //!< Texture can be used as a depth attachment
|
||||
STENCIL_ATTACHMENT = 0x0004, //!< Texture can be used as a stencil attachment
|
||||
UPLOADABLE = 0x0008, //!< Data can be uploaded into this texture (default)
|
||||
SAMPLEABLE = 0x0010, //!< Texture can be sampled (default)
|
||||
SUBPASS_INPUT = 0x0020, //!< Texture can be used as a subpass input
|
||||
BLIT_SRC = 0x0040, //!< Texture can be used the source of a blit()
|
||||
BLIT_DST = 0x0080, //!< Texture can be used the destination of a blit()
|
||||
PROTECTED = 0x0100, //!< Texture can be used the destination of a blit()
|
||||
enum class TextureUsage : uint8_t {
|
||||
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
|
||||
};
|
||||
|
||||
@@ -1186,27 +1172,11 @@ struct StencilState {
|
||||
|
||||
//! Stencil operations for front-facing polygons
|
||||
StencilOperations front = {
|
||||
.stencilFunc = StencilFunction::A,
|
||||
.stencilOpStencilFail = StencilOperation::KEEP,
|
||||
.padding0 = 0,
|
||||
.stencilOpDepthFail = StencilOperation::KEEP,
|
||||
.stencilOpDepthStencilPass = StencilOperation::KEEP,
|
||||
.padding1 = 0,
|
||||
.ref = 0,
|
||||
.readMask = 0xff,
|
||||
.writeMask = 0xff };
|
||||
.stencilFunc = StencilFunction::A, .ref = 0, .readMask = 0xff, .writeMask = 0xff };
|
||||
|
||||
//! Stencil operations for back-facing polygons
|
||||
StencilOperations back = {
|
||||
.stencilFunc = StencilFunction::A,
|
||||
.stencilOpStencilFail = StencilOperation::KEEP,
|
||||
.padding0 = 0,
|
||||
.stencilOpDepthFail = StencilOperation::KEEP,
|
||||
.stencilOpDepthStencilPass = StencilOperation::KEEP,
|
||||
.padding1 = 0,
|
||||
.ref = 0,
|
||||
.readMask = 0xff,
|
||||
.writeMask = 0xff };
|
||||
.stencilFunc = StencilFunction::A, .ref = 0, .readMask = 0xff, .writeMask = 0xff };
|
||||
|
||||
//! Whether stencil-buffer writes are enabled
|
||||
bool stencilWrite = false;
|
||||
@@ -1242,14 +1212,6 @@ enum class Workaround : uint16_t {
|
||||
DISABLE_THREAD_AFFINITY
|
||||
};
|
||||
|
||||
//! The type of technique for stereoscopic rendering
|
||||
enum class StereoscopicType : uint8_t {
|
||||
// Stereoscopic rendering is performed using instanced rendering technique.
|
||||
INSTANCED,
|
||||
// Stereoscopic rendering is performed using the multiview feature from the graphics backend.
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFlags>
|
||||
|
||||
@@ -39,7 +39,6 @@ struct HwStream;
|
||||
struct HwSwapChain;
|
||||
struct HwTexture;
|
||||
struct HwTimerQuery;
|
||||
struct HwVertexBufferInfo;
|
||||
struct HwVertexBuffer;
|
||||
|
||||
/*
|
||||
@@ -63,6 +62,14 @@ public:
|
||||
// clear the handle, this doesn't free associated resources
|
||||
void clear() noexcept { object = nullid; }
|
||||
|
||||
// compare handles
|
||||
bool operator==(const HandleBase& rhs) const noexcept { return object == rhs.object; }
|
||||
bool operator!=(const HandleBase& rhs) const noexcept { return object != rhs.object; }
|
||||
bool operator<(const HandleBase& rhs) const noexcept { return object < rhs.object; }
|
||||
bool operator<=(const HandleBase& rhs) const noexcept { return object <= rhs.object; }
|
||||
bool operator>(const HandleBase& rhs) const noexcept { return object > rhs.object; }
|
||||
bool operator>=(const HandleBase& rhs) const noexcept { return object >= rhs.object; }
|
||||
|
||||
// get this handle's handleId
|
||||
HandleId getId() const noexcept { return object; }
|
||||
|
||||
@@ -94,14 +101,6 @@ struct Handle : public HandleBase {
|
||||
|
||||
explicit Handle(HandleId id) noexcept : HandleBase(id) { }
|
||||
|
||||
// compare handles of the same type
|
||||
bool operator==(const Handle& rhs) const noexcept { return getId() == rhs.getId(); }
|
||||
bool operator!=(const Handle& rhs) const noexcept { return getId() != rhs.getId(); }
|
||||
bool operator<(const Handle& rhs) const noexcept { return getId() < rhs.getId(); }
|
||||
bool operator<=(const Handle& rhs) const noexcept { return getId() <= rhs.getId(); }
|
||||
bool operator>(const Handle& rhs) const noexcept { return getId() > rhs.getId(); }
|
||||
bool operator>=(const Handle& rhs) const noexcept { return getId() >= rhs.getId(); }
|
||||
|
||||
// type-safe Handle cast
|
||||
template<typename B, typename = std::enable_if_t<std::is_base_of<T, B>::value> >
|
||||
Handle(Handle<B> const& base) noexcept : HandleBase(base) { } // NOLINT(hicpp-explicit-conversions,google-explicit-constructor)
|
||||
@@ -115,19 +114,18 @@ private:
|
||||
|
||||
// Types used by the command stream
|
||||
// (we use this renaming because the macro-system doesn't deal well with "<" and ">")
|
||||
using BufferObjectHandle = Handle<HwBufferObject>;
|
||||
using FenceHandle = Handle<HwFence>;
|
||||
using IndexBufferHandle = Handle<HwIndexBuffer>;
|
||||
using ProgramHandle = Handle<HwProgram>;
|
||||
using RenderPrimitiveHandle = Handle<HwRenderPrimitive>;
|
||||
using RenderTargetHandle = Handle<HwRenderTarget>;
|
||||
using SamplerGroupHandle = Handle<HwSamplerGroup>;
|
||||
using StreamHandle = Handle<HwStream>;
|
||||
using SwapChainHandle = Handle<HwSwapChain>;
|
||||
using TextureHandle = Handle<HwTexture>;
|
||||
using TimerQueryHandle = Handle<HwTimerQuery>;
|
||||
using VertexBufferHandle = Handle<HwVertexBuffer>;
|
||||
using VertexBufferInfoHandle = Handle<HwVertexBufferInfo>;
|
||||
using BufferObjectHandle = Handle<HwBufferObject>;
|
||||
using FenceHandle = Handle<HwFence>;
|
||||
using IndexBufferHandle = Handle<HwIndexBuffer>;
|
||||
using ProgramHandle = Handle<HwProgram>;
|
||||
using RenderPrimitiveHandle = Handle<HwRenderPrimitive>;
|
||||
using RenderTargetHandle = Handle<HwRenderTarget>;
|
||||
using SamplerGroupHandle = Handle<HwSamplerGroup>;
|
||||
using StreamHandle = Handle<HwStream>;
|
||||
using SwapChainHandle = Handle<HwSwapChain>;
|
||||
using TextureHandle = Handle<HwTexture>;
|
||||
using TimerQueryHandle = Handle<HwTimerQuery>;
|
||||
using VertexBufferHandle = Handle<HwVertexBuffer>;
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
@@ -29,13 +29,11 @@ namespace filament::backend {
|
||||
//! \privatesection
|
||||
|
||||
struct PipelineState {
|
||||
Handle<HwProgram> program; // 4
|
||||
Handle<HwVertexBufferInfo> vertexBufferInfo; // 4
|
||||
RasterState rasterState; // 4
|
||||
StencilState stencilState; // 12
|
||||
PolygonOffset polygonOffset; // 8
|
||||
PrimitiveType primitiveType = PrimitiveType::TRIANGLES; // 1
|
||||
uint8_t padding[3] = {}; // 3
|
||||
Handle<HwProgram> program;
|
||||
RasterState rasterState;
|
||||
StencilState stencilState;
|
||||
PolygonOffset polygonOffset;
|
||||
Viewport scissor{ 0, 0, (uint32_t)INT32_MAX, (uint32_t)INT32_MAX };
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL and Metal backends.
|
||||
* Currently only honored by the GL backend.
|
||||
*/
|
||||
bool disableParallelShaderCompile = false;
|
||||
};
|
||||
|
||||
@@ -22,9 +22,7 @@
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Invocable.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
@@ -64,22 +62,6 @@ public:
|
||||
*/
|
||||
virtual void terminate() noexcept = 0;
|
||||
|
||||
/**
|
||||
* Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag.
|
||||
* The default implementation returns false.
|
||||
*
|
||||
* @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise.
|
||||
*/
|
||||
virtual bool isSRGBSwapChainSupported() const noexcept;
|
||||
|
||||
/**
|
||||
* Return whether protected contexts are supported by this backend.
|
||||
* If protected context are supported, the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag can be
|
||||
* used when creating a SwapChain.
|
||||
* The default implementation returns false.
|
||||
*/
|
||||
virtual bool isProtectedContextSupported() const noexcept;
|
||||
|
||||
/**
|
||||
* Called by the driver to create a SwapChain for this driver.
|
||||
*
|
||||
@@ -92,6 +74,14 @@ public:
|
||||
virtual SwapChain* UTILS_NONNULL createSwapChain(
|
||||
void* UTILS_NULLABLE nativeWindow, uint64_t flags) noexcept = 0;
|
||||
|
||||
/**
|
||||
* Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag.
|
||||
* The default implementation returns false.
|
||||
*
|
||||
* @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise.
|
||||
*/
|
||||
virtual bool isSRGBSwapChainSupported() const noexcept;
|
||||
|
||||
/**
|
||||
* Called by the driver create a headless SwapChain.
|
||||
*
|
||||
@@ -141,26 +131,6 @@ public:
|
||||
SwapChain* UTILS_NONNULL drawSwapChain,
|
||||
SwapChain* UTILS_NONNULL readSwapChain) noexcept = 0;
|
||||
|
||||
/**
|
||||
* Called by the driver to make the OpenGL context active on the calling thread and bind
|
||||
* the drawSwapChain to the default render target (FBO) created with createDefaultRenderTarget.
|
||||
* The context used is either the default context or the protected context. When a context
|
||||
* change is necessary, the preContextChange and postContextChange callbacks are called,
|
||||
* before and after the context change respectively. postContextChange is given the index
|
||||
* of the new context (0 for default and 1 for protected).
|
||||
* The default implementation just calls makeCurrent(SwapChain*, SwapChain*).
|
||||
*
|
||||
* @param drawSwapChain SwapChain to draw to. It must be bound to the default FBO.
|
||||
* @param readSwapChain SwapChain to read from (for operation like `glBlitFramebuffer`)
|
||||
* @param preContextChange called before the context changes
|
||||
* @param postContextChange called after the context changes
|
||||
*/
|
||||
virtual void makeCurrent(
|
||||
SwapChain* UTILS_NONNULL drawSwapChain,
|
||||
SwapChain* UTILS_NONNULL readSwapChain,
|
||||
utils::Invocable<void()> preContextChange,
|
||||
utils::Invocable<void(size_t index)> postContextChange) noexcept;
|
||||
|
||||
/**
|
||||
* Called by the driver once the current frame finishes drawing. Typically, this should present
|
||||
* the drawSwapChain. This is for example where `eglMakeCurrent()` would be called.
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
#include <EGL/eglext.h>
|
||||
#include <EGL/eglplatform.h>
|
||||
|
||||
#include <utils/Invocable.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -43,11 +41,15 @@ class PlatformEGL : public OpenGLPlatform {
|
||||
public:
|
||||
|
||||
PlatformEGL() noexcept;
|
||||
bool isExtraContextSupported() const noexcept override;
|
||||
void createContext(bool shared) override;
|
||||
void releaseContext() noexcept override;
|
||||
|
||||
// Return true if we're on an OpenGL platform (as opposed to OpenGL ES). false by default.
|
||||
virtual bool isOpenGL() const noexcept;
|
||||
|
||||
protected:
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Helper for EGL configs and attributes parameters
|
||||
|
||||
@@ -87,23 +89,13 @@ protected:
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// OpenGLPlatform Interface
|
||||
|
||||
bool isExtraContextSupported() const noexcept override;
|
||||
void createContext(bool shared) override;
|
||||
void releaseContext() noexcept override;
|
||||
|
||||
void terminate() noexcept override;
|
||||
|
||||
bool isProtectedContextSupported() const noexcept override;
|
||||
|
||||
bool isSRGBSwapChainSupported() const noexcept override;
|
||||
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
|
||||
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
|
||||
void destroySwapChain(SwapChain* swapChain) noexcept override;
|
||||
|
||||
void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override;
|
||||
void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain,
|
||||
utils::Invocable<void()> preContextChange,
|
||||
utils::Invocable<void(size_t index)> postContextChange) noexcept override;
|
||||
void commit(SwapChain* swapChain) noexcept override;
|
||||
|
||||
bool canCreateFence() noexcept override;
|
||||
@@ -130,22 +122,15 @@ protected:
|
||||
static void clearGlError() noexcept;
|
||||
|
||||
/**
|
||||
* Always use this instead of eglMakeCurrent(), as it tracks some state.
|
||||
* Always use this instead of eglMakeCurrent().
|
||||
*/
|
||||
EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept;
|
||||
|
||||
/**
|
||||
* Returns true if the swapchain is protected
|
||||
*/
|
||||
static bool isSwapChainProtected(Platform::SwapChain const* swapChain) noexcept;
|
||||
|
||||
// TODO: this should probably use getters instead.
|
||||
EGLDisplay mEGLDisplay = EGL_NO_DISPLAY;
|
||||
EGLContext mEGLContext = EGL_NO_CONTEXT;
|
||||
EGLContext mEGLContextProtected = EGL_NO_CONTEXT;
|
||||
EGLSurface mCurrentDrawSurface = EGL_NO_SURFACE;
|
||||
EGLSurface mCurrentReadSurface = EGL_NO_SURFACE;
|
||||
EGLContext mCurrentContext = EGL_NO_CONTEXT;
|
||||
EGLSurface mEGLDummySurface = EGL_NO_SURFACE;
|
||||
// mEGLConfig is valid only if ext.egl.KHR_no_config_context is false
|
||||
EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR;
|
||||
@@ -163,26 +148,13 @@ protected:
|
||||
bool KHR_gl_colorspace = false;
|
||||
bool KHR_no_config_context = false;
|
||||
bool KHR_surfaceless_context = false;
|
||||
bool EXT_protected_content = false;
|
||||
} egl;
|
||||
} ext;
|
||||
|
||||
struct SwapChainEGL : public Platform::SwapChain {
|
||||
EGLSurface sur = EGL_NO_SURFACE;
|
||||
Config attribs{};
|
||||
EGLNativeWindowType nativeWindow{};
|
||||
EGLConfig config{};
|
||||
uint64_t flags{};
|
||||
};
|
||||
|
||||
void initializeGlExtensions() noexcept;
|
||||
|
||||
protected:
|
||||
EGLConfig findSwapChainConfig(uint64_t flags, bool window, bool pbuffer) const;
|
||||
|
||||
private:
|
||||
EGLBoolean makeCurrent(EGLContext context,
|
||||
EGLSurface drawSurface, EGLSurface readSurface) noexcept;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -167,14 +167,11 @@ DECL_DRIVER_API_0(resetState)
|
||||
* -----------------------
|
||||
*/
|
||||
|
||||
DECL_DRIVER_API_R_N(backend::VertexBufferInfoHandle, createVertexBufferInfo,
|
||||
DECL_DRIVER_API_R_N(backend::VertexBufferHandle, createVertexBuffer,
|
||||
uint8_t, bufferCount,
|
||||
uint8_t, attributeCount,
|
||||
backend::AttributeArray, attributes)
|
||||
|
||||
DECL_DRIVER_API_R_N(backend::VertexBufferHandle, createVertexBuffer,
|
||||
uint32_t, vertexCount,
|
||||
backend::VertexBufferInfoHandle, vbih)
|
||||
backend::AttributeArray, attributes)
|
||||
|
||||
DECL_DRIVER_API_R_N(backend::IndexBufferHandle, createIndexBuffer,
|
||||
backend::ElementType, elementType,
|
||||
@@ -227,7 +224,11 @@ DECL_DRIVER_API_R_N(backend::SamplerGroupHandle, createSamplerGroup,
|
||||
DECL_DRIVER_API_R_N(backend::RenderPrimitiveHandle, createRenderPrimitive,
|
||||
backend::VertexBufferHandle, vbh,
|
||||
backend::IndexBufferHandle, ibh,
|
||||
backend::PrimitiveType, pt)
|
||||
backend::PrimitiveType, pt,
|
||||
uint32_t, offset,
|
||||
uint32_t, minIndex,
|
||||
uint32_t, maxIndex,
|
||||
uint32_t, count)
|
||||
|
||||
DECL_DRIVER_API_R_N(backend::ProgramHandle, createProgram,
|
||||
backend::Program&&, program)
|
||||
@@ -263,7 +264,6 @@ DECL_DRIVER_API_R_0(backend::TimerQueryHandle, createTimerQuery)
|
||||
*/
|
||||
|
||||
DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh)
|
||||
DECL_DRIVER_API_N(destroyVertexBufferInfo,backend::VertexBufferInfoHandle, vbih)
|
||||
DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh)
|
||||
DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh)
|
||||
DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph)
|
||||
@@ -298,16 +298,14 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchMultiSampleSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedContentSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isStereoSupported, backend::StereoscopicType, stereoscopicType)
|
||||
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(bool, isProtectedTexturesSupported)
|
||||
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_N(void, setupExternalImage, void*, image)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(backend::TimerQueryResult, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isWorkaroundNeeded, backend::Workaround, workaround)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(backend::FeatureLevel, getFeatureLevel)
|
||||
|
||||
@@ -493,32 +491,15 @@ DECL_DRIVER_API_N(blit,
|
||||
math::uint2, srcOrigin,
|
||||
math::uint2, size)
|
||||
|
||||
DECL_DRIVER_API_N(bindPipeline,
|
||||
backend::PipelineState, state)
|
||||
|
||||
DECL_DRIVER_API_N(bindRenderPrimitive,
|
||||
backend::RenderPrimitiveHandle, rph)
|
||||
|
||||
DECL_DRIVER_API_N(draw2,
|
||||
uint32_t, indexOffset,
|
||||
uint32_t, indexCount,
|
||||
uint32_t, instanceCount)
|
||||
|
||||
DECL_DRIVER_API_N(draw,
|
||||
backend::PipelineState, state,
|
||||
backend::RenderPrimitiveHandle, rph,
|
||||
uint32_t, indexOffset,
|
||||
uint32_t, indexCount,
|
||||
uint32_t, instanceCount)
|
||||
|
||||
|
||||
DECL_DRIVER_API_N(dispatchCompute,
|
||||
backend::ProgramHandle, program,
|
||||
math::uint3, workGroupCount)
|
||||
|
||||
DECL_DRIVER_API_N(scissor,
|
||||
Viewport, scissor)
|
||||
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
|
||||
@@ -24,31 +24,35 @@
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define HandleAllocatorGL HandleAllocator<32, 64, 136> // ~4520 / pool / MiB
|
||||
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
|
||||
#define HandleAllocatorMTL HandleAllocator<32, 48, 552> // ~1660 / pool / MiB
|
||||
#if !defined(NDEBUG) && UTILS_HAS_RTTI
|
||||
# define HANDLE_TYPE_SAFETY 1
|
||||
#else
|
||||
# define HANDLE_TYPE_SAFETY 0
|
||||
#endif
|
||||
|
||||
#define HandleAllocatorGL HandleAllocator<16, 64, 208>
|
||||
#define HandleAllocatorVK HandleAllocator<16, 64, 880>
|
||||
#define HandleAllocatorMTL HandleAllocator<16, 64, 584>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/*
|
||||
* A utility class to efficiently allocate and manage Handle<>
|
||||
*/
|
||||
template<size_t P0, size_t P1, size_t P2>
|
||||
template <size_t P0, size_t P1, size_t P2>
|
||||
class HandleAllocator {
|
||||
public:
|
||||
|
||||
HandleAllocator(const char* name, size_t size) noexcept;
|
||||
HandleAllocator(HandleAllocator const& rhs) = delete;
|
||||
HandleAllocator& operator=(HandleAllocator const& rhs) = delete;
|
||||
@@ -66,9 +70,14 @@ public:
|
||||
*/
|
||||
template<typename D, typename ... ARGS>
|
||||
Handle<D> allocateAndConstruct(ARGS&& ... args) noexcept {
|
||||
Handle<D> h{ allocateHandle<D>() };
|
||||
Handle<D> h{ allocateHandle<sizeof(D)>() };
|
||||
D* addr = handle_cast<D*>(h);
|
||||
new(addr) D(std::forward<ARGS>(args)...);
|
||||
#if HANDLE_TYPE_SAFETY
|
||||
mLock.lock();
|
||||
mHandleTypeId[addr] = typeid(D).name();
|
||||
mLock.unlock();
|
||||
#endif
|
||||
return h;
|
||||
}
|
||||
|
||||
@@ -84,7 +93,13 @@ public:
|
||||
*/
|
||||
template<typename D>
|
||||
Handle<D> allocate() noexcept {
|
||||
Handle<D> h{ allocateHandle<D>() };
|
||||
Handle<D> h{ allocateHandle<sizeof(D)>() };
|
||||
#if HANDLE_TYPE_SAFETY
|
||||
D* addr = handle_cast<D*>(h);
|
||||
mLock.lock();
|
||||
mHandleTypeId[addr] = typeid(D).name();
|
||||
mLock.unlock();
|
||||
#endif
|
||||
return h;
|
||||
}
|
||||
|
||||
@@ -101,10 +116,17 @@ public:
|
||||
assert_invariant(handle);
|
||||
D* addr = handle_cast<D*>(const_cast<Handle<B>&>(handle));
|
||||
assert_invariant(addr);
|
||||
|
||||
// currently we implement construct<> with dtor+ctor, we could use operator= also
|
||||
// but all our dtors are trivial, ~D() is actually a noop.
|
||||
addr->~D();
|
||||
new(addr) D(std::forward<ARGS>(args)...);
|
||||
|
||||
#if HANDLE_TYPE_SAFETY
|
||||
mLock.lock();
|
||||
mHandleTypeId[addr] = typeid(D).name();
|
||||
mLock.unlock();
|
||||
#endif
|
||||
return addr;
|
||||
}
|
||||
|
||||
@@ -121,6 +143,12 @@ public:
|
||||
D* addr = handle_cast<D*>(const_cast<Handle<B>&>(handle));
|
||||
assert_invariant(addr);
|
||||
new(addr) D(std::forward<ARGS>(args)...);
|
||||
|
||||
#if HANDLE_TYPE_SAFETY
|
||||
mLock.lock();
|
||||
mHandleTypeId[addr] = typeid(D).name();
|
||||
mLock.unlock();
|
||||
#endif
|
||||
return addr;
|
||||
}
|
||||
|
||||
@@ -136,8 +164,19 @@ public:
|
||||
void deallocate(Handle<B>& handle, D const* p) noexcept {
|
||||
// allow to destroy the nullptr, similarly to operator delete
|
||||
if (p) {
|
||||
#if HANDLE_TYPE_SAFETY
|
||||
mLock.lock();
|
||||
auto typeId = mHandleTypeId[p];
|
||||
mHandleTypeId.erase(p);
|
||||
mLock.unlock();
|
||||
if (UTILS_UNLIKELY(typeId != typeid(D).name())) {
|
||||
utils::slog.e << "Destroying handle " << handle.getId() << ", type " << typeid(D).name()
|
||||
<< ", but handle's actual type is " << typeId << utils::io::endl;
|
||||
std::terminate();
|
||||
}
|
||||
#endif
|
||||
p->~D();
|
||||
deallocateHandle<D>(handle.getId());
|
||||
deallocateHandle<sizeof(D)>(handle.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,17 +204,7 @@ public:
|
||||
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
|
||||
handle_cast(Handle<B>& handle) noexcept {
|
||||
assert_invariant(handle);
|
||||
auto [p, tag] = handleToPointer(handle.getId());
|
||||
|
||||
if (isPoolHandle(handle.getId())) {
|
||||
// check for use after free
|
||||
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
|
||||
auto const pNode = static_cast<typename Allocator::Node*>(p);
|
||||
uint8_t const expectedAge = pNode[-1].age;
|
||||
ASSERT_POSTCONDITION(expectedAge == age,
|
||||
"use-after-free of Handle with id=%d", handle.getId());
|
||||
}
|
||||
|
||||
void* const p = handleToPointer(handle.getId());
|
||||
return static_cast<Dp>(p);
|
||||
}
|
||||
|
||||
@@ -190,57 +219,29 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
template<typename D>
|
||||
static constexpr size_t getBucketSize() noexcept {
|
||||
if constexpr (sizeof(D) <= P0) { return P0; }
|
||||
if constexpr (sizeof(D) <= P1) { return P1; }
|
||||
static_assert(sizeof(D) <= P2);
|
||||
return P2;
|
||||
}
|
||||
|
||||
// template <int P0, int P1, int P2>
|
||||
class Allocator {
|
||||
friend class HandleAllocator;
|
||||
static constexpr size_t MIN_ALIGNMENT = alignof(std::max_align_t);
|
||||
struct Node { uint8_t age; };
|
||||
// Note: using the `extra` parameter of PoolAllocator<>, even with a 1-byte structure,
|
||||
// generally increases all pool allocations by 8-bytes because of alignment restrictions.
|
||||
template<size_t SIZE>
|
||||
using Pool = utils::PoolAllocator<SIZE, MIN_ALIGNMENT, sizeof(Node)>;
|
||||
Pool<P0> mPool0;
|
||||
Pool<P1> mPool1;
|
||||
Pool<P2> mPool2;
|
||||
utils::PoolAllocator<P0, 16> mPool0;
|
||||
utils::PoolAllocator<P1, 16> mPool1;
|
||||
utils::PoolAllocator<P2, 16> mPool2;
|
||||
UTILS_UNUSED_IN_RELEASE const utils::AreaPolicy::HeapArea& mArea;
|
||||
public:
|
||||
static constexpr size_t MIN_ALIGNMENT_SHIFT = 4;
|
||||
explicit Allocator(const utils::AreaPolicy::HeapArea& area);
|
||||
|
||||
static constexpr size_t getAlignment() noexcept { return MIN_ALIGNMENT; }
|
||||
|
||||
// this is in fact always called with a constexpr size argument
|
||||
[[nodiscard]] inline void* alloc(size_t size, size_t, size_t, uint8_t* outAge) noexcept {
|
||||
[[nodiscard]] inline void* alloc(size_t size, size_t, size_t extra = 0) noexcept {
|
||||
void* p = nullptr;
|
||||
if (size <= mPool0.getSize()) p = mPool0.alloc(size);
|
||||
else if (size <= mPool1.getSize()) p = mPool1.alloc(size);
|
||||
else if (size <= mPool2.getSize()) p = mPool2.alloc(size);
|
||||
if (UTILS_LIKELY(p)) {
|
||||
Node const* const pNode = static_cast<Node const*>(p);
|
||||
// we are guaranteed to have at least sizeof<Node> bytes of extra storage before
|
||||
// the allocation address.
|
||||
*outAge = pNode[-1].age;
|
||||
}
|
||||
if (size <= mPool0.getSize()) p = mPool0.alloc(size, 16, extra);
|
||||
else if (size <= mPool1.getSize()) p = mPool1.alloc(size, 16, extra);
|
||||
else if (size <= mPool2.getSize()) p = mPool2.alloc(size, 16, extra);
|
||||
return p;
|
||||
}
|
||||
|
||||
// this is in fact always called with a constexpr size argument
|
||||
inline void free(void* p, size_t size, uint8_t age) noexcept {
|
||||
inline void free(void* p, size_t size) noexcept {
|
||||
assert_invariant(p >= mArea.begin() && (char*)p + size <= (char*)mArea.end());
|
||||
|
||||
// check for double-free
|
||||
Node* const pNode = static_cast<Node*>(p);
|
||||
uint8_t& expectedAge = pNode[-1].age;
|
||||
ASSERT_POSTCONDITION(expectedAge == age,
|
||||
"double-free of Handle of size %d at %p", size, p);
|
||||
expectedAge = (expectedAge + 1) & 0xF; // fixme
|
||||
|
||||
if (size <= mPool0.getSize()) { mPool0.free(p); return; }
|
||||
if (size <= mPool1.getSize()) { mPool1.free(p); return; }
|
||||
if (size <= mPool2.getSize()) { mPool2.free(p); return; }
|
||||
@@ -262,16 +263,24 @@ private:
|
||||
// allocateHandle()/deallocateHandle() selects the pool to use at compile-time based on the
|
||||
// allocation size this is always inlined, because all these do is to call
|
||||
// allocateHandleInPool()/deallocateHandleFromPool() with the right pool size.
|
||||
template<typename D>
|
||||
template<size_t SIZE>
|
||||
HandleBase::HandleId allocateHandle() noexcept {
|
||||
constexpr size_t BUCKET_SIZE = getBucketSize<D>();
|
||||
return allocateHandleInPool<BUCKET_SIZE>();
|
||||
if constexpr (SIZE <= P0) { return allocateHandleInPool<P0>(); }
|
||||
if constexpr (SIZE <= P1) { return allocateHandleInPool<P1>(); }
|
||||
static_assert(SIZE <= P2);
|
||||
return allocateHandleInPool<P2>();
|
||||
}
|
||||
|
||||
template<typename D>
|
||||
template<size_t SIZE>
|
||||
void deallocateHandle(HandleBase::HandleId id) noexcept {
|
||||
constexpr size_t BUCKET_SIZE = getBucketSize<D>();
|
||||
deallocateHandleFromPool<BUCKET_SIZE>(id);
|
||||
if constexpr (SIZE <= P0) {
|
||||
deallocateHandleFromPool<P0>(id);
|
||||
} else if constexpr (SIZE <= P1) {
|
||||
deallocateHandleFromPool<P1>(id);
|
||||
} else {
|
||||
static_assert(SIZE <= P2);
|
||||
deallocateHandleFromPool<P2>(id);
|
||||
}
|
||||
}
|
||||
|
||||
// allocateHandleInPool()/deallocateHandleFromPool() is NOT inlined, which will cause three
|
||||
@@ -280,11 +289,9 @@ private:
|
||||
template<size_t SIZE>
|
||||
UTILS_NOINLINE
|
||||
HandleBase::HandleId allocateHandleInPool() noexcept {
|
||||
uint8_t age;
|
||||
void* p = mHandleArena.alloc(SIZE, alignof(std::max_align_t), 0, &age);
|
||||
void* p = mHandleArena.alloc(SIZE);
|
||||
if (UTILS_LIKELY(p)) {
|
||||
uint32_t const tag = (uint32_t(age) << HANDLE_AGE_SHIFT) & HANDLE_AGE_MASK;
|
||||
return arenaPointerToHandle(p, tag);
|
||||
return pointerToHandle(p);
|
||||
} else {
|
||||
return allocateHandleSlow(SIZE);
|
||||
}
|
||||
@@ -294,51 +301,42 @@ private:
|
||||
UTILS_NOINLINE
|
||||
void deallocateHandleFromPool(HandleBase::HandleId id) noexcept {
|
||||
if (UTILS_LIKELY(isPoolHandle(id))) {
|
||||
auto [p, tag] = handleToPointer(id);
|
||||
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
|
||||
mHandleArena.free(p, SIZE, age);
|
||||
void* p = handleToPointer(id);
|
||||
mHandleArena.free(p, SIZE);
|
||||
} else {
|
||||
deallocateHandleSlow(id, SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
// we handle a 4 bits age per address
|
||||
static constexpr uint32_t HANDLE_HEAP_FLAG = 0x80000000u; // pool vs heap handle
|
||||
static constexpr uint32_t HANDLE_AGE_MASK = 0x78000000u; // handle's age
|
||||
static constexpr uint32_t HANDLE_INDEX_MASK = 0x07FFFFFFu; // handle index
|
||||
static constexpr uint32_t HANDLE_TAG_MASK = HANDLE_AGE_MASK;
|
||||
static constexpr uint32_t HANDLE_AGE_SHIFT = 27;
|
||||
static constexpr uint32_t HEAP_HANDLE_FLAG = 0x80000000u;
|
||||
|
||||
static bool isPoolHandle(HandleBase::HandleId id) noexcept {
|
||||
return (id & HANDLE_HEAP_FLAG) == 0u;
|
||||
return (id & HEAP_HANDLE_FLAG) == 0u;
|
||||
}
|
||||
|
||||
HandleBase::HandleId allocateHandleSlow(size_t size) noexcept;
|
||||
void deallocateHandleSlow(HandleBase::HandleId id, size_t size) noexcept;
|
||||
|
||||
// We inline this because it's just 4 instructions in the fast case
|
||||
inline std::pair<void*, uint32_t> handleToPointer(HandleBase::HandleId id) const noexcept {
|
||||
inline void* handleToPointer(HandleBase::HandleId id) const noexcept {
|
||||
// note: the null handle will end-up returning nullptr b/c it'll be handled as
|
||||
// a non-pool handle.
|
||||
if (UTILS_LIKELY(isPoolHandle(id))) {
|
||||
char* const base = (char*)mHandleArena.getArea().begin();
|
||||
uint32_t const tag = id & HANDLE_TAG_MASK;
|
||||
size_t const offset = (id & HANDLE_INDEX_MASK) * Allocator::getAlignment();
|
||||
return { static_cast<void*>(base + offset), tag };
|
||||
size_t offset = id << Allocator::MIN_ALIGNMENT_SHIFT;
|
||||
return static_cast<void*>(base + offset);
|
||||
}
|
||||
return { handleToPointerSlow(id), 0 };
|
||||
return handleToPointerSlow(id);
|
||||
}
|
||||
|
||||
void* handleToPointerSlow(HandleBase::HandleId id) const noexcept;
|
||||
|
||||
// We inline this because it's just 3 instructions
|
||||
inline HandleBase::HandleId arenaPointerToHandle(void* p, uint32_t tag) const noexcept {
|
||||
inline HandleBase::HandleId pointerToHandle(void* p) const noexcept {
|
||||
char* const base = (char*)mHandleArena.getArea().begin();
|
||||
size_t const offset = (char*)p - base;
|
||||
assert_invariant((offset % Allocator::getAlignment()) == 0);
|
||||
auto id = HandleBase::HandleId(offset / Allocator::getAlignment());
|
||||
id |= tag & HANDLE_TAG_MASK;
|
||||
assert_invariant((id & HANDLE_HEAP_FLAG) == 0);
|
||||
size_t offset = (char*)p - base;
|
||||
auto id = HandleBase::HandleId(offset >> Allocator::MIN_ALIGNMENT_SHIFT);
|
||||
assert_invariant((id & HEAP_HANDLE_FLAG) == 0);
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -348,6 +346,9 @@ private:
|
||||
mutable utils::Mutex mLock;
|
||||
tsl::robin_map<HandleBase::HandleId, void*> mOverflowMap;
|
||||
HandleBase::HandleId mId = 0;
|
||||
#if HANDLE_TYPE_SAFETY
|
||||
mutable std::unordered_map<const void*, const char*> mHandleTypeId;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -49,25 +49,21 @@ struct AcquiredImage;
|
||||
struct HwBase {
|
||||
};
|
||||
|
||||
|
||||
struct HwVertexBufferInfo : public HwBase {
|
||||
struct HwVertexBuffer : public HwBase {
|
||||
AttributeArray attributes{}; // 8 * MAX_VERTEX_ATTRIBUTE_COUNT
|
||||
uint32_t vertexCount{}; // 4
|
||||
uint8_t bufferCount{}; // 1
|
||||
uint8_t attributeCount{}; // 1
|
||||
bool padding[2]{}; // 2
|
||||
HwVertexBufferInfo() noexcept = default;
|
||||
HwVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount) noexcept
|
||||
: bufferCount(bufferCount),
|
||||
attributeCount(attributeCount) {
|
||||
}
|
||||
};
|
||||
bool padding{}; // 1
|
||||
uint8_t bufferObjectsVersion{}; // 1 -> total struct is 136 bytes
|
||||
|
||||
struct HwVertexBuffer : public HwBase {
|
||||
uint32_t vertexCount{}; // 4
|
||||
uint8_t bufferObjectsVersion{0xff}; // 1
|
||||
bool padding[3]{}; // 2
|
||||
HwVertexBuffer() noexcept = default;
|
||||
explicit HwVertexBuffer(uint32_t vertextCount) noexcept
|
||||
: vertexCount(vertextCount) {
|
||||
HwVertexBuffer(uint8_t bufferCount, uint8_t attributeCount, uint32_t elementCount,
|
||||
AttributeArray const& attributes) noexcept
|
||||
: attributes(attributes),
|
||||
vertexCount(elementCount),
|
||||
bufferCount(bufferCount),
|
||||
attributeCount(attributeCount) {
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,6 +88,11 @@ struct HwIndexBuffer : public HwBase {
|
||||
};
|
||||
|
||||
struct HwRenderPrimitive : public HwBase {
|
||||
uint32_t offset{};
|
||||
uint32_t minIndex{};
|
||||
uint32_t maxIndex{};
|
||||
uint32_t count{};
|
||||
uint32_t maxVertexCount{};
|
||||
PrimitiveType type = PrimitiveType::TRIANGLES;
|
||||
};
|
||||
|
||||
@@ -113,9 +114,7 @@ struct HwTexture : public HwBase {
|
||||
uint8_t levels : 4; // This allows up to 15 levels (max texture size of 32768 x 32768)
|
||||
uint8_t samples : 4; // Sample count per pixel (should always be a power of 2)
|
||||
TextureFormat format{};
|
||||
uint8_t reserved0 = 0;
|
||||
TextureUsage usage{};
|
||||
uint16_t reserved1 = 0;
|
||||
HwStream* hwStream = nullptr;
|
||||
|
||||
HwTexture() noexcept : levels{}, samples{} {}
|
||||
|
||||
@@ -16,22 +16,9 @@
|
||||
|
||||
#include "private/backend/HandleAllocator.h"
|
||||
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -41,34 +28,14 @@ template <size_t P0, size_t P1, size_t P2>
|
||||
UTILS_NOINLINE
|
||||
HandleAllocator<P0, P1, P2>::Allocator::Allocator(AreaPolicy::HeapArea const& area)
|
||||
: mArea(area) {
|
||||
|
||||
// The largest handle this allocator can generate currently depends on the architecture's
|
||||
// min alignment, typically 8 or 16 bytes.
|
||||
// e.g. On Android armv8, the alignment is 16 bytes, so for a 1 MiB heap, the largest handle
|
||||
// index will be 65536. Note that this is not the same as the number of handles (which
|
||||
// will always be less).
|
||||
// Because our maximum representable handle currently is 0x07FFFFFF, the maximum no-nonsensical
|
||||
// heap size is 2 GiB, which amounts to 7.6 millions handles per pool (in the GL case).
|
||||
size_t const maxHeapSize = std::min(area.size(), HANDLE_INDEX_MASK * getAlignment());
|
||||
|
||||
if (UTILS_UNLIKELY(maxHeapSize != area.size())) {
|
||||
slog.w << "HandleAllocator heap size reduced to "
|
||||
<< maxHeapSize << " from " << area.size() << io::endl;
|
||||
}
|
||||
|
||||
// make sure we start with a clean arena. This is needed to ensure that all blocks start
|
||||
// with an age of 0.
|
||||
memset(area.data(), 0, maxHeapSize);
|
||||
|
||||
// size the different pools so that they can all contain the same number of handles
|
||||
size_t const count = maxHeapSize / (P0 + P1 + P2);
|
||||
char* const p0 = static_cast<char*>(area.begin());
|
||||
char* const p1 = p0 + count * P0;
|
||||
char* const p2 = p1 + count * P1;
|
||||
|
||||
mPool0 = Pool<P0>(p0, count * P0);
|
||||
mPool1 = Pool<P1>(p1, count * P1);
|
||||
mPool2 = Pool<P2>(p2, count * P2);
|
||||
// TODO: we probably need a better way to set the size of these pools
|
||||
const size_t unit = area.size() / 32;
|
||||
const size_t offsetPool1 = unit;
|
||||
const size_t offsetPool2 = 16 * unit;
|
||||
char* const p = (char*)area.begin();
|
||||
mPool0 = PoolAllocator< P0, 16>(p, p + offsetPool1);
|
||||
mPool1 = PoolAllocator< P1, 16>(p + offsetPool1, p + offsetPool2);
|
||||
mPool2 = PoolAllocator< P2, 16>(p + offsetPool2, area.end());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -106,17 +73,11 @@ template <size_t P0, size_t P1, size_t P2>
|
||||
HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size) noexcept {
|
||||
void* p = ::malloc(size);
|
||||
std::unique_lock lock(mLock);
|
||||
|
||||
HandleBase::HandleId id = (++mId) | HANDLE_HEAP_FLAG;
|
||||
|
||||
ASSERT_POSTCONDITION(mId < HANDLE_HEAP_FLAG,
|
||||
"No more Handle ids available! This can happen if HandleAllocator arena has been full"
|
||||
" for a while. Please increase FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB");
|
||||
|
||||
HandleBase::HandleId id = (++mId) | HEAP_HANDLE_FLAG;
|
||||
mOverflowMap.emplace(id, p);
|
||||
lock.unlock();
|
||||
|
||||
if (UTILS_UNLIKELY(id == (HANDLE_HEAP_FLAG | 1u))) { // meaning id was zero
|
||||
if (UTILS_UNLIKELY(id == (HEAP_HANDLE_FLAG|1u))) { // meaning id was zero
|
||||
PANIC_LOG("HandleAllocator arena is full, using slower system heap. Please increase "
|
||||
"the appropriate constant (e.g. FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB).");
|
||||
}
|
||||
@@ -125,7 +86,7 @@ HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size
|
||||
|
||||
template <size_t P0, size_t P1, size_t P2>
|
||||
void HandleAllocator<P0, P1, P2>::deallocateHandleSlow(HandleBase::HandleId id, size_t) noexcept {
|
||||
assert_invariant(id & HANDLE_HEAP_FLAG);
|
||||
assert_invariant(id & HEAP_HANDLE_FLAG);
|
||||
void* p = nullptr;
|
||||
auto& overflowMap = mOverflowMap;
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@ struct MetalContext {
|
||||
std::array<BufferState, MAX_SSBO_COUNT> ssboState;
|
||||
CullModeStateTracker cullModeState;
|
||||
WindingStateTracker windingState;
|
||||
Handle<HwRenderPrimitive> currentRenderPrimitive;
|
||||
|
||||
// State caches.
|
||||
DepthStencilStateCache depthStencilStateCache;
|
||||
|
||||
@@ -133,9 +133,12 @@ private:
|
||||
mHandleAllocator.deallocate(handle, p);
|
||||
}
|
||||
|
||||
inline void setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
|
||||
inline void setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph,
|
||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh);
|
||||
|
||||
inline void setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
|
||||
uint32_t offset, uint32_t minIndex, uint32_t maxIndex, uint32_t count);
|
||||
|
||||
void finalizeSamplerGroup(MetalSamplerGroup* sg);
|
||||
void enumerateBoundBuffers(BufferObjectBinding bindingType,
|
||||
const std::function<void(const BufferState&, MetalBuffer*, uint32_t)>& f);
|
||||
|
||||
@@ -43,42 +43,6 @@ namespace filament {
|
||||
namespace backend {
|
||||
|
||||
Driver* MetalDriverFactory::create(MetalPlatform* const platform, const Platform::DriverConfig& driverConfig) {
|
||||
#if 0
|
||||
// this is useful for development, but too verbose even for debug builds
|
||||
// For reference on a 64-bits machine in Release mode:
|
||||
// MetalTimerQuery : 16 few
|
||||
// HwStream : 24 few
|
||||
// MetalRenderPrimitive : 24 many
|
||||
// MetalVertexBuffer : 32 moderate
|
||||
// -- less than or equal 32 bytes
|
||||
// MetalIndexBuffer : 40 moderate
|
||||
// MetalFence : 48 few
|
||||
// MetalBufferObject : 48 many
|
||||
// -- less than or equal 48 bytes
|
||||
// MetalSamplerGroup : 112 few
|
||||
// MetalProgram : 152 moderate
|
||||
// MetalTexture : 152 moderate
|
||||
// MetalSwapChain : 184 few
|
||||
// MetalRenderTarget : 272 few
|
||||
// MetalVertexBufferInfo : 552 moderate
|
||||
// -- less than or equal to 552 bytes
|
||||
|
||||
utils::slog.d
|
||||
<< "\nMetalSwapChain: " << sizeof(MetalSwapChain)
|
||||
<< "\nMetalBufferObject: " << sizeof(MetalBufferObject)
|
||||
<< "\nMetalVertexBuffer: " << sizeof(MetalVertexBuffer)
|
||||
<< "\nMetalVertexBufferInfo: " << sizeof(MetalVertexBufferInfo)
|
||||
<< "\nMetalIndexBuffer: " << sizeof(MetalIndexBuffer)
|
||||
<< "\nMetalSamplerGroup: " << sizeof(MetalSamplerGroup)
|
||||
<< "\nMetalRenderPrimitive: " << sizeof(MetalRenderPrimitive)
|
||||
<< "\nMetalTexture: " << sizeof(MetalTexture)
|
||||
<< "\nMetalTimerQuery: " << sizeof(MetalTimerQuery)
|
||||
<< "\nHwStream: " << sizeof(HwStream)
|
||||
<< "\nMetalRenderTarget: " << sizeof(MetalRenderTarget)
|
||||
<< "\nMetalFence: " << sizeof(MetalFence)
|
||||
<< "\nMetalProgram: " << sizeof(MetalProgram)
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
return MetalDriver::create(platform, driverConfig);
|
||||
}
|
||||
|
||||
@@ -180,10 +144,7 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
|
||||
mContext->eventListener = [[MTLSharedEventListener alloc] initWithDispatchQueue:queue];
|
||||
}
|
||||
|
||||
const MetalShaderCompiler::Mode compilerMode = driverConfig.disableParallelShaderCompile
|
||||
? MetalShaderCompiler::Mode::SYNCHRONOUS
|
||||
: MetalShaderCompiler::Mode::ASYNCHRONOUS;
|
||||
mContext->shaderCompiler = new MetalShaderCompiler(mContext->device, *this, compilerMode);
|
||||
mContext->shaderCompiler = new MetalShaderCompiler(mContext->device, *this);
|
||||
mContext->shaderCompiler->init();
|
||||
|
||||
#if defined(FILAMENT_METAL_PROFILING)
|
||||
@@ -284,16 +245,10 @@ void MetalDriver::finish(int) {
|
||||
[oneOffBuffer waitUntilCompleted];
|
||||
}
|
||||
|
||||
void MetalDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount,
|
||||
uint8_t attributeCount, AttributeArray attributes) {
|
||||
construct_handle<MetalVertexBufferInfo>(vbih, *mContext,
|
||||
bufferCount, attributeCount, attributes);
|
||||
}
|
||||
|
||||
void MetalDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh,
|
||||
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih) {
|
||||
MetalVertexBufferInfo const* const vbi = handle_cast<const MetalVertexBufferInfo>(vbih);
|
||||
construct_handle<MetalVertexBuffer>(vbh, *mContext, vertexCount, vbi->bufferCount, vbih);
|
||||
void MetalDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint8_t bufferCount,
|
||||
uint8_t attributeCount, uint32_t vertexCount, AttributeArray attributes) {
|
||||
construct_handle<MetalVertexBuffer>(vbh, *mContext, bufferCount,
|
||||
attributeCount, vertexCount, attributes);
|
||||
}
|
||||
|
||||
void MetalDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
|
||||
@@ -361,9 +316,11 @@ void MetalDriver::createSamplerGroupR(
|
||||
|
||||
void MetalDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
|
||||
PrimitiveType pt) {
|
||||
PrimitiveType pt, uint32_t offset,
|
||||
uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
|
||||
construct_handle<MetalRenderPrimitive>(rph);
|
||||
MetalDriver::setRenderPrimitiveBuffer(rph, pt, vbh, ibh);
|
||||
MetalDriver::setRenderPrimitiveBuffer(rph, vbh, ibh);
|
||||
MetalDriver::setRenderPrimitiveRange(rph, pt, offset, minIndex, maxIndex, count);
|
||||
}
|
||||
|
||||
void MetalDriver::createProgramR(Handle<HwProgram> rph, Program&& program) {
|
||||
@@ -449,10 +406,6 @@ void MetalDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||
// nothing to do, timer query was constructed in createTimerQueryS
|
||||
}
|
||||
|
||||
Handle<HwVertexBufferInfo> MetalDriver::createVertexBufferInfoS() noexcept {
|
||||
return alloc_handle<MetalVertexBufferInfo>();
|
||||
}
|
||||
|
||||
Handle<HwVertexBuffer> MetalDriver::createVertexBufferS() noexcept {
|
||||
return alloc_handle<MetalVertexBuffer>();
|
||||
}
|
||||
@@ -517,12 +470,6 @@ Handle<HwTimerQuery> MetalDriver::createTimerQueryS() noexcept {
|
||||
return alloc_and_construct_handle<MetalTimerQuery, HwTimerQuery>();
|
||||
}
|
||||
|
||||
void MetalDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
|
||||
if (vbih) {
|
||||
destruct_handle<MetalVertexBufferInfo>(vbih);
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
|
||||
if (vbh) {
|
||||
destruct_handle<MetalVertexBuffer>(vbh);
|
||||
@@ -775,33 +722,18 @@ bool MetalDriver::isSRGBSwapChainSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isProtectedContentSupported() {
|
||||
// the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag is not supported
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isStereoSupported(backend::StereoscopicType stereoscopicType) {
|
||||
switch (stereoscopicType) {
|
||||
case backend::StereoscopicType::INSTANCED:
|
||||
return true;
|
||||
case backend::StereoscopicType::MULTIVIEW:
|
||||
// TODO: implement multiview feature in Metal.
|
||||
return false;
|
||||
}
|
||||
bool MetalDriver::isStereoSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isParallelShaderCompileSupported() {
|
||||
return mContext->shaderCompiler->isParallelShaderCompileSupported();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isDepthStencilResolveSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isProtectedTexturesSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -928,10 +860,9 @@ void MetalDriver::setExternalImagePlane(Handle<HwTexture> th, void* image, uint3
|
||||
void MetalDriver::setExternalStream(Handle<HwTexture> th, Handle<HwStream> sh) {
|
||||
}
|
||||
|
||||
TimerQueryResult MetalDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||
bool MetalDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||
auto* tq = handle_cast<MetalTimerQuery>(tqh);
|
||||
return mContext->timerQueryImpl->getQueryResult(tq, elapsedTime) ?
|
||||
TimerQueryResult::AVAILABLE : TimerQueryResult::NOT_READY;
|
||||
return mContext->timerQueryImpl->getQueryResult(tq, elapsedTime);
|
||||
}
|
||||
|
||||
void MetalDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
@@ -1098,14 +1029,23 @@ void MetalDriver::endRenderPass(int dummy) {
|
||||
mContext->currentRenderPassEncoder = nil;
|
||||
}
|
||||
|
||||
void MetalDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
|
||||
void MetalDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph,
|
||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh) {
|
||||
auto primitive = handle_cast<MetalRenderPrimitive>(rph);
|
||||
auto vertexBuffer = handle_cast<MetalVertexBuffer>(vbh);
|
||||
auto indexBuffer = handle_cast<MetalIndexBuffer>(ibh);
|
||||
MetalVertexBufferInfo const* const vbi = handle_cast<MetalVertexBufferInfo>(vertexBuffer->vbih);
|
||||
primitive->setBuffers(vbi, vertexBuffer, indexBuffer);
|
||||
primitive->setBuffers(vertexBuffer, indexBuffer);
|
||||
}
|
||||
|
||||
void MetalDriver::setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph,
|
||||
PrimitiveType pt, uint32_t offset, uint32_t minIndex, uint32_t maxIndex,
|
||||
uint32_t count) {
|
||||
auto primitive = handle_cast<MetalRenderPrimitive>(rph);
|
||||
primitive->type = pt;
|
||||
primitive->offset = offset * primitive->indexBuffer->elementSize;
|
||||
primitive->count = count;
|
||||
primitive->minIndex = minIndex;
|
||||
primitive->maxIndex = maxIndex > minIndex ? maxIndex : primitive->maxVertexCount - 1;
|
||||
}
|
||||
|
||||
void MetalDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain> schRead) {
|
||||
@@ -1607,13 +1547,10 @@ void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::bindPipeline(PipelineState ps) {
|
||||
void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph, uint32_t instanceCount) {
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
|
||||
"bindPipeline() without a valid command encoder.");
|
||||
|
||||
MetalVertexBufferInfo const* const vbi =
|
||||
handle_cast<MetalVertexBufferInfo>(ps.vertexBufferInfo);
|
||||
|
||||
"Attempted to draw without a valid command encoder.");
|
||||
auto primitive = handle_cast<MetalRenderPrimitive>(rph);
|
||||
auto program = handle_cast<MetalProgram>(ps.program);
|
||||
const auto& rs = ps.rasterState;
|
||||
|
||||
@@ -1627,7 +1564,7 @@ void MetalDriver::bindPipeline(PipelineState ps) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_PRECONDITION(bool(functions), "Attempting to bind an invalid Metal program.");
|
||||
ASSERT_PRECONDITION(bool(functions), "Attempting to draw with an invalid Metal program.");
|
||||
|
||||
auto [fragment, vertex] = functions.getRasterFunctions();
|
||||
|
||||
@@ -1654,7 +1591,7 @@ void MetalDriver::bindPipeline(PipelineState ps) {
|
||||
MetalPipelineState const pipelineState {
|
||||
.vertexFunction = vertex,
|
||||
.fragmentFunction = fragment,
|
||||
.vertexDescription = vbi->vertexDescription,
|
||||
.vertexDescription = primitive->vertexDescription,
|
||||
.colorAttachmentPixelFormat = {
|
||||
colorPixelFormat[0],
|
||||
colorPixelFormat[1],
|
||||
@@ -1748,6 +1685,50 @@ void MetalDriver::bindPipeline(PipelineState ps) {
|
||||
mContext->currentPolygonOffset = ps.polygonOffset;
|
||||
}
|
||||
|
||||
// Set scissor-rectangle.
|
||||
// In order to do this, we compute the intersection between:
|
||||
// 1. the scissor rectangle
|
||||
// 2. the render target attachment dimensions (important, as the scissor can't be set larger)
|
||||
// fmax/min are used below to guard against NaN and because the MTLViewport/MTLRegion
|
||||
// coordinates are doubles.
|
||||
MTLRegion scissor = mContext->currentRenderTarget->getRegionFromClientRect(ps.scissor);
|
||||
const float sleft = scissor.origin.x, sright = scissor.origin.x + scissor.size.width;
|
||||
const float stop = scissor.origin.y, sbottom = scissor.origin.y + scissor.size.height;
|
||||
|
||||
// Attachment extent
|
||||
const auto attachmentSize = mContext->currentRenderTarget->getAttachmentSize();
|
||||
const float aleft = 0.0f, atop = 0.0f;
|
||||
const float aright = static_cast<float>(attachmentSize.x);
|
||||
const float abottom = static_cast<float>(attachmentSize.y);
|
||||
|
||||
const auto left = std::fmax(sleft, aleft);
|
||||
const auto right = std::fmin(sright, aright);
|
||||
const auto top = std::fmax(stop, atop);
|
||||
const auto bottom = std::fmin(sbottom, abottom);
|
||||
|
||||
MTLScissorRect scissorRect = {
|
||||
.x = static_cast<NSUInteger>(left),
|
||||
.y = static_cast<NSUInteger>(top),
|
||||
.width = static_cast<NSUInteger>(right - left),
|
||||
.height = static_cast<NSUInteger>(bottom - top)
|
||||
};
|
||||
|
||||
[mContext->currentRenderPassEncoder setScissorRect:scissorRect];
|
||||
|
||||
// Bind uniform buffers.
|
||||
MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil };
|
||||
NSUInteger offsets[Program::UNIFORM_BINDING_COUNT] = { 0 };
|
||||
|
||||
enumerateBoundBuffers(BufferObjectBinding::UNIFORM,
|
||||
[&uniformsToBind, &offsets](const BufferState& state, MetalBuffer* buffer,
|
||||
uint32_t index) {
|
||||
uniformsToBind[index] = buffer;
|
||||
offsets[index] = state.offset;
|
||||
});
|
||||
MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
|
||||
UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT,
|
||||
uniformsToBind, offsets, Program::UNIFORM_BINDING_COUNT);
|
||||
|
||||
// Bind sampler groups (argument buffers).
|
||||
for (size_t s = 0; s < Program::SAMPLER_BINDING_COUNT; s++) {
|
||||
MetalSamplerGroup* const samplerGroup = mContext->samplerBindings[s];
|
||||
@@ -1778,29 +1759,19 @@ void MetalDriver::bindPipeline(PipelineState ps) {
|
||||
atIndex:(SAMPLER_GROUP_BINDING_START + s)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
|
||||
"bindRenderPrimitive() without a valid command encoder.");
|
||||
|
||||
// Bind the user vertex buffers.
|
||||
|
||||
MetalBuffer* vertexBuffers[MAX_VERTEX_BUFFER_COUNT] = {};
|
||||
size_t vertexBufferOffsets[MAX_VERTEX_BUFFER_COUNT] = {};
|
||||
size_t maxBufferIndex = 0;
|
||||
|
||||
MetalRenderPrimitive const* const primitive = handle_cast<MetalRenderPrimitive>(rph);
|
||||
MetalVertexBufferInfo const* const vbi =
|
||||
handle_cast<MetalVertexBufferInfo>(primitive->vertexBuffer->vbih);
|
||||
|
||||
mContext->currentRenderPrimitive = rph;
|
||||
|
||||
auto vb = primitive->vertexBuffer;
|
||||
for (auto m : vbi->bufferMapping) {
|
||||
for (auto m : primitive->bufferMapping) {
|
||||
assert_invariant(
|
||||
m.bufferArgumentIndex >= USER_VERTEX_BUFFER_BINDING_START &&
|
||||
m.bufferArgumentIndex < USER_VERTEX_BUFFER_BINDING_START + MAX_VERTEX_BUFFER_COUNT);
|
||||
size_t const vertexBufferIndex = m.bufferArgumentIndex - USER_VERTEX_BUFFER_BINDING_START;
|
||||
size_t vertexBufferIndex = m.bufferArgumentIndex - USER_VERTEX_BUFFER_BINDING_START;
|
||||
vertexBuffers[vertexBufferIndex] = vb->buffers[m.sourceBufferIndex];
|
||||
maxBufferIndex = std::max(maxBufferIndex, vertexBufferIndex);
|
||||
}
|
||||
@@ -1815,50 +1786,19 @@ void MetalDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
[mContext->currentRenderPassEncoder setVertexBytes:bytes
|
||||
length:16
|
||||
atIndex:ZERO_VERTEX_BUFFER_BINDING];
|
||||
}
|
||||
|
||||
void MetalDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
|
||||
"draw() without a valid command encoder.");
|
||||
|
||||
// Bind uniform buffers.
|
||||
MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil };
|
||||
NSUInteger offsets[Program::UNIFORM_BINDING_COUNT] = { 0 };
|
||||
|
||||
enumerateBoundBuffers(BufferObjectBinding::UNIFORM,
|
||||
[&uniformsToBind, &offsets](const BufferState& state, MetalBuffer* buffer,
|
||||
uint32_t index) {
|
||||
uniformsToBind[index] = buffer;
|
||||
offsets[index] = state.offset;
|
||||
});
|
||||
MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
|
||||
UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT,
|
||||
uniformsToBind, offsets, Program::UNIFORM_BINDING_COUNT);
|
||||
|
||||
auto primitive = handle_cast<MetalRenderPrimitive>(mContext->currentRenderPrimitive);
|
||||
|
||||
MetalIndexBuffer* indexBuffer = primitive->indexBuffer;
|
||||
|
||||
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(mContext);
|
||||
id<MTLBuffer> metalIndexBuffer = indexBuffer->buffer.getGpuBufferForDraw(cmdBuffer);
|
||||
[mContext->currentRenderPassEncoder drawIndexedPrimitives:getMetalPrimitiveType(primitive->type)
|
||||
indexCount:indexCount
|
||||
indexCount:primitive->count
|
||||
indexType:getIndexType(indexBuffer->elementSize)
|
||||
indexBuffer:metalIndexBuffer
|
||||
indexBufferOffset:indexOffset * primitive->indexBuffer->elementSize
|
||||
indexBufferOffset:primitive->offset
|
||||
instanceCount:instanceCount];
|
||||
}
|
||||
|
||||
void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) {
|
||||
MetalRenderPrimitive const* const rp = handle_cast<MetalRenderPrimitive>(rph);
|
||||
ps.primitiveType = rp->type;
|
||||
ps.vertexBufferInfo = rp->vertexBuffer->vbih;
|
||||
bindPipeline(ps);
|
||||
bindRenderPrimitive(rph);
|
||||
draw2(indexOffset, indexCount, instanceCount);
|
||||
}
|
||||
|
||||
void MetalDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"dispatchCompute must be called outside of a render pass.");
|
||||
@@ -1928,38 +1868,6 @@ void MetalDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGro
|
||||
[computeEncoder endEncoding];
|
||||
}
|
||||
|
||||
void MetalDriver::scissor(Viewport scissorBox) {
|
||||
// Set scissor-rectangle.
|
||||
// In order to do this, we compute the intersection between:
|
||||
// 1. the scissor rectangle
|
||||
// 2. the render target attachment dimensions (important, as the scissor can't be set larger)
|
||||
// fmax/min are used below to guard against NaN and because the MTLViewport/MTLRegion
|
||||
// coordinates are doubles.
|
||||
MTLRegion scissor = mContext->currentRenderTarget->getRegionFromClientRect(scissorBox);
|
||||
const float sleft = scissor.origin.x, sright = scissor.origin.x + scissor.size.width;
|
||||
const float stop = scissor.origin.y, sbottom = scissor.origin.y + scissor.size.height;
|
||||
|
||||
// Attachment extent
|
||||
const auto attachmentSize = mContext->currentRenderTarget->getAttachmentSize();
|
||||
const float aleft = 0.0f, atop = 0.0f;
|
||||
const float aright = static_cast<float>(attachmentSize.x);
|
||||
const float abottom = static_cast<float>(attachmentSize.y);
|
||||
|
||||
const auto left = std::fmax(sleft, aleft);
|
||||
const auto right = std::fmin(sright, aright);
|
||||
const auto top = std::fmax(stop, atop);
|
||||
const auto bottom = std::fmin(sbottom, abottom);
|
||||
|
||||
MTLScissorRect scissorRect = {
|
||||
.x = static_cast<NSUInteger>(left),
|
||||
.y = static_cast<NSUInteger>(top),
|
||||
.width = static_cast<NSUInteger>(right - left),
|
||||
.height = static_cast<NSUInteger>(bottom - top)
|
||||
};
|
||||
|
||||
[mContext->currentRenderPassEncoder setScissorRect:scissorRect];
|
||||
}
|
||||
|
||||
void MetalDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"beginTimerQuery must be called outside of a render pass.");
|
||||
|
||||
@@ -145,9 +145,28 @@ private:
|
||||
MetalBuffer buffer;
|
||||
};
|
||||
|
||||
struct MetalVertexBufferInfo : public HwVertexBufferInfo {
|
||||
MetalVertexBufferInfo(MetalContext& context,
|
||||
uint8_t bufferCount, uint8_t attributeCount, AttributeArray const& attributes);
|
||||
struct MetalVertexBuffer : public HwVertexBuffer {
|
||||
MetalVertexBuffer(MetalContext& context, uint8_t bufferCount, uint8_t attributeCount,
|
||||
uint32_t vertexCount, AttributeArray const& attributes);
|
||||
|
||||
utils::FixedCapacityVector<MetalBuffer*> buffers;
|
||||
};
|
||||
|
||||
struct MetalIndexBuffer : public HwIndexBuffer {
|
||||
MetalIndexBuffer(MetalContext& context, BufferUsage usage, uint8_t elementSize,
|
||||
uint32_t indexCount);
|
||||
|
||||
MetalBuffer buffer;
|
||||
};
|
||||
|
||||
struct MetalRenderPrimitive : public HwRenderPrimitive {
|
||||
MetalRenderPrimitive();
|
||||
void setBuffers(MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer);
|
||||
// The pointers to MetalVertexBuffer and MetalIndexBuffer are "weak".
|
||||
// The MetalVertexBuffer and MetalIndexBuffer must outlive the MetalRenderPrimitive.
|
||||
|
||||
MetalVertexBuffer* vertexBuffer = nullptr;
|
||||
MetalIndexBuffer* indexBuffer = nullptr;
|
||||
|
||||
// This struct is used to create the pipeline description to describe vertex assembly.
|
||||
VertexDescription vertexDescription = {};
|
||||
@@ -166,32 +185,6 @@ struct MetalVertexBufferInfo : public HwVertexBufferInfo {
|
||||
utils::FixedCapacityVector<Entry> bufferMapping;
|
||||
};
|
||||
|
||||
struct MetalVertexBuffer : public HwVertexBuffer {
|
||||
MetalVertexBuffer(MetalContext& context,
|
||||
uint32_t vertexCount, uint32_t bufferCount, Handle<HwVertexBufferInfo> vbih);
|
||||
|
||||
Handle<HwVertexBufferInfo> vbih;
|
||||
utils::FixedCapacityVector<MetalBuffer*> buffers;
|
||||
};
|
||||
|
||||
struct MetalIndexBuffer : public HwIndexBuffer {
|
||||
MetalIndexBuffer(MetalContext& context, BufferUsage usage, uint8_t elementSize,
|
||||
uint32_t indexCount);
|
||||
|
||||
MetalBuffer buffer;
|
||||
};
|
||||
|
||||
struct MetalRenderPrimitive : public HwRenderPrimitive {
|
||||
MetalRenderPrimitive();
|
||||
void setBuffers(MetalVertexBufferInfo const* const vbi,
|
||||
MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer);
|
||||
// The pointers to MetalVertexBuffer and MetalIndexBuffer are "weak".
|
||||
// The MetalVertexBuffer and MetalIndexBuffer must outlive the MetalRenderPrimitive.
|
||||
|
||||
MetalVertexBuffer* vertexBuffer = nullptr;
|
||||
MetalIndexBuffer* indexBuffer = nullptr;
|
||||
};
|
||||
|
||||
class MetalProgram : public HwProgram {
|
||||
public:
|
||||
MetalProgram(MetalContext& context, Program&& program) noexcept;
|
||||
|
||||
@@ -340,12 +340,23 @@ void MetalBufferObject::updateBufferUnsynchronized(void* data, size_t size, uint
|
||||
buffer.copyIntoBufferUnsynchronized(data, size, byteOffset);
|
||||
}
|
||||
|
||||
MetalVertexBufferInfo::MetalVertexBufferInfo(MetalContext& context, uint8_t bufferCount,
|
||||
uint8_t attributeCount, AttributeArray const& attributes)
|
||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||
bufferMapping(utils::FixedCapacityVector<Entry>::with_capacity(MAX_VERTEX_BUFFER_COUNT)) {
|
||||
MetalVertexBuffer::MetalVertexBuffer(MetalContext& context, uint8_t bufferCount,
|
||||
uint8_t attributeCount, uint32_t vertexCount, AttributeArray const& attributes)
|
||||
: HwVertexBuffer(bufferCount, attributeCount, vertexCount, attributes), buffers(bufferCount, nullptr) {}
|
||||
|
||||
const size_t maxAttributeCount = attributes.size();
|
||||
MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uint8_t elementSize,
|
||||
uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount),
|
||||
buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { }
|
||||
|
||||
MetalRenderPrimitive::MetalRenderPrimitive()
|
||||
: bufferMapping(utils::FixedCapacityVector<Entry>::with_capacity(MAX_VERTEX_BUFFER_COUNT)) {}
|
||||
|
||||
void MetalRenderPrimitive::setBuffers(MetalVertexBuffer* vertexBuffer, MetalIndexBuffer*
|
||||
indexBuffer) {
|
||||
this->vertexBuffer = vertexBuffer;
|
||||
this->indexBuffer = indexBuffer;
|
||||
|
||||
const size_t attributeCount = vertexBuffer->attributes.size();
|
||||
|
||||
auto& mapping = bufferMapping;
|
||||
mapping.clear();
|
||||
@@ -385,8 +396,8 @@ MetalVertexBufferInfo::MetalVertexBufferInfo(MetalContext& context, uint8_t buff
|
||||
}
|
||||
};
|
||||
|
||||
for (uint32_t attributeIndex = 0; attributeIndex < maxAttributeCount; attributeIndex++) {
|
||||
const auto& attribute = attributes[attributeIndex];
|
||||
for (uint32_t attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) {
|
||||
const auto& attribute = vertexBuffer->attributes[attributeIndex];
|
||||
|
||||
// If the attribute is unused, bind it to the zero buffer. It's a Metal error for a shader
|
||||
// to read from missing vertex attributes.
|
||||
@@ -413,24 +424,6 @@ MetalVertexBufferInfo::MetalVertexBufferInfo(MetalContext& context, uint8_t buff
|
||||
}
|
||||
}
|
||||
|
||||
MetalVertexBuffer::MetalVertexBuffer(MetalContext& context,
|
||||
uint32_t vertexCount, uint32_t bufferCount, Handle<HwVertexBufferInfo> vbih)
|
||||
: HwVertexBuffer(vertexCount), vbih(vbih), buffers(bufferCount, nullptr) {
|
||||
}
|
||||
|
||||
MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uint8_t elementSize,
|
||||
uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount),
|
||||
buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { }
|
||||
|
||||
MetalRenderPrimitive::MetalRenderPrimitive() {
|
||||
}
|
||||
|
||||
void MetalRenderPrimitive::setBuffers(MetalVertexBufferInfo const* const vbi,
|
||||
MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer) {
|
||||
this->vertexBuffer = vertexBuffer;
|
||||
this->indexBuffer = indexBuffer;
|
||||
}
|
||||
|
||||
MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept
|
||||
: HwProgram(program.getName()), mContext(context) {
|
||||
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -41,81 +39,43 @@ class MetalShaderCompiler {
|
||||
struct MetalProgramToken;
|
||||
|
||||
public:
|
||||
enum class Mode {
|
||||
SYNCHRONOUS, // synchronous shader compilation
|
||||
ASYNCHRONOUS // asynchronous shader compilation
|
||||
};
|
||||
|
||||
class MetalFunctionBundle {
|
||||
public:
|
||||
using Raster = std::tuple<id<MTLFunction>, id<MTLFunction>>;
|
||||
using Compute = id<MTLFunction>;
|
||||
using Error = std::tuple<NSString*, NSString*>; // error message, Program name
|
||||
struct None {};
|
||||
|
||||
MetalFunctionBundle() : mPrograms{None{}} {}
|
||||
|
||||
explicit operator bool() const { return isValid(); }
|
||||
|
||||
bool isValid() const noexcept {
|
||||
return std::holds_alternative<Raster>(mPrograms) ||
|
||||
std::holds_alternative<Compute>(mPrograms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an NSException if this MetalFunctionBundle either contains an error or is empty.
|
||||
*
|
||||
* If this MetalFunctionBundle contains an error, will throw a MetalCompilationFailure
|
||||
* NSException with the error string and program name passed to
|
||||
* MetalFunctionBundle::error(NSString*, NSString*).
|
||||
*
|
||||
* If this MetalFunctionBundle is empty, will throw a MetalEmptyFunctionBundle NSException.
|
||||
*/
|
||||
void validate() const;
|
||||
|
||||
Raster getRasterFunctions() const {
|
||||
assert_invariant(std::holds_alternative<Raster>(mPrograms));
|
||||
return std::get<Raster>(mPrograms);
|
||||
}
|
||||
|
||||
Compute getComputeFunction() const {
|
||||
assert_invariant(std::holds_alternative<Compute>(mPrograms));
|
||||
return std::get<Compute>(mPrograms);
|
||||
}
|
||||
|
||||
static MetalFunctionBundle none() {
|
||||
return MetalFunctionBundle(None{});
|
||||
}
|
||||
|
||||
static MetalFunctionBundle raster(id<MTLFunction> fragment, id<MTLFunction> vertex) {
|
||||
MetalFunctionBundle() = default;
|
||||
MetalFunctionBundle(id<MTLFunction> fragment, id<MTLFunction> vertex)
|
||||
: functions{fragment, vertex} {
|
||||
assert_invariant(fragment && vertex);
|
||||
assert_invariant(fragment.functionType == MTLFunctionTypeFragment);
|
||||
assert_invariant(vertex.functionType == MTLFunctionTypeVertex);
|
||||
return MetalFunctionBundle(Raster{fragment, vertex});
|
||||
}
|
||||
|
||||
static MetalFunctionBundle compute(id<MTLFunction> compute) {
|
||||
explicit MetalFunctionBundle(id<MTLFunction> compute) : functions{compute, nil} {
|
||||
assert_invariant(compute);
|
||||
assert_invariant(compute.functionType == MTLFunctionTypeKernel);
|
||||
return MetalFunctionBundle(Compute{compute});
|
||||
}
|
||||
|
||||
static MetalFunctionBundle error(NSString* errorMessage, NSString* programName) {
|
||||
return MetalFunctionBundle(Error{errorMessage, programName});
|
||||
std::pair<id<MTLFunction>, id<MTLFunction>> getRasterFunctions() const noexcept {
|
||||
assert_invariant(functions[0].functionType == MTLFunctionTypeFragment);
|
||||
assert_invariant(functions[1].functionType == MTLFunctionTypeVertex);
|
||||
return {functions[0], functions[1]};
|
||||
}
|
||||
|
||||
id<MTLFunction> getComputeFunction() const noexcept {
|
||||
assert_invariant(functions[0].functionType == MTLFunctionTypeKernel);
|
||||
return functions[0];
|
||||
}
|
||||
|
||||
explicit operator bool() const { return functions[0] != nil; }
|
||||
|
||||
private:
|
||||
MetalFunctionBundle(None&& t) : mPrograms(std::move(t)) {}
|
||||
MetalFunctionBundle(Raster&& t) : mPrograms(std::move(t)) {}
|
||||
MetalFunctionBundle(Compute&& t) : mPrograms(std::move(t)) {}
|
||||
MetalFunctionBundle(Error&& t) : mPrograms(std::move(t)) {}
|
||||
|
||||
std::variant<Raster, Compute, None, Error> mPrograms;
|
||||
// Can hold two functions, either:
|
||||
// - fragment and vertex (for rasterization pipelines)
|
||||
// - compute (for compute pipelines)
|
||||
id<MTLFunction> functions[2] = {nil, nil};
|
||||
};
|
||||
|
||||
using program_token_t = std::shared_ptr<MetalProgramToken>;
|
||||
|
||||
explicit MetalShaderCompiler(id<MTLDevice> device, MetalDriver& driver, Mode mode);
|
||||
explicit MetalShaderCompiler(id<MTLDevice> device, MetalDriver& driver);
|
||||
|
||||
MetalShaderCompiler(MetalShaderCompiler const& rhs) = delete;
|
||||
MetalShaderCompiler(MetalShaderCompiler&& rhs) = delete;
|
||||
@@ -125,15 +85,15 @@ public:
|
||||
void init() noexcept;
|
||||
void terminate() noexcept;
|
||||
|
||||
bool isParallelShaderCompileSupported() const noexcept;
|
||||
|
||||
// Creates a program, either synchronously or asynchronously, depending on the Mode
|
||||
// MetalShaderCompiler was constructed with.
|
||||
// Creates a program asynchronously
|
||||
program_token_t createProgram(utils::CString const& name, Program&& program);
|
||||
|
||||
// Returns the functions, blocking if necessary. The Token is destroyed and becomes invalid.
|
||||
MetalFunctionBundle getProgram(program_token_t& token);
|
||||
|
||||
// Destroys a valid token and all associated resources. Used to "cancel" a program compilation.
|
||||
static void terminate(program_token_t& token);
|
||||
|
||||
void notifyWhenAllProgramsAreReady(
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
||||
|
||||
@@ -143,7 +103,6 @@ private:
|
||||
CompilerThreadPool mCompilerThreadPool;
|
||||
id<MTLDevice> mDevice;
|
||||
CallbackManager mCallbackManager;
|
||||
Mode mMode;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -70,31 +70,22 @@ struct MetalShaderCompiler::MetalProgramToken : ProgramToken {
|
||||
|
||||
MetalShaderCompiler::MetalProgramToken::~MetalProgramToken() = default;
|
||||
|
||||
MetalShaderCompiler::MetalShaderCompiler(id<MTLDevice> device, MetalDriver& driver, Mode mode)
|
||||
MetalShaderCompiler::MetalShaderCompiler(id<MTLDevice> device, MetalDriver& driver)
|
||||
: mDevice(device),
|
||||
mCallbackManager(driver),
|
||||
mMode(mode) {
|
||||
mCallbackManager(driver) {
|
||||
|
||||
}
|
||||
|
||||
void MetalShaderCompiler::init() noexcept {
|
||||
const uint32_t poolSize = 1;
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
mCompilerThreadPool.init(poolSize, []() {}, []() {});
|
||||
}
|
||||
const uint32_t poolSize = 2;
|
||||
mCompilerThreadPool.init(poolSize, []() {}, []() {});
|
||||
}
|
||||
|
||||
void MetalShaderCompiler::terminate() noexcept {
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
mCompilerThreadPool.terminate();
|
||||
}
|
||||
mCompilerThreadPool.terminate();
|
||||
mCallbackManager.terminate();
|
||||
}
|
||||
|
||||
bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
|
||||
return mMode == Mode::ASYNCHRONOUS;
|
||||
}
|
||||
|
||||
/* static */ MetalShaderCompiler::MetalFunctionBundle MetalShaderCompiler::compileProgram(
|
||||
const Program& program, id<MTLDevice> device) {
|
||||
std::array<id<MTLFunction>, Program::SHADER_TYPE_COUNT> functions = { nil };
|
||||
@@ -127,16 +118,13 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
|
||||
options:options
|
||||
error:&error];
|
||||
if (library == nil) {
|
||||
NSString* errorMessage = @"unknown error";
|
||||
if (error) {
|
||||
auto description =
|
||||
[error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
utils::slog.w << description << utils::io::endl;
|
||||
errorMessage = error.localizedDescription;
|
||||
}
|
||||
PANIC_LOG("Failed to compile Metal program.");
|
||||
NSString* programName = [NSString stringWithFormat:@"%s", program.getName().c_str_safe()];
|
||||
return MetalFunctionBundle::error(errorMessage, programName);
|
||||
return {};
|
||||
}
|
||||
|
||||
MTLFunctionConstantValues* constants = [MTLFunctionConstantValues new];
|
||||
@@ -172,15 +160,14 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
|
||||
assert_invariant(isRasterizationProgram != isComputeProgram);
|
||||
|
||||
if (isRasterizationProgram) {
|
||||
return MetalFunctionBundle::raster(fragmentFunction, vertexFunction);
|
||||
return {fragmentFunction, vertexFunction};
|
||||
}
|
||||
|
||||
if (isComputeProgram) {
|
||||
return MetalFunctionBundle::compute(computeFunction);
|
||||
return MetalFunctionBundle{computeFunction};
|
||||
}
|
||||
|
||||
// Should never reach here.
|
||||
return MetalFunctionBundle::none();
|
||||
return {};
|
||||
}
|
||||
|
||||
MetalShaderCompiler::program_token_t MetalShaderCompiler::createProgram(
|
||||
@@ -189,26 +176,14 @@ MetalShaderCompiler::program_token_t MetalShaderCompiler::createProgram(
|
||||
|
||||
token->handle = mCallbackManager.get();
|
||||
|
||||
switch (mMode) {
|
||||
case Mode::ASYNCHRONOUS: {
|
||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, name, device = mDevice, program = std::move(program), token]() {
|
||||
MetalFunctionBundle compiledProgram = compileProgram(program, device);
|
||||
token->set(compiledProgram);
|
||||
mCallbackManager.put(token->handle);
|
||||
});
|
||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, name, device = mDevice, program = std::move(program), token]() {
|
||||
MetalFunctionBundle compiledProgram = compileProgram(program, device);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::SYNCHRONOUS: {
|
||||
MetalFunctionBundle compiledProgram = compileProgram(program, mDevice);
|
||||
token->set(compiledProgram);
|
||||
mCallbackManager.put(token->handle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
token->set(compiledProgram);
|
||||
mCallbackManager.put(token->handle);
|
||||
});
|
||||
|
||||
return token;
|
||||
}
|
||||
@@ -216,46 +191,38 @@ MetalShaderCompiler::program_token_t MetalShaderCompiler::createProgram(
|
||||
MetalShaderCompiler::MetalFunctionBundle MetalShaderCompiler::getProgram(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
if (!token->isReady()) {
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
job();
|
||||
}
|
||||
if (!token->isReady()) {
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
job();
|
||||
}
|
||||
}
|
||||
|
||||
// The job isn't guaranteed to have finished yet. We may have failed to dequeue it above,
|
||||
// which means it's currently running. In that case get() will block until it finishes.
|
||||
|
||||
MetalShaderCompiler::MetalFunctionBundle program = token->get();
|
||||
|
||||
token = nullptr;
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
/* static */ void MetalShaderCompiler::terminate(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||
if (!job) {
|
||||
// The job is being executed right now (or has already executed).
|
||||
token->wait();
|
||||
} else {
|
||||
// The job has not executed yet.
|
||||
token->compiler.mCallbackManager.put(token->handle);
|
||||
}
|
||||
|
||||
token.reset();
|
||||
}
|
||||
|
||||
void MetalShaderCompiler::notifyWhenAllProgramsAreReady(
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
mCallbackManager.setCallback(handler, callback, user);
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void MetalShaderCompiler::MetalFunctionBundle::validate() const {
|
||||
if (UTILS_UNLIKELY(std::holds_alternative<Error>(mPrograms))) {
|
||||
auto [errorMessage, programName] = std::get<Error>(mPrograms);
|
||||
NSString* reason =
|
||||
[NSString stringWithFormat:
|
||||
@"Attempting to draw with an id<MTLFunction> that failed to compile.\n"
|
||||
@"Program: %@\n"
|
||||
@"%@", programName, errorMessage];
|
||||
[[NSException exceptionWithName:@"MetalCompilationFailure"
|
||||
reason:reason
|
||||
userInfo:nil] raise];
|
||||
} else if (UTILS_UNLIKELY(std::holds_alternative<None>(mPrograms))) {
|
||||
NSString* reason = @"Attempting to draw with an empty id<MTLFunction>.";
|
||||
[[NSException exceptionWithName:@"MetalEmptyFunctionBundle"
|
||||
reason:reason
|
||||
userInfo:nil] raise];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -77,9 +77,6 @@ void NoopDriver::finish(int) {
|
||||
void NoopDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
}
|
||||
|
||||
void NoopDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
|
||||
}
|
||||
|
||||
void NoopDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
|
||||
}
|
||||
|
||||
@@ -177,11 +174,7 @@ bool NoopDriver::isSRGBSwapChainSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isProtectedContentSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isStereoSupported(backend::StereoscopicType) {
|
||||
bool NoopDriver::isStereoSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -193,10 +186,6 @@ bool NoopDriver::isDepthStencilResolveSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isProtectedTexturesSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isWorkaroundNeeded(Workaround) {
|
||||
return false;
|
||||
}
|
||||
@@ -252,8 +241,8 @@ void NoopDriver::update3DImage(Handle<HwTexture> th,
|
||||
void NoopDriver::setupExternalImage(void* image) {
|
||||
}
|
||||
|
||||
TimerQueryResult NoopDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||
return TimerQueryResult::ERROR;
|
||||
bool NoopDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void NoopDriver::setExternalImage(Handle<HwTexture> th, void* image) {
|
||||
@@ -350,26 +339,13 @@ void NoopDriver::blit(
|
||||
math::uint2 size) {
|
||||
}
|
||||
|
||||
void NoopDriver::bindPipeline(PipelineState pipelineState) {
|
||||
}
|
||||
|
||||
void NoopDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
}
|
||||
|
||||
void NoopDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
}
|
||||
|
||||
void NoopDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
uint32_t instanceCount) {
|
||||
}
|
||||
|
||||
void NoopDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
}
|
||||
|
||||
void NoopDriver::scissor(
|
||||
Viewport scissor) {
|
||||
}
|
||||
|
||||
void NoopDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
// change to true to display all GL extensions in the console on start-up
|
||||
@@ -49,7 +48,7 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
|
||||
#endif
|
||||
}
|
||||
|
||||
OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept {
|
||||
OpenGLContext::OpenGLContext() noexcept {
|
||||
|
||||
state.vao.p = &mDefaultVAO;
|
||||
|
||||
@@ -232,46 +231,6 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform) noexcept {
|
||||
glDebugMessageCallback(cb, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
mTimerQueryFactory = TimerQueryFactory::init(platform, *this);
|
||||
}
|
||||
|
||||
OpenGLContext::~OpenGLContext() noexcept {
|
||||
delete mTimerQueryFactory;
|
||||
}
|
||||
|
||||
void OpenGLContext::destroyWithContext(
|
||||
size_t index, std::function<void(OpenGLContext&)> const& closure) noexcept {
|
||||
if (index == 0) {
|
||||
// Note: we only need to delay the destruction of objects on the unprotected context
|
||||
// (index 0) because the protected context is always immediately destroyed and all its
|
||||
// active objects and bindings are then automatically destroyed.
|
||||
// TODO: this is only guaranteed for EGLPlatform, but that's the only one we care about.
|
||||
mDestroyWithNormalContext.push_back(closure);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::unbindEverything() noexcept {
|
||||
// TODO: we're supposed to unbind everything here so that resources don't get
|
||||
// stuck in this context (contextIndex) when destroyed in the other context.
|
||||
// However, because EGLPlatform always immediately destroys the protected context (1),
|
||||
// the bindings will automatically be severed when we switch back to the default context.
|
||||
// Since bindings now only exist in one context, we don't have a ref-counting issue to
|
||||
// worry about.
|
||||
}
|
||||
|
||||
void OpenGLContext::synchronizeStateAndCache(size_t index) noexcept {
|
||||
|
||||
// if we're just switching back to context 0, run all the pending destructors
|
||||
if (index == 0) {
|
||||
auto list = std::move(mDestroyWithNormalContext);
|
||||
for (auto&& fn: list) {
|
||||
fn(*this);
|
||||
}
|
||||
}
|
||||
|
||||
contextIndex = index;
|
||||
resetState();
|
||||
}
|
||||
|
||||
void OpenGLContext::setDefaultState() noexcept {
|
||||
@@ -640,7 +599,6 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
|
||||
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);
|
||||
ext->EXT_protected_textures = exts.has("GL_EXT_protected_textures"sv);
|
||||
#endif
|
||||
ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
@@ -664,7 +622,6 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
|
||||
ext->OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"sv);
|
||||
ext->OES_texture_npot = exts.has("GL_OES_texture_npot"sv);
|
||||
ext->OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"sv);
|
||||
ext->OVR_multiview2 = exts.has("GL_OVR_multiview2"sv);
|
||||
ext->WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv);
|
||||
ext->WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
|
||||
ext->WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
|
||||
@@ -729,7 +686,6 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor)
|
||||
ext->OES_standard_derivatives = true;
|
||||
ext->OES_texture_npot = true;
|
||||
ext->OES_vertex_array_object = true;
|
||||
ext->OVR_multiview2 = exts.has("GL_OVR_multiview2"sv);
|
||||
ext->WEBGL_compressed_texture_etc = false;
|
||||
ext->WEBGL_compressed_texture_s3tc = false;
|
||||
ext->WEBGL_compressed_texture_s3tc_srgb = false;
|
||||
@@ -861,22 +817,19 @@ void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum targe
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLContext::deleteVertexArray(GLuint vao) noexcept {
|
||||
if (UTILS_LIKELY(vao)) {
|
||||
procs.deleteVertexArrays(1, &vao);
|
||||
// if the destroyed VAO is bound, clear the binding.
|
||||
if (state.vao.p->vao[contextIndex] == vao) {
|
||||
void OpenGLContext::deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept {
|
||||
procs.deleteVertexArrays(n, arrays);
|
||||
// if one of the destroyed VAO is bound, clear the binding.
|
||||
for (GLsizei i = 0; i < n; ++i) {
|
||||
if (state.vao.p->vao == arrays[i]) {
|
||||
bindVertexArray(nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::resetState() noexcept {
|
||||
// Force GL state to match the Filament state
|
||||
|
||||
// increase the state version so other parts of the state know to reset
|
||||
state.age++;
|
||||
|
||||
if (state.major > 2) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, state.draw_fbo);
|
||||
@@ -893,8 +846,11 @@ void OpenGLContext::resetState() noexcept {
|
||||
glUseProgram(state.program.use);
|
||||
|
||||
// state.vao
|
||||
state.vao.p = nullptr;
|
||||
bindVertexArray(nullptr);
|
||||
if (state.vao.p) {
|
||||
procs.bindVertexArray(state.vao.p->vao);
|
||||
} else {
|
||||
bindVertexArray(nullptr);
|
||||
}
|
||||
|
||||
// state.raster
|
||||
glFrontFace(state.raster.frontFace);
|
||||
@@ -1050,22 +1006,7 @@ void OpenGLContext::resetState() noexcept {
|
||||
state.window.viewport.w
|
||||
);
|
||||
glDepthRangef(state.window.depthRange.x, state.window.depthRange.y);
|
||||
}
|
||||
|
||||
void OpenGLContext::createTimerQuery(GLTimerQuery* query) {
|
||||
mTimerQueryFactory->createTimerQuery(query);
|
||||
}
|
||||
|
||||
void OpenGLContext::destroyTimerQuery(GLTimerQuery* query) {
|
||||
mTimerQueryFactory->destroyTimerQuery(query);
|
||||
}
|
||||
|
||||
void OpenGLContext::beginTimeElapsedQuery(GLTimerQuery* query) {
|
||||
mTimerQueryFactory->beginTimeElapsedQuery(query);
|
||||
}
|
||||
|
||||
void OpenGLContext::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) {
|
||||
mTimerQueryFactory->endTimeElapsedQuery(driver, query);
|
||||
|
||||
}
|
||||
|
||||
} // namesapce filament
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include "OpenGLTimerQuery.h"
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
@@ -29,17 +27,15 @@
|
||||
#include "GLUtils.h"
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class OpenGLPlatform;
|
||||
|
||||
class OpenGLContext final : public TimerQueryFactoryInterface {
|
||||
class OpenGLContext {
|
||||
public:
|
||||
static constexpr const size_t MAX_TEXTURE_UNIT_COUNT = MAX_SAMPLER_COUNT;
|
||||
static constexpr const size_t DUMMY_TEXTURE_BINDING = 7; // highest binding guaranteed to work with ES2
|
||||
@@ -50,29 +46,19 @@ public:
|
||||
struct RenderPrimitive {
|
||||
static_assert(MAX_VERTEX_ATTRIBUTE_COUNT <= 16);
|
||||
|
||||
GLuint vao[2] = {}; // 4
|
||||
GLuint vao = 0; // 4
|
||||
GLuint elementArray = 0; // 4
|
||||
mutable utils::bitset<uint16_t> vertexAttribArray; // 2
|
||||
utils::bitset<uint16_t> vertexAttribArray; // 2
|
||||
|
||||
// if this differs from vertexBufferWithObjects->bufferObjectsVersion, this VAO needs to
|
||||
// be updated (see OpenGLDriver::updateVertexArrayObject())
|
||||
// If this version number does not match vertexBufferWithObjects->bufferObjectsVersion,
|
||||
// then the VAO needs to be updated.
|
||||
uint8_t vertexBufferVersion = 0; // 1
|
||||
|
||||
// if this differs from OpenGLContext::state.age, this VAO needs to
|
||||
// be updated (see OpenGLDriver::updateVertexArrayObject())
|
||||
uint8_t stateVersion = 0; // 1
|
||||
|
||||
// If this differs from OpenGLContext::state.age, this VAO's name needs to be updated.
|
||||
// See OpenGLContext::bindVertexArray()
|
||||
uint8_t nameVersion = 0; // 1
|
||||
|
||||
// Size in bytes of indices in the index buffer
|
||||
uint8_t indicesSize = 0; // 1
|
||||
|
||||
// The optional 32-bit handle to a GLVertexBuffer is necessary only if the referenced
|
||||
// VertexBuffer supports buffer objects. If this is zero, then the VBO handles array is
|
||||
// immutable.
|
||||
Handle<HwVertexBuffer> vertexBufferWithObjects; // 4
|
||||
Handle<HwVertexBuffer> vertexBufferWithObjects = {}; // 4
|
||||
|
||||
GLenum getIndicesType() const noexcept {
|
||||
return indicesSize == 4 ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT;
|
||||
@@ -81,18 +67,7 @@ public:
|
||||
|
||||
static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept;
|
||||
|
||||
explicit OpenGLContext(OpenGLPlatform& platform) noexcept;
|
||||
~OpenGLContext() noexcept;
|
||||
|
||||
// TimerQueryInterface ------------------------------------------------------------------------
|
||||
|
||||
// note: OpenGLContext being final ensures (clang) these are not called through the vtable
|
||||
void createTimerQuery(GLTimerQuery* query) override;
|
||||
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
OpenGLContext() noexcept;
|
||||
|
||||
template<int MAJOR, int MINOR>
|
||||
inline bool isAtLeastGL() const noexcept {
|
||||
@@ -150,8 +125,8 @@ public:
|
||||
|
||||
inline void bindFramebuffer(GLenum target, GLuint buffer) noexcept;
|
||||
|
||||
inline void enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept;
|
||||
inline void disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept;
|
||||
inline void enableVertexAttribArray(GLuint index) noexcept;
|
||||
inline void disableVertexAttribArray(GLuint index) noexcept;
|
||||
inline void enable(GLenum cap) noexcept;
|
||||
inline void disable(GLenum cap) noexcept;
|
||||
inline void frontFace(GLenum mode) noexcept;
|
||||
@@ -173,9 +148,7 @@ public:
|
||||
inline void depthRange(GLclampf near, GLclampf far) noexcept;
|
||||
|
||||
void deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept;
|
||||
void deleteVertexArray(GLuint vao) noexcept;
|
||||
|
||||
void destroyWithContext(size_t index, std::function<void(OpenGLContext&)> const& closure) noexcept;
|
||||
void deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept;
|
||||
|
||||
// glGet*() values
|
||||
struct Gets {
|
||||
@@ -210,7 +183,6 @@ public:
|
||||
bool EXT_discard_framebuffer;
|
||||
bool EXT_multisampled_render_to_texture2;
|
||||
bool EXT_multisampled_render_to_texture;
|
||||
bool EXT_protected_textures;
|
||||
bool EXT_shader_framebuffer_fetch;
|
||||
bool EXT_texture_compression_bptc;
|
||||
bool EXT_texture_compression_etc2;
|
||||
@@ -233,7 +205,6 @@ public:
|
||||
bool OES_standard_derivatives;
|
||||
bool OES_texture_npot;
|
||||
bool OES_vertex_array_object;
|
||||
bool OVR_multiview2;
|
||||
bool WEBGL_compressed_texture_etc;
|
||||
bool WEBGL_compressed_texture_s3tc;
|
||||
bool WEBGL_compressed_texture_s3tc_srgb;
|
||||
@@ -319,19 +290,8 @@ public:
|
||||
|
||||
FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; }
|
||||
|
||||
// This is the index of the context in use. Must be 0 or 1. This is used to manange the
|
||||
// OpenGL name of ContainerObjects within each context.
|
||||
uint32_t contextIndex = 0;
|
||||
|
||||
// Try to keep the State structure sorted by data-access patterns
|
||||
struct State {
|
||||
State() noexcept = default;
|
||||
// make sure we don't copy this state by accident
|
||||
State(State const& rhs) = delete;
|
||||
State(State&& rhs) noexcept = delete;
|
||||
State& operator=(State const& rhs) = delete;
|
||||
State& operator=(State&& rhs) noexcept = delete;
|
||||
|
||||
GLint major = 0;
|
||||
GLint minor = 0;
|
||||
|
||||
@@ -436,7 +396,6 @@ public:
|
||||
vec4gli viewport { 0 };
|
||||
vec2glf depthRange { 0.0f, 1.0f };
|
||||
} window;
|
||||
uint8_t age = 0;
|
||||
} state;
|
||||
|
||||
struct Procs {
|
||||
@@ -456,14 +415,9 @@ public:
|
||||
void (* maxShaderCompilerThreadsKHR)(GLuint count);
|
||||
} procs{};
|
||||
|
||||
void unbindEverything() noexcept;
|
||||
void synchronizeStateAndCache(size_t index) noexcept;
|
||||
|
||||
private:
|
||||
ShaderModel mShaderModel = ShaderModel::MOBILE;
|
||||
FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1;
|
||||
TimerQueryFactoryInterface* mTimerQueryFactory = nullptr;
|
||||
std::vector<std::function<void(OpenGLContext&)>> mDestroyWithNormalContext;
|
||||
|
||||
const std::array<std::tuple<bool const&, char const*, char const*>, sizeof(bugs)> mBugDatabase{{
|
||||
{ bugs.disable_glFlush,
|
||||
@@ -675,26 +629,11 @@ void OpenGLContext::depthRange(GLclampf near, GLclampf far) noexcept {
|
||||
void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
|
||||
RenderPrimitive* vao = p ? const_cast<RenderPrimitive *>(p) : &mDefaultVAO;
|
||||
update_state(state.vao.p, vao, [&]() {
|
||||
|
||||
// See if we need to create a name for this VAO on the fly, this would happen if:
|
||||
// - we're not the default VAO, because its name is always 0
|
||||
// - our name is 0, this could happen if this VAO was created in the "other" context
|
||||
// - the nameVersion is out of date *and* we're on the protected context, in this case:
|
||||
// - the name must be stale from a previous use of this context because we always
|
||||
// destroy the protected context when we're done with it.
|
||||
bool const recreateVaoName = p != &mDefaultVAO &&
|
||||
((vao->vao[contextIndex] == 0) ||
|
||||
(vao->nameVersion != state.age && contextIndex == 1));
|
||||
if (UTILS_UNLIKELY(recreateVaoName)) {
|
||||
vao->nameVersion = state.age;
|
||||
procs.genVertexArrays(1, &vao->vao[contextIndex]);
|
||||
}
|
||||
|
||||
procs.bindVertexArray(vao->vao[contextIndex]);
|
||||
procs.bindVertexArray(vao->vao);
|
||||
// update GL_ELEMENT_ARRAY_BUFFER, which is updated by glBindVertexArray
|
||||
size_t const targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
|
||||
state.buffers.genericBinding[targetIndex] = vao->elementArray;
|
||||
if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding || recreateVaoName)) {
|
||||
if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding)) {
|
||||
// This shouldn't be needed, but it looks like some drivers don't do the implicit
|
||||
// glBindBuffer().
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao->elementArray);
|
||||
@@ -778,22 +717,20 @@ void OpenGLContext::useProgram(GLuint program) noexcept {
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLContext::enableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept {
|
||||
assert_invariant(rp);
|
||||
assert_invariant(index < rp->vertexAttribArray.size());
|
||||
bool const force = rp->stateVersion != state.age;
|
||||
if (UTILS_UNLIKELY(force || !rp->vertexAttribArray[index])) {
|
||||
rp->vertexAttribArray.set(index);
|
||||
void OpenGLContext::enableVertexAttribArray(GLuint index) noexcept {
|
||||
assert_invariant(state.vao.p);
|
||||
assert_invariant(index < state.vao.p->vertexAttribArray.size());
|
||||
if (UTILS_UNLIKELY(!state.vao.p->vertexAttribArray[index])) {
|
||||
state.vao.p->vertexAttribArray.set(index);
|
||||
glEnableVertexAttribArray(index);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::disableVertexAttribArray(RenderPrimitive const* rp, GLuint index) noexcept {
|
||||
assert_invariant(rp);
|
||||
assert_invariant(index < rp->vertexAttribArray.size());
|
||||
bool const force = rp->stateVersion != state.age;
|
||||
if (UTILS_UNLIKELY(force || rp->vertexAttribArray[index])) {
|
||||
rp->vertexAttribArray.unset(index);
|
||||
void OpenGLContext::disableVertexAttribArray(GLuint index) noexcept {
|
||||
assert_invariant(state.vao.p);
|
||||
assert_invariant(index < state.vao.p->vertexAttribArray.size());
|
||||
if (UTILS_UNLIKELY(state.vao.p->vertexAttribArray[index])) {
|
||||
state.vao.p->vertexAttribArray.unset(index);
|
||||
glDisableVertexAttribArray(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,28 +90,26 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
#if 0
|
||||
// this is useful for development, but too verbose even for debug builds
|
||||
// For reference on a 64-bits machine in Release mode:
|
||||
// GLFence : 8 few
|
||||
// GLIndexBuffer : 8 moderate
|
||||
// GLSamplerGroup : 16 few
|
||||
// GLSwapChain : 16 few
|
||||
// GLTimerQuery : 16 few
|
||||
// GLFence : 24 few
|
||||
// GLRenderPrimitive : 32 many
|
||||
// GLBufferObject : 32 many
|
||||
// -- less than or equal 32 bytes
|
||||
// OpenGLProgram : 56 moderate
|
||||
// GLTexture : 64 moderate
|
||||
// GLSamplerGroup : 8 few
|
||||
// -- less than or equal 16 bytes
|
||||
// GLBufferObject : 24 many
|
||||
// GLSync : 24 few
|
||||
// GLTimerQuery : 32 few
|
||||
// OpenGLProgram : 32 moderate
|
||||
// GLRenderPrimitive : 48 many
|
||||
// -- less than or equal 64 bytes
|
||||
// GLVertexBuffer : 76 moderate
|
||||
// GLStream : 104 few
|
||||
// GLTexture : 72 moderate
|
||||
// GLRenderTarget : 112 few
|
||||
// GLVertexBufferInfo : 132 moderate
|
||||
// -- less than or equal to 136 bytes
|
||||
// GLStream : 184 few
|
||||
// GLVertexBuffer : 200 moderate
|
||||
// -- less than or equal to 208 bytes
|
||||
|
||||
slog.d
|
||||
<< "\nGLSwapChain: " << sizeof(GLSwapChain)
|
||||
<< "HwFence: " << sizeof(HwFence)
|
||||
<< "\nGLBufferObject: " << sizeof(GLBufferObject)
|
||||
<< "\nGLVertexBuffer: " << sizeof(GLVertexBuffer)
|
||||
<< "\nGLVertexBufferInfo: " << sizeof(GLVertexBufferInfo)
|
||||
<< "\nGLIndexBuffer: " << sizeof(GLIndexBuffer)
|
||||
<< "\nGLSamplerGroup: " << sizeof(GLSamplerGroup)
|
||||
<< "\nGLRenderPrimitive: " << sizeof(GLRenderPrimitive)
|
||||
@@ -119,7 +117,7 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
<< "\nGLTimerQuery: " << sizeof(GLTimerQuery)
|
||||
<< "\nGLStream: " << sizeof(GLStream)
|
||||
<< "\nGLRenderTarget: " << sizeof(GLRenderTarget)
|
||||
<< "\nGLFence: " << sizeof(GLFence)
|
||||
<< "\nGLSync: " << sizeof(GLSync)
|
||||
<< "\nOpenGLProgram: " << sizeof(OpenGLProgram)
|
||||
<< io::endl;
|
||||
#endif
|
||||
@@ -170,7 +168,7 @@ OpenGLDriver::DebugMarker::~DebugMarker() noexcept {
|
||||
|
||||
OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
|
||||
: mPlatform(*platform),
|
||||
mContext(mPlatform),
|
||||
mContext(),
|
||||
mShaderCompilerService(*this),
|
||||
mHandleAllocator("Handles", driverConfig.handleArenaSize),
|
||||
mSamplerMap(32),
|
||||
@@ -194,6 +192,8 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
|
||||
assert_invariant(mContext.ext.EXT_disjoint_timer_query);
|
||||
#endif
|
||||
|
||||
mTimerQueryImpl = OpenGLTimerQueryFactory::init(mPlatform, *this);
|
||||
|
||||
mShaderCompilerService.init();
|
||||
}
|
||||
|
||||
@@ -238,6 +238,8 @@ void OpenGLDriver::terminate() {
|
||||
}
|
||||
#endif
|
||||
|
||||
delete mTimerQueryImpl;
|
||||
|
||||
mPlatform.terminate();
|
||||
}
|
||||
|
||||
@@ -384,10 +386,6 @@ void OpenGLDriver::setStencilState(StencilState ss) noexcept {
|
||||
// Creating driver objects
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
Handle<HwVertexBufferInfo> OpenGLDriver::createVertexBufferInfoS() noexcept {
|
||||
return initHandle<GLVertexBufferInfo>();
|
||||
}
|
||||
|
||||
Handle<HwVertexBuffer> OpenGLDriver::createVertexBufferS() noexcept {
|
||||
return initHandle<GLVertexBuffer>();
|
||||
}
|
||||
@@ -448,21 +446,14 @@ Handle<HwTimerQuery> OpenGLDriver::createTimerQueryS() noexcept {
|
||||
return initHandle<GLTimerQuery>();
|
||||
}
|
||||
|
||||
void OpenGLDriver::createVertexBufferInfoR(
|
||||
Handle<HwVertexBufferInfo> vbih,
|
||||
uint8_t bufferCount,
|
||||
uint8_t attributeCount,
|
||||
AttributeArray attributes) {
|
||||
DEBUG_MARKER()
|
||||
construct<GLVertexBufferInfo>(vbih, bufferCount, attributeCount, attributes);
|
||||
}
|
||||
|
||||
void OpenGLDriver::createVertexBufferR(
|
||||
Handle<HwVertexBuffer> vbh,
|
||||
uint32_t vertexCount,
|
||||
Handle<HwVertexBufferInfo> vbih) {
|
||||
uint8_t bufferCount,
|
||||
uint8_t attributeCount,
|
||||
uint32_t elementCount,
|
||||
AttributeArray attributes) {
|
||||
DEBUG_MARKER()
|
||||
construct<GLVertexBuffer>(vbh, vertexCount, vbih);
|
||||
construct<GLVertexBuffer>(vbh, bufferCount, attributeCount, elementCount, attributes);
|
||||
}
|
||||
|
||||
void OpenGLDriver::createIndexBufferR(
|
||||
@@ -510,33 +501,31 @@ void OpenGLDriver::createBufferObjectR(Handle<HwBufferObject> boh,
|
||||
|
||||
void OpenGLDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
|
||||
PrimitiveType pt) {
|
||||
PrimitiveType pt, uint32_t offset,
|
||||
uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
auto& gl = mContext;
|
||||
|
||||
GLVertexBuffer const* const eb = handle_cast<const GLVertexBuffer*>(vbh);
|
||||
GLIndexBuffer const* const ib = handle_cast<const GLIndexBuffer*>(ibh);
|
||||
assert_invariant(ib->elementSize == 2 || ib->elementSize == 4);
|
||||
|
||||
GLVertexBuffer* const vb = handle_cast<GLVertexBuffer*>(vbh);
|
||||
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
GLRenderPrimitive* rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
rp->gl.indicesSize = (ib->elementSize == 4u) ? 4u : 2u;
|
||||
rp->gl.vertexBufferWithObjects = vbh;
|
||||
rp->type = pt;
|
||||
rp->vbih = vb->vbih;
|
||||
rp->offset = offset * rp->gl.indicesSize;
|
||||
rp->count = count;
|
||||
rp->minIndex = minIndex;
|
||||
rp->maxIndex = maxIndex > minIndex ? maxIndex : rp->maxVertexCount - 1; // sanitize max index
|
||||
|
||||
// create a name for this VAO in the current context
|
||||
gl.procs.genVertexArrays(1, &rp->gl.vao[gl.contextIndex]);
|
||||
gl.procs.genVertexArrays(1, &rp->gl.vao);
|
||||
|
||||
// this implies our name is up-to-date
|
||||
rp->gl.nameVersion = gl.state.age;
|
||||
|
||||
// binding the VAO will actually create it
|
||||
gl.bindVertexArray(&rp->gl);
|
||||
|
||||
// Note: we don't update the vertex buffer bindings in the VAO just yet, we will do that
|
||||
// later in draw() or bindRenderPrimitive(). At this point, the HwVertexBuffer might not
|
||||
// have all its buffers set.
|
||||
// update the VBO bindings in the VAO
|
||||
updateVertexArrayObject(rp, eb);
|
||||
|
||||
// this records the index buffer into the currently bound VAO
|
||||
gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib->gl.buffer);
|
||||
@@ -583,22 +572,13 @@ void OpenGLDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t size
|
||||
|
||||
UTILS_NOINLINE
|
||||
void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t,
|
||||
uint32_t width, uint32_t height, uint32_t depth, bool useProtectedMemory) noexcept {
|
||||
uint32_t width, uint32_t height, uint32_t depth) noexcept {
|
||||
|
||||
auto& gl = mContext;
|
||||
|
||||
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
|
||||
gl.activeTexture(OpenGLContext::DUMMY_TEXTURE_BINDING);
|
||||
|
||||
#ifdef GL_EXT_protected_textures
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (UTILS_UNLIKELY(useProtectedMemory)) {
|
||||
assert_invariant(gl.ext.EXT_protected_textures);
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_PROTECTED_EXT, 1);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
switch (t->gl.target) {
|
||||
case GL_TEXTURE_2D:
|
||||
case GL_TEXTURE_CUBE_MAP:
|
||||
@@ -689,12 +669,6 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
|
||||
GLenum internalFormat = getInternalFormat(format);
|
||||
assert_invariant(internalFormat);
|
||||
|
||||
if (UTILS_UNLIKELY(usage & TextureUsage::PROTECTED)) {
|
||||
// renderbuffers don't have a protected mode, so we need to use a texture
|
||||
// because protected textures are only supported on GLES 3.2, MSAA will be available.
|
||||
usage |= TextureUsage::SAMPLEABLE;
|
||||
}
|
||||
|
||||
auto& gl = mContext;
|
||||
samples = std::clamp(samples, uint8_t(1u), uint8_t(gl.gets.max_samples));
|
||||
GLTexture* t = construct<GLTexture>(th, target, levels, samples, w, h, depth, format, usage);
|
||||
@@ -764,8 +738,7 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
textureStorage(t, w, h, depth, bool(usage & TextureUsage::PROTECTED));
|
||||
textureStorage(t, w, h, depth);
|
||||
}
|
||||
} else {
|
||||
assert_invariant(any(usage & (
|
||||
@@ -774,7 +747,6 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
|
||||
TextureUsage::STENCIL_ATTACHMENT)));
|
||||
assert_invariant(levels == 1);
|
||||
assert_invariant(target == SamplerType::SAMPLER_2D);
|
||||
assert_invariant(none(usage & TextureUsage::PROTECTED));
|
||||
t->gl.internalFormat = internalFormat;
|
||||
t->gl.target = GL_RENDERBUFFER;
|
||||
glGenRenderbuffers(1, &t->gl.id);
|
||||
@@ -871,65 +843,61 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
||||
}
|
||||
|
||||
void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb) {
|
||||
// NOTE: this is called from draw() and must be as efficient as possible.
|
||||
|
||||
auto& gl = mContext;
|
||||
|
||||
// NOTE: this is called from draw() and must be as efficient as possible.
|
||||
|
||||
|
||||
if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) {
|
||||
// The VAO for the given render primitive must already be bound.
|
||||
#ifndef NDEBUG
|
||||
GLint vaoBinding;
|
||||
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vaoBinding);
|
||||
assert_invariant(vaoBinding == (GLint)rp->gl.vao[gl.contextIndex]);
|
||||
assert_invariant(vaoBinding == (GLint)rp->gl.vao);
|
||||
#endif
|
||||
rp->gl.vertexBufferVersion = vb->bufferObjectsVersion;
|
||||
} else {
|
||||
// if we don't have OES_vertex_array_object, we never update the buffer version so
|
||||
// that it's always reset in draw
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(rp->gl.vertexBufferVersion == vb->bufferObjectsVersion &&
|
||||
rp->gl.stateVersion == gl.state.age)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GLVertexBufferInfo const* const vbi = handle_cast<const GLVertexBufferInfo*>(vb->vbih);
|
||||
|
||||
for (size_t i = 0, n = vbi->attributes.size(); i < n; i++) {
|
||||
const auto& attribute = vbi->attributes[i];
|
||||
rp->maxVertexCount = vb->vertexCount;
|
||||
for (size_t i = 0, n = vb->attributes.size(); i < n; i++) {
|
||||
const auto& attribute = vb->attributes[i];
|
||||
const uint8_t bi = attribute.buffer;
|
||||
if (bi != Attribute::BUFFER_UNUSED) {
|
||||
// if a buffer is defined it must not be invalid.
|
||||
assert_invariant(vb->gl.buffers[bi]);
|
||||
|
||||
// if w're on ES2, the user shouldn't use FLAG_INTEGER_TARGET
|
||||
// Invoking glVertexAttribPointer without a bound VBO is an invalid operation, so we must
|
||||
// take care to avoid it. This can occur when VertexBuffer is only partially populated with
|
||||
// BufferObject items.
|
||||
if (bi != Attribute::BUFFER_UNUSED && UTILS_LIKELY(vb->gl.buffers[bi] != 0)) {
|
||||
|
||||
assert_invariant(!(gl.isES2() && (attribute.flags & Attribute::FLAG_INTEGER_TARGET)));
|
||||
|
||||
gl.bindBuffer(GL_ARRAY_BUFFER, vb->gl.buffers[bi]);
|
||||
GLuint const index = i;
|
||||
GLint const size = getComponentCount(attribute.type);
|
||||
GLenum const type = getComponentType(attribute.type);
|
||||
GLboolean const normalized = getNormalization(attribute.flags & Attribute::FLAG_NORMALIZED);
|
||||
GLsizei const stride = attribute.stride;
|
||||
void const* pointer = reinterpret_cast<void const *>(attribute.offset);
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (UTILS_UNLIKELY(attribute.flags & Attribute::FLAG_INTEGER_TARGET)) {
|
||||
// integer attributes can't be floats
|
||||
assert_invariant(type == GL_BYTE || type == GL_UNSIGNED_BYTE || type == GL_SHORT ||
|
||||
type == GL_UNSIGNED_SHORT || type == GL_INT || type == GL_UNSIGNED_INT);
|
||||
glVertexAttribIPointer(index, size, type, stride, pointer);
|
||||
glVertexAttribIPointer(GLuint(i),
|
||||
(GLint)getComponentCount(attribute.type),
|
||||
getComponentType(attribute.type),
|
||||
attribute.stride,
|
||||
(void*) uintptr_t(attribute.offset));
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
glVertexAttribPointer(index, size, type, normalized, stride, pointer);
|
||||
glVertexAttribPointer(GLuint(i),
|
||||
(GLint)getComponentCount(attribute.type),
|
||||
getComponentType(attribute.type),
|
||||
getNormalization(attribute.flags & Attribute::FLAG_NORMALIZED),
|
||||
attribute.stride,
|
||||
(void*) uintptr_t(attribute.offset));
|
||||
}
|
||||
|
||||
gl.enableVertexAttribArray(&rp->gl, GLuint(i));
|
||||
gl.enableVertexAttribArray(GLuint(i));
|
||||
} else {
|
||||
// In some OpenGL implementations, we must supply a properly-typed placeholder for
|
||||
// every integer input that is declared in the vertex shader.
|
||||
// Note that the corresponding doesn't have to be enabled and in fact won't be. If it
|
||||
// was enabled, it would indicate a user-error (providing the wrong type of array).
|
||||
// With a disabled array, the vertex shader gets the attribute from glVertexAttrib,
|
||||
// and must have the proper intergerness.
|
||||
// But at this point, we don't know what the shader requirements are, and so we must
|
||||
// rely on the attribute.
|
||||
|
||||
// In some OpenGL implementations, we must supply a properly-typed placeholder for
|
||||
// every integer input that is declared in the vertex shader, even if disabled.
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (UTILS_UNLIKELY(attribute.flags & Attribute::FLAG_INTEGER_TARGET)) {
|
||||
if (!gl.isES2()) {
|
||||
@@ -942,17 +910,9 @@ void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer
|
||||
glVertexAttrib4f(GLuint(i), 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
gl.disableVertexAttribArray(&rp->gl, GLuint(i));
|
||||
gl.disableVertexAttribArray(GLuint(i));
|
||||
}
|
||||
}
|
||||
|
||||
rp->gl.stateVersion = gl.state.age;
|
||||
if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) {
|
||||
rp->gl.vertexBufferVersion = vb->bufferObjectsVersion;
|
||||
} else {
|
||||
// if we don't have OES_vertex_array_object, we never update the buffer version so
|
||||
// that it's always reset in draw
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
@@ -1443,21 +1403,13 @@ void OpenGLDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch,
|
||||
void OpenGLDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||
DEBUG_MARKER()
|
||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||
mContext.createTimerQuery(tq);
|
||||
mTimerQueryImpl->createTimerQuery(tq);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Destroying driver objects
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void OpenGLDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
|
||||
DEBUG_MARKER()
|
||||
if (vbih) {
|
||||
GLVertexBufferInfo const* vbi = handle_cast<const GLVertexBufferInfo*>(vbih);
|
||||
destruct(vbih, vbi);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
|
||||
DEBUG_MARKER()
|
||||
if (vbh) {
|
||||
@@ -1497,20 +1449,7 @@ void OpenGLDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
if (rph) {
|
||||
auto& gl = mContext;
|
||||
GLRenderPrimitive const* rp = handle_cast<const GLRenderPrimitive*>(rph);
|
||||
gl.deleteVertexArray(rp->gl.vao[gl.contextIndex]);
|
||||
|
||||
// If we have a name in the "other" context, we need to schedule the destroy for
|
||||
// later, because it can't be done here. VAOs are "container objects" and are not
|
||||
// shared between contexts.
|
||||
size_t const otherContextIndex = 1 - gl.contextIndex;
|
||||
GLuint const nameInOtherContext = rp->gl.vao[otherContextIndex];
|
||||
if (UTILS_UNLIKELY(nameInOtherContext)) {
|
||||
gl.destroyWithContext(otherContextIndex,
|
||||
[name = nameInOtherContext](OpenGLContext& gl) {
|
||||
gl.deleteVertexArray(name);
|
||||
});
|
||||
}
|
||||
|
||||
gl.deleteVertexArrays(1, &rp->gl.vao);
|
||||
destruct(rph, rp);
|
||||
}
|
||||
}
|
||||
@@ -1651,7 +1590,7 @@ void OpenGLDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
|
||||
if (tqh) {
|
||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||
mContext.destroyTimerQuery(tq);
|
||||
mTimerQueryImpl->destroyTimerQuery(tq);
|
||||
destruct(tqh, tq);
|
||||
}
|
||||
}
|
||||
@@ -1942,7 +1881,7 @@ bool OpenGLDriver::isFrameBufferFetchMultiSampleSupported() {
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isFrameTimeSupported() {
|
||||
return TimerQueryFactory::isGpuTimeSupported();
|
||||
return OpenGLTimerQueryFactory::isGpuTimeSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isAutoDepthResolveSupported() {
|
||||
@@ -1960,24 +1899,12 @@ bool OpenGLDriver::isSRGBSwapChainSupported() {
|
||||
return mPlatform.isSRGBSwapChainSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isProtectedContentSupported() {
|
||||
return mPlatform.isProtectedContextSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isStereoSupported(backend::StereoscopicType stereoscopicType) {
|
||||
// Instanced-stereo requires instancing and EXT_clip_cull_distance.
|
||||
// Multiview-stereo requires ES 3.0 and OVR_multiview2.
|
||||
bool OpenGLDriver::isStereoSupported() {
|
||||
// Stereo requires instancing and EXT_clip_cull_distance.
|
||||
if (UTILS_UNLIKELY(mContext.isES2())) {
|
||||
return false;
|
||||
}
|
||||
switch (stereoscopicType) {
|
||||
case backend::StereoscopicType::INSTANCED:
|
||||
return mContext.ext.EXT_clip_cull_distance;
|
||||
case backend::StereoscopicType::MULTIVIEW:
|
||||
return mContext.ext.OVR_multiview2;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return mContext.ext.EXT_clip_cull_distance;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isParallelShaderCompileSupported() {
|
||||
@@ -1988,10 +1915,6 @@ bool OpenGLDriver::isDepthStencilResolveSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isProtectedTexturesSupported() {
|
||||
return getContext().ext.EXT_protected_textures;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -2059,18 +1982,7 @@ void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain>
|
||||
|
||||
GLSwapChain* scDraw = handle_cast<GLSwapChain*>(schDraw);
|
||||
GLSwapChain* scRead = handle_cast<GLSwapChain*>(schRead);
|
||||
|
||||
mPlatform.makeCurrent(scDraw->swapChain, scRead->swapChain,
|
||||
[this]() {
|
||||
// OpenGL context is about to change, unbind everything
|
||||
mContext.unbindEverything();
|
||||
},
|
||||
[this](size_t index) {
|
||||
// OpenGL context has changed, resynchronize the state with the cache
|
||||
mContext.synchronizeStateAndCache(index);
|
||||
slog.d << "*** OpenGL context change : " << (index ? "protected" : "default") << io::endl;
|
||||
});
|
||||
|
||||
mPlatform.makeCurrent(scDraw->swapChain, scRead->swapChain);
|
||||
mCurrentDrawSwapChain = scDraw;
|
||||
|
||||
// From the GL spec for glViewport and glScissor:
|
||||
@@ -2718,18 +2630,18 @@ void OpenGLDriver::replaceStream(GLTexture* texture, GLStream* newStream) noexce
|
||||
void OpenGLDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
DEBUG_MARKER()
|
||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||
mContext.beginTimeElapsedQuery(tq);
|
||||
mTimerQueryImpl->beginTimeElapsedQuery(tq);
|
||||
}
|
||||
|
||||
void OpenGLDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
DEBUG_MARKER()
|
||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||
mContext.endTimeElapsedQuery(*this, tq);
|
||||
mTimerQueryImpl->endTimeElapsedQuery(tq);
|
||||
}
|
||||
|
||||
TimerQueryResult OpenGLDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||
bool OpenGLDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||
return TimerQueryFactoryInterface::getTimerQueryValue(tq, elapsedTime);
|
||||
return OpenGLTimerQueryInterface::getTimerQueryValue(tq, elapsedTime);
|
||||
}
|
||||
|
||||
void OpenGLDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
@@ -3786,52 +3698,51 @@ void OpenGLDriver::updateTextureLodRange(GLTexture* texture, int8_t targetLevel)
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindPipeline(PipelineState state) {
|
||||
void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint32_t instanceCount) {
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
setRasterState(state.rasterState);
|
||||
setStencilState(state.stencilState);
|
||||
gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant);
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
|
||||
mValidProgram = useProgram(p);
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
|
||||
|
||||
bool const success = useProgram(p);
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
// Avoid fatal (or cascading) errors that can occur during the draw call when the program
|
||||
// is invalid. The shader compile error has already been dumped to the console at this
|
||||
// point, so it's fine to simply return early.
|
||||
return;
|
||||
}
|
||||
|
||||
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
|
||||
// Gracefully do nothing if the render primitive has not been set up.
|
||||
VertexBufferHandle vb = rp->gl.vertexBufferWithObjects;
|
||||
if (UTILS_UNLIKELY(!vb)) {
|
||||
mBoundRenderPrimitive = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
gl.bindVertexArray(&rp->gl);
|
||||
|
||||
// If necessary, mutate the bindings in the VAO.
|
||||
gl.bindVertexArray(&rp->gl);
|
||||
GLVertexBuffer const* const glvb = handle_cast<GLVertexBuffer*>(vb);
|
||||
updateVertexArrayObject(rp, glvb);
|
||||
|
||||
mBoundRenderPrimitive = rp;
|
||||
}
|
||||
|
||||
void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
|
||||
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
|
||||
return;
|
||||
if (UTILS_UNLIKELY(rp->gl.vertexBufferVersion != glvb->bufferObjectsVersion)) {
|
||||
updateVertexArrayObject(rp, glvb);
|
||||
}
|
||||
|
||||
setRasterState(state.rasterState);
|
||||
setStencilState(state.stencilState);
|
||||
|
||||
gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant);
|
||||
|
||||
setScissor(state.scissor);
|
||||
|
||||
if (UTILS_LIKELY(instanceCount <= 1)) {
|
||||
glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize));
|
||||
glDrawElements(GLenum(rp->type), (GLsizei)rp->count, rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(rp->offset));
|
||||
} else {
|
||||
assert_invariant(!mContext.isES2());
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glDrawElementsInstanced(GLenum(rp->type), (GLsizei)indexCount,
|
||||
rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize),
|
||||
glDrawElementsInstanced(GLenum(rp->type), (GLsizei)rp->count,
|
||||
rp->gl.getIndicesType(), reinterpret_cast<const void*>(rp->offset),
|
||||
(GLsizei)instanceCount);
|
||||
#endif
|
||||
}
|
||||
@@ -3843,21 +3754,6 @@ void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::scissor(Viewport scissor) {
|
||||
setScissor(scissor);
|
||||
}
|
||||
|
||||
void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) {
|
||||
DEBUG_MARKER()
|
||||
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
state.primitiveType = rp->type;
|
||||
state.vertexBufferInfo = rp->vbih;
|
||||
bindPipeline(state);
|
||||
bindRenderPrimitive(rph);
|
||||
draw2(indexOffset, indexCount, instanceCount);
|
||||
}
|
||||
|
||||
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
getShaderCompilerService().tick();
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "DriverBase.h"
|
||||
#include "GLUtils.h"
|
||||
#include "OpenGLContext.h"
|
||||
#include "OpenGLTimerQuery.h"
|
||||
#include "ShaderCompilerService.h"
|
||||
|
||||
#include "private/backend/Driver.h"
|
||||
@@ -39,12 +38,8 @@
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB
|
||||
# define FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB 4
|
||||
#endif
|
||||
@@ -56,7 +51,7 @@ class PixelBufferDescriptor;
|
||||
struct TargetBufferInfo;
|
||||
|
||||
class OpenGLProgram;
|
||||
class TimerQueryFactoryInterface;
|
||||
class OpenGLTimerQueryInterface;
|
||||
|
||||
class OpenGLDriver final : public DriverBase {
|
||||
inline explicit OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept;
|
||||
@@ -99,22 +94,8 @@ public:
|
||||
uint16_t age = 0;
|
||||
};
|
||||
|
||||
struct GLVertexBufferInfo : public HwVertexBufferInfo {
|
||||
GLVertexBufferInfo() noexcept = default;
|
||||
GLVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
||||
AttributeArray const& attributes)
|
||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||
attributes(attributes) {
|
||||
}
|
||||
AttributeArray attributes;
|
||||
};
|
||||
|
||||
struct GLVertexBuffer : public HwVertexBuffer {
|
||||
GLVertexBuffer() noexcept = default;
|
||||
GLVertexBuffer(uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih)
|
||||
: HwVertexBuffer(vertexCount), vbih(vbih) {
|
||||
}
|
||||
Handle<HwVertexBufferInfo> vbih;
|
||||
using HwVertexBuffer::HwVertexBuffer;
|
||||
struct {
|
||||
// 4 * MAX_VERTEX_ATTRIBUTE_COUNT bytes
|
||||
std::array<GLuint, MAX_VERTEX_ATTRIBUTE_COUNT> buffers{};
|
||||
@@ -142,7 +123,6 @@ public:
|
||||
struct GLRenderPrimitive : public HwRenderPrimitive {
|
||||
using HwRenderPrimitive::HwRenderPrimitive;
|
||||
OpenGLContext::RenderPrimitive gl;
|
||||
Handle<HwVertexBufferInfo> vbih;
|
||||
};
|
||||
|
||||
struct GLTexture : public HwTexture {
|
||||
@@ -167,7 +147,15 @@ public:
|
||||
OpenGLPlatform::ExternalTexture* externalTexture = nullptr;
|
||||
};
|
||||
|
||||
using GLTimerQuery = filament::backend::GLTimerQuery;
|
||||
struct GLTimerQuery : public HwTimerQuery {
|
||||
struct State {
|
||||
struct {
|
||||
GLuint query;
|
||||
} gl;
|
||||
std::atomic<int64_t> elapsed{};
|
||||
};
|
||||
std::shared_ptr<State> state;
|
||||
};
|
||||
|
||||
struct GLStream : public HwStream {
|
||||
using HwStream::HwStream;
|
||||
@@ -221,8 +209,8 @@ private:
|
||||
OpenGLContext mContext;
|
||||
ShaderCompilerService mShaderCompilerService;
|
||||
|
||||
friend class TimerQueryFactory;
|
||||
friend class TimerQueryNativeFactory;
|
||||
friend class OpenGLTimerQueryFactory;
|
||||
friend class TimerQueryNative;
|
||||
OpenGLContext& getContext() noexcept { return mContext; }
|
||||
|
||||
ShaderCompilerService& getShaderCompilerService() noexcept {
|
||||
@@ -323,8 +311,8 @@ private:
|
||||
void renderBufferStorage(GLuint rbo, GLenum internalformat, uint32_t width,
|
||||
uint32_t height, uint8_t samples) const noexcept;
|
||||
|
||||
void textureStorage(OpenGLDriver::GLTexture* t, uint32_t width, uint32_t height,
|
||||
uint32_t depth, bool useProtectedMemory) noexcept;
|
||||
void textureStorage(GLTexture* t,
|
||||
uint32_t width, uint32_t height, uint32_t depth) noexcept;
|
||||
|
||||
/* State tracking GL wrappers... */
|
||||
|
||||
@@ -367,10 +355,6 @@ private:
|
||||
GLboolean mRenderPassDepthWrite{};
|
||||
GLboolean mRenderPassStencilWrite{};
|
||||
|
||||
GLRenderPrimitive const* mBoundRenderPrimitive = nullptr;
|
||||
bool mValidProgram = false;
|
||||
|
||||
|
||||
void clearWithRasterPipe(TargetBufferFlags clearFlags,
|
||||
math::float4 const& linearColor, GLfloat depth, GLint stencil) noexcept;
|
||||
|
||||
@@ -413,6 +397,9 @@ private:
|
||||
void executeEveryNowAndThenOps() noexcept;
|
||||
std::vector<std::function<bool()>> mEveryNowAndThenOps;
|
||||
|
||||
// timer query implementation
|
||||
OpenGLTimerQueryInterface* mTimerQueryImpl = nullptr;
|
||||
|
||||
const Platform::DriverConfig mDriverConfig;
|
||||
Platform::DriverConfig const& getDriverConfig() const noexcept { return mDriverConfig; }
|
||||
|
||||
|
||||
@@ -18,17 +18,6 @@
|
||||
|
||||
#include "OpenGLDriverFactory.h"
|
||||
|
||||
#include <backend/AcquiredImage.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <utils/Invocable.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
Driver* OpenGLPlatform::createDefaultDriver(OpenGLPlatform* platform,
|
||||
@@ -38,15 +27,6 @@ Driver* OpenGLPlatform::createDefaultDriver(OpenGLPlatform* platform,
|
||||
|
||||
OpenGLPlatform::~OpenGLPlatform() noexcept = default;
|
||||
|
||||
void OpenGLPlatform::makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain,
|
||||
utils::Invocable<void()>, utils::Invocable<void(size_t)>) noexcept {
|
||||
makeCurrent(drawSwapChain, readSwapChain);
|
||||
}
|
||||
|
||||
bool OpenGLPlatform::isProtectedContextSupported() const noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool OpenGLPlatform::isSRGBSwapChainSupported() const noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -16,132 +16,110 @@
|
||||
|
||||
#include "OpenGLTimerQuery.h"
|
||||
|
||||
#include "GLUtils.h"
|
||||
#include "OpenGLDriver.h"
|
||||
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace backend;
|
||||
using namespace GLUtils;
|
||||
|
||||
class OpenGLDriver;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
bool TimerQueryFactory::mGpuTimeSupported = false;
|
||||
bool OpenGLTimerQueryFactory::mGpuTimeSupported = false;
|
||||
|
||||
TimerQueryFactoryInterface* TimerQueryFactory::init(
|
||||
OpenGLPlatform& platform, OpenGLContext& context) noexcept {
|
||||
(void)context;
|
||||
OpenGLTimerQueryInterface* OpenGLTimerQueryFactory::init(
|
||||
OpenGLPlatform& platform, OpenGLDriver& driver) noexcept {
|
||||
(void)driver;
|
||||
|
||||
TimerQueryFactoryInterface* impl = nullptr;
|
||||
OpenGLTimerQueryInterface* impl;
|
||||
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
|
||||
auto& context = driver.getContext();
|
||||
if (context.ext.EXT_disjoint_timer_query) {
|
||||
// timer queries are available
|
||||
if (context.bugs.dont_use_timer_query && platform.canCreateFence()) {
|
||||
// however, they don't work well, revert to using fences if we can.
|
||||
impl = new(std::nothrow) TimerQueryFenceFactory(platform);
|
||||
impl = new(std::nothrow) OpenGLTimerQueryFence(platform);
|
||||
} else {
|
||||
impl = new(std::nothrow) TimerQueryNativeFactory(context);
|
||||
impl = new(std::nothrow) TimerQueryNative(driver);
|
||||
}
|
||||
mGpuTimeSupported = true;
|
||||
} else
|
||||
#endif
|
||||
if (platform.canCreateFence()) {
|
||||
// no timer queries, but we can use fences
|
||||
impl = new(std::nothrow) TimerQueryFenceFactory(platform);
|
||||
impl = new(std::nothrow) OpenGLTimerQueryFence(platform);
|
||||
mGpuTimeSupported = true;
|
||||
} else {
|
||||
// no queries, no fences -- that's a problem
|
||||
impl = new(std::nothrow) TimerQueryFallbackFactory();
|
||||
impl = new(std::nothrow) TimerQueryFallback();
|
||||
mGpuTimeSupported = false;
|
||||
}
|
||||
assert_invariant(impl);
|
||||
return impl;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
TimerQueryFactoryInterface::~TimerQueryFactoryInterface() = default;
|
||||
OpenGLTimerQueryInterface::~OpenGLTimerQueryInterface() = default;
|
||||
|
||||
// This is a backend synchronous call
|
||||
TimerQueryResult TimerQueryFactoryInterface::getTimerQueryValue(
|
||||
GLTimerQuery* tq, uint64_t* elapsedTime) noexcept {
|
||||
bool OpenGLTimerQueryInterface::getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept {
|
||||
if (UTILS_LIKELY(tq->state)) {
|
||||
int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed);
|
||||
if (elapsed > 0) {
|
||||
bool const available = elapsed > 0;
|
||||
if (available) {
|
||||
*elapsedTime = elapsed;
|
||||
return TimerQueryResult::AVAILABLE;
|
||||
}
|
||||
return TimerQueryResult(elapsed);
|
||||
return available;
|
||||
}
|
||||
return TimerQueryResult::ERROR;
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
|
||||
|
||||
TimerQueryNativeFactory::TimerQueryNativeFactory(OpenGLContext& context)
|
||||
: mContext(context) {
|
||||
TimerQueryNative::TimerQueryNative(OpenGLDriver& driver)
|
||||
: mDriver(driver) {
|
||||
}
|
||||
|
||||
TimerQueryNativeFactory::~TimerQueryNativeFactory() = default;
|
||||
TimerQueryNative::~TimerQueryNative() = default;
|
||||
|
||||
void TimerQueryNativeFactory::createTimerQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(!tq->state);
|
||||
|
||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
||||
mContext.procs.genQueries(1u, &tq->state->gl.query);
|
||||
void TimerQueryNative::createTimerQuery(GLTimerQuery* tq) {
|
||||
if (UTILS_UNLIKELY(!tq->state)) {
|
||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
||||
}
|
||||
mDriver.getContext().procs.genQueries(1u, &tq->state->gl.query);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void TimerQueryNativeFactory::destroyTimerQuery(GLTimerQuery* tq) {
|
||||
void TimerQueryNative::destroyTimerQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
|
||||
mContext.procs.deleteQueries(1u, &tq->state->gl.query);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
|
||||
tq->state.reset();
|
||||
}
|
||||
|
||||
void TimerQueryNativeFactory::beginTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
|
||||
tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY), std::memory_order_relaxed);
|
||||
mContext.procs.beginQuery(GL_TIME_ELAPSED, tq->state->gl.query);
|
||||
mDriver.getContext().procs.deleteQueries(1u, &tq->state->gl.query);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void TimerQueryNativeFactory::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* tq) {
|
||||
void TimerQueryNative::beginTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
tq->state->elapsed.store(0);
|
||||
mDriver.getContext().procs.beginQuery(GL_TIME_ELAPSED, tq->state->gl.query);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
mContext.procs.endQuery(GL_TIME_ELAPSED);
|
||||
void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
mDriver.getContext().procs.endQuery(GL_TIME_ELAPSED);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
|
||||
std::weak_ptr<GLTimerQuery::State> const weak = tq->state;
|
||||
|
||||
driver.runEveryNowAndThen([&context = mContext, weak]() -> bool {
|
||||
mDriver.runEveryNowAndThen([context = mDriver.getContext(), weak]() -> bool {
|
||||
auto state = weak.lock();
|
||||
if (state) {
|
||||
GLuint available = 0;
|
||||
@@ -155,8 +133,6 @@ void TimerQueryNativeFactory::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQ
|
||||
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
|
||||
context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime);
|
||||
state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed);
|
||||
} else {
|
||||
state->elapsed.store(int64_t(TimerQueryResult::ERROR), std::memory_order_relaxed);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -166,7 +142,7 @@ void TimerQueryNativeFactory::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQ
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
TimerQueryFenceFactory::TimerQueryFenceFactory(OpenGLPlatform& platform)
|
||||
OpenGLTimerQueryFence::OpenGLTimerQueryFence(OpenGLPlatform& platform)
|
||||
: mPlatform(platform) {
|
||||
mQueue.reserve(2);
|
||||
mThread = std::thread([this]() {
|
||||
@@ -190,8 +166,7 @@ TimerQueryFenceFactory::TimerQueryFenceFactory(OpenGLPlatform& platform)
|
||||
});
|
||||
}
|
||||
|
||||
TimerQueryFenceFactory::~TimerQueryFenceFactory() {
|
||||
assert_invariant(mQueue.empty());
|
||||
OpenGLTimerQueryFence::~OpenGLTimerQueryFence() {
|
||||
if (mThread.joinable()) {
|
||||
std::unique_lock<utils::Mutex> lock(mLock);
|
||||
mExitRequested = true;
|
||||
@@ -203,26 +178,27 @@ TimerQueryFenceFactory::~TimerQueryFenceFactory() {
|
||||
}
|
||||
}
|
||||
|
||||
void TimerQueryFenceFactory::push(TimerQueryFenceFactory::Job&& job) {
|
||||
void OpenGLTimerQueryFence::enqueue(OpenGLTimerQueryFence::Job&& job) {
|
||||
std::unique_lock<utils::Mutex> const lock(mLock);
|
||||
mQueue.push_back(std::move(job));
|
||||
mQueue.push_back(std::forward<Job>(job));
|
||||
mCondition.notify_one();
|
||||
}
|
||||
|
||||
void TimerQueryFenceFactory::createTimerQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(!tq->state);
|
||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
||||
void OpenGLTimerQueryFence::createTimerQuery(GLTimerQuery* tq) {
|
||||
if (UTILS_UNLIKELY(!tq->state)) {
|
||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
||||
}
|
||||
}
|
||||
|
||||
void TimerQueryFenceFactory::destroyTimerQuery(GLTimerQuery* tq) {
|
||||
void OpenGLTimerQueryFence::destroyTimerQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
tq->state.reset();
|
||||
}
|
||||
|
||||
void TimerQueryFenceFactory::beginTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY), std::memory_order_relaxed);
|
||||
tq->state->elapsed.store(0);
|
||||
|
||||
Platform::Fence* fence = mPlatform.createFence();
|
||||
std::weak_ptr<GLTimerQuery::State> const weak = tq->state;
|
||||
|
||||
// FIXME: this implementation of beginTimeElapsedQuery is usually wrong; it ends up
|
||||
@@ -231,11 +207,12 @@ void TimerQueryFenceFactory::beginTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
// on a dummy target for instance, or somehow latch the begin time at the next renderpass
|
||||
// start.
|
||||
|
||||
push([&platform = mPlatform, fence = mPlatform.createFence(), weak]() {
|
||||
push([&platform = mPlatform, fence, weak]() {
|
||||
auto state = weak.lock();
|
||||
if (state) {
|
||||
platform.waitFence(fence, FENCE_WAIT_FOR_EVER);
|
||||
state->then = clock::now().time_since_epoch().count();
|
||||
int64_t const then = clock::now().time_since_epoch().count();
|
||||
state->elapsed.store(-then, std::memory_order_relaxed);
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_ASYNC_BEGIN("OpenGLTimerQueryFence", intptr_t(state.get()));
|
||||
}
|
||||
@@ -243,16 +220,19 @@ void TimerQueryFenceFactory::beginTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
});
|
||||
}
|
||||
|
||||
void TimerQueryFenceFactory::endTimeElapsedQuery(OpenGLDriver&, GLTimerQuery* tq) {
|
||||
void OpenGLTimerQueryFence::endTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
Platform::Fence* fence = mPlatform.createFence();
|
||||
std::weak_ptr<GLTimerQuery::State> const weak = tq->state;
|
||||
|
||||
push([&platform = mPlatform, fence = mPlatform.createFence(), weak]() {
|
||||
push([&platform = mPlatform, fence, weak]() {
|
||||
auto state = weak.lock();
|
||||
if (state) {
|
||||
platform.waitFence(fence, FENCE_WAIT_FOR_EVER);
|
||||
int64_t const now = clock::now().time_since_epoch().count();
|
||||
state->elapsed.store(now - state->then, std::memory_order_relaxed);
|
||||
int64_t const then = state->elapsed.load(std::memory_order_relaxed);
|
||||
assert_invariant(then < 0);
|
||||
state->elapsed.store(now + then, std::memory_order_relaxed);
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_ASYNC_END("OpenGLTimerQueryFence", intptr_t(state.get()));
|
||||
}
|
||||
@@ -262,32 +242,34 @@ void TimerQueryFenceFactory::endTimeElapsedQuery(OpenGLDriver&, GLTimerQuery* tq
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
TimerQueryFallbackFactory::TimerQueryFallbackFactory() = default;
|
||||
TimerQueryFallback::TimerQueryFallback() = default;
|
||||
|
||||
TimerQueryFallbackFactory::~TimerQueryFallbackFactory() = default;
|
||||
TimerQueryFallback::~TimerQueryFallback() = default;
|
||||
|
||||
void TimerQueryFallbackFactory::createTimerQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(!tq->state);
|
||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
||||
void TimerQueryFallback::createTimerQuery(GLTimerQuery* tq) {
|
||||
if (UTILS_UNLIKELY(!tq->state)) {
|
||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
||||
}
|
||||
}
|
||||
|
||||
void TimerQueryFallbackFactory::destroyTimerQuery(GLTimerQuery* tq) {
|
||||
void TimerQueryFallback::destroyTimerQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
tq->state.reset();
|
||||
}
|
||||
|
||||
void TimerQueryFallbackFactory::beginTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
void TimerQueryFallback::beginTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
// this implementation measures the CPU time, but we have no h/w support
|
||||
tq->state->then = clock::now().time_since_epoch().count();
|
||||
tq->state->elapsed.store(int64_t(TimerQueryResult::NOT_READY), std::memory_order_relaxed);
|
||||
int64_t const then = clock::now().time_since_epoch().count();
|
||||
tq->state->elapsed.store(-then, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void TimerQueryFallbackFactory::endTimeElapsedQuery(OpenGLDriver&, GLTimerQuery* tq) {
|
||||
void TimerQueryFallback::endTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
// this implementation measures the CPU time, but we have no h/w support
|
||||
int64_t const now = clock::now().time_since_epoch().count();
|
||||
tq->state->elapsed.store(now - tq->state->then, std::memory_order_relaxed);
|
||||
int64_t const then = tq->state->elapsed.load(std::memory_order_relaxed);
|
||||
assert_invariant(then < 0);
|
||||
tq->state->elapsed.store(now + then, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -17,41 +17,18 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_OPENGL_TIMERQUERY_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_TIMERQUERY_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include "OpenGLDriver.h"
|
||||
|
||||
#include <utils/Condition.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include "gl_headers.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class OpenGLPlatform;
|
||||
class OpenGLContext;
|
||||
class OpenGLDriver;
|
||||
class TimerQueryFactoryInterface;
|
||||
|
||||
struct GLTimerQuery : public HwTimerQuery {
|
||||
struct State {
|
||||
struct {
|
||||
GLuint query;
|
||||
} gl;
|
||||
int64_t then{};
|
||||
std::atomic<int64_t> elapsed{};
|
||||
};
|
||||
std::shared_ptr<State> state;
|
||||
};
|
||||
class OpenGLTimerQueryInterface;
|
||||
|
||||
/*
|
||||
* We need two implementation of timer queries (only elapsed time), because
|
||||
@@ -61,80 +38,83 @@ struct GLTimerQuery : public HwTimerQuery {
|
||||
* These classes implement the various strategies...
|
||||
*/
|
||||
|
||||
class TimerQueryFactory {
|
||||
|
||||
class OpenGLTimerQueryFactory {
|
||||
static bool mGpuTimeSupported;
|
||||
public:
|
||||
static TimerQueryFactoryInterface* init(
|
||||
OpenGLPlatform& platform, OpenGLContext& context) noexcept;
|
||||
static OpenGLTimerQueryInterface* init(
|
||||
OpenGLPlatform& platform, OpenGLDriver& driver) noexcept;
|
||||
|
||||
static bool isGpuTimeSupported() noexcept {
|
||||
return mGpuTimeSupported;
|
||||
}
|
||||
};
|
||||
|
||||
class TimerQueryFactoryInterface {
|
||||
class OpenGLTimerQueryInterface {
|
||||
protected:
|
||||
using GLTimerQuery = filament::backend::GLTimerQuery;
|
||||
using GLTimerQuery = OpenGLDriver::GLTimerQuery;
|
||||
using clock = std::chrono::steady_clock;
|
||||
|
||||
public:
|
||||
virtual ~TimerQueryFactoryInterface();
|
||||
virtual ~OpenGLTimerQueryInterface();
|
||||
virtual void createTimerQuery(GLTimerQuery* query) = 0;
|
||||
virtual void destroyTimerQuery(GLTimerQuery* query) = 0;
|
||||
virtual void beginTimeElapsedQuery(GLTimerQuery* query) = 0;
|
||||
virtual void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) = 0;
|
||||
virtual void endTimeElapsedQuery(GLTimerQuery* query) = 0;
|
||||
|
||||
static TimerQueryResult getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept;
|
||||
static bool getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept;
|
||||
};
|
||||
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
|
||||
|
||||
class TimerQueryNativeFactory final : public TimerQueryFactoryInterface {
|
||||
class TimerQueryNative : public OpenGLTimerQueryInterface {
|
||||
public:
|
||||
explicit TimerQueryNativeFactory(OpenGLContext& context);
|
||||
~TimerQueryNativeFactory() override;
|
||||
explicit TimerQueryNative(OpenGLDriver& driver);
|
||||
~TimerQueryNative() override;
|
||||
private:
|
||||
void createTimerQuery(GLTimerQuery* query) override;
|
||||
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override;
|
||||
OpenGLContext& mContext;
|
||||
void endTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
OpenGLDriver& mDriver;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
class TimerQueryFenceFactory final : public TimerQueryFactoryInterface {
|
||||
class OpenGLTimerQueryFence : public OpenGLTimerQueryInterface {
|
||||
public:
|
||||
explicit TimerQueryFenceFactory(OpenGLPlatform& platform);
|
||||
~TimerQueryFenceFactory() override;
|
||||
explicit OpenGLTimerQueryFence(OpenGLPlatform& platform);
|
||||
~OpenGLTimerQueryFence() override;
|
||||
private:
|
||||
using Job = std::function<void()>;
|
||||
using Container = std::vector<Job>;
|
||||
|
||||
void createTimerQuery(GLTimerQuery* query) override;
|
||||
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||
void beginTimeElapsedQuery(GLTimerQuery* tq) override;
|
||||
void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* tq) override;
|
||||
void endTimeElapsedQuery(GLTimerQuery* tq) override;
|
||||
|
||||
void push(Job&& job);
|
||||
void enqueue(Job&& job);
|
||||
template<typename CALLABLE, typename ... ARGS>
|
||||
void push(CALLABLE&& func, ARGS&& ... args) {
|
||||
enqueue(Job(std::bind(std::forward<CALLABLE>(func), std::forward<ARGS>(args)...)));
|
||||
}
|
||||
|
||||
OpenGLPlatform& mPlatform;
|
||||
std::thread mThread;
|
||||
mutable utils::Mutex mLock;
|
||||
mutable utils::Condition mCondition;
|
||||
Container mQueue;
|
||||
std::vector<Job> mQueue;
|
||||
bool mExitRequested = false;
|
||||
};
|
||||
|
||||
class TimerQueryFallbackFactory final : public TimerQueryFactoryInterface {
|
||||
class TimerQueryFallback : public OpenGLTimerQueryInterface {
|
||||
public:
|
||||
explicit TimerQueryFallbackFactory();
|
||||
~TimerQueryFallbackFactory() override;
|
||||
explicit TimerQueryFallback();
|
||||
~TimerQueryFallback() override;
|
||||
private:
|
||||
void createTimerQuery(GLTimerQuery* query) override;
|
||||
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
void endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQuery* query) override;
|
||||
void endTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -261,11 +261,12 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
[this, &gl, program = std::move(program), token]() mutable {
|
||||
// compile the shaders
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
|
||||
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
|
||||
compileShaders(gl,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
shaders,
|
||||
token->shaderSourceCode);
|
||||
shaderSourceCode);
|
||||
|
||||
// link the program
|
||||
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
|
||||
|
||||
@@ -72,7 +72,6 @@ PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR;
|
||||
// On Android, If we want to support a build system less than ANDROID_API 21, we need to
|
||||
// use getProcAddress for ES3.1 and above entry points.
|
||||
PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
|
||||
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR;
|
||||
#endif
|
||||
static std::once_flag sGlExtInitialized;
|
||||
#endif // __EMSCRIPTEN__
|
||||
@@ -120,7 +119,6 @@ void importGLESExtensionsEntryPoints() {
|
||||
#endif
|
||||
#if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
|
||||
getProcAddress(glDispatchCompute, "glDispatchCompute");
|
||||
getProcAddress(glFramebufferTextureMultiviewOVR, "glFramebufferTextureMultiviewOVR");
|
||||
#endif
|
||||
});
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
@@ -153,7 +153,6 @@ extern PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR;
|
||||
#endif
|
||||
#if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
|
||||
extern PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
|
||||
extern PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR;
|
||||
#endif
|
||||
#endif // __EMSCRIPTEN__
|
||||
} // namespace glext
|
||||
|
||||
@@ -18,32 +18,16 @@
|
||||
|
||||
#include "opengl/GLUtils.h"
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#if defined(__ANDROID__)
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <sys/system_properties.h>
|
||||
#endif
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <new>
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#ifndef EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE
|
||||
# define EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE 0x3483
|
||||
@@ -64,6 +48,7 @@ UTILS_PRIVATE PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = {};
|
||||
}
|
||||
using namespace glext;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Utilities
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
@@ -152,8 +137,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
||||
ext.egl.KHR_gl_colorspace = extensions.has("EGL_KHR_gl_colorspace");
|
||||
ext.egl.KHR_create_context = extensions.has("EGL_KHR_create_context");
|
||||
ext.egl.KHR_no_config_context = extensions.has("EGL_KHR_no_config_context");
|
||||
ext.egl.KHR_surfaceless_context = extensions.has("EGL_KHR_surfaceless_context");
|
||||
ext.egl.EXT_protected_content = extensions.has("EGL_EXT_protected_content");
|
||||
ext.egl.KHR_surfaceless_context = extensions.has("KHR_surfaceless_context");
|
||||
if (ext.egl.KHR_create_context) {
|
||||
// KHR_create_context implies KHR_surfaceless_context for ES3.x contexts
|
||||
ext.egl.KHR_surfaceless_context = true;
|
||||
@@ -282,7 +266,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(makeCurrent(mEGLContext, mEGLDummySurface, mEGLDummySurface) == EGL_FALSE)) {
|
||||
if (UTILS_UNLIKELY(!makeCurrent(mEGLDummySurface, mEGLDummySurface))) {
|
||||
// eglMakeCurrent failed
|
||||
logEglError("eglMakeCurrent");
|
||||
goto error;
|
||||
@@ -306,13 +290,9 @@ error:
|
||||
if (mEGLContext) {
|
||||
eglDestroyContext(mEGLDisplay, mEGLContext);
|
||||
}
|
||||
if (mEGLContextProtected) {
|
||||
eglDestroyContext(mEGLDisplay, mEGLContextProtected);
|
||||
}
|
||||
|
||||
mEGLDummySurface = EGL_NO_SURFACE;
|
||||
mEGLContext = EGL_NO_CONTEXT;
|
||||
mEGLContextProtected = EGL_NO_CONTEXT;
|
||||
|
||||
eglTerminate(mEGLDisplay);
|
||||
eglReleaseThread();
|
||||
@@ -324,10 +304,6 @@ bool PlatformEGL::isExtraContextSupported() const noexcept {
|
||||
return ext.egl.KHR_surfaceless_context;
|
||||
}
|
||||
|
||||
bool PlatformEGL::isProtectedContextSupported() const noexcept {
|
||||
return ext.egl.EXT_protected_content;
|
||||
}
|
||||
|
||||
void PlatformEGL::createContext(bool shared) {
|
||||
EGLConfig config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig;
|
||||
|
||||
@@ -363,21 +339,10 @@ void PlatformEGL::releaseContext() noexcept {
|
||||
}
|
||||
|
||||
EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept {
|
||||
return makeCurrent(mCurrentContext, drawSurface, readSurface);
|
||||
}
|
||||
|
||||
EGLBoolean PlatformEGL::makeCurrent(EGLContext context,
|
||||
EGLSurface drawSurface, EGLSurface readSurface) noexcept {
|
||||
if (UTILS_UNLIKELY((mCurrentContext != context ||
|
||||
drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) {
|
||||
EGLBoolean const success = eglMakeCurrent(
|
||||
mEGLDisplay, drawSurface, readSurface, context);
|
||||
if (success) {
|
||||
mCurrentDrawSurface = drawSurface;
|
||||
mCurrentReadSurface = readSurface;
|
||||
mCurrentContext = context;
|
||||
}
|
||||
return success;
|
||||
if (UTILS_UNLIKELY((drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) {
|
||||
mCurrentDrawSurface = drawSurface;
|
||||
mCurrentReadSurface = readSurface;
|
||||
return eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext);
|
||||
}
|
||||
return EGL_TRUE;
|
||||
}
|
||||
@@ -389,9 +354,6 @@ void PlatformEGL::terminate() noexcept {
|
||||
eglDestroySurface(mEGLDisplay, mEGLDummySurface);
|
||||
}
|
||||
eglDestroyContext(mEGLDisplay, mEGLContext);
|
||||
if (mEGLContextProtected != EGL_NO_CONTEXT) {
|
||||
eglDestroyContext(mEGLDisplay, mEGLContextProtected);
|
||||
}
|
||||
for (auto context : mAdditionalContexts) {
|
||||
eglDestroyContext(mEGLDisplay, context);
|
||||
}
|
||||
@@ -462,20 +424,10 @@ EGLConfig PlatformEGL::findSwapChainConfig(uint64_t flags, bool window, bool pbu
|
||||
return config;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
bool PlatformEGL::isSRGBSwapChainSupported() const noexcept {
|
||||
return ext.egl.KHR_gl_colorspace;
|
||||
}
|
||||
|
||||
bool PlatformEGL::isSwapChainProtected(Platform::SwapChain const* swapChain) noexcept {
|
||||
if (swapChain) {
|
||||
SwapChainEGL const* const sc = static_cast<SwapChainEGL const*>(swapChain);
|
||||
return bool(sc->flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Platform::SwapChain* PlatformEGL::createSwapChain(
|
||||
void* nativeWindow, uint64_t flags) noexcept {
|
||||
|
||||
@@ -496,16 +448,6 @@ Platform::SwapChain* PlatformEGL::createSwapChain(
|
||||
if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
|
||||
attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR;
|
||||
}
|
||||
} else {
|
||||
flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE;
|
||||
}
|
||||
|
||||
if (ext.egl.EXT_protected_content) {
|
||||
if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
|
||||
attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
|
||||
}
|
||||
} else {
|
||||
flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
|
||||
}
|
||||
|
||||
EGLSurface sur = eglCreateWindowSurface(mEGLDisplay, config,
|
||||
@@ -519,14 +461,7 @@ Platform::SwapChain* PlatformEGL::createSwapChain(
|
||||
// this is not fatal
|
||||
eglSurfaceAttrib(mEGLDisplay, sur, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED);
|
||||
|
||||
SwapChainEGL* const sc = new(std::nothrow) SwapChainEGL({
|
||||
.sur = sur,
|
||||
.attribs = std::move(attribs),
|
||||
.nativeWindow = (EGLNativeWindowType)nativeWindow,
|
||||
.config = config,
|
||||
.flags = flags
|
||||
});
|
||||
return sc;
|
||||
return (SwapChain*)sur;
|
||||
}
|
||||
|
||||
Platform::SwapChain* PlatformEGL::createSwapChain(
|
||||
@@ -552,16 +487,6 @@ Platform::SwapChain* PlatformEGL::createSwapChain(
|
||||
if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
|
||||
attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR;
|
||||
}
|
||||
} else {
|
||||
flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE;
|
||||
}
|
||||
|
||||
if (ext.egl.EXT_protected_content) {
|
||||
if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
|
||||
attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
|
||||
}
|
||||
} else {
|
||||
flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
|
||||
}
|
||||
|
||||
EGLSurface sur = eglCreatePbufferSurface(mEGLDisplay, config, attribs.data());
|
||||
@@ -570,102 +495,33 @@ Platform::SwapChain* PlatformEGL::createSwapChain(
|
||||
logEglError("eglCreatePbufferSurface");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SwapChainEGL* const sc = new(std::nothrow) SwapChainEGL({
|
||||
.sur = sur,
|
||||
.attribs = std::move(attribs),
|
||||
.config = config
|
||||
});
|
||||
return sc;
|
||||
return (SwapChain*)sur;
|
||||
}
|
||||
|
||||
void PlatformEGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
|
||||
if (swapChain) {
|
||||
SwapChainEGL const* const sc = static_cast<SwapChainEGL const*>(swapChain);
|
||||
if (sc->sur != EGL_NO_SURFACE) {
|
||||
makeCurrent(mCurrentContext, mEGLDummySurface, mEGLDummySurface);
|
||||
eglDestroySurface(mEGLDisplay, sc->sur);
|
||||
delete sc;
|
||||
}
|
||||
EGLSurface sur = (EGLSurface) swapChain;
|
||||
if (sur != EGL_NO_SURFACE) {
|
||||
makeCurrent(mEGLDummySurface, mEGLDummySurface);
|
||||
eglDestroySurface(mEGLDisplay, sur);
|
||||
}
|
||||
}
|
||||
|
||||
void PlatformEGL::makeCurrent(Platform::SwapChain* drawSwapChain,
|
||||
Platform::SwapChain* readSwapChain) noexcept {
|
||||
SwapChainEGL const* const dsc = static_cast<SwapChainEGL const*>(drawSwapChain);
|
||||
SwapChainEGL const* const rsc = static_cast<SwapChainEGL const*>(readSwapChain);
|
||||
makeCurrent(mEGLContext, dsc->sur, rsc->sur);
|
||||
}
|
||||
|
||||
void PlatformEGL::makeCurrent(Platform::SwapChain* drawSwapChain,
|
||||
Platform::SwapChain* readSwapChain,
|
||||
utils::Invocable<void()> preContextChange,
|
||||
utils::Invocable<void(size_t index)> postContextChange) noexcept {
|
||||
SwapChainEGL const* const dsc = static_cast<SwapChainEGL const*>(drawSwapChain);
|
||||
SwapChainEGL const* const rsc = static_cast<SwapChainEGL const*>(readSwapChain);
|
||||
EGLContext context = mEGLContext;
|
||||
if (ext.egl.EXT_protected_content) {
|
||||
bool const swapChainProtected = PlatformEGL::isSwapChainProtected(dsc);
|
||||
if (UTILS_UNLIKELY(swapChainProtected)) {
|
||||
// we need a protected context
|
||||
if (UTILS_UNLIKELY(mEGLContextProtected == EGL_NO_CONTEXT)) {
|
||||
// we don't have one, create it!
|
||||
EGLConfig config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig;
|
||||
Config protectedContextAttribs{ mContextAttribs };
|
||||
protectedContextAttribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
|
||||
mEGLContextProtected = eglCreateContext(mEGLDisplay, config, mEGLContext,
|
||||
protectedContextAttribs.data());
|
||||
if (UTILS_UNLIKELY(mEGLContextProtected == EGL_NO_CONTEXT)) {
|
||||
// couldn't create the protected context
|
||||
logEglError("eglCreateContext[EGL_PROTECTED_CONTENT_EXT]");
|
||||
ext.egl.EXT_protected_content = false;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
context = mEGLContextProtected;
|
||||
error: ;
|
||||
}
|
||||
|
||||
bool const contextChange = context != mCurrentContext;
|
||||
if (UTILS_UNLIKELY(contextChange)) {
|
||||
preContextChange();
|
||||
EGLBoolean const success = makeCurrent(context, dsc->sur, rsc->sur);
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
logEglError("PlatformEGL::makeCurrent");
|
||||
if (mEGLContextProtected != EGL_NO_CONTEXT) {
|
||||
eglDestroyContext(mEGLDisplay, mEGLContextProtected);
|
||||
mEGLContextProtected = EGL_NO_CONTEXT;
|
||||
}
|
||||
context = mEGLContext;
|
||||
}
|
||||
if (UTILS_LIKELY(!swapChainProtected && mEGLContextProtected != EGL_NO_CONTEXT)) {
|
||||
// We don't need the protected context anymore, unbind and destroy right away.
|
||||
eglDestroyContext(mEGLDisplay, mEGLContextProtected);
|
||||
mEGLContextProtected = EGL_NO_CONTEXT;
|
||||
}
|
||||
size_t const contextIndex = (context == mEGLContext) ? 0 : 1;
|
||||
postContextChange(contextIndex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
EGLBoolean const success = makeCurrent(context, dsc->sur, rsc->sur);
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
logEglError("PlatformEGL::makeCurrent");
|
||||
Platform::SwapChain* readSwapChain) noexcept {
|
||||
EGLSurface drawSur = (EGLSurface) drawSwapChain;
|
||||
EGLSurface readSur = (EGLSurface) readSwapChain;
|
||||
if (drawSur != EGL_NO_SURFACE || readSur != EGL_NO_SURFACE) {
|
||||
makeCurrent(drawSur, readSur);
|
||||
}
|
||||
}
|
||||
|
||||
void PlatformEGL::commit(Platform::SwapChain* swapChain) noexcept {
|
||||
if (swapChain) {
|
||||
SwapChainEGL const* const sc = static_cast<SwapChainEGL const*>(swapChain);
|
||||
if (sc->sur != EGL_NO_SURFACE) {
|
||||
eglSwapBuffers(mEGLDisplay, sc->sur);
|
||||
}
|
||||
EGLSurface sur = (EGLSurface) swapChain;
|
||||
if (sur != EGL_NO_SURFACE) {
|
||||
eglSwapBuffers(mEGLDisplay, sur);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
bool PlatformEGL::canCreateFence() noexcept {
|
||||
return true;
|
||||
}
|
||||
@@ -704,10 +560,8 @@ FenceStatus PlatformEGL::waitFence(
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
OpenGLPlatform::ExternalTexture* PlatformEGL::createExternalImageTexture() noexcept {
|
||||
ExternalTexture* outTexture = new(std::nothrow) ExternalTexture{};
|
||||
ExternalTexture* outTexture = new ExternalTexture{};
|
||||
glGenTextures(1, &outTexture->id);
|
||||
if (UTILS_LIKELY(ext.gl.OES_EGL_image_external_essl3)) {
|
||||
outTexture->target = GL_TEXTURE_EXTERNAL_OES;
|
||||
@@ -736,8 +590,6 @@ bool PlatformEGL::setExternalImage(void* externalImage,
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
void PlatformEGL::initializeGlExtensions() noexcept {
|
||||
// We're guaranteed to be on an ES platform, since we're using EGL
|
||||
GLUtils::unordered_string_set glExtensions;
|
||||
@@ -783,3 +635,5 @@ void PlatformEGL::Config::erase(EGLint name) noexcept {
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -14,34 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <backend/AcquiredImage.h>
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/platforms/PlatformEGL.h>
|
||||
#include <backend/platforms/PlatformEGLAndroid.h>
|
||||
|
||||
#include "opengl/GLUtils.h"
|
||||
#include "ExternalStreamManagerAndroid.h"
|
||||
|
||||
#include <android/api-level.h>
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <new>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// We require filament to be built with an API 19 toolchain, before that, OpenGLES 3.0 didn't exist
|
||||
// Actually, OpenGL ES 3.0 was added to API 18, but API 19 is the better target and
|
||||
// the minimum for Jetpack at the time of this comment.
|
||||
@@ -178,28 +165,14 @@ int PlatformEGLAndroid::getOSVersion() const noexcept {
|
||||
|
||||
AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage source) noexcept {
|
||||
// Convert the AHardwareBuffer to EGLImage.
|
||||
AHardwareBuffer const* const pHardwareBuffer = (const AHardwareBuffer*)source.image;
|
||||
|
||||
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(pHardwareBuffer);
|
||||
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID((const AHardwareBuffer*)source.image);
|
||||
if (!clientBuffer) {
|
||||
slog.e << "Unable to get EGLClientBuffer from AHardwareBuffer." << io::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
PlatformEGL::Config attributes;
|
||||
|
||||
if (__builtin_available(android 26, *)) {
|
||||
AHardwareBuffer_Desc desc;
|
||||
AHardwareBuffer_describe(pHardwareBuffer, &desc);
|
||||
bool const isProtectedContent =
|
||||
desc.usage & AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
|
||||
if (isProtectedContent) {
|
||||
attributes[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
EGLImageKHR eglImage = eglCreateImageKHR(mEGLDisplay,
|
||||
EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attributes.data());
|
||||
// Note that this cannot be used to stream protected video (for now) because we do not set EGL_PROTECTED_CONTENT_EXT.
|
||||
EGLint attrs[] = { EGL_NONE, EGL_NONE };
|
||||
EGLImageKHR eglImage = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs);
|
||||
if (eglImage == EGL_NO_IMAGE_KHR) {
|
||||
slog.e << "eglCreateImageKHR returned no image." << io::endl;
|
||||
return {};
|
||||
|
||||
@@ -435,8 +435,8 @@ void VulkanCommands::popGroupMarker() {
|
||||
auto const [marker, startTime] = mGroupMarkers->pop();
|
||||
auto const endTime = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> diff = endTime - startTime;
|
||||
utils::slog.d << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms"
|
||||
<< utils::io::endl;
|
||||
utils::slog.d << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms\n"
|
||||
<< utils::io::flush;
|
||||
#else
|
||||
mGroupMarkers->pop();
|
||||
#endif
|
||||
|
||||
@@ -39,12 +39,7 @@
|
||||
// not required for validation.
|
||||
|
||||
// FVK is short for Filament Vulkan
|
||||
|
||||
// Enables Android systrace
|
||||
#define FVK_DEBUG_SYSTRACE 0x00000001
|
||||
|
||||
// Group markers are used to denote collection of GPU commands. It is typically at the granualarity
|
||||
// of a renderpass.
|
||||
#define FVK_DEBUG_GROUP_MARKERS 0x00000002
|
||||
#define FVK_DEBUG_TEXTURE 0x00000004
|
||||
#define FVK_DEBUG_LAYOUT_TRANSITION 0x00000008
|
||||
@@ -60,15 +55,11 @@
|
||||
#define FVK_DEBUG_PIPELINE_CACHE 0x00002000
|
||||
#define FVK_DEBUG_ALLOCATION 0x00004000
|
||||
|
||||
// Enable the debug utils extension if it is available.
|
||||
#define FVK_DEBUG_DEBUG_UTILS 0x00008000
|
||||
|
||||
#define FVK_DEBUG_RESOURCE_LEAK 0x00010000
|
||||
|
||||
// Usefaul default combinations
|
||||
#define FVK_DEBUG_EVERYTHING 0xFFFFFFFF
|
||||
#define FVK_DEBUG_PERFORMANCE \
|
||||
FVK_DEBUG_SYSTRACE
|
||||
FVK_DEBUG_SYSTRACE | \
|
||||
FVK_DEBUG_GROUP_MARKERS
|
||||
|
||||
#define FVK_DEBUG_CORRECTNESS \
|
||||
FVK_DEBUG_VALIDATION | \
|
||||
@@ -81,40 +72,21 @@
|
||||
FVK_DEBUG_PRINT_GROUP_MARKERS
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE)
|
||||
#define FVK_DEBUG_FLAGS FVK_DEBUG_PERFORMANCE
|
||||
#else
|
||||
#define FVK_DEBUG_FLAGS 0
|
||||
#endif
|
||||
|
||||
#define FVK_ENABLED(flags) (((FVK_DEBUG_FLAGS) & (flags)) == (flags))
|
||||
|
||||
// Group marker only works only if validation or debug utils is enabled since it uses
|
||||
// vkCmd(Begin/End)DebugUtilsLabelEXT or vkCmdDebugMarker(Begin/End)EXT
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
static_assert(FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) || FVK_ENABLED(FVK_DEBUG_VALIDATION));
|
||||
#endif
|
||||
#define FVK_ENABLED(flags) ((FVK_DEBUG_FLAGS) & (flags))
|
||||
#define FVK_ENABLED_BOOL(flags) ((bool) FVK_ENABLED(flags))
|
||||
|
||||
// Ensure dependencies are met between debug options
|
||||
#if FVK_ENABLED(FVK_DEBUG_PRINT_GROUP_MARKERS)
|
||||
static_assert(FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS));
|
||||
#endif
|
||||
|
||||
// Only enable debug utils if validation is enabled.
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
|
||||
#endif
|
||||
|
||||
// end dependcy checks
|
||||
|
||||
// Shorthand for combination of enabled debug flags
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) || FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
#define FVK_ENABLED_DEBUG_SAMPLER_NAME 1
|
||||
#else
|
||||
#define FVK_ENABLED_DEBUG_SAMPLER_NAME 0
|
||||
#endif
|
||||
|
||||
// end shorthands
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SYSTRACE)
|
||||
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanImageUtility.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanTexture.h"
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
@@ -118,9 +117,7 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
|
||||
utils::slog.e << utils::io::endl;
|
||||
return VK_FALSE;
|
||||
}
|
||||
#endif // FVK_EANBLED(FVK_DEBUG_VALIDATION)
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT types, const VkDebugUtilsMessengerCallbackDataEXT* cbdata,
|
||||
void* pUserData) {
|
||||
@@ -139,63 +136,9 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla
|
||||
utils::slog.e << utils::io::endl;
|
||||
return VK_FALSE;
|
||||
}
|
||||
#endif // FVK_EANBLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
|
||||
}// anonymous namespace
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
using DebugUtils = VulkanDriver::DebugUtils;
|
||||
DebugUtils* DebugUtils::mSingleton = nullptr;
|
||||
|
||||
DebugUtils::DebugUtils(VkInstance instance, VkDevice device, VulkanContext const* context)
|
||||
: mInstance(instance), mDevice(device), mEnabled(context->isDebugUtilsSupported()) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
// Also initialize debug utils messenger here
|
||||
if (mEnabled) {
|
||||
VkDebugUtilsMessengerCreateInfoEXT const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
|
||||
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT,
|
||||
.pfnUserCallback = debugUtilsCallback,
|
||||
};
|
||||
VkResult result = vkCreateDebugUtilsMessengerEXT(instance, &createInfo,
|
||||
VKALLOC, &mDebugMessenger);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan debug messenger.");
|
||||
}
|
||||
#endif // FVK_EANBLED(FVK_DEBUG_VALIDATION)
|
||||
}
|
||||
|
||||
DebugUtils* DebugUtils::get() {
|
||||
assert_invariant(DebugUtils::mSingleton);
|
||||
return DebugUtils::mSingleton;
|
||||
}
|
||||
|
||||
DebugUtils::~DebugUtils() {
|
||||
if (mDebugMessenger) {
|
||||
vkDestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, VKALLOC);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugUtils::setName(VkObjectType type, uint64_t handle, char const* name) {
|
||||
auto impl = DebugUtils::get();
|
||||
if (!impl->mEnabled) {
|
||||
return;
|
||||
}
|
||||
VkDebugUtilsObjectNameInfoEXT const info = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.objectType = type,
|
||||
.objectHandle = handle,
|
||||
.pObjectName = name,
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(impl->mDevice, &info);
|
||||
}
|
||||
#endif // FVK_EANBLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
} // anonymous namespace
|
||||
|
||||
using ImgUtil = VulkanImageUtility;
|
||||
|
||||
@@ -216,26 +159,35 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mReadPixels(mPlatform->getDevice()),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
DebugUtils::mSingleton =
|
||||
new DebugUtils(mPlatform->getInstance(), mPlatform->getDevice(), &context);
|
||||
#endif
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
UTILS_UNUSED const PFN_vkCreateDebugReportCallbackEXT createDebugReportCallback
|
||||
= vkCreateDebugReportCallbackEXT;
|
||||
if (!context.isDebugUtilsSupported() && createDebugReportCallback) {
|
||||
VkResult result;
|
||||
if (mContext.isDebugUtilsSupported()) {
|
||||
VkDebugUtilsMessengerCreateInfoEXT const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
|
||||
.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT,
|
||||
.pfnUserCallback = debugUtilsCallback,
|
||||
};
|
||||
result = vkCreateDebugUtilsMessengerEXT(mPlatform->getInstance(), &createInfo, VKALLOC,
|
||||
&mDebugMessenger);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan debug messenger.");
|
||||
} else if (createDebugReportCallback) {
|
||||
VkDebugReportCallbackCreateInfoEXT const cbinfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
|
||||
.flags = VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT,
|
||||
.pfnCallback = debugReportCallback,
|
||||
};
|
||||
VkResult result = createDebugReportCallback(mPlatform->getInstance(), &cbinfo, VKALLOC,
|
||||
result = createDebugReportCallback(mPlatform->getInstance(), &cbinfo, VKALLOC,
|
||||
&mDebugCallback);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan debug callback.");
|
||||
}
|
||||
#endif
|
||||
|
||||
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
|
||||
mCommands = std::make_unique<VulkanCommands>(mPlatform->getDevice(),
|
||||
mPlatform->getGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(), &mContext,
|
||||
@@ -261,43 +213,6 @@ VulkanDriver::~VulkanDriver() noexcept = default;
|
||||
UTILS_NOINLINE
|
||||
Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig) noexcept {
|
||||
#if 0
|
||||
// this is useful for development, but too verbose even for debug builds
|
||||
// For reference on a 64-bits machine in Release mode:
|
||||
// VulkanSamplerGroup : 16 few
|
||||
// HwStream : 24 few
|
||||
// VulkanFence : 32 few
|
||||
// VulkanProgram : 32 moderate
|
||||
// VulkanIndexBuffer : 64 moderate
|
||||
// VulkanBufferObject : 64 many
|
||||
// -- less than or equal 64 bytes
|
||||
// VulkanRenderPrimitive : 72 many
|
||||
// VulkanVertexBufferInfo : 96 moderate
|
||||
// VulkanSwapChain : 104 few
|
||||
// VulkanTimerQuery : 160 few
|
||||
// -- less than or equal 160 bytes
|
||||
// VulkanVertexBuffer : 192 moderate
|
||||
// VulkanTexture : 224 moderate
|
||||
// VulkanRenderTarget : 312 few
|
||||
// -- less than or equal to 312 bytes
|
||||
|
||||
utils::slog.d
|
||||
<< "\nVulkanSwapChain: " << sizeof(VulkanSwapChain)
|
||||
<< "\nVulkanBufferObject: " << sizeof(VulkanBufferObject)
|
||||
<< "\nVulkanVertexBuffer: " << sizeof(VulkanVertexBuffer)
|
||||
<< "\nVulkanVertexBufferInfo: " << sizeof(VulkanVertexBufferInfo)
|
||||
<< "\nVulkanIndexBuffer: " << sizeof(VulkanIndexBuffer)
|
||||
<< "\nVulkanSamplerGroup: " << sizeof(VulkanSamplerGroup)
|
||||
<< "\nVulkanRenderPrimitive: " << sizeof(VulkanRenderPrimitive)
|
||||
<< "\nVulkanTexture: " << sizeof(VulkanTexture)
|
||||
<< "\nVulkanTimerQuery: " << sizeof(VulkanTimerQuery)
|
||||
<< "\nHwStream: " << sizeof(HwStream)
|
||||
<< "\nVulkanRenderTarget: " << sizeof(VulkanRenderTarget)
|
||||
<< "\nVulkanFence: " << sizeof(VulkanFence)
|
||||
<< "\nVulkanProgram: " << sizeof(VulkanProgram)
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
|
||||
assert_invariant(platform);
|
||||
size_t defaultSize = FVK_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig validConfig {driverConfig};
|
||||
@@ -336,12 +251,9 @@ void VulkanDriver::terminate() {
|
||||
if (mDebugCallback) {
|
||||
vkDestroyDebugReportCallbackEXT(mPlatform->getInstance(), mDebugCallback, VKALLOC);
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
assert_invariant(DebugUtils::mSingleton);
|
||||
delete DebugUtils::mSingleton;
|
||||
#endif
|
||||
|
||||
if (mDebugMessenger) {
|
||||
vkDestroyDebugUtilsMessengerEXT(mPlatform->getInstance(), mDebugMessenger, VKALLOC);
|
||||
}
|
||||
mPlatform->terminate();
|
||||
}
|
||||
|
||||
@@ -361,11 +273,6 @@ void VulkanDriver::collectGarbage() {
|
||||
mCommands->gc();
|
||||
mStagePool.gc();
|
||||
mFramebufferCache.gc();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
mResourceAllocator.print();
|
||||
#endif
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
||||
@@ -417,10 +324,12 @@ void VulkanDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t coun
|
||||
|
||||
void VulkanDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
|
||||
PrimitiveType pt) {
|
||||
auto rp = mResourceAllocator.construct<VulkanRenderPrimitive>(rph,
|
||||
&mResourceAllocator, pt, vbh, ibh);
|
||||
PrimitiveType pt, uint32_t offset,
|
||||
uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
|
||||
auto rp = mResourceAllocator.construct<VulkanRenderPrimitive>(rph, &mResourceAllocator);
|
||||
mResourceManager.acquire(rp);
|
||||
VulkanDriver::setRenderPrimitiveBuffer(rph, vbh, ibh);
|
||||
VulkanDriver::setRenderPrimitiveRange(rph, pt, offset, minIndex, maxIndex, count);
|
||||
}
|
||||
|
||||
void VulkanDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
@@ -431,26 +340,10 @@ void VulkanDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
mResourceManager.release(rp);
|
||||
}
|
||||
|
||||
void VulkanDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount,
|
||||
uint8_t attributeCount, AttributeArray attributes) {
|
||||
auto vbi = mResourceAllocator.construct<VulkanVertexBufferInfo>(vbih,
|
||||
bufferCount, attributeCount, attributes);
|
||||
mResourceManager.acquire(vbi);
|
||||
}
|
||||
|
||||
void VulkanDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
|
||||
if (!vbih) {
|
||||
return;
|
||||
}
|
||||
auto vbi = mResourceAllocator.handle_cast<VulkanVertexBufferInfo*>(vbih);
|
||||
mResourceManager.release(vbi);
|
||||
}
|
||||
|
||||
|
||||
void VulkanDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh,
|
||||
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih) {
|
||||
auto vertexBuffer = mResourceAllocator.construct<VulkanVertexBuffer>(vbh,
|
||||
mContext, mStagePool, &mResourceAllocator, vertexCount, vbih);
|
||||
void VulkanDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint8_t bufferCount,
|
||||
uint8_t attributeCount, uint32_t elementCount, AttributeArray attributes) {
|
||||
auto vertexBuffer = mResourceAllocator.construct<VulkanVertexBuffer>(vbh, mContext, mStagePool,
|
||||
&mResourceAllocator, bufferCount, attributeCount, elementCount, attributes);
|
||||
mResourceManager.acquire(vertexBuffer);
|
||||
}
|
||||
|
||||
@@ -657,10 +550,6 @@ void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||
// nothing to do, timer query was constructed in createTimerQueryS
|
||||
}
|
||||
|
||||
Handle<HwVertexBufferInfo> VulkanDriver::createVertexBufferInfoS() noexcept {
|
||||
return mResourceAllocator.allocHandle<VulkanVertexBufferInfo>();
|
||||
}
|
||||
|
||||
Handle<HwVertexBuffer> VulkanDriver::createVertexBufferS() noexcept {
|
||||
return mResourceAllocator.allocHandle<VulkanVertexBuffer>();
|
||||
}
|
||||
@@ -875,19 +764,8 @@ bool VulkanDriver::isSRGBSwapChainSupported() {
|
||||
return mIsSRGBSwapChainSupported;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isProtectedContentSupported() {
|
||||
// the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag is not supported
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isStereoSupported(backend::StereoscopicType stereoscopicType) {
|
||||
switch (stereoscopicType) {
|
||||
case backend::StereoscopicType::INSTANCED:
|
||||
return true;
|
||||
case backend::StereoscopicType::MULTIVIEW:
|
||||
// TODO: implement multiview feature in Vulkan.
|
||||
return false;
|
||||
}
|
||||
bool VulkanDriver::isStereoSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isParallelShaderCompileSupported() {
|
||||
@@ -899,10 +777,6 @@ bool VulkanDriver::isDepthStencilResolveSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isProtectedTexturesSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU: {
|
||||
@@ -989,7 +863,8 @@ void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t in
|
||||
auto vb = mResourceAllocator.handle_cast<VulkanVertexBuffer*>(vbh);
|
||||
auto bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||
assert_invariant(bo->bindingType == BufferObjectBinding::VERTEX);
|
||||
vb->setBuffer(mResourceAllocator, bo, index);
|
||||
|
||||
vb->setBuffer(bo, index);
|
||||
}
|
||||
|
||||
void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
|
||||
@@ -1048,10 +923,10 @@ void VulkanDriver::update3DImage(Handle<HwTexture> th, uint32_t level, uint32_t
|
||||
void VulkanDriver::setupExternalImage(void* image) {
|
||||
}
|
||||
|
||||
TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||
bool VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||
VulkanTimerQuery* vtq = mResourceAllocator.handle_cast<VulkanTimerQuery*>(tqh);
|
||||
if (!vtq->isCompleted()) {
|
||||
return TimerQueryResult::NOT_READY;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto results = mTimestamps->getResult(vtq);
|
||||
@@ -1061,7 +936,7 @@ TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint
|
||||
uint64_t available1 = results[3];
|
||||
|
||||
if (available0 == 0 || available1 == 0) {
|
||||
return TimerQueryResult::NOT_READY;
|
||||
return false;
|
||||
}
|
||||
|
||||
ASSERT_POSTCONDITION(timestamp1 >= timestamp0, "Timestamps are not monotonically increasing.");
|
||||
@@ -1073,7 +948,7 @@ TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint
|
||||
float const period = mContext.getPhysicalDeviceLimits().timestampPeriod;
|
||||
uint64_t delta = uint64_t(float(timestamp1 - timestamp0) * period);
|
||||
*elapsedTime = delta;
|
||||
return TimerQueryResult::AVAILABLE;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanDriver::setExternalImage(Handle<HwTexture> th, void* image) {
|
||||
@@ -1153,7 +1028,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
|
||||
VulkanRenderTarget* const rt = mResourceAllocator.handle_cast<VulkanRenderTarget*>(rth);
|
||||
const VkExtent2D extent = rt->getExtent();
|
||||
assert_invariant(rt == mDefaultRenderTarget || extent.width > 0 && extent.height > 0);
|
||||
assert_invariant(extent.width > 0 && extent.height > 0);
|
||||
|
||||
// Filament has the expectation that the contents of the swap chain are not preserved on the
|
||||
// first render pass. Note however that its contents are often preserved on subsequent render
|
||||
@@ -1305,14 +1180,22 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
}
|
||||
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
|
||||
|
||||
// Assign a label to the framebuffer for debugging purposes.
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS | FVK_DEBUG_DEBUG_UTILS)
|
||||
auto const topMarker = mCommands->getTopGroupMarker();
|
||||
if (!topMarker.empty()) {
|
||||
DebugUtils::setName(VK_OBJECT_TYPE_FRAMEBUFFER, reinterpret_cast<uint64_t>(vkfb),
|
||||
topMarker.c_str());
|
||||
// Assign a label to the framebuffer for debugging purposes.
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
if (UTILS_UNLIKELY(mContext.isDebugUtilsSupported())) {
|
||||
auto const topMarker = mCommands->getTopGroupMarker();
|
||||
if (!topMarker.empty()) {
|
||||
const VkDebugUtilsObjectNameInfoEXT info = {
|
||||
VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
nullptr,
|
||||
VK_OBJECT_TYPE_FRAMEBUFFER,
|
||||
reinterpret_cast<uint64_t>(vkfb),
|
||||
topMarker.c_str(),
|
||||
};
|
||||
vkSetDebugUtilsObjectNameEXT(mPlatform->getDevice(), &info);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// The current command buffer now owns a reference to the render target and its attachments.
|
||||
// Note that we must acquire parent textures, not sidecars.
|
||||
@@ -1466,6 +1349,24 @@ void VulkanDriver::nextSubpass(int) {
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph,
|
||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh) {
|
||||
auto primitive = mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
primitive->setBuffers(mResourceAllocator.handle_cast<VulkanVertexBuffer*>(vbh),
|
||||
mResourceAllocator.handle_cast<VulkanIndexBuffer*>(ibh));
|
||||
}
|
||||
|
||||
void VulkanDriver::setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph,
|
||||
PrimitiveType pt, uint32_t offset,
|
||||
uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
|
||||
auto& primitive = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
primitive.setPrimitiveType(pt);
|
||||
primitive.offset = offset * primitive.indexBuffer->elementSize;
|
||||
primitive.count = count;
|
||||
primitive.minIndex = minIndex;
|
||||
primitive.maxIndex = maxIndex > minIndex ? maxIndex : primitive.maxVertexCount - 1;
|
||||
}
|
||||
|
||||
void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("makeCurrent");
|
||||
@@ -1711,58 +1612,68 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
|
||||
const uint32_t instanceCount) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("draw");
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
const VulkanVertexBufferInfo& vbi =
|
||||
*mResourceAllocator.handle_cast<VulkanVertexBufferInfo*>(pipelineState.vertexBufferInfo);
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
|
||||
Handle<HwProgram> programHandle = pipelineState.program;
|
||||
RasterState rasterState = pipelineState.rasterState;
|
||||
PolygonOffset depthOffset = pipelineState.polygonOffset;
|
||||
Viewport const& scissorBox = pipelineState.scissor;
|
||||
|
||||
auto* program = mResourceAllocator.handle_cast<VulkanProgram*>(programHandle);
|
||||
commands->acquire(program);
|
||||
commands->acquire(prim.indexBuffer);
|
||||
commands->acquire(prim.vertexBuffer);
|
||||
|
||||
// If this is a debug build, validate the current shader.
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
if (program->bundle.vertex == VK_NULL_HANDLE || program->bundle.fragment == VK_NULL_HANDLE) {
|
||||
utils::slog.e << "Binding missing shader: " << program->name.c_str() << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Update the VK raster state.
|
||||
const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
||||
|
||||
VulkanPipelineCache::RasterState const vulkanRasterState{
|
||||
.cullMode = getCullMode(rasterState.culling),
|
||||
.frontFace = getFrontFace(rasterState.inverseFrontFaces),
|
||||
.depthBiasEnable = (depthOffset.constant || depthOffset.slope) ? true : false,
|
||||
.blendEnable = rasterState.hasBlending(),
|
||||
.depthWriteEnable = rasterState.depthWrite,
|
||||
.alphaToCoverageEnable = rasterState.alphaToCoverage,
|
||||
.srcColorBlendFactor = getBlendFactor(rasterState.blendFunctionSrcRGB),
|
||||
.dstColorBlendFactor = getBlendFactor(rasterState.blendFunctionDstRGB),
|
||||
.srcAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionSrcAlpha),
|
||||
.dstAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionDstAlpha),
|
||||
.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0),
|
||||
.rasterizationSamples = rt->getSamples(),
|
||||
.colorTargetCount = rt->getColorTargetCount(mCurrentRenderPass),
|
||||
.colorBlendOp = rasterState.blendEquationRGB,
|
||||
.alphaBlendOp = rasterState.blendEquationAlpha,
|
||||
.depthCompareOp = rasterState.depthFunc,
|
||||
.depthBiasConstantFactor = depthOffset.constant,
|
||||
.depthBiasSlopeFactor = depthOffset.slope
|
||||
};
|
||||
|
||||
// unfortunately in Vulkan the topology is per pipeline
|
||||
VkPrimitiveTopology const topology =
|
||||
VulkanPipelineCache::getPrimitiveTopology(pipelineState.primitiveType);
|
||||
auto vkraster = mPipelineCache.getCurrentRasterState();
|
||||
vkraster.cullMode = getCullMode(rasterState.culling);
|
||||
vkraster.frontFace = getFrontFace(rasterState.inverseFrontFaces);
|
||||
vkraster.depthBiasEnable = (depthOffset.constant || depthOffset.slope) ? true : false;
|
||||
vkraster.depthBiasConstantFactor = depthOffset.constant;
|
||||
vkraster.depthBiasSlopeFactor = depthOffset.slope;
|
||||
vkraster.blendEnable = rasterState.hasBlending();
|
||||
vkraster.srcColorBlendFactor = getBlendFactor(rasterState.blendFunctionSrcRGB);
|
||||
vkraster.dstColorBlendFactor = getBlendFactor(rasterState.blendFunctionDstRGB);
|
||||
vkraster.colorBlendOp = rasterState.blendEquationRGB;
|
||||
vkraster.srcAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionSrcAlpha);
|
||||
vkraster.dstAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionDstAlpha);
|
||||
vkraster.alphaBlendOp = rasterState.blendEquationAlpha;
|
||||
vkraster.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0);
|
||||
vkraster.depthWriteEnable = rasterState.depthWrite;
|
||||
vkraster.depthCompareOp = rasterState.depthFunc;
|
||||
vkraster.rasterizationSamples = rt->getSamples();
|
||||
vkraster.alphaToCoverageEnable = rasterState.alphaToCoverage;
|
||||
vkraster.colorTargetCount = rt->getColorTargetCount(mCurrentRenderPass);
|
||||
mPipelineCache.setCurrentRasterState(vkraster);
|
||||
|
||||
// Declare fixed-size arrays that get passed to the pipeCache and to vkCmdBindVertexBuffers.
|
||||
VkVertexInputAttributeDescription const* attribDesc = vbi.getAttribDescriptions();
|
||||
VkVertexInputBindingDescription const* bufferDesc = vbi.getBufferDescriptions();
|
||||
uint32_t const bufferCount = prim.vertexBuffer->attributes.size();
|
||||
VkVertexInputAttributeDescription const* attribDesc = prim.vertexBuffer->getAttribDescriptions();
|
||||
VkVertexInputBindingDescription const* bufferDesc = prim.vertexBuffer->getBufferDescriptions();
|
||||
VkBuffer const* buffers = prim.vertexBuffer->getVkBuffers();
|
||||
VkDeviceSize const* offsets = prim.vertexBuffer->getOffsets();
|
||||
|
||||
// Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls.
|
||||
mPipelineCache.bindProgram(program);
|
||||
mPipelineCache.bindRasterState(vulkanRasterState);
|
||||
mPipelineCache.bindPrimitiveTopology(topology);
|
||||
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi.getAttributeCount());
|
||||
mPipelineCache.bindRasterState(mPipelineCache.getCurrentRasterState());
|
||||
mPipelineCache.bindPrimitiveTopology(prim.primitiveTopology);
|
||||
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, bufferCount);
|
||||
|
||||
// Query the program for the mapping from (SamplerGroupBinding,Offset) to (SamplerBinding),
|
||||
// where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract
|
||||
@@ -1772,11 +1683,7 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
|
||||
|
||||
auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex();
|
||||
VulkanPipelineCache::UsageFlags usage = program->getUsage();
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
auto const& bindingToName = program->getBindingToName();
|
||||
#endif
|
||||
VulkanPipelineCache::UsageFlags usage = program->getUsage();
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) {
|
||||
@@ -1810,21 +1717,15 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
// matching characteristics. (e.g. if the missing texture is a 3D texture)
|
||||
if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
utils::slog.w << "Uninitialized texture bound to '" << bindingToName[binding] << "'";
|
||||
utils::slog.w << "Uninitialized texture bound to '" << sampler.name.c_str() << "'";
|
||||
utils::slog.w << " in material '" << program->name.c_str() << "'";
|
||||
utils::slog.w << " at binding point " << +binding << utils::io::endl;
|
||||
utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl;
|
||||
#endif
|
||||
texture = mEmptyTexture.get();
|
||||
}
|
||||
|
||||
SamplerParams const& samplerParams = boundSampler->s;
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(samplerParams);
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
VulkanDriver::DebugUtils::setName(VK_OBJECT_TYPE_SAMPLER,
|
||||
reinterpret_cast<uint64_t>(vksampler), bindingToName[binding].c_str());
|
||||
#endif
|
||||
|
||||
VkImageView imageView = VK_NULL_HANDLE;
|
||||
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
|
||||
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
|
||||
@@ -1848,51 +1749,6 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
|
||||
mPipelineCache.bindSamplers(samplerInfo, samplerTextures, usage);
|
||||
|
||||
// Bind a new pipeline if the pipeline state changed.
|
||||
// If allocation failed, skip the draw call and bail. We do not emit an error since the
|
||||
// validation layer will already do so.
|
||||
if (!mPipelineCache.bindPipeline(commands)) {
|
||||
return;
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("bindRenderPrimitive");
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
commands->acquire(prim.indexBuffer);
|
||||
commands->acquire(prim.vertexBuffer);
|
||||
|
||||
// This *must* match the VulkanVertexBufferInfo that was bound in bindPipeline(). But we want
|
||||
// to allow to call this before bindPipeline(), so the validation can only happen in draw()
|
||||
VulkanVertexBufferInfo const* const vbi =
|
||||
mResourceAllocator.handle_cast<VulkanVertexBufferInfo*>(prim.vertexBuffer->vbih);
|
||||
|
||||
uint32_t const bufferCount = vbi->getAttributeCount();
|
||||
VkDeviceSize const* offsets = vbi->getOffsets();
|
||||
VkBuffer const* buffers = prim.vertexBuffer->getVkBuffers();
|
||||
|
||||
// Next bind the vertex buffers and index buffer. One potential performance improvement is to
|
||||
// avoid rebinding these if they are already bound, but since we do not (yet) support subranges
|
||||
// it would be rare for a client to make consecutive draw calls with the same render primitive.
|
||||
vkCmdBindVertexBuffers(cmdbuffer, 0, bufferCount, buffers, offsets);
|
||||
vkCmdBindIndexBuffer(cmdbuffer, prim.indexBuffer->buffer.getGpuBuffer(), 0,
|
||||
prim.indexBuffer->indexType);
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("draw2");
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
|
||||
// Bind new descriptor sets if they need to change.
|
||||
// If descriptor set allocation failed, skip the draw call and bail. No need to emit an error
|
||||
// message since the validation layers already do so.
|
||||
@@ -1900,34 +1756,6 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
return;
|
||||
}
|
||||
|
||||
// Finally, make the actual draw call. TODO: support subranges
|
||||
const uint32_t firstIndex = indexOffset;
|
||||
const int32_t vertexOffset = 0;
|
||||
const uint32_t firstInstId = 0;
|
||||
|
||||
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) {
|
||||
VulkanRenderPrimitive* const rp = mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
state.primitiveType = rp->type;
|
||||
state.vertexBufferInfo = rp->vertexBuffer->vbih;
|
||||
bindPipeline(state);
|
||||
bindRenderPrimitive(rph);
|
||||
draw2(indexOffset, indexCount, instanceCount);
|
||||
}
|
||||
|
||||
void VulkanDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
// FIXME: implement me
|
||||
}
|
||||
|
||||
void VulkanDriver::scissor(Viewport scissorBox) {
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
|
||||
// Set scissoring.
|
||||
// clamp left-bottom to 0,0 and avoid overflows
|
||||
constexpr int32_t maxvali = std::numeric_limits<int32_t>::max();
|
||||
@@ -1946,9 +1774,35 @@ void VulkanDriver::scissor(Viewport scissorBox) {
|
||||
.extent = { uint32_t(r - l), uint32_t(t - b) }
|
||||
};
|
||||
|
||||
const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
||||
rt->transformClientRectToPlatform(&scissor);
|
||||
mPipelineCache.bindScissor(cmdbuffer, scissor);
|
||||
|
||||
// Bind a new pipeline if the pipeline state changed.
|
||||
// If allocation failed, skip the draw call and bail. We do not emit an error since the
|
||||
// validation layer will already do so.
|
||||
if (!mPipelineCache.bindPipeline(commands)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Next bind the vertex buffers and index buffer. One potential performance improvement is to
|
||||
// avoid rebinding these if they are already bound, but since we do not (yet) support subranges
|
||||
// it would be rare for a client to make consecutive draw calls with the same render primitive.
|
||||
vkCmdBindVertexBuffers(cmdbuffer, 0, bufferCount, buffers, offsets);
|
||||
vkCmdBindIndexBuffer(cmdbuffer, prim.indexBuffer->buffer.getGpuBuffer(), 0,
|
||||
prim.indexBuffer->indexType);
|
||||
|
||||
// Finally, make the actual draw call. TODO: support subranges
|
||||
const uint32_t indexCount = prim.count;
|
||||
const uint32_t firstIndex = prim.offset / prim.indexBuffer->elementSize;
|
||||
const int32_t vertexOffset = 0;
|
||||
const uint32_t firstInstId = 0;
|
||||
|
||||
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
// FIXME: implement me
|
||||
}
|
||||
|
||||
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
|
||||
@@ -45,30 +45,6 @@ public:
|
||||
static Driver* create(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig) noexcept;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
// Encapsulates the VK_EXT_debug_utils extension. In particular, we use
|
||||
// vkSetDebugUtilsObjectNameEXT and vkCreateDebugUtilsMessengerEXT
|
||||
class DebugUtils {
|
||||
public:
|
||||
static void setName(VkObjectType type, uint64_t handle, char const* name);
|
||||
|
||||
private:
|
||||
static DebugUtils* get();
|
||||
|
||||
DebugUtils(VkInstance instance, VkDevice device, VulkanContext const* context);
|
||||
~DebugUtils();
|
||||
|
||||
VkInstance const mInstance;
|
||||
VkDevice const mDevice;
|
||||
bool const mEnabled;
|
||||
VkDebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE;
|
||||
|
||||
static DebugUtils* mSingleton;
|
||||
|
||||
friend class VulkanDriver;
|
||||
};
|
||||
#endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
|
||||
private:
|
||||
void debugCommandBegin(CommandStream* cmds, bool synchronous,
|
||||
const char* methodName) noexcept override;
|
||||
@@ -101,6 +77,12 @@ private:
|
||||
VulkanDriver& operator=(VulkanDriver const&) = delete;
|
||||
|
||||
private:
|
||||
inline void setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, Handle<HwVertexBuffer> vbh,
|
||||
Handle<HwIndexBuffer> ibh);
|
||||
|
||||
inline void setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
|
||||
uint32_t offset, uint32_t minIndex, uint32_t maxIndex, uint32_t count);
|
||||
|
||||
void collectGarbage();
|
||||
|
||||
VulkanPlatform* mPlatform = nullptr;
|
||||
@@ -113,6 +95,7 @@ private:
|
||||
VulkanRenderPass mCurrentRenderPass = {};
|
||||
VmaAllocator mAllocator = VK_NULL_HANDLE;
|
||||
VkDebugReportCallbackEXT mDebugCallback = VK_NULL_HANDLE;
|
||||
VkDebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE;
|
||||
|
||||
VulkanContext mContext = {};
|
||||
VulkanResourceAllocator mResourceAllocator;
|
||||
|
||||
@@ -16,9 +16,7 @@
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include "VulkanDriver.h"
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanDriver.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "spirv/VulkanSpirvUtils.h"
|
||||
|
||||
@@ -80,29 +78,8 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
};
|
||||
VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, &module);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create shader module.");
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
std::string name{ builder.getName().c_str(), builder.getName().size() };
|
||||
switch (static_cast<ShaderStage>(i)) {
|
||||
case ShaderStage::VERTEX:
|
||||
name += "_vs";
|
||||
break;
|
||||
case ShaderStage::FRAGMENT:
|
||||
name += "_fs";
|
||||
break;
|
||||
default:
|
||||
PANIC_POSTCONDITION("Unexpected stage");
|
||||
break;
|
||||
}
|
||||
VulkanDriver::DebugUtils::setName(VK_OBJECT_TYPE_SHADER_MODULE,
|
||||
reinterpret_cast<uint64_t>(module), name.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
auto& bindingToName = mInfo->bindingToName;
|
||||
#endif
|
||||
|
||||
auto& groupInfo = builder.getSamplerGroupInfo();
|
||||
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
|
||||
auto& usage = mInfo->usage;
|
||||
@@ -113,17 +90,13 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
uint32_t const binding = samplers[i].binding;
|
||||
bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i);
|
||||
usage = VulkanPipelineCache::getUsageFlags(binding, group.stageFlags, usage);
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
bindingToName[binding] = samplers[i].name.c_str();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << modules[0]
|
||||
<< ", " << modules[1] << ")" << utils::io::endl;
|
||||
#endif
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << bundle.vertex
|
||||
<< ", " << bundle.fragment << ")" << utils::io::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
|
||||
@@ -289,21 +262,22 @@ uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) co
|
||||
return count;
|
||||
}
|
||||
|
||||
VulkanVertexBufferInfo::VulkanVertexBufferInfo(
|
||||
uint8_t bufferCount, uint8_t attributeCount, AttributeArray const& attributes)
|
||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||
VulkanResource(VulkanResourceType::VERTEX_BUFFER_INFO),
|
||||
mInfo(attributes.size()) {
|
||||
VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||
VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount,
|
||||
uint32_t elementCount, AttributeArray const& attribs)
|
||||
: HwVertexBuffer(bufferCount, attributeCount, elementCount, attribs),
|
||||
VulkanResource(VulkanResourceType::VERTEX_BUFFER),
|
||||
mInfo(new PipelineInfo(attribs.size())),
|
||||
mResources(allocator) {
|
||||
auto attribDesc = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
|
||||
auto bufferDesc = mInfo->mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
|
||||
auto offsets = mInfo->mSoa.data<PipelineInfo::OFFSETS>();
|
||||
auto attribToBufferIndex = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
|
||||
std::fill(mInfo->mSoa.begin<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(),
|
||||
mInfo->mSoa.end<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(), -1);
|
||||
|
||||
auto attribDesc = mInfo.mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
|
||||
auto bufferDesc = mInfo.mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
|
||||
auto offsets = mInfo.mSoa.data<PipelineInfo::OFFSETS>();
|
||||
auto attribToBufferIndex = mInfo.mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
|
||||
std::fill(mInfo.mSoa.begin<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(),
|
||||
mInfo.mSoa.end<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(), -1);
|
||||
|
||||
for (uint32_t attribIndex = 0; attribIndex < attributes.size(); attribIndex++) {
|
||||
Attribute attrib = attributes[attribIndex];
|
||||
for (uint32_t attribIndex = 0; attribIndex < attribs.size(); attribIndex++) {
|
||||
Attribute attrib = attribs[attribIndex];
|
||||
bool const isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET;
|
||||
bool const isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED;
|
||||
VkFormat vkformat = getVkFormat(attrib.type, isNormalized, isInteger);
|
||||
@@ -315,7 +289,7 @@ VulkanVertexBufferInfo::VulkanVertexBufferInfo(
|
||||
// expects to receive floats or ints.
|
||||
if (attrib.buffer == Attribute::BUFFER_UNUSED) {
|
||||
vkformat = isInteger ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_SNORM;
|
||||
attrib = attributes[0];
|
||||
attrib = attribs[0];
|
||||
}
|
||||
offsets[attribIndex] = attrib.offset;
|
||||
attribDesc[attribIndex] = {
|
||||
@@ -331,23 +305,14 @@ VulkanVertexBufferInfo::VulkanVertexBufferInfo(
|
||||
}
|
||||
}
|
||||
|
||||
VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||
VulkanResourceAllocator* allocator,
|
||||
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih)
|
||||
: HwVertexBuffer(vertexCount),
|
||||
VulkanResource(VulkanResourceType::VERTEX_BUFFER),
|
||||
vbih(vbih),
|
||||
mBuffers(MAX_VERTEX_BUFFER_COUNT), // TODO: can we do better here?
|
||||
mResources(allocator) {
|
||||
VulkanVertexBuffer::~VulkanVertexBuffer() {
|
||||
delete mInfo;
|
||||
}
|
||||
|
||||
void VulkanVertexBuffer::setBuffer(VulkanResourceAllocator const& allocator,
|
||||
VulkanBufferObject* bufferObject, uint32_t index) {
|
||||
VulkanVertexBufferInfo const* const vbi =
|
||||
const_cast<VulkanResourceAllocator&>(allocator).handle_cast<VulkanVertexBufferInfo*>(vbih);
|
||||
size_t const count = vbi->getAttributeCount();
|
||||
VkBuffer* const vkbuffers = getVkBuffers();
|
||||
int8_t const* const attribToBuffer = vbi->getAttributeToBuffer();
|
||||
void VulkanVertexBuffer::setBuffer(VulkanBufferObject* bufferObject, uint32_t index) {
|
||||
size_t count = attributes.size();
|
||||
auto vkbuffers = mInfo->mSoa.data<PipelineInfo::VK_BUFFER>();
|
||||
auto attribToBuffer = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
|
||||
for (uint8_t attribIndex = 0; attribIndex < count; attribIndex++) {
|
||||
if (attribToBuffer[attribIndex] == static_cast<int8_t>(index)) {
|
||||
vkbuffers[attribIndex] = bufferObject->buffer.getGpuBuffer();
|
||||
@@ -363,6 +328,35 @@ VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool&
|
||||
buffer(allocator, stagePool, getBufferObjectUsage(bindingType), byteCount),
|
||||
bindingType(bindingType) {}
|
||||
|
||||
void VulkanRenderPrimitive::setPrimitiveType(PrimitiveType pt) {
|
||||
this->type = pt;
|
||||
switch (pt) {
|
||||
case PrimitiveType::POINTS:
|
||||
primitiveTopology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
break;
|
||||
case PrimitiveType::LINES:
|
||||
primitiveTopology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
break;
|
||||
case PrimitiveType::LINE_STRIP:
|
||||
primitiveTopology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
break;
|
||||
case PrimitiveType::TRIANGLES:
|
||||
primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
break;
|
||||
case PrimitiveType::TRIANGLE_STRIP:
|
||||
primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderPrimitive::setBuffers(VulkanVertexBuffer* vertexBuffer,
|
||||
VulkanIndexBuffer* indexBuffer) {
|
||||
this->vertexBuffer = vertexBuffer;
|
||||
this->indexBuffer = indexBuffer;
|
||||
mResources.acquire(vertexBuffer);
|
||||
mResources.acquire(indexBuffer);
|
||||
}
|
||||
|
||||
VulkanTimerQuery::VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices)
|
||||
: VulkanThreadSafeResource(VulkanResourceType::TIMER_QUERY),
|
||||
mStartingQueryIndex(std::get<0>(indices)),
|
||||
@@ -394,15 +388,4 @@ bool VulkanTimerQuery::isCompleted() noexcept {
|
||||
|
||||
VulkanTimerQuery::~VulkanTimerQuery() = default;
|
||||
|
||||
VulkanRenderPrimitive::VulkanRenderPrimitive(VulkanResourceAllocator* resourceAllocator,
|
||||
PrimitiveType pt, Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh)
|
||||
: VulkanResource(VulkanResourceType::RENDER_PRIMITIVE),
|
||||
mResources(resourceAllocator) {
|
||||
type = pt;
|
||||
vertexBuffer = resourceAllocator->handle_cast<VulkanVertexBuffer*>(vbh);
|
||||
indexBuffer = resourceAllocator->handle_cast<VulkanIndexBuffer*>(ibh);
|
||||
mResources.acquire(vertexBuffer);
|
||||
mResources.acquire(indexBuffer);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -64,12 +64,6 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
return mInfo->bindingToSamplerIndex;
|
||||
}
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
inline utils::FixedCapacityVector<std::string> const& getBindingToName() const {
|
||||
return mInfo->bindingToName;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
// TODO: handle compute shaders.
|
||||
// The expected order of shaders - from frontend to backend - is vertex, fragment, compute.
|
||||
@@ -86,12 +80,6 @@ private:
|
||||
// We store the samplerGroupIndex as the top 8-bit and the index within each group as the lower 8-bit.
|
||||
utils::FixedCapacityVector<uint16_t> bindingToSamplerIndex;
|
||||
VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE };
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
// We store the sampler name mapped from binding index (only for debug purposes).
|
||||
utils::FixedCapacityVector<std::string> bindingToName;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
PipelineInfo* mInfo;
|
||||
@@ -141,72 +129,55 @@ private:
|
||||
|
||||
struct VulkanBufferObject;
|
||||
|
||||
struct VulkanVertexBufferInfo : public HwVertexBufferInfo, VulkanResource {
|
||||
VulkanVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
||||
AttributeArray const& attributes);
|
||||
struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
|
||||
VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||
VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount,
|
||||
uint32_t elementCount, AttributeArray const& attributes);
|
||||
|
||||
inline VkVertexInputAttributeDescription const* getAttribDescriptions() const {
|
||||
return mInfo.mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
|
||||
~VulkanVertexBuffer();
|
||||
|
||||
void setBuffer(VulkanBufferObject* bufferObject, uint32_t index);
|
||||
|
||||
inline VkVertexInputAttributeDescription const* getAttribDescriptions() {
|
||||
return mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
|
||||
}
|
||||
|
||||
inline VkVertexInputBindingDescription const* getBufferDescriptions() const {
|
||||
return mInfo.mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
|
||||
inline VkVertexInputBindingDescription const* getBufferDescriptions() {
|
||||
return mInfo->mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
|
||||
}
|
||||
|
||||
inline int8_t const* getAttributeToBuffer() const {
|
||||
return mInfo.mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
|
||||
inline VkBuffer const* getVkBuffers() const {
|
||||
return mInfo->mSoa.data<PipelineInfo::VK_BUFFER>();
|
||||
}
|
||||
|
||||
inline VkDeviceSize const* getOffsets() const {
|
||||
return mInfo.mSoa.data<PipelineInfo::OFFSETS>();
|
||||
}
|
||||
|
||||
size_t getAttributeCount() const noexcept {
|
||||
return mInfo.mSoa.size();
|
||||
return mInfo->mSoa.data<PipelineInfo::OFFSETS>();
|
||||
}
|
||||
|
||||
private:
|
||||
struct PipelineInfo {
|
||||
PipelineInfo(size_t size) : mSoa(size /* capacity */) {
|
||||
PipelineInfo(size_t size)
|
||||
: mSoa(size /* capacity */) {
|
||||
mSoa.resize(size);
|
||||
}
|
||||
|
||||
// These correspond to the index of the element in the SoA
|
||||
// These corresponds to the index of the element in the SoA
|
||||
static constexpr uint8_t ATTRIBUTE_DESCRIPTION = 0;
|
||||
static constexpr uint8_t BUFFER_DESCRIPTION = 1;
|
||||
static constexpr uint8_t OFFSETS = 2;
|
||||
static constexpr uint8_t ATTRIBUTE_TO_BUFFER_INDEX = 3;
|
||||
static constexpr uint8_t VK_BUFFER = 2;
|
||||
static constexpr uint8_t OFFSETS = 3;
|
||||
static constexpr uint8_t ATTRIBUTE_TO_BUFFER_INDEX = 4;
|
||||
|
||||
utils::StructureOfArrays<
|
||||
VkVertexInputAttributeDescription,
|
||||
VkVertexInputBindingDescription,
|
||||
VkBuffer,
|
||||
VkDeviceSize,
|
||||
int8_t
|
||||
> mSoa;
|
||||
};
|
||||
|
||||
PipelineInfo mInfo;
|
||||
};
|
||||
|
||||
struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
|
||||
VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||
VulkanResourceAllocator* allocator,
|
||||
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih);
|
||||
|
||||
void setBuffer(VulkanResourceAllocator const& allocator,
|
||||
VulkanBufferObject* bufferObject, uint32_t index);
|
||||
|
||||
inline VkBuffer const* getVkBuffers() const {
|
||||
return mBuffers.data();
|
||||
}
|
||||
|
||||
inline VkBuffer* getVkBuffers() {
|
||||
return mBuffers.data();
|
||||
}
|
||||
|
||||
Handle<HwVertexBufferInfo> vbih;
|
||||
private:
|
||||
utils::FixedCapacityVector<VkBuffer> mBuffers;
|
||||
PipelineInfo* mInfo;
|
||||
FixedSizeVulkanResourceManager<MAX_VERTEX_BUFFER_COUNT> mResources;
|
||||
};
|
||||
|
||||
@@ -239,15 +210,19 @@ struct VulkanSamplerGroup : public HwSamplerGroup, VulkanResource {
|
||||
};
|
||||
|
||||
struct VulkanRenderPrimitive : public HwRenderPrimitive, VulkanResource {
|
||||
VulkanRenderPrimitive(VulkanResourceAllocator* resourceAllocator,
|
||||
PrimitiveType pt, Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh);
|
||||
VulkanRenderPrimitive(VulkanResourceAllocator* allocator)
|
||||
: VulkanResource(VulkanResourceType::RENDER_PRIMITIVE),
|
||||
mResources(allocator) {}
|
||||
|
||||
~VulkanRenderPrimitive() {
|
||||
mResources.clear();
|
||||
}
|
||||
|
||||
void setPrimitiveType(PrimitiveType pt);
|
||||
void setBuffers(VulkanVertexBuffer* vertexBuffer, VulkanIndexBuffer* indexBuffer);
|
||||
VulkanVertexBuffer* vertexBuffer = nullptr;
|
||||
VulkanIndexBuffer* indexBuffer = nullptr;
|
||||
VkPrimitiveTopology primitiveTopology;
|
||||
|
||||
private:
|
||||
// Keep references to the vertex buffer and the index buffer.
|
||||
|
||||
@@ -34,6 +34,8 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
static VulkanPipelineCache::RasterState createDefaultRasterState();
|
||||
|
||||
static VkShaderStageFlags getShaderStageFlags(VulkanPipelineCache::UsageFlags key, uint16_t binding) {
|
||||
// NOTE: if you modify this function, you also need to modify getUsageFlags.
|
||||
assert_invariant(binding < MAX_SAMPLER_COUNT);
|
||||
@@ -71,7 +73,8 @@ VulkanPipelineCache::UsageFlags VulkanPipelineCache::disableUsageFlags(uint16_t
|
||||
}
|
||||
|
||||
VulkanPipelineCache::VulkanPipelineCache(VulkanResourceAllocator* allocator)
|
||||
: mResourceAllocator(allocator),
|
||||
: mCurrentRasterState(createDefaultRasterState()),
|
||||
mResourceAllocator(allocator),
|
||||
mPipelineBoundResources(allocator) {
|
||||
mDummyBufferWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
mDummyBufferWriteInfo.pNext = nullptr;
|
||||
@@ -563,18 +566,10 @@ VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreateP
|
||||
void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept {
|
||||
mPipelineRequirements.shaders[0] = program->getVertexShader();
|
||||
mPipelineRequirements.shaders[1] = program->getFragmentShader();
|
||||
|
||||
// If this is a debug build, validate the current shader.
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
if (mPipelineRequirements.shaders[0] == VK_NULL_HANDLE ||
|
||||
mPipelineRequirements.shaders[1] == VK_NULL_HANDLE) {
|
||||
utils::slog.e << "Binding missing shader: " << program->name.c_str() << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindRasterState(const RasterState& rasterState) noexcept {
|
||||
mPipelineRequirements.rasterState = rasterState;
|
||||
mPipelineRequirements.rasterState = mCurrentRasterState = rasterState;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept {
|
||||
@@ -636,8 +631,12 @@ void VulkanPipelineCache::unbindImageView(VkImageView imageView) noexcept {
|
||||
|
||||
void VulkanPipelineCache::bindUniformBufferObject(uint32_t bindingIndex,
|
||||
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||
VkBuffer buffer = bufferObject->buffer.getGpuBuffer();
|
||||
bindUniformBuffer(bindingIndex, bufferObject->buffer.getGpuBuffer(), offset, size);
|
||||
mPipelineBoundResources.acquire(bufferObject);
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindUniformBuffer(uint32_t bindingIndex, VkBuffer buffer,
|
||||
VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||
ASSERT_POSTCONDITION(bindingIndex < UBUFFER_BINDING_COUNT,
|
||||
"Uniform bindings overflow: index = %d, capacity = %d.", bindingIndex,
|
||||
UBUFFER_BINDING_COUNT);
|
||||
@@ -653,8 +652,6 @@ void VulkanPipelineCache::bindUniformBufferObject(uint32_t bindingIndex,
|
||||
|
||||
key.uniformBufferOffsets[bindingIndex] = offset;
|
||||
key.uniformBufferSizes[bindingIndex] = size;
|
||||
|
||||
mPipelineBoundResources.acquire(bufferObject);
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
|
||||
@@ -920,6 +917,23 @@ bool VulkanPipelineCache::DescEqual::operator()(const DescriptorKey& k1,
|
||||
return true;
|
||||
}
|
||||
|
||||
static VulkanPipelineCache::RasterState createDefaultRasterState() {
|
||||
return VulkanPipelineCache::RasterState {
|
||||
.cullMode = VK_CULL_MODE_NONE,
|
||||
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
|
||||
.depthBiasEnable = VK_FALSE,
|
||||
.blendEnable = VK_FALSE,
|
||||
.depthWriteEnable = VK_TRUE,
|
||||
.alphaToCoverageEnable = true,
|
||||
.colorWriteMask = 0xf,
|
||||
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.colorTargetCount = 1,
|
||||
.depthCompareOp = SamplerCompareFunc::LE,
|
||||
.depthBiasConstantFactor = 0.0f,
|
||||
.depthBiasSlopeFactor = 0.0f,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@@ -157,6 +157,8 @@ public:
|
||||
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
|
||||
void bindUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject,
|
||||
VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept;
|
||||
void bindUniformBuffer(uint32_t bindingIndex, VkBuffer buffer,
|
||||
VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept;
|
||||
void bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
|
||||
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept;
|
||||
void bindInputAttachment(uint32_t bindingIndex, VkDescriptorImageInfo imageInfo) noexcept;
|
||||
@@ -191,19 +193,19 @@ public:
|
||||
mDummyTargetInfo.imageView = imageView;
|
||||
}
|
||||
|
||||
static VkPrimitiveTopology getPrimitiveTopology(PrimitiveType pt) noexcept {
|
||||
switch (pt) {
|
||||
case PrimitiveType::POINTS:
|
||||
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
case PrimitiveType::LINES:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
case PrimitiveType::LINE_STRIP:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
case PrimitiveType::TRIANGLES:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
case PrimitiveType::TRIANGLE_STRIP:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
||||
}
|
||||
// Acquires a resource to be bound to the current pipeline. The ownership of the resource
|
||||
// will be transferred to the corresponding pipeline when pipeline is bound.
|
||||
void acquireResource(VulkanResource* resource) {
|
||||
mPipelineBoundResources.acquire(resource);
|
||||
}
|
||||
|
||||
inline RasterState getCurrentRasterState() const noexcept {
|
||||
return mCurrentRasterState;
|
||||
}
|
||||
|
||||
// We need to update this outside of bindRasterState due to VulkanDriver::draw.
|
||||
inline void setCurrentRasterState(RasterState const& rasterState) noexcept {
|
||||
mCurrentRasterState = rasterState;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -411,6 +413,7 @@ private:
|
||||
VmaAllocator mAllocator = VK_NULL_HANDLE;
|
||||
|
||||
// Current requirements for the pipeline layout, pipeline, and descriptor sets.
|
||||
RasterState mCurrentRasterState;
|
||||
PipelineKey mPipelineRequirements = {};
|
||||
DescriptorKey mDescriptorRequirements = {};
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include <private/backend/HandleAllocator.h>
|
||||
@@ -30,17 +29,18 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
#define RESOURCE_TYPE_COUNT (static_cast<int>(VulkanResourceType::END_TYPE))
|
||||
#define DEBUG_RESOURCE_LEAKS FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
// RESOURCE_TYPE_COUNT matches the count of enum VulkanResourceType.
|
||||
#define RESOURCE_TYPE_COUNT 12
|
||||
#define DEBUG_RESOURCE_LEAKS 0
|
||||
|
||||
#if DEBUG_RESOURCE_LEAKS
|
||||
#define TRACK_INCREMENT() \
|
||||
if (!IS_HEAP_ALLOC_TYPE(obj->getType())) { \
|
||||
mDebugOnlyResourceCount[static_cast<size_t>(obj->getType())]++; \
|
||||
#define TRACK_INCREMENT() \
|
||||
if (!IS_MANAGED_TYPE(obj->mType)) { \
|
||||
mDebugOnlyResourceCount[static_cast<size_t>(obj->mType)]++; \
|
||||
}
|
||||
#define TRACK_DECREMENT() \
|
||||
if (!IS_HEAP_ALLOC_TYPE(obj->getType())) { \
|
||||
mDebugOnlyResourceCount[static_cast<size_t>(obj->getType())]--; \
|
||||
#define TRACK_DECREMENT() \
|
||||
if (!IS_MANAGED_TYPE(obj->mType)) { \
|
||||
mDebugOnlyResourceCount[static_cast<size_t>(obj->mType)]--; \
|
||||
}
|
||||
#else
|
||||
// No-op
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanResourceAllocator.h"
|
||||
#include "caching/VulkanDescriptorSet.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -53,24 +53,16 @@ void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType t
|
||||
case VulkanResourceType::TIMER_QUERY:
|
||||
allocator->destruct<VulkanTimerQuery>(Handle<HwTimerQuery>(id));
|
||||
break;
|
||||
case VulkanResourceType::VERTEX_BUFFER_INFO:
|
||||
allocator->destruct<VulkanVertexBufferInfo>(Handle<HwVertexBufferInfo>(id));
|
||||
break;
|
||||
case VulkanResourceType::VERTEX_BUFFER:
|
||||
allocator->destruct<VulkanVertexBuffer>(Handle<HwVertexBuffer>(id));
|
||||
break;
|
||||
case VulkanResourceType::RENDER_PRIMITIVE:
|
||||
allocator->destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
|
||||
break;
|
||||
case VulkanResourceType::DESCRIPTOR_SET:
|
||||
allocator->destruct<VulkanDescriptorSet>(Handle<VulkanDescriptorSet>(id));
|
||||
break;
|
||||
|
||||
// If the resource is heap allocated, then the resource manager just skip refcounted
|
||||
// destruction.
|
||||
case VulkanResourceType::FENCE:
|
||||
case VulkanResourceType::HEAP_ALLOCATED:
|
||||
case VulkanResourceType::END_TYPE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANRESOURCES_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANRESOURCES_H
|
||||
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <tsl/robin_set.h>
|
||||
@@ -45,14 +43,10 @@ enum class VulkanResourceType : uint8_t {
|
||||
TEXTURE,
|
||||
TIMER_QUERY,
|
||||
VERTEX_BUFFER,
|
||||
VERTEX_BUFFER_INFO,
|
||||
DESCRIPTOR_SET,
|
||||
|
||||
// Below are resources that are managed manually (i.e. not ref counted).
|
||||
FENCE,
|
||||
HEAP_ALLOCATED,
|
||||
|
||||
END_TYPE, // A placeholder
|
||||
};
|
||||
|
||||
#define IS_HEAP_ALLOC_TYPE(f) \
|
||||
@@ -68,16 +62,15 @@ struct VulkanResourceBase {
|
||||
protected:
|
||||
explicit VulkanResourceBase(VulkanResourceType type)
|
||||
: mRefCount(IS_HEAP_ALLOC_TYPE(type) ? 1 : 0),
|
||||
mType(uint32_t(type)),
|
||||
mHandleId(0) {
|
||||
}
|
||||
mType(type),
|
||||
mHandleId(0) {}
|
||||
|
||||
private:
|
||||
inline VulkanResourceType getType() const noexcept {
|
||||
return VulkanResourceType(mType);
|
||||
inline VulkanResourceType getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
inline HandleBase::HandleId getId() const noexcept {
|
||||
inline HandleBase::HandleId getId() {
|
||||
return mHandleId;
|
||||
}
|
||||
|
||||
@@ -86,27 +79,25 @@ private:
|
||||
}
|
||||
|
||||
inline void ref() noexcept {
|
||||
if (IS_HEAP_ALLOC_TYPE(getType())) {
|
||||
if (IS_HEAP_ALLOC_TYPE(mType)) {
|
||||
return;
|
||||
}
|
||||
assert_invariant(mRefCount < ((1<<24) - 1));
|
||||
++mRefCount;
|
||||
}
|
||||
|
||||
inline void deref() noexcept {
|
||||
if (IS_HEAP_ALLOC_TYPE(getType())) {
|
||||
if (IS_HEAP_ALLOC_TYPE(mType)) {
|
||||
return;
|
||||
}
|
||||
assert_invariant(mRefCount > 0);
|
||||
--mRefCount;
|
||||
}
|
||||
|
||||
inline size_t refcount() const noexcept {
|
||||
inline size_t refcount() noexcept {
|
||||
return mRefCount;
|
||||
}
|
||||
|
||||
uint32_t mRefCount : 24; // 16M is enough for the refcount
|
||||
uint32_t mType : 8; // must be uint32_t or MSVC doesn't pack it. no codegen impact w/ clang.
|
||||
size_t mRefCount = 0;
|
||||
VulkanResourceType mType = VulkanResourceType::BUFFER_OBJECT;
|
||||
HandleBase::HandleId mHandleId;
|
||||
|
||||
friend struct VulkanThreadSafeResource;
|
||||
@@ -116,8 +107,6 @@ private:
|
||||
friend class VulkanResourceManagerImpl;
|
||||
};
|
||||
|
||||
static_assert(sizeof(VulkanResourceBase) == 8, "VulkanResourceBase should be 8 bytes");
|
||||
|
||||
struct VulkanThreadSafeResource {
|
||||
protected:
|
||||
explicit VulkanThreadSafeResource(VulkanResourceType type)
|
||||
@@ -167,7 +156,62 @@ namespace {
|
||||
// When the size of the resource set is known to be small, (for example for VulkanRenderPrimitive),
|
||||
// we just use a std::array to back the set.
|
||||
template<std::size_t SIZE>
|
||||
using FixedCapacityResourceSet = CappedArray<VulkanResource*, SIZE>;
|
||||
class FixedCapacityResourceSet {
|
||||
private:
|
||||
using FixedSizeArray = std::array<VulkanResource*, SIZE>;
|
||||
|
||||
public:
|
||||
using const_iterator = typename FixedSizeArray::const_iterator;
|
||||
|
||||
inline ~FixedCapacityResourceSet() {
|
||||
clear();
|
||||
}
|
||||
|
||||
inline const_iterator begin() {
|
||||
if (mInd == 0) {
|
||||
return mArray.cend();
|
||||
}
|
||||
return mArray.cbegin();
|
||||
}
|
||||
|
||||
inline const_iterator end() {
|
||||
if (mInd == 0) {
|
||||
return mArray.cend();
|
||||
}
|
||||
if (mInd < SIZE) {
|
||||
return mArray.begin() + mInd;
|
||||
}
|
||||
return mArray.cend();
|
||||
}
|
||||
|
||||
inline const_iterator find(VulkanResource* resource) {
|
||||
return std::find(begin(), end(), resource);
|
||||
}
|
||||
|
||||
inline void insert(VulkanResource* resource) {
|
||||
assert_invariant(mInd < SIZE);
|
||||
mArray[mInd++] = resource;
|
||||
}
|
||||
|
||||
inline void erase(VulkanResource* resource) {
|
||||
assert_invariant(false && "FixedCapacityResourceSet::erase should not be called");
|
||||
}
|
||||
|
||||
inline void clear() {
|
||||
if (mInd == 0) {
|
||||
return;
|
||||
}
|
||||
mInd = 0;
|
||||
}
|
||||
|
||||
inline size_t size() {
|
||||
return mInd;
|
||||
}
|
||||
|
||||
private:
|
||||
FixedSizeArray mArray{nullptr};
|
||||
size_t mInd = 0;
|
||||
};
|
||||
|
||||
// robin_set/map are useful for sets that are acquire only and the set will be iterated when the set
|
||||
// is cleared.
|
||||
|
||||
@@ -35,7 +35,7 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
|
||||
mStagePool(stagePool),
|
||||
mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow),
|
||||
mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize),
|
||||
mCurrentImageReadyIndex(0),
|
||||
mImageReady(VK_NULL_HANDLE),
|
||||
mAcquired(false),
|
||||
mIsFirstRenderPass(true) {
|
||||
swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent);
|
||||
@@ -46,15 +46,10 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
|
||||
};
|
||||
|
||||
// No need to wait on this semaphore before drawing when in Headless mode.
|
||||
if (mHeadless) {
|
||||
// Set all sempahores to VK_NULL_HANDLE
|
||||
memset(mImageReady, 0, sizeof(mImageReady[0]) * IMAGE_READY_SEMAPHORE_COUNT);
|
||||
} else {
|
||||
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
|
||||
VkResult result =
|
||||
vkCreateSemaphore(mPlatform->getDevice(), &createInfo, nullptr, mImageReady + i);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Failed to create semaphore");
|
||||
}
|
||||
if (!mHeadless) {
|
||||
VkResult result =
|
||||
vkCreateSemaphore(mPlatform->getDevice(), &createInfo, nullptr, &mImageReady);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Failed to create semaphore");
|
||||
}
|
||||
|
||||
update();
|
||||
@@ -67,11 +62,9 @@ VulkanSwapChain::~VulkanSwapChain() {
|
||||
mCommands->wait();
|
||||
|
||||
mPlatform->destroy(swapChain);
|
||||
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
|
||||
if (mImageReady[i] != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(mPlatform->getDevice(), mImageReady[i], VKALLOC);
|
||||
}
|
||||
}
|
||||
if (mImageReady != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(mPlatform->getDevice(), mImageReady, VKALLOC);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanSwapChain::update() {
|
||||
@@ -138,13 +131,11 @@ void VulkanSwapChain::acquire(bool& resized) {
|
||||
update();
|
||||
}
|
||||
|
||||
mCurrentImageReadyIndex = (mCurrentImageReadyIndex + 1) % IMAGE_READY_SEMAPHORE_COUNT;
|
||||
const VkSemaphore imageReady = mImageReady[mCurrentImageReadyIndex];
|
||||
VkResult const result = mPlatform->acquire(swapChain, imageReady, &mCurrentSwapIndex);
|
||||
VkResult const result = mPlatform->acquire(swapChain, mImageReady, &mCurrentSwapIndex);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR,
|
||||
"Cannot acquire in swapchain.");
|
||||
if (imageReady != VK_NULL_HANDLE) {
|
||||
mCommands->injectDependency(imageReady);
|
||||
if (mImageReady != VK_NULL_HANDLE) {
|
||||
mCommands->injectDependency(mImageReady);
|
||||
}
|
||||
mAcquired = true;
|
||||
}
|
||||
|
||||
@@ -69,8 +69,6 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource {
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS;
|
||||
|
||||
void update();
|
||||
|
||||
VulkanPlatform* mPlatform;
|
||||
@@ -85,8 +83,7 @@ private:
|
||||
utils::FixedCapacityVector<std::unique_ptr<VulkanTexture>> mColors;
|
||||
std::unique_ptr<VulkanTexture> mDepth;
|
||||
VkExtent2D mExtent;
|
||||
VkSemaphore mImageReady[IMAGE_READY_SEMAPHORE_COUNT];
|
||||
uint32_t mCurrentImageReadyIndex;
|
||||
VkSemaphore mImageReady;
|
||||
uint32_t mCurrentSwapIndex;
|
||||
bool mAcquired;
|
||||
bool mIsFirstRenderPass;
|
||||
|
||||
@@ -159,7 +159,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.samples = (VkSampleCountFlagBits) samples;
|
||||
|
||||
VkResult error = vkCreateImage(mDevice, &imageInfo, VKALLOC, &mTextureImage);
|
||||
if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
|
||||
if (error || FVK_ENABLED_BOOL(FVK_DEBUG_TEXTURE)) {
|
||||
utils::slog.d << "vkCreateImage: "
|
||||
<< "image = " << mTextureImage << ", "
|
||||
<< "result = " << error << ", "
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VkFormat getVkFormat(ElementType type, bool normalized, bool integer);
|
||||
@@ -90,100 +88,10 @@ utils::FixedCapacityVector<OutType> enumerate(
|
||||
#undef EXPAND_ENUM_NO_ARGS
|
||||
#undef EXPAND_ENUM_ARGS
|
||||
|
||||
|
||||
// Useful shorthands
|
||||
using VkFormatList = utils::FixedCapacityVector<VkFormat>;
|
||||
|
||||
// An Array that will be fixed capacity, but the "size" (as in user added elements) is variable.
|
||||
// Note that this class is movable.
|
||||
template<typename T, uint16_t CAPACITY>
|
||||
class CappedArray {
|
||||
private:
|
||||
using FixedSizeArray = std::array<T, CAPACITY>;
|
||||
public:
|
||||
using const_iterator = typename FixedSizeArray::const_iterator;
|
||||
using iterator = typename FixedSizeArray::iterator;
|
||||
|
||||
CappedArray() = default;
|
||||
|
||||
// Delete copy constructor/assignment.
|
||||
CappedArray(CappedArray const& rhs) = delete;
|
||||
CappedArray& operator=(CappedArray& rhs) = delete;
|
||||
|
||||
CappedArray(CappedArray&& rhs) noexcept {
|
||||
this->swap(rhs);
|
||||
}
|
||||
|
||||
CappedArray& operator=(CappedArray&& rhs) noexcept {
|
||||
this->swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline ~CappedArray() {
|
||||
clear();
|
||||
}
|
||||
|
||||
inline const_iterator begin() {
|
||||
if (mInd == 0) {
|
||||
return mArray.cend();
|
||||
}
|
||||
return mArray.cbegin();
|
||||
}
|
||||
|
||||
inline const_iterator end() {
|
||||
if (mInd > 0 && mInd < CAPACITY) {
|
||||
return mArray.begin() + mInd;
|
||||
}
|
||||
return mArray.cend();
|
||||
}
|
||||
|
||||
inline T back() {
|
||||
assert_invariant(mInd > 0);
|
||||
return *(mArray.begin() + mInd);
|
||||
}
|
||||
|
||||
inline void pop_back() {
|
||||
assert_invariant(mInd > 0);
|
||||
mInd--;
|
||||
}
|
||||
|
||||
inline const_iterator find(T item) {
|
||||
return std::find(begin(), end(), item);
|
||||
}
|
||||
|
||||
inline void insert(T item) {
|
||||
assert_invariant(mInd < CAPACITY);
|
||||
mArray[mInd++] = item;
|
||||
}
|
||||
|
||||
inline void erase(T item) {
|
||||
PANIC_PRECONDITION("CappedArray::erase should not be called");
|
||||
}
|
||||
|
||||
inline void clear() {
|
||||
if (mInd == 0) {
|
||||
return;
|
||||
}
|
||||
mInd = 0;
|
||||
}
|
||||
|
||||
inline T& operator[](uint16_t ind) {
|
||||
return mArray[ind];
|
||||
}
|
||||
|
||||
inline size_t size() {
|
||||
return mInd;
|
||||
}
|
||||
|
||||
private:
|
||||
void swap(CappedArray& rhs) {
|
||||
std::swap(mArray, rhs.mArray);
|
||||
std::swap(mInd, rhs.mInd);
|
||||
}
|
||||
|
||||
FixedSizeArray mArray;
|
||||
size_t mInd = 0;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANUTILITY_H
|
||||
|
||||
@@ -1,647 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "VulkanDescriptorSet.h"
|
||||
|
||||
#include <vulkan/VulkanConstants.h>
|
||||
#include <vulkan/VulkanResources.h>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename MaskType>
|
||||
using LayoutMap = tsl::robin_map<MaskType, VkDescriptorSetLayout>;
|
||||
using LayoutArray = VulkanDescriptorSetManager::LayoutArray;
|
||||
using UniformBufferBitmask = VulkanDescriptorSetManager::UniformBufferBitmask;
|
||||
using SamplerBitmask = VulkanDescriptorSetManager::SamplerBitmask;
|
||||
|
||||
constexpr uint8_t EXPECTED_IN_FLIGHT_FRAMES = 3; // Asssume triple buffering
|
||||
|
||||
#define PORT_CONSTANT(NameSpace, K) constexpr decltype(NameSpace::K) K = NameSpace::K;
|
||||
|
||||
PORT_CONSTANT(VulkanDescriptorSetManager, MAX_SUPPORTED_SHADER_STAGE)
|
||||
PORT_CONSTANT(VulkanDescriptorSetManager, VERTEX_STAGE)
|
||||
PORT_CONSTANT(VulkanDescriptorSetManager, FRAGMENT_STAGE)
|
||||
|
||||
PORT_CONSTANT(Program, UNIFORM_BINDING_COUNT)
|
||||
PORT_CONSTANT(Program, SAMPLER_BINDING_COUNT)
|
||||
|
||||
#undef PORT_CONSTANT
|
||||
|
||||
// Use constexpr to statically generate a bit count table for 8-bit numbers.
|
||||
struct BitCountHelper {
|
||||
constexpr BitCountHelper() : data{} {
|
||||
for (uint16_t i = 0; i < 256; ++i) {
|
||||
data[i] = 0;
|
||||
for (auto j = i; j > 0; j /= 2) {
|
||||
if (j & 1) {
|
||||
data[i]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
uint8_t data[256];
|
||||
} BitCountImpl;
|
||||
|
||||
inline uint8_t countBits(uint32_t num) {
|
||||
static constexpr uint8_t const* BIT_COUNT = BitCountImpl.data;
|
||||
return BIT_COUNT[num & 0xFF] + BIT_COUNT[(num >> 8) & 0xFF] + BIT_COUNT[(num >> 16) & 0xFF] +
|
||||
BIT_COUNT[(num >> 24) & 0xFF];
|
||||
}
|
||||
|
||||
struct SamplerSet {
|
||||
using Mask = UniformBufferBitmask;
|
||||
struct Key {
|
||||
Mask mask;
|
||||
static_assert(sizeof(Mask) == 4);
|
||||
uint32_t padding;// We need a padding to ensure 8-byte alignement.
|
||||
VkDescriptorSetLayout layout;
|
||||
VkSampler sampler[SAMPLER_BINDING_COUNT];
|
||||
VkImageView imageView[SAMPLER_BINDING_COUNT];
|
||||
VkImageLayout imageLayout[SAMPLER_BINDING_COUNT];
|
||||
};
|
||||
|
||||
struct Equal {
|
||||
bool operator()(Key const& k1, Key const& k2) const {
|
||||
if (k1.mask != k2.mask) return false;
|
||||
if (k1.layout != k2.layout) return false;
|
||||
|
||||
for (uint8_t i = 0, bitCount = countBits(k1.mask); i < bitCount; ++i) {
|
||||
if (k1.sampler[i] != k2.sampler[i] || k1.imageView[i] != k2.imageView[i] ||
|
||||
k1.imageLayout[i] != k2.imageLayout[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
using HashFn = utils::hash::MurmurHashFn<Key>;
|
||||
using Cache = std::unordered_map<Key, VulkanDescriptorSet*, HashFn, Equal>;
|
||||
};
|
||||
|
||||
struct UBOSet {
|
||||
using Mask = SamplerBitmask;
|
||||
|
||||
struct Key {
|
||||
Mask mask;
|
||||
static_assert(sizeof(Mask) == 4);
|
||||
uint32_t padding;// We need a padding to ensure 8-byte alignement.
|
||||
VkDescriptorSetLayout layout;
|
||||
VkBuffer buffers[UNIFORM_BINDING_COUNT]; // 80 0
|
||||
VkDeviceSize offsets[UNIFORM_BINDING_COUNT];// 40 1592
|
||||
VkDeviceSize sizes[UNIFORM_BINDING_COUNT]; // 40 1632
|
||||
};
|
||||
|
||||
struct Equal {
|
||||
bool operator()(Key const& k1, Key const& k2) const {
|
||||
if (k1.mask != k2.mask) return false;
|
||||
if (k1.layout != k2.layout) return false;
|
||||
|
||||
for (uint8_t i = 0, bitCount = countBits(k1.mask); i < bitCount; ++i) {
|
||||
if (k1.buffers[i] != k2.buffers[i] || k1.offsets[i] != k2.offsets[i] ||
|
||||
k1.sizes[i] != k2.sizes[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
using HashFn = utils::hash::MurmurHashFn<Key>;
|
||||
using Cache = std::unordered_map<Key, VulkanDescriptorSet*, HashFn, Equal>;
|
||||
};
|
||||
|
||||
constexpr uint8_t MAX_BINDINGS = UNIFORM_BINDING_COUNT * MAX_SUPPORTED_SHADER_STAGE;
|
||||
static_assert(MAX_BINDINGS >= SAMPLER_BINDING_COUNT * MAX_SUPPORTED_SHADER_STAGE);
|
||||
|
||||
template<typename TYPE>
|
||||
class DescriptorPool {
|
||||
private:
|
||||
static constexpr uint32_t COUNT_MULTIPLIER =
|
||||
MAX_SUPPORTED_SHADER_STAGE * EXPECTED_IN_FLIGHT_FRAMES;
|
||||
static constexpr uint32_t CAPACITY =
|
||||
std::is_same_v<TYPE, UBOSet> ? Program::UNIFORM_BINDING_COUNT * COUNT_MULTIPLIER
|
||||
: Program::SAMPLER_BINDING_COUNT * COUNT_MULTIPLIER;
|
||||
|
||||
public:
|
||||
DescriptorPool()
|
||||
: mDevice(VK_NULL_HANDLE),
|
||||
mResourceAllocator(nullptr) {}
|
||||
|
||||
DescriptorPool(VkDevice device, VulkanResourceAllocator* allocator)
|
||||
: mDevice(device),
|
||||
mResourceAllocator(allocator) {
|
||||
VkDescriptorPoolSize sizes[1] = {
|
||||
{
|
||||
.type = std::is_same_v<TYPE, UBOSet> ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
|
||||
: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.descriptorCount = CAPACITY,
|
||||
},
|
||||
};
|
||||
VkDescriptorPoolCreateInfo info{
|
||||
.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
|
||||
.maxSets = EXPECTED_IN_FLIGHT_FRAMES,
|
||||
.poolSizeCount = 1,
|
||||
.pPoolSizes = sizes,
|
||||
};
|
||||
vkCreateDescriptorPool(mDevice, &info, VKALLOC, &mPool);
|
||||
}
|
||||
|
||||
DescriptorPool(DescriptorPool const&) = delete;
|
||||
DescriptorPool& operator=(DescriptorPool const&) = delete;
|
||||
|
||||
~DescriptorPool() {
|
||||
vkDestroyDescriptorPool(mDevice, mPool, VKALLOC);
|
||||
}
|
||||
|
||||
VulkanDescriptorSet* obtainSet(VkDescriptorSetLayout layout) {
|
||||
if (mCount == CAPACITY) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (auto unused = mUnused.find(layout);
|
||||
unused != mUnused.end() && !unused->second.empty()) {
|
||||
auto& unusedList = unused->second;
|
||||
auto set = unusedList.back();
|
||||
unusedList.pop_back();
|
||||
mCount++;
|
||||
return set;
|
||||
}
|
||||
|
||||
// Creating a new set
|
||||
VkDescriptorSetLayout layouts[1] = {layout};
|
||||
VkDescriptorSetAllocateInfo allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
.descriptorPool = mPool,
|
||||
.descriptorSetCount = 1,
|
||||
.pSetLayouts = layouts,
|
||||
};
|
||||
VkDescriptorSet vkSet;
|
||||
UTILS_UNUSED VkResult result = vkAllocateDescriptorSets(mDevice, &allocInfo, &vkSet);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Cannot allocate descriptor set. error=%d",
|
||||
(int) result);
|
||||
|
||||
mCount++;
|
||||
return createSet(layout, vkSet);
|
||||
}
|
||||
|
||||
private:
|
||||
inline VulkanDescriptorSet* createSet(VkDescriptorSetLayout layout, VkDescriptorSet vkSet) {
|
||||
return VulkanDescriptorSet::create(mResourceAllocator, vkSet, layout,
|
||||
[this](VulkanDescriptorSet* set) {
|
||||
auto const layout = set->layout;
|
||||
auto const vkSet = set->vkSet;
|
||||
auto listIter = mUnused.find(layout);
|
||||
assert_invariant(listIter != mUnused.end());
|
||||
auto& unusedList = listIter->second;
|
||||
|
||||
// At this point, we know that the pointer will be stale, and we need to remove
|
||||
// any non-ref-counted references to it.
|
||||
// TODO: this will be inefficient
|
||||
auto iter = std::find(unusedList.begin(), unusedList.end(), set);
|
||||
unusedList.erase(iter);
|
||||
|
||||
// We are recycling - release the vk resource back into the pool. Note that the
|
||||
// vk handle has not changed, but we need to change the backend handle to allow
|
||||
// for proper refcounting of resources referenced in this set.
|
||||
unusedList.push_back(createSet(layout, vkSet));
|
||||
mCount--;
|
||||
});
|
||||
}
|
||||
|
||||
VkDevice mDevice;
|
||||
VulkanResourceAllocator* mResourceAllocator;
|
||||
VkDescriptorPool mPool;
|
||||
|
||||
uint32_t mCount = 0;
|
||||
std::unordered_map<VkDescriptorSetLayout, std::vector<VulkanDescriptorSet*>> mUnused;
|
||||
};
|
||||
|
||||
template<typename TYPE>
|
||||
class DescriptorInfinitePool {
|
||||
public:
|
||||
DescriptorInfinitePool(VkDevice device, VulkanResourceAllocator* allocator)
|
||||
: mDevice(device),
|
||||
mResourceAllocator(allocator) {}
|
||||
|
||||
~DescriptorInfinitePool() {
|
||||
for (auto pool: mPools) {
|
||||
delete pool;
|
||||
}
|
||||
}
|
||||
|
||||
VulkanDescriptorSet* obtainSet(VkDescriptorSetLayout layout) {
|
||||
for (auto pool: mPools) {
|
||||
auto set = pool->obtainSet(layout);
|
||||
if (set) {
|
||||
return set;
|
||||
}
|
||||
}
|
||||
// We need to increase the number of pools
|
||||
mPools.push_back(new DescriptorPool<TYPE>{mDevice, mResourceAllocator});
|
||||
auto pool = mPools.back();
|
||||
return pool->obtainSet(layout);
|
||||
}
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
VulkanResourceAllocator* mResourceAllocator;
|
||||
std::vector<DescriptorPool<TYPE>*> mPools;
|
||||
};
|
||||
|
||||
// This holds a cache of descriptor sets of a TYPE (UBO or Sampler). The Key is defined as the
|
||||
// layout and the content (for example specific samplers).
|
||||
template<typename TYPE, typename Cache, typename Key>
|
||||
class DescriptorSetCache {
|
||||
public:
|
||||
DescriptorSetCache(VkDevice device, VulkanResourceAllocator* allocator)
|
||||
: mPool(device, allocator),
|
||||
mResourceManager(allocator) {}
|
||||
|
||||
std::pair<VulkanDescriptorSet*, bool> obtainSet(Key const& key) {
|
||||
mAge++;
|
||||
if (auto result = mCache.find(key); result != mCache.end()) {
|
||||
auto set = result->second;
|
||||
auto const oldAge = mReverseHistory[set];
|
||||
mHistory.erase(oldAge);
|
||||
mHistory[mAge] = set;
|
||||
mReverseHistory[set] = mAge;
|
||||
return {set, true};
|
||||
}
|
||||
|
||||
VulkanDescriptorSet* set = mPool.obtainSet(key.layout);
|
||||
mCache[key] = set;
|
||||
mReverseCache[set] = key;
|
||||
mHistory[mAge] = set;
|
||||
mReverseHistory[set] = mAge;
|
||||
mResourceManager.acquire(set);
|
||||
return {set, false};
|
||||
}
|
||||
|
||||
// We need to periodically purge the descriptor sets so that we're not holding on to resources
|
||||
// unnecessarily. The strategy for purging needs to be examined more.
|
||||
void gc() noexcept {
|
||||
constexpr uint32_t ALLOWED_ENTRIES = EXPECTED_IN_FLIGHT_FRAMES * 3;
|
||||
int32_t const toCutCount = mHistory.size() - ALLOWED_ENTRIES;
|
||||
|
||||
if (toCutCount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> remove;
|
||||
for (auto entry = mHistory.begin(); entry != mHistory.end(); entry++) {
|
||||
auto const& set = entry->second;
|
||||
Key const& key = mReverseCache[set];
|
||||
mCache.erase(key);
|
||||
mReverseCache.erase(set);
|
||||
mReverseHistory.erase(set);
|
||||
|
||||
remove.push_back(entry->first);
|
||||
mResourceManager.release(set);
|
||||
}
|
||||
for (auto removeAge: remove) {
|
||||
mHistory.erase(removeAge);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
DescriptorInfinitePool<TYPE> mPool;
|
||||
|
||||
// TODO: combine some of these data structures
|
||||
Cache mCache;
|
||||
std::unordered_map<VulkanDescriptorSet*, Key> mReverseCache;
|
||||
|
||||
// Use the ordering for purging if needed;
|
||||
std::map<uint64_t, VulkanDescriptorSet*> mHistory;
|
||||
std::unordered_map<VulkanDescriptorSet*, uint64_t> mReverseHistory;
|
||||
uint64_t mAge;
|
||||
|
||||
// Note that instead of owning the resources (i.e. descriptor set) in the pools, we keep them
|
||||
// here since all the allocated sets have to pass through this class, and this class has better
|
||||
// knowledge to make decisions about gc.
|
||||
VulkanResourceManager mResourceManager;
|
||||
};
|
||||
|
||||
using UBOSetCache = DescriptorSetCache<UBOSet, UBOSet::Cache, UBOSet::Key>;
|
||||
using SamplerSetCache = DescriptorSetCache<SamplerSet, SamplerSet::Cache, SamplerSet::Key>;
|
||||
|
||||
template<typename MaskType>
|
||||
class LayoutCache {
|
||||
public:
|
||||
explicit LayoutCache(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
~LayoutCache() {
|
||||
for (auto [mask, layout]: mLayouts) {
|
||||
vkDestroyDescriptorSetLayout(mDevice, layout, VKALLOC);
|
||||
}
|
||||
mLayouts.clear();
|
||||
}
|
||||
|
||||
VkDescriptorSetLayout getLayout(MaskType stageMask) noexcept {
|
||||
if (stageMask == 0) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
if (auto iter = mLayouts.find(stageMask); iter != mLayouts.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
VkDescriptorType descriptorType;
|
||||
if constexpr (std::is_same_v<MaskType, UBOSet::Mask>) {
|
||||
descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
} else if constexpr (std::is_same_v<MaskType, SamplerSet::Mask>) {
|
||||
descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
}
|
||||
|
||||
VkDescriptorSetLayoutBinding toBind[MAX_BINDINGS];
|
||||
uint32_t count = 0;
|
||||
for (uint8_t i = 0, maxBindings = sizeof(stageMask) * 4; i < maxBindings; i++) {
|
||||
VkShaderStageFlags stages = 0;
|
||||
if ((stageMask & (VERTEX_STAGE << i)) != 0) {
|
||||
stages |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||
}
|
||||
if ((stageMask & (FRAGMENT_STAGE << i)) == 0) {
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
}
|
||||
if (stages == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& bindInfo = toBind[count++];
|
||||
bindInfo = {
|
||||
.binding = i,
|
||||
.descriptorType = descriptorType,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = stages,
|
||||
};
|
||||
}
|
||||
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.bindingCount = count,
|
||||
.pBindings = toBind,
|
||||
};
|
||||
|
||||
VkDescriptorSetLayout handle;
|
||||
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &handle);
|
||||
mLayouts[stageMask] = handle;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
LayoutMap<MaskType> mLayouts;
|
||||
};
|
||||
|
||||
using UBOSetLayoutCache = LayoutCache<UBOSet::Mask>;
|
||||
using SamplerSetLayoutCache = LayoutCache<SamplerSet::Mask>;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanDescriptorSet::VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet,
|
||||
VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET),
|
||||
resources(allocator),
|
||||
vkSet(rawSet),
|
||||
layout(layout),
|
||||
mOnRecycleFn(std::move(onRecycleFn)) {}
|
||||
|
||||
VulkanDescriptorSet::~VulkanDescriptorSet() {
|
||||
if (mOnRecycleFn) {
|
||||
mOnRecycleFn(this);
|
||||
}
|
||||
}
|
||||
|
||||
VulkanDescriptorSet* VulkanDescriptorSet::create(VulkanResourceAllocator* allocator,
|
||||
VkDescriptorSet rawSet, VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn) {
|
||||
auto handle = allocator->allocHandle<VulkanDescriptorSet>();
|
||||
auto set = allocator->construct<VulkanDescriptorSet>(handle, allocator, rawSet, layout,
|
||||
std::move(onRecycleFn));
|
||||
return set;
|
||||
}
|
||||
|
||||
class VulkanDescriptorSetManager::Impl {
|
||||
public:
|
||||
Impl(VkDevice device, VulkanResourceAllocator* allocator)
|
||||
: mDevice(device),
|
||||
mUBOLayoutCache(device),
|
||||
mUBOCache(device, allocator),
|
||||
mSamplerLayoutCache(device),
|
||||
mSamplerCache(device, allocator),
|
||||
mResources(allocator) {}
|
||||
|
||||
void setUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject,
|
||||
VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||
mUbos.push_back({bindingIndex, bufferObject, offset, size});
|
||||
|
||||
// Between "set" and "commit", we need to ref the buffer object to avoid it being collected.
|
||||
mResources.acquire(bufferObject);
|
||||
}
|
||||
|
||||
void setSamplers(SamplerArray&& samplers) {
|
||||
mSamplers = std::move(samplers);
|
||||
for (auto const& sampler: mSamplers) {
|
||||
mResources.acquire(sampler.texture);
|
||||
}
|
||||
}
|
||||
|
||||
void gc() noexcept {
|
||||
mUBOCache.gc();
|
||||
mSamplerCache.gc();
|
||||
}
|
||||
|
||||
// This will write/update all of the descriptor set (and create a set if a one of the same
|
||||
// layout is not available).
|
||||
void bind(VulkanCommandBuffer* commands, GetPipelineLayoutFunction& getPipelineLayoutFn) {
|
||||
LayoutArray layouts = {VK_NULL_HANDLE, VK_NULL_HANDLE};
|
||||
std::array<VkDescriptorSet, 2> descSets = {VK_NULL_HANDLE, VK_NULL_HANDLE};
|
||||
uint8_t descSetCount = 0;
|
||||
|
||||
VkWriteDescriptorSet descriptorWrites[UNIFORM_BINDING_COUNT + SAMPLER_BINDING_COUNT];
|
||||
VkDescriptorBufferInfo uboWrite[UNIFORM_BINDING_COUNT];
|
||||
VkDescriptorImageInfo samplerWrite[SAMPLER_BINDING_COUNT];
|
||||
uint32_t nwrites = 0;
|
||||
|
||||
UBOSet::Key uboKey = {};
|
||||
auto& uboMask = uboKey.mask;
|
||||
uint8_t uboCount = 0;
|
||||
for (auto const& ubo: mUbos) {
|
||||
auto const& binding = std::get<0>(ubo);
|
||||
|
||||
uboKey.buffers[uboCount] = std::get<1>(ubo)->buffer.getGpuBuffer();
|
||||
uboKey.offsets[uboCount] = std::get<2>(ubo);
|
||||
uboKey.sizes[uboCount] = std::get<3>(ubo);
|
||||
uboCount++;
|
||||
|
||||
// Currently we let ubo be visible in all stages.
|
||||
uboMask |= (VERTEX_STAGE << binding);
|
||||
uboMask |= (FRAGMENT_STAGE << binding);
|
||||
}
|
||||
|
||||
if (uboMask) {
|
||||
layouts[UBO_SET_INDEX] = uboKey.layout = mUBOLayoutCache.getLayout(uboMask);
|
||||
|
||||
auto [descriptorSet, cached] = mUBOCache.obtainSet(uboKey);
|
||||
descSets[descSetCount++] = descriptorSet->vkSet;
|
||||
|
||||
// We need to write to the descriptor set since it wasn't cached.
|
||||
if (!cached) {
|
||||
// If it wasn't cached before, we need to ref the resources that it touches.
|
||||
for (auto const& ubo: mUbos) {
|
||||
auto const buffer = std::get<1>(ubo);
|
||||
descriptorSet->resources.acquire(buffer);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < uboCount; ++i) {
|
||||
auto const& binding = std::get<0>(mUbos[i]);
|
||||
VkWriteDescriptorSet& descriptorWrite = descriptorWrites[nwrites++];
|
||||
auto& writeInfo = uboWrite[i];
|
||||
writeInfo = {
|
||||
.buffer = uboKey.buffers[i],
|
||||
.offset = uboKey.offsets[i],
|
||||
.range = uboKey.sizes[i],
|
||||
};
|
||||
descriptorWrite = {
|
||||
.dstSet = descriptorSet->vkSet,
|
||||
.dstBinding = binding,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
.pBufferInfo = &writeInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
commands->acquire(descriptorSet);
|
||||
}
|
||||
|
||||
SamplerSet::Key samplerKey = {};
|
||||
auto& samplerMask = samplerKey.mask;
|
||||
uint8_t samplerCount = 0;
|
||||
for (auto const sampler: mSamplers) {
|
||||
samplerMask |= (sampler.stage << sampler.binding);
|
||||
|
||||
samplerKey.sampler[samplerCount] = sampler.info.sampler;
|
||||
samplerKey.imageView[samplerCount] = sampler.info.imageView;
|
||||
samplerKey.imageLayout[samplerCount] = sampler.info.imageLayout;
|
||||
samplerCount++;
|
||||
}
|
||||
|
||||
if (samplerMask) {
|
||||
layouts[SAMPLER_SET_INDEX] = samplerKey.layout =
|
||||
mSamplerLayoutCache.getLayout(samplerMask);
|
||||
|
||||
auto [descriptorSet, cached] = mSamplerCache.obtainSet(samplerKey);
|
||||
descSets[descSetCount++] = descriptorSet->vkSet;
|
||||
|
||||
if (!cached) {
|
||||
for (auto const& sampler: mSamplers) {
|
||||
descriptorSet->resources.acquire(sampler.texture);
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < samplerCount; ++i) {
|
||||
auto const& binding = mSamplers[i].binding;
|
||||
VkWriteDescriptorSet& descriptorWrite = descriptorWrites[nwrites++];
|
||||
auto& writeInfo = samplerWrite[i];
|
||||
writeInfo = mSamplers[i].info;
|
||||
descriptorWrite = {
|
||||
.dstSet = descriptorSet->vkSet,
|
||||
.dstBinding = binding,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.pImageInfo = &writeInfo,
|
||||
};
|
||||
}
|
||||
}
|
||||
commands->acquire(descriptorSet);
|
||||
}
|
||||
if (nwrites) {
|
||||
vkUpdateDescriptorSets(mDevice, nwrites, descriptorWrites, 0, nullptr);
|
||||
}
|
||||
|
||||
VkPipelineLayout const pipelineLayout = getPipelineLayoutFn(layouts);
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
|
||||
BoundState state {
|
||||
.cmdbuf = cmdbuffer,
|
||||
.pipelineLayout = pipelineLayout,
|
||||
};
|
||||
|
||||
if (state == mPreviousBoundState) {
|
||||
return;
|
||||
}
|
||||
|
||||
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0,
|
||||
descSetCount, descSets.data(), 0, nullptr);
|
||||
|
||||
mPreviousBoundState = state;
|
||||
|
||||
// Once bound, the resources are now ref'd in the descriptor set and the references in this
|
||||
// class can be released and the descriptor set is ref'd by the command buffer.
|
||||
mResources.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
struct BoundState {
|
||||
VkCommandBuffer cmdbuf;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
|
||||
bool operator==(BoundState const& b) const {
|
||||
return cmdbuf == b.cmdbuf && pipelineLayout == b.pipelineLayout;
|
||||
}
|
||||
};
|
||||
|
||||
VkDevice mDevice;
|
||||
|
||||
UBOSetLayoutCache mUBOLayoutCache;
|
||||
UBOSetCache mUBOCache;
|
||||
|
||||
SamplerSetLayoutCache mSamplerLayoutCache;
|
||||
SamplerSetCache mSamplerCache;
|
||||
|
||||
std::vector<std::tuple<uint32_t, VulkanBufferObject*, VkDeviceSize, VkDeviceSize>> mUbos;
|
||||
SamplerArray mSamplers;
|
||||
VulkanAcquireOnlyResourceManager mResources;
|
||||
|
||||
BoundState mPreviousBoundState;
|
||||
};
|
||||
|
||||
VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device,
|
||||
VulkanResourceAllocator* resourceAllocator)
|
||||
: mImpl(new Impl(device, resourceAllocator)) {}
|
||||
|
||||
void VulkanDescriptorSetManager::terminate() noexcept {
|
||||
assert_invariant(mImpl);
|
||||
delete mImpl;
|
||||
mImpl = nullptr;
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::gc() noexcept { mImpl->gc(); }
|
||||
|
||||
void VulkanDescriptorSetManager::bind(VulkanCommandBuffer* commands,
|
||||
GetPipelineLayoutFunction& getPipelineLayoutFn) {
|
||||
mImpl->bind(commands, getPipelineLayoutFn);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::setUniformBufferObject(uint32_t bindingIndex,
|
||||
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||
mImpl->setUniformBufferObject(bindingIndex, bufferObject, offset, size);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::setSamplers(SamplerArray&& samplers) {
|
||||
mImpl->setSamplers(std::move(samplers));
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSET_H
|
||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSET_H
|
||||
|
||||
#include <vulkan/VulkanResourceAllocator.h>
|
||||
#include <vulkan/VulkanTexture.h>
|
||||
#include <vulkan/VulkanUtility.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Program.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
// We need to make this class public to enable allocation on the HandleAllocator.
|
||||
struct VulkanDescriptorSet : public VulkanResourceBase {
|
||||
public:
|
||||
// Because we need to recycle descriptor set not used, we allow for a callback that the "Pool"
|
||||
// can use to repackage the vk handle.
|
||||
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
|
||||
|
||||
VulkanDescriptorSet(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet,
|
||||
VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn);
|
||||
|
||||
~VulkanDescriptorSet();
|
||||
|
||||
static VulkanDescriptorSet* create(VulkanResourceAllocator* allocator, VkDescriptorSet rawSet,
|
||||
VkDescriptorSetLayout layout, OnRecycle&& onRecycleFn);
|
||||
|
||||
// TODO: maybe change to fixed size for performance.
|
||||
VulkanAcquireOnlyResourceManager resources;
|
||||
|
||||
VkDescriptorSet const vkSet;
|
||||
VkDescriptorSetLayout const layout;
|
||||
|
||||
private:
|
||||
OnRecycle mOnRecycleFn;
|
||||
};
|
||||
|
||||
// Abstraction over the pool and the layout cache.
|
||||
class VulkanDescriptorSetManager {
|
||||
public:
|
||||
using StageBitMask = uint32_t;
|
||||
using UniformBufferBitmask = StageBitMask;
|
||||
using SamplerBitmask = StageBitMask;
|
||||
|
||||
static constexpr uint8_t CONCURRENT_DESCRIPTOR_SET_COUNT = 2;// UBO and samplers
|
||||
static constexpr uint8_t MAX_SUPPORTED_SHADER_STAGE = 2; // Vertex and fragment.
|
||||
|
||||
static_assert(sizeof(UniformBufferBitmask) * 8 >=
|
||||
(Program::UNIFORM_BINDING_COUNT) *MAX_SUPPORTED_SHADER_STAGE);
|
||||
static_assert(sizeof(SamplerBitmask) * 8 >=
|
||||
Program::SAMPLER_BINDING_COUNT * MAX_SUPPORTED_SHADER_STAGE);
|
||||
|
||||
static constexpr StageBitMask VERTEX_STAGE = 0x1;
|
||||
static constexpr StageBitMask FRAGMENT_STAGE = (0x1 << (sizeof(StageBitMask) / 4));
|
||||
static constexpr uint8_t UBO_SET_INDEX = 0;
|
||||
static constexpr uint8_t SAMPLER_SET_INDEX = 1;
|
||||
|
||||
using LayoutArray = std::array<VkDescriptorSetLayout, CONCURRENT_DESCRIPTOR_SET_COUNT>;
|
||||
struct SamplerBundle {
|
||||
VkDescriptorImageInfo info = {};
|
||||
VulkanTexture* texture = nullptr;
|
||||
uint8_t binding = 0;
|
||||
SamplerBitmask stage = 0;
|
||||
};
|
||||
using SamplerArray = CappedArray<SamplerBundle, Program::SAMPLER_BINDING_COUNT>;
|
||||
using GetPipelineLayoutFunction = std::function<VkPipelineLayout(LayoutArray)>;
|
||||
|
||||
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
void gc() noexcept;
|
||||
|
||||
// This will write/update all of the descriptor set.
|
||||
void bind(VulkanCommandBuffer* commands, GetPipelineLayoutFunction& getPipelineFn);
|
||||
|
||||
void setUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject,
|
||||
VkDeviceSize offset, VkDeviceSize size) noexcept;
|
||||
|
||||
void setSamplers(SamplerArray&& samplers);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl* mImpl;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSET_H
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <utils/PrivateImplementation-impl.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#define SWAPCHAIN_RET_FUNC(func, handle, ...) \
|
||||
if (mImpl->mSurfaceSwapChains.find(handle) != mImpl->mSurfaceSwapChains.end()) { \
|
||||
@@ -159,9 +158,7 @@ ExtensionSet getInstanceExtensions() {
|
||||
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
|
||||
|
||||
// Request these if available.
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
|
||||
#endif
|
||||
VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
@@ -184,9 +181,7 @@ ExtensionSet getInstanceExtensions() {
|
||||
|
||||
ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
|
||||
std::string_view const TARGET_EXTS[] = {
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
VK_EXT_DEBUG_MARKER_EXTENSION_NAME,
|
||||
#endif
|
||||
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE1_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE2_EXTENSION_NAME,
|
||||
@@ -342,22 +337,36 @@ std::tuple<ExtensionSet, ExtensionSet> pruneExtensions(VkPhysicalDevice device,
|
||||
ExtensionSet const& instExts, ExtensionSet const& deviceExts) {
|
||||
ExtensionSet newInstExts = instExts;
|
||||
ExtensionSet newDeviceExts = deviceExts;
|
||||
if (vkGetPhysicalDeviceProperties2KHR) {
|
||||
VkPhysicalDeviceDriverProperties driverProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES,
|
||||
};
|
||||
VkPhysicalDeviceProperties2 physicalDeviceProperties2 = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
||||
.pNext = &driverProperties,
|
||||
};
|
||||
vkGetPhysicalDeviceProperties2KHR(device, &physicalDeviceProperties2);
|
||||
char* driverInfo = driverProperties.driverInfo;
|
||||
|
||||
if (newInstExts.find(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) != newInstExts.end()) {
|
||||
// Workaround for Mesa drivers. See issue #6192
|
||||
if ((driverInfo && strstr(driverInfo, "Mesa"))) {
|
||||
newInstExts.erase(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
// debugUtils and debugMarkers extensions are used mutually exclusively.
|
||||
if (newInstExts.find(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) != newInstExts.end()
|
||||
&& newDeviceExts.find(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) != newDeviceExts.end()) {
|
||||
newDeviceExts.erase(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
// debugMarker must also request debugReport the instance extension. So check if that's present.
|
||||
if (newDeviceExts.find(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) != newDeviceExts.end()
|
||||
&& newInstExts.find(VK_EXT_DEBUG_REPORT_EXTENSION_NAME) == newInstExts.end()) {
|
||||
newDeviceExts.erase(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
|
||||
}
|
||||
#endif
|
||||
|
||||
return std::tuple(newInstExts, newDeviceExts);
|
||||
}
|
||||
@@ -662,12 +671,6 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
context.mDebugMarkersSupported
|
||||
= deviceExts.find(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) != deviceExts.end();
|
||||
|
||||
#ifdef NDEBUG
|
||||
// If we are in release build, we should not have turned on debug extensions
|
||||
ASSERT_POSTCONDITION(!context.mDebugUtilsSupported && !context.mDebugMarkersSupported,
|
||||
"Debug utils should not be enabled in release build.");
|
||||
#endif
|
||||
|
||||
context.mDepthFormats = findAttachmentDepthFormats(mImpl->mPhysicalDevice);
|
||||
|
||||
assert_invariant(context.mDepthFormats.size() > 0);
|
||||
|
||||
@@ -143,7 +143,7 @@ void BackendTest::renderTriangle(Handle<HwRenderTarget> renderTarget,
|
||||
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
||||
state.rasterState.culling = CullingMode::NONE;
|
||||
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 1);
|
||||
|
||||
api.endRenderPass();
|
||||
}
|
||||
|
||||
@@ -48,9 +48,7 @@ TrianglePrimitive::TrianglePrimitive(filament::backend::DriverApi& driverApi,
|
||||
|
||||
const size_t size = sizeof(math::float2) * 3;
|
||||
mBufferObject = mDriverApi.createBufferObject(size, BufferObjectBinding::VERTEX, BufferUsage::STATIC);
|
||||
mVertexBufferInfo = mDriverApi.createVertexBufferInfo(1, 1, attributes);
|
||||
mVertexBuffer = mDriverApi.createVertexBuffer(mVertexCount, mVertexBufferInfo);
|
||||
|
||||
mVertexBuffer = mDriverApi.createVertexBuffer(1, 1, mVertexCount, attributes);
|
||||
mDriverApi.setVertexBufferObject(mVertexBuffer, 0, mBufferObject);
|
||||
BufferDescriptor vertexBufferDesc(gVertices, size);
|
||||
mDriverApi.updateBufferObject(mBufferObject, std::move(vertexBufferDesc), 0);
|
||||
@@ -62,7 +60,7 @@ TrianglePrimitive::TrianglePrimitive(filament::backend::DriverApi& driverApi,
|
||||
mDriverApi.updateIndexBuffer(mIndexBuffer, std::move(indexBufferDesc), 0);
|
||||
|
||||
mRenderPrimitive = mDriverApi.createRenderPrimitive(
|
||||
mVertexBuffer, mIndexBuffer, PrimitiveType::TRIANGLES);
|
||||
mVertexBuffer, mIndexBuffer, PrimitiveType::TRIANGLES, 0, 0, 2, 3);
|
||||
}
|
||||
|
||||
void TrianglePrimitive::updateVertices(const filament::math::float2 vertices[3]) noexcept {
|
||||
@@ -104,7 +102,6 @@ void TrianglePrimitive::updateIndices(const index_type* indices, int count, int
|
||||
TrianglePrimitive::~TrianglePrimitive() {
|
||||
mDriverApi.destroyBufferObject(mBufferObject);
|
||||
mDriverApi.destroyVertexBuffer(mVertexBuffer);
|
||||
mDriverApi.destroyVertexBufferInfo(mVertexBufferInfo);
|
||||
mDriverApi.destroyIndexBuffer(mIndexBuffer);
|
||||
mDriverApi.destroyRenderPrimitive(mRenderPrimitive);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ public:
|
||||
|
||||
using PrimitiveHandle = filament::backend::Handle<filament::backend::HwRenderPrimitive>;
|
||||
using BufferObjectHandle = filament::backend::Handle<filament::backend::HwBufferObject>;
|
||||
using VertexInfoHandle = filament::backend::Handle<filament::backend::HwVertexBufferInfo>;
|
||||
using VertexHandle = filament::backend::Handle<filament::backend::HwVertexBuffer>;
|
||||
using IndexHandle = filament::backend::Handle<filament::backend::HwIndexBuffer>;
|
||||
|
||||
@@ -64,7 +63,6 @@ private:
|
||||
|
||||
PrimitiveHandle mRenderPrimitive;
|
||||
BufferObjectHandle mBufferObject;
|
||||
VertexInfoHandle mVertexBufferInfo;
|
||||
VertexHandle mVertexBuffer;
|
||||
IndexHandle mIndexBuffer;
|
||||
|
||||
|
||||
@@ -405,7 +405,7 @@ TEST_F(BackendTest, ColorResolve) {
|
||||
api.beginFrame(0, 0);
|
||||
api.beginRenderPass(srcRenderTarget, params);
|
||||
api.bindUniformBuffer(0, ubuffer);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
api.endFrame(0);
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ TEST_F(BackendTest, VertexBufferUpdate) {
|
||||
}
|
||||
|
||||
getDriverApi().beginRenderPass(defaultRenderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 1);
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
triangleIndex++;
|
||||
|
||||
@@ -218,7 +218,7 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
.sourceLevel = float(sourceLevel),
|
||||
});
|
||||
api.beginRenderPass(renderTargets[targetLevel], params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
.sourceLevel = float(sourceLevel),
|
||||
});
|
||||
api.beginRenderPass(renderTargets[targetLevel], params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ TEST_F(BackendTest, MRT) {
|
||||
|
||||
// Draw a triangle.
|
||||
getDriverApi().beginRenderPass(renderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 1);
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
getDriverApi().flush();
|
||||
|
||||
@@ -168,7 +168,7 @@ TEST_F(BackendTest, SetMinMaxLevel) {
|
||||
ps.rasterState.colorWrite = true;
|
||||
ps.rasterState.depthWrite = false;
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ TEST_F(BackendTest, SetMinMaxLevel) {
|
||||
params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
|
||||
PipelineState state;
|
||||
state.scissor = params.viewport;
|
||||
state.program = textureProgram;
|
||||
state.rasterState.colorWrite = true;
|
||||
state.rasterState.depthWrite = false;
|
||||
@@ -202,8 +203,7 @@ TEST_F(BackendTest, SetMinMaxLevel) {
|
||||
// Because the min level is 1, the result color should be the white triangle drawn in the
|
||||
// previous pass.
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
api.scissor(params.viewport);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
// Adjust the base mip to 2.
|
||||
@@ -221,8 +221,7 @@ TEST_F(BackendTest, SetMinMaxLevel) {
|
||||
params.flags.clear = TargetBufferFlags::NONE;
|
||||
params.flags.discardStart = TargetBufferFlags::NONE;
|
||||
api.beginRenderPass(defaultRenderTarget, params);
|
||||
api.scissor(params.viewport);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.commit(swapChain);
|
||||
|
||||
@@ -104,7 +104,7 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
|
||||
|
||||
// Render a triangle.
|
||||
getDriverApi().beginRenderPass(defaultRenderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 1);
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
getDriverApi().flush();
|
||||
|
||||
@@ -309,7 +309,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
state.rasterState.depthWrite = false;
|
||||
state.rasterState.depthFunc = RasterState::DepthFunc::A;
|
||||
state.rasterState.culling = CullingMode::NONE;
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 1);
|
||||
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
@@ -438,7 +438,7 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
|
||||
|
||||
// Render some content, just so we don't read back uninitialized data.
|
||||
getDriverApi().beginRenderPass(renderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 1);
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
PixelBufferDescriptor descriptor(buffer, renderTargetSize * renderTargetSize * 4,
|
||||
|
||||
@@ -120,7 +120,7 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
|
||||
// Render a triangle.
|
||||
getDriverApi().beginRenderPass(defaultRenderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 1);
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
getDriverApi().flush();
|
||||
@@ -242,7 +242,7 @@ TEST_F(BackendTest, RenderExternalImage) {
|
||||
|
||||
// Render a triangle.
|
||||
getDriverApi().beginRenderPass(defaultRenderTarget, params);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
getDriverApi().draw(state, triangle.getRenderPrimitive(), 1);
|
||||
getDriverApi().endRenderPass();
|
||||
|
||||
getDriverApi().flush();
|
||||
|
||||
@@ -134,13 +134,13 @@ TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
ps.program = program;
|
||||
ps.rasterState.colorWrite = true;
|
||||
ps.rasterState.depthWrite = false;
|
||||
ps.scissor = scissor;
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0);
|
||||
|
||||
api.beginRenderPass(srcRenderTarget, params);
|
||||
api.scissor(scissor);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
readPixelsAndAssertHash("scissor", kSrcTexWidth >> 1, kSrcTexHeight >> 1, fullRenderTarget,
|
||||
@@ -225,21 +225,20 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
|
||||
ps.program = program;
|
||||
ps.rasterState.colorWrite = true;
|
||||
ps.rasterState.depthWrite = false;
|
||||
ps.scissor = scissor;
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0);
|
||||
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
api.scissor(scissor);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
params.viewport = topLeftViewport;
|
||||
params.flags.clear = TargetBufferFlags::NONE;
|
||||
params.flags.discardStart = TargetBufferFlags::NONE;
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
api.scissor(scissor);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
readPixelsAndAssertHash(
|
||||
|
||||
@@ -113,7 +113,7 @@ public:
|
||||
api.beginFrame(0, 0);
|
||||
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
api.draw(ps, smallTriangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(ps, smallTriangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
// Step 2: Render a larger triangle with the stencil test enabled.
|
||||
@@ -126,7 +126,7 @@ public:
|
||||
ps.stencilState.front.ref = 0u;
|
||||
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.commit(swapChain);
|
||||
@@ -240,7 +240,7 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
api.beginFrame(0, 0);
|
||||
|
||||
api.beginRenderPass(renderTarget0, params);
|
||||
api.draw(ps, smallTriangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(ps, smallTriangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
// Step 2: Render a larger triangle with the stencil test enabled.
|
||||
@@ -255,7 +255,7 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
ps.stencilState.front.ref = 0u;
|
||||
|
||||
api.beginRenderPass(renderTarget1, params);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
api.commit(swapChain);
|
||||
|
||||
@@ -178,7 +178,6 @@ public:
|
||||
using Backend = backend::Backend;
|
||||
using DriverConfig = backend::Platform::DriverConfig;
|
||||
using FeatureLevel = backend::FeatureLevel;
|
||||
using StereoscopicType = backend::StereoscopicType;
|
||||
|
||||
/**
|
||||
* Config is used to define the memory footprint used by the engine, such as the
|
||||
@@ -298,25 +297,6 @@ public:
|
||||
*/
|
||||
size_t textureUseAfterFreePoolSize = 0;
|
||||
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL and Metal backends.
|
||||
*/
|
||||
bool disableParallelShaderCompile = false;
|
||||
|
||||
/*
|
||||
* The type of technique for stereoscopic rendering.
|
||||
*
|
||||
* This setting determines the algorithm used when stereoscopic rendering is enabled. This
|
||||
* decision applies to the entire Engine for the lifetime of the Engine. E.g., multiple
|
||||
* Views created from the Engine must use the same stereoscopic type.
|
||||
*
|
||||
* Each view can enable stereoscopic rendering via the StereoscopicOptions::enable flag.
|
||||
*
|
||||
* @see View::setStereoscopicOptions
|
||||
*/
|
||||
StereoscopicType stereoscopicType = StereoscopicType::INSTANCED;
|
||||
|
||||
/*
|
||||
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
|
||||
* between 1 and Engine::getMaxStereoscopicEyes() (inclusive).
|
||||
@@ -327,14 +307,16 @@ public:
|
||||
uint8_t stereoscopicEyeCount = 2;
|
||||
|
||||
/*
|
||||
* @deprecated This value is no longer used.
|
||||
* Size in MiB of the frame graph texture cache. This should be adjusted based on the
|
||||
* size of used render targets (typically the screen).
|
||||
*/
|
||||
uint32_t resourceAllocatorCacheSizeMB = 64;
|
||||
|
||||
/*
|
||||
* This value determines for how many frames are texture entries kept in the cache.
|
||||
* The default value of 30 corresponds to about half a second at 60 fps.
|
||||
*/
|
||||
uint32_t resourceAllocatorCacheMaxAge = 2;
|
||||
uint32_t resourceAllocatorCacheMaxAge = 30;
|
||||
};
|
||||
|
||||
|
||||
@@ -594,12 +576,12 @@ public:
|
||||
size_t getMaxAutomaticInstances() const noexcept;
|
||||
|
||||
/**
|
||||
* Queries the device and platform for support of the given stereoscopic type.
|
||||
* Queries the device and platform for instanced stereo rendering support.
|
||||
*
|
||||
* @return true if the given stereo rendering is supported, false otherwise
|
||||
* @return true if stereo rendering is supported, false otherwise
|
||||
* @see View::setStereoscopicOptions
|
||||
*/
|
||||
bool isStereoSupported(StereoscopicType stereoscopicType) const noexcept;
|
||||
bool isStereoSupported() const noexcept;
|
||||
|
||||
/**
|
||||
* Retrieves the configuration settings of this Engine.
|
||||
|
||||
@@ -583,6 +583,8 @@ public:
|
||||
VertexBuffer* UTILS_NULLABLE vertices = nullptr;
|
||||
IndexBuffer* UTILS_NULLABLE indices = nullptr;
|
||||
size_t offset = 0;
|
||||
size_t minIndex = 0;
|
||||
size_t maxIndex = 0;
|
||||
size_t count = 0;
|
||||
MaterialInstance const* UTILS_NULLABLE materialInstance = nullptr;
|
||||
PrimitiveType type = PrimitiveType::TRIANGLES;
|
||||
|
||||
@@ -225,29 +225,14 @@ public:
|
||||
* @see View.setStencilBufferEnabled
|
||||
* @see View.setPostProcessingEnabled
|
||||
*/
|
||||
static constexpr uint64_t CONFIG_HAS_STENCIL_BUFFER = backend::SWAP_CHAIN_CONFIG_HAS_STENCIL_BUFFER;
|
||||
static constexpr uint64_t CONFIG_HAS_STENCIL_BUFFER = backend::SWAP_CHAIN_HAS_STENCIL_BUFFER;
|
||||
|
||||
/**
|
||||
* The SwapChain contains protected content. Only supported when isProtectedContentSupported()
|
||||
* is true.
|
||||
*/
|
||||
static constexpr uint64_t CONFIG_PROTECTED_CONTENT = backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
|
||||
|
||||
/**
|
||||
* Return whether createSwapChain supports the CONFIG_PROTECTED_CONTENT flag.
|
||||
* Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag.
|
||||
* The default implementation returns false.
|
||||
*
|
||||
* @param engine A pointer to the filament Engine
|
||||
* @return true if CONFIG_PROTECTED_CONTENT is supported, false otherwise.
|
||||
*/
|
||||
static bool isProtectedContentSupported(Engine& engine) noexcept;
|
||||
|
||||
/**
|
||||
* Return whether createSwapChain supports the CONFIG_SRGB_COLORSPACE flag.
|
||||
* The default implementation returns false.
|
||||
*
|
||||
* @param engine A pointer to the filament Engine
|
||||
* @return true if CONFIG_SRGB_COLORSPACE is supported, false otherwise.
|
||||
* @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise.
|
||||
*/
|
||||
static bool isSRGBSwapChainSupported(Engine& engine) noexcept;
|
||||
|
||||
@@ -268,10 +253,6 @@ public:
|
||||
* automatically schedule itself for presentation. Instead, the application must call the
|
||||
* PresentCallable passed to the FrameScheduledCallback.
|
||||
*
|
||||
* There may be only one FrameScheduledCallback set per SwapChain. A call to
|
||||
* SwapChain::setFrameScheduledCallback will overwrite any previous FrameScheduledCallbacks set
|
||||
* on the same SwapChain.
|
||||
*
|
||||
* 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
|
||||
@@ -291,16 +272,6 @@ public:
|
||||
void setFrameScheduledCallback(FrameScheduledCallback UTILS_NULLABLE callback,
|
||||
void* UTILS_NULLABLE user = nullptr);
|
||||
|
||||
/**
|
||||
* Returns the SwapChain::FrameScheduledCallback that was previously set with
|
||||
* SwapChain::setFrameScheduledCallback, or nullptr if one is not set.
|
||||
*
|
||||
* @return the previously-set FrameScheduledCallback, or nullptr
|
||||
*
|
||||
* @see SwapChain::setFrameCompletedCallback
|
||||
*/
|
||||
UTILS_NULLABLE FrameScheduledCallback getFrameScheduledCallback() const noexcept;
|
||||
|
||||
/**
|
||||
* FrameCompletedCallback is a callback function that notifies an application when a frame's
|
||||
* contents have completed rendering on the GPU.
|
||||
|
||||
@@ -88,9 +88,6 @@ public:
|
||||
/** @return whether a backend supports a particular format. */
|
||||
static bool isTextureFormatSupported(Engine& engine, InternalFormat format) noexcept;
|
||||
|
||||
/** @return whether this backend supports protected textures. */
|
||||
static bool isProtectedTexturesSupported(Engine& engine) noexcept;
|
||||
|
||||
/** @return whether a backend supports texture swizzling. */
|
||||
static bool isTextureSwizzleSupported(Engine& engine) noexcept;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include <math/mathfwd.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -39,12 +39,10 @@ namespace filament {
|
||||
*
|
||||
* - Configurable tone mapping operators
|
||||
* - GenericToneMapper
|
||||
* - AgXToneMapper
|
||||
* - Fixed-aesthetic tone mapping operators
|
||||
* - ACESToneMapper
|
||||
* - ACESLegacyToneMapper
|
||||
* - FilmicToneMapper
|
||||
* - PBRNeutralToneMapper
|
||||
* - Debug/validation tone mapping operators
|
||||
* - LinearToneMapper
|
||||
* - DisplayRangeToneMapper
|
||||
@@ -119,22 +117,11 @@ struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper {
|
||||
math::float3 operator()(math::float3 x) const noexcept override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Khronos PBR Neutral tone mapping operator. This tone mapper was designed
|
||||
* to preserve the appearance of materials across lighting conditions while
|
||||
* avoiding artifacts in the highlights in high dynamic range conditions.
|
||||
*/
|
||||
struct UTILS_PUBLIC PBRNeutralToneMapper final : public ToneMapper {
|
||||
PBRNeutralToneMapper() noexcept;
|
||||
~PBRNeutralToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 x) const noexcept override;
|
||||
};
|
||||
|
||||
/**
|
||||
* AgX tone mapping operator.
|
||||
*/
|
||||
struct UTILS_PUBLIC AgxToneMapper final : public ToneMapper {
|
||||
|
||||
enum class AgxLook : uint8_t {
|
||||
NONE = 0, //!< Base contrast with no look applied
|
||||
PUNCHY, //!< A punchy and more chroma laden look for sRGB displays
|
||||
@@ -198,6 +185,9 @@ struct UTILS_PUBLIC GenericToneMapper final : public ToneMapper {
|
||||
/** Returns the contrast of the curve as a strictly positive value. */
|
||||
float getContrast() const noexcept;
|
||||
|
||||
/** Returns how fast scene referred values map to output white as a value between 0.0 and 1.0. */
|
||||
float getShoulder() const noexcept;
|
||||
|
||||
/** Returns the middle gray point for input values as a value between 0.0 and 1.0. */
|
||||
float getMidGrayIn() const noexcept;
|
||||
|
||||
|
||||
@@ -719,7 +719,7 @@ public:
|
||||
void setDebugCamera(Camera* UTILS_NULLABLE camera) noexcept;
|
||||
|
||||
//! debugging: returns a Camera from the point of view of *the* dominant directional light used for shadowing.
|
||||
Camera const* UTILS_NULLABLE getDirectionalShadowCamera() const noexcept;
|
||||
Camera const* UTILS_NULLABLE getDirectionalLightCamera() const noexcept;
|
||||
|
||||
|
||||
/** Result of a picking query */
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BIMAP_H
|
||||
#define TNT_FILAMENT_BIMAP_H
|
||||
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
/*
|
||||
* A semi-generic custom bimap. This bimap stores a key/value pair and can retrieve
|
||||
* the value from the key and the key from the value.
|
||||
* It is optimized for large keys and small values. The keys are stored out-of-line and are
|
||||
* never moved.
|
||||
*/
|
||||
template<typename Key, typename Value,
|
||||
typename KeyHash = std::hash<Key>,
|
||||
typename ValueHash = std::hash<Value>,
|
||||
typename Allocator = std::allocator<Key>>
|
||||
class Bimap {
|
||||
|
||||
struct KeyDelegate {
|
||||
Key const* pKey = nullptr;
|
||||
bool operator==(KeyDelegate const& rhs) const noexcept {
|
||||
return *pKey == *rhs.pKey;
|
||||
}
|
||||
};
|
||||
|
||||
// KeyWrapperHash delegates the hash computation to KeyHash
|
||||
struct KeyHasherDelegate {
|
||||
size_t operator()(KeyDelegate const& p) const noexcept {
|
||||
KeyHash const h;
|
||||
return h(*p.pKey);
|
||||
}
|
||||
};
|
||||
|
||||
using ForwardMap = tsl::robin_map<KeyDelegate, Value, KeyHasherDelegate>;
|
||||
using BackwardMap = tsl::robin_map<Value, KeyDelegate, ValueHash>;
|
||||
|
||||
Allocator mAllocator;
|
||||
ForwardMap mForwardMap;
|
||||
BackwardMap mBackwardMap;
|
||||
|
||||
public:
|
||||
Bimap() = default;
|
||||
explicit Bimap(Allocator&& allocator)
|
||||
: mAllocator(std::forward<Allocator>(allocator)) {
|
||||
}
|
||||
|
||||
void reserve(size_t capacity) {
|
||||
mForwardMap.reserve(capacity);
|
||||
mBackwardMap.reserve(capacity);
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return mForwardMap.empty() && mBackwardMap.empty();
|
||||
}
|
||||
|
||||
// insert a new key/value pair
|
||||
void insert(Key const& key, Value const& value) noexcept {
|
||||
Key* pKey = mAllocator.allocate(1); // allocate storage for the key
|
||||
new((void*)pKey) Key{ key }; // copy-construct the key
|
||||
mForwardMap.insert({{ pKey }, value });
|
||||
mBackwardMap.insert({ value, { pKey }});
|
||||
}
|
||||
|
||||
typename ForwardMap::iterator end() { return mForwardMap.end(); }
|
||||
|
||||
// Find the value iterator from the key in O(1)
|
||||
typename ForwardMap::const_iterator find(Key const& key) const {
|
||||
return mForwardMap.find(KeyDelegate{ .pKey = &key });
|
||||
}
|
||||
typename ForwardMap::iterator find(Key const& key) {
|
||||
return mForwardMap.find(KeyDelegate{ .pKey = &key });
|
||||
}
|
||||
|
||||
// Find the key iterator from the value in O(1). precondition, the value must exist.
|
||||
typename BackwardMap::const_iterator find(Value const& value) const {
|
||||
auto pos = mBackwardMap.find(value);
|
||||
assert_invariant( pos != mBackwardMap.end() );
|
||||
return pos;
|
||||
}
|
||||
typename BackwardMap::iterator find(Value& value) const {
|
||||
return mBackwardMap.find(value);
|
||||
}
|
||||
|
||||
// erase a key/value pair using an iterator to the value
|
||||
void erase(typename BackwardMap::const_iterator it) {
|
||||
// find the key
|
||||
Key const& key = *(it->second.pKey);
|
||||
// and its iterator
|
||||
auto pos = find(key);
|
||||
// destroy the key
|
||||
it->second.pKey->~Key();
|
||||
// free its memory
|
||||
mAllocator.deallocate(const_cast<Key *>(it->second.pKey), 1);
|
||||
// remove the entries from both maps
|
||||
mForwardMap.erase(pos);
|
||||
mBackwardMap.erase(it);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
|
||||
#endif // TNT_FILAMENT_BIMAP_H
|
||||
@@ -331,8 +331,8 @@ const Engine::Config& Engine::getConfig() const noexcept {
|
||||
return downcast(this)->getConfig();
|
||||
}
|
||||
|
||||
bool Engine::isStereoSupported(StereoscopicType stereoscopicType) const noexcept {
|
||||
return downcast(this)->isStereoSupported(stereoscopicType);
|
||||
bool Engine::isStereoSupported() const noexcept {
|
||||
return downcast(this)->isStereoSupported();
|
||||
}
|
||||
|
||||
size_t Engine::getMaxStereoscopicEyes() noexcept {
|
||||
|
||||
@@ -54,19 +54,10 @@ void FrameInfoManager::terminate(DriverApi& driver) noexcept {
|
||||
void FrameInfoManager::beginFrame(DriverApi& driver,Config const& config, uint32_t) noexcept {
|
||||
driver.beginTimerQuery(mQueries[mIndex]);
|
||||
uint64_t elapsed = 0;
|
||||
TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast], &elapsed);
|
||||
switch (result) {
|
||||
case TimerQueryResult::NOT_READY:
|
||||
// nothing to do
|
||||
break;
|
||||
case TimerQueryResult::ERROR:
|
||||
mLast = (mLast + 1) % POOL_COUNT;
|
||||
break;
|
||||
case TimerQueryResult::AVAILABLE:
|
||||
mLast = (mLast + 1) % POOL_COUNT;
|
||||
// conversion to our duration happens here
|
||||
mFrameTime = std::chrono::duration<uint64_t, std::nano>(elapsed);
|
||||
break;
|
||||
if (driver.getTimerQueryValue(mQueries[mLast], &elapsed)) {
|
||||
mLast = (mLast + 1) % POOL_COUNT;
|
||||
// conversion to our duration happens here
|
||||
mFrameTime = std::chrono::duration<uint64_t, std::nano>(elapsed);
|
||||
}
|
||||
update(config, mFrameTime);
|
||||
}
|
||||
|
||||
@@ -16,16 +16,6 @@
|
||||
|
||||
#include "HwRenderPrimitiveFactory.h"
|
||||
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <private/backend/DriverApi.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace filament {
|
||||
@@ -33,60 +23,96 @@ namespace filament {
|
||||
using namespace utils;
|
||||
using namespace backend;
|
||||
|
||||
size_t HwRenderPrimitiveFactory::KeyHash::operator()(
|
||||
HwRenderPrimitiveFactory::Key const& p) const noexcept {
|
||||
return utils::hash::combine(
|
||||
p.params.vbh.getId(),
|
||||
utils::hash::combine(p.params.ibh.getId(), (size_t)p.params.type));
|
||||
bool operator<(HwRenderPrimitiveFactory::Key const& lhs,
|
||||
HwRenderPrimitiveFactory::Key const& rhs) noexcept {
|
||||
if (lhs.vbh == rhs.vbh) {
|
||||
if (lhs.ibh == rhs.ibh) {
|
||||
if (lhs.offset == rhs.offset) {
|
||||
if (lhs.count == rhs.count) {
|
||||
return lhs.type < rhs.type;
|
||||
} else {
|
||||
return lhs.count < rhs.count;
|
||||
}
|
||||
} else {
|
||||
return lhs.offset < rhs.offset;
|
||||
}
|
||||
} else {
|
||||
return lhs.ibh < rhs.ibh;
|
||||
}
|
||||
} else {
|
||||
return lhs.vbh < rhs.vbh;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(HwRenderPrimitiveFactory::Key const& lhs,
|
||||
inline bool operator<(HwRenderPrimitiveFactory::Entry const& lhs,
|
||||
HwRenderPrimitiveFactory::Entry const& rhs) noexcept {
|
||||
return lhs.key < rhs.key;
|
||||
}
|
||||
|
||||
inline bool operator<(HwRenderPrimitiveFactory::Key const& lhs,
|
||||
HwRenderPrimitiveFactory::Entry const& rhs) noexcept {
|
||||
return lhs < rhs.key;
|
||||
}
|
||||
|
||||
inline bool operator<(HwRenderPrimitiveFactory::Entry const& lhs,
|
||||
HwRenderPrimitiveFactory::Key const& rhs) noexcept {
|
||||
return lhs.params.vbh == rhs.params.vbh &&
|
||||
lhs.params.ibh == rhs.params.ibh &&
|
||||
lhs.params.type == rhs.params.type;
|
||||
return lhs.key < rhs;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
HwRenderPrimitiveFactory::HwRenderPrimitiveFactory()
|
||||
: mArena("HwRenderPrimitiveFactory::mArena", SET_ARENA_SIZE),
|
||||
mBimap(mArena) {
|
||||
mBimap.reserve(256);
|
||||
mSet(mArena) {
|
||||
mMap.reserve(256);
|
||||
}
|
||||
|
||||
HwRenderPrimitiveFactory::~HwRenderPrimitiveFactory() noexcept = default;
|
||||
|
||||
void HwRenderPrimitiveFactory::terminate(DriverApi&) noexcept {
|
||||
assert_invariant(mBimap.empty());
|
||||
void HwRenderPrimitiveFactory::terminate(DriverApi& driver) noexcept {
|
||||
assert_invariant(mMap.empty());
|
||||
assert_invariant(mSet.empty());
|
||||
}
|
||||
|
||||
auto HwRenderPrimitiveFactory::create(DriverApi& driver,
|
||||
backend::VertexBufferHandle vbh,
|
||||
backend::IndexBufferHandle ibh,
|
||||
backend::PrimitiveType type) noexcept -> Handle {
|
||||
RenderPrimitiveHandle HwRenderPrimitiveFactory::create(DriverApi& driver,
|
||||
VertexBufferHandle vbh, IndexBufferHandle ibh,
|
||||
PrimitiveType type, uint32_t offset, uint32_t minIndex, uint32_t maxIndex,
|
||||
uint32_t count) noexcept {
|
||||
|
||||
const Key key = { vbh, ibh, offset, count, type };
|
||||
|
||||
// see if we already have seen this RenderPrimitive
|
||||
Key const key{{ vbh, ibh, type }, 1 };
|
||||
auto pos = mBimap.find(key);
|
||||
auto pos = mSet.find(key);
|
||||
|
||||
// the common case is that we've never seen it (i.e.: no reuse)
|
||||
if (UTILS_LIKELY(pos == mBimap.end())) {
|
||||
auto handle = driver.createRenderPrimitive(vbh, ibh, type);
|
||||
mBimap.insert(key, { handle });
|
||||
if (UTILS_LIKELY(pos == mSet.end())) {
|
||||
// create the backend object
|
||||
auto handle = driver.createRenderPrimitive(vbh, ibh,
|
||||
type, offset, minIndex, maxIndex, count);
|
||||
// insert key/handle in our set with a refcount of 1
|
||||
// IMPORTANT: std::set<> doesn't invalidate iterators in insert/erase
|
||||
auto [ipos, _] = mSet.insert({ key, handle, 1 });
|
||||
// map the handle back to the key/payload
|
||||
mMap.insert({ handle.getId(), ipos });
|
||||
return handle;
|
||||
}
|
||||
|
||||
pos->first.pKey->refs++;
|
||||
return pos->second.handle;
|
||||
pos->refs++;
|
||||
return pos->handle;
|
||||
}
|
||||
|
||||
void HwRenderPrimitiveFactory::destroy(DriverApi& driver, Handle handle) noexcept {
|
||||
void HwRenderPrimitiveFactory::destroy(DriverApi& driver, RenderPrimitiveHandle rph) noexcept {
|
||||
// look for this handle in our map
|
||||
auto pos = mBimap.find(Value{ handle });
|
||||
if (--pos->second.pKey->refs == 0) {
|
||||
mBimap.erase(pos);
|
||||
driver.destroyRenderPrimitive(handle);
|
||||
auto pos = mMap.find(rph.getId());
|
||||
|
||||
// it must be there
|
||||
assert_invariant(pos != mMap.end());
|
||||
|
||||
// check the refcount and destroy if needed
|
||||
auto ipos = pos->second;
|
||||
if (--ipos->refs == 0) {
|
||||
mSet.erase(ipos);
|
||||
mMap.erase(pos);
|
||||
driver.destroyRenderPrimitive(rph);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,17 +17,17 @@
|
||||
#ifndef TNT_FILAMENT_HWRENDERPRIMITIVEFACTORY_H
|
||||
#define TNT_FILAMENT_HWRENDERPRIMITIVEFACTORY_H
|
||||
|
||||
#include "Bimap.h"
|
||||
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <private/backend/DriverApi.h>
|
||||
|
||||
#include <utils/Allocator.h>
|
||||
|
||||
#include <functional>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
@@ -36,7 +36,6 @@ class FEngine;
|
||||
|
||||
class HwRenderPrimitiveFactory {
|
||||
public:
|
||||
using Handle = backend::RenderPrimitiveHandle;
|
||||
|
||||
HwRenderPrimitiveFactory();
|
||||
~HwRenderPrimitiveFactory() noexcept;
|
||||
@@ -48,64 +47,70 @@ public:
|
||||
|
||||
void terminate(backend::DriverApi& driver) noexcept;
|
||||
|
||||
struct Parameters { // 20 bytes
|
||||
backend::RenderPrimitiveHandle create(backend::DriverApi& driver,
|
||||
backend::VertexBufferHandle vbh,
|
||||
backend::IndexBufferHandle ibh,
|
||||
backend::PrimitiveType type,
|
||||
uint32_t offset,
|
||||
uint32_t minIndex,
|
||||
uint32_t maxIndex,
|
||||
uint32_t count) noexcept;
|
||||
|
||||
void destroy(backend::DriverApi& driver,
|
||||
backend::RenderPrimitiveHandle rph) noexcept;
|
||||
|
||||
private:
|
||||
struct Key { // 20 bytes
|
||||
backend::VertexBufferHandle vbh; // 4
|
||||
backend::IndexBufferHandle ibh; // 4
|
||||
uint32_t offset; // 4
|
||||
uint32_t count; // 4
|
||||
backend::PrimitiveType type; // 4
|
||||
};
|
||||
|
||||
Handle create(backend::DriverApi& driver,
|
||||
backend::VertexBufferHandle vbh,
|
||||
backend::IndexBufferHandle ibh,
|
||||
backend::PrimitiveType type) noexcept;
|
||||
|
||||
void destroy(backend::DriverApi& driver, Handle handle) noexcept;
|
||||
|
||||
private:
|
||||
struct Key {
|
||||
Parameters params;
|
||||
mutable uint32_t refs; // 4 bytes
|
||||
struct Entry { // 28 bytes
|
||||
Key key; // 20
|
||||
backend::RenderPrimitiveHandle handle; // 4
|
||||
mutable uint32_t refs; // 4
|
||||
};
|
||||
|
||||
struct KeyHash {
|
||||
size_t operator()(Key const& p) const noexcept;
|
||||
};
|
||||
|
||||
friend bool operator==(Key const& lhs, Key const& rhs) noexcept;
|
||||
|
||||
|
||||
struct Value { // 4 bytes
|
||||
Handle handle;
|
||||
};
|
||||
|
||||
struct ValueHash {
|
||||
size_t operator()(Value const& p) const noexcept {
|
||||
std::hash<Handle::HandleId> const h;
|
||||
return h(p.handle.getId());
|
||||
}
|
||||
};
|
||||
|
||||
friend bool operator==(Value const& lhs, Value const& rhs) noexcept {
|
||||
return lhs.handle == rhs.handle;
|
||||
}
|
||||
|
||||
// Size of the arena used for the "set" part of the bimap
|
||||
static constexpr size_t SET_ARENA_SIZE = 4 * 1024 * 1024;
|
||||
|
||||
// Arena for the set<>, using a pool allocator inside a heap area.
|
||||
using PoolAllocatorArena = utils::Arena<
|
||||
utils::PoolAllocatorWithFallback<sizeof(Key)>,
|
||||
using Arena = utils::Arena<
|
||||
utils::PoolAllocator<64>, // this seems to work with clang and msvc
|
||||
utils::LockingPolicy::NoLock,
|
||||
#ifndef NDEBUG
|
||||
utils::TrackingPolicy::HighWatermark,
|
||||
#else
|
||||
utils::TrackingPolicy::Untracked,
|
||||
#endif
|
||||
utils::AreaPolicy::HeapArea>;
|
||||
|
||||
using Set = std::set<
|
||||
Entry,
|
||||
std::less<>,
|
||||
utils::STLAllocator<Entry, Arena>>;
|
||||
|
||||
using Map = tsl::robin_map<
|
||||
backend::RenderPrimitiveHandle::HandleId,
|
||||
Set::const_iterator>;
|
||||
|
||||
// Arena where the set memory is allocated
|
||||
PoolAllocatorArena mArena;
|
||||
Arena mArena;
|
||||
|
||||
// The special Bimap
|
||||
Bimap<Key, Value, KeyHash, ValueHash,
|
||||
utils::STLAllocator<Key, PoolAllocatorArena>> mBimap;
|
||||
// set of HwRenderPrimitive data
|
||||
Set mSet;
|
||||
|
||||
// map of RenderPrimitiveHandle to Set Entry
|
||||
Map mMap;
|
||||
|
||||
friend bool operator<(Key const& lhs, Key const& rhs) noexcept;
|
||||
friend bool operator<(Key const& lhs, Entry const& rhs) noexcept;
|
||||
friend bool operator<(Entry const& lhs, Key const& rhs) noexcept;
|
||||
friend bool operator<(Entry const& lhs, Entry const& rhs) noexcept;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "HwVertexBufferInfoFactory.h"
|
||||
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <private/backend/DriverApi.h>
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace utils;
|
||||
using namespace backend;
|
||||
|
||||
size_t HwVertexBufferInfoFactory::Parameters::hash() const noexcept {
|
||||
static_assert((sizeof(*this) % sizeof(uint32_t)) == 0);
|
||||
return utils::hash::murmur3(
|
||||
reinterpret_cast<uint32_t const*>(this), sizeof(Parameters) / sizeof(uint32_t), 0);
|
||||
}
|
||||
|
||||
bool operator==(HwVertexBufferInfoFactory::Parameters const& lhs,
|
||||
HwVertexBufferInfoFactory::Parameters const& rhs) noexcept {
|
||||
return !memcmp(&lhs, &rhs, sizeof(HwVertexBufferInfoFactory::Parameters));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
HwVertexBufferInfoFactory::HwVertexBufferInfoFactory()
|
||||
: mArena("HwVertexBufferInfoFactory::mArena", SET_ARENA_SIZE),
|
||||
mBimap(mArena) {
|
||||
mBimap.reserve(256);
|
||||
}
|
||||
|
||||
HwVertexBufferInfoFactory::~HwVertexBufferInfoFactory() noexcept = default;
|
||||
|
||||
void HwVertexBufferInfoFactory::terminate(DriverApi&) noexcept {
|
||||
assert_invariant(mBimap.empty());
|
||||
}
|
||||
|
||||
auto HwVertexBufferInfoFactory::create(DriverApi& driver,
|
||||
uint8_t bufferCount,
|
||||
uint8_t attributeCount,
|
||||
backend::AttributeArray attributes) noexcept -> Handle {
|
||||
|
||||
Key const key{{ bufferCount, attributeCount, {}, attributes }, 1 };
|
||||
auto pos = mBimap.find(key);
|
||||
|
||||
// the common case is that we've never seen it (i.e.: no reuse)
|
||||
if (UTILS_LIKELY(pos == mBimap.end())) {
|
||||
auto handle = driver.createVertexBufferInfo(
|
||||
bufferCount, attributeCount,
|
||||
attributes);
|
||||
mBimap.insert(key, { handle });
|
||||
return handle;
|
||||
}
|
||||
|
||||
++(pos->first.pKey->refs);
|
||||
return pos->second.handle;
|
||||
}
|
||||
|
||||
void HwVertexBufferInfoFactory::destroy(DriverApi& driver, Handle handle) noexcept {
|
||||
// look for this handle in our map
|
||||
auto pos = mBimap.find(Value{ handle });
|
||||
if (--(pos->second.pKey->refs) == 0) {
|
||||
mBimap.erase(pos);
|
||||
driver.destroyVertexBufferInfo(handle);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_HWVERTEXBUFFERINFOFACTORY_H
|
||||
#define TNT_FILAMENT_HWVERTEXBUFFERINFOFACTORY_H
|
||||
|
||||
#include "Bimap.h"
|
||||
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utils/Allocator.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
class FEngine;
|
||||
|
||||
class HwVertexBufferInfoFactory {
|
||||
public:
|
||||
using Handle = backend::VertexBufferInfoHandle;
|
||||
|
||||
HwVertexBufferInfoFactory();
|
||||
~HwVertexBufferInfoFactory() noexcept;
|
||||
|
||||
HwVertexBufferInfoFactory(HwVertexBufferInfoFactory const& rhs) = delete;
|
||||
HwVertexBufferInfoFactory(HwVertexBufferInfoFactory&& rhs) noexcept = delete;
|
||||
HwVertexBufferInfoFactory& operator=(HwVertexBufferInfoFactory const& rhs) = delete;
|
||||
HwVertexBufferInfoFactory& operator=(HwVertexBufferInfoFactory&& rhs) noexcept = delete;
|
||||
|
||||
void terminate(backend::DriverApi& driver) noexcept;
|
||||
|
||||
struct Parameters { // 136 bytes
|
||||
uint8_t bufferCount;
|
||||
uint8_t attributeCount;
|
||||
uint8_t padding[2] = {};
|
||||
backend::AttributeArray attributes;
|
||||
size_t hash() const noexcept;
|
||||
};
|
||||
|
||||
friend bool operator==(Parameters const& lhs, Parameters const& rhs) noexcept;
|
||||
|
||||
Handle create(backend::DriverApi& driver,
|
||||
uint8_t bufferCount,
|
||||
uint8_t attributeCount,
|
||||
backend::AttributeArray attributes) noexcept;
|
||||
|
||||
void destroy(backend::DriverApi& driver, Handle handle) noexcept;
|
||||
|
||||
private:
|
||||
struct Key { // 140 bytes
|
||||
Parameters params;
|
||||
mutable uint32_t refs; // 4 bytes
|
||||
bool operator==(Key const& rhs) const noexcept {
|
||||
return params == rhs.params;
|
||||
}
|
||||
};
|
||||
|
||||
struct KeyHasher {
|
||||
size_t operator()(Key const& p) const noexcept {
|
||||
return p.params.hash();
|
||||
}
|
||||
};
|
||||
|
||||
struct Value {
|
||||
Handle handle;
|
||||
};
|
||||
|
||||
struct ValueHasher {
|
||||
size_t operator()(Value v) const noexcept {
|
||||
std::hash<Handle::HandleId> const hasher;
|
||||
return hasher(v.handle.getId());
|
||||
}
|
||||
};
|
||||
|
||||
friend bool operator==(Value const& lhs, Value const& rhs) noexcept {
|
||||
return lhs.handle == rhs.handle;
|
||||
}
|
||||
|
||||
// Size of the arena used for the "set" part of the bimap
|
||||
static constexpr size_t SET_ARENA_SIZE = 4 * 1024 * 1024;
|
||||
|
||||
// Arena for the set<>, using a pool allocator inside a heap area.
|
||||
using PoolAllocatorArena = utils::Arena<
|
||||
utils::PoolAllocatorWithFallback<sizeof(Key)>,
|
||||
utils::LockingPolicy::NoLock,
|
||||
utils::TrackingPolicy::Untracked,
|
||||
utils::AreaPolicy::HeapArea>;
|
||||
|
||||
|
||||
// Arena where the set memory is allocated
|
||||
PoolAllocatorArena mArena;
|
||||
|
||||
// The special Bimap
|
||||
Bimap<Key, Value, KeyHasher, ValueHasher,
|
||||
utils::STLAllocator<Key, PoolAllocatorArena>> mBimap;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_FILAMENT_HWVERTEXBUFFERINFOFACTORY_H
|
||||
@@ -41,7 +41,6 @@
|
||||
#include "details/Material.h"
|
||||
#include "details/MaterialInstance.h"
|
||||
#include "details/Texture.h"
|
||||
#include "details/VertexBuffer.h"
|
||||
|
||||
#include "generated/resources/materials.h"
|
||||
|
||||
@@ -185,16 +184,15 @@ FMaterial* PostProcessManager::PostProcessMaterial::getMaterial(FEngine& engine)
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
std::pair<backend::PipelineState, backend::Viewport>
|
||||
PostProcessManager::PostProcessMaterial::getPipelineState(
|
||||
PipelineState PostProcessManager::PostProcessMaterial::getPipelineState(
|
||||
FEngine& engine, Variant::type_t variantKey) const noexcept {
|
||||
FMaterial* const material = getMaterial(engine);
|
||||
material->prepareProgram(Variant{ variantKey });
|
||||
return {{
|
||||
return {
|
||||
.program = material->getProgram(Variant{ variantKey }),
|
||||
.vertexBufferInfo = engine.getFullScreenVertexBuffer()->getVertexBufferInfoHandle(),
|
||||
.rasterState = material->getRasterState() },
|
||||
material->getDefaultInstance()->getScissor() };
|
||||
.rasterState = material->getRasterState(),
|
||||
.scissor = material->getDefaultInstance()->getScissor()
|
||||
};
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
@@ -264,7 +262,6 @@ static const PostProcessManager::MaterialInfo sMaterialListFeatureLevel0[] = {
|
||||
static const PostProcessManager::MaterialInfo sMaterialList[] = {
|
||||
{ "bilateralBlur", MATERIAL(BILATERALBLUR) },
|
||||
{ "bilateralBlurBentNormals", MATERIAL(BILATERALBLURBENTNORMALS) },
|
||||
{ "blitArray", MATERIAL(BLITARRAY) },
|
||||
{ "bloomDownsample", MATERIAL(BLOOMDOWNSAMPLE) },
|
||||
{ "bloomDownsample2x", MATERIAL(BLOOMDOWNSAMPLE2X) },
|
||||
{ "bloomDownsample9", MATERIAL(BLOOMDOWNSAMPLE9) },
|
||||
@@ -384,7 +381,7 @@ backend::Handle<backend::HwTexture> PostProcessManager::getZeroTextureArray() co
|
||||
|
||||
UTILS_NOINLINE
|
||||
void PostProcessManager::render(FrameGraphResources::RenderPassInfo const& out,
|
||||
backend::PipelineState const& pipeline, backend::Viewport const& scissor,
|
||||
backend::PipelineState const& pipeline,
|
||||
DriverApi& driver) const noexcept {
|
||||
|
||||
assert_invariant(
|
||||
@@ -395,8 +392,7 @@ void PostProcessManager::render(FrameGraphResources::RenderPassInfo const& out,
|
||||
FEngine const& engine = mEngine;
|
||||
Handle<HwRenderPrimitive> const fullScreenQuad = engine.getFullScreenRenderPrimitive();
|
||||
driver.beginRenderPass(out.target, out.params);
|
||||
driver.scissor(scissor);
|
||||
driver.draw(pipeline, fullScreenQuad, 0, 3, 1);
|
||||
driver.draw(pipeline, fullScreenQuad, 1);
|
||||
driver.endRenderPass();
|
||||
}
|
||||
|
||||
@@ -882,8 +878,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
|
||||
mi->commit(driver);
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = material.getPipelineState(mEngine);
|
||||
pipeline.first.rasterState.depthFunc = RasterState::DepthFunc::L;
|
||||
PipelineState pipeline(material.getPipelineState(mEngine));
|
||||
pipeline.rasterState.depthFunc = RasterState::DepthFunc::L;
|
||||
assert_invariant(ssao.params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH);
|
||||
render(ssao, pipeline, driver);
|
||||
});
|
||||
@@ -997,8 +993,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
|
||||
mi->commit(driver);
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = material.getPipelineState(mEngine);
|
||||
pipeline.first.rasterState.depthFunc = RasterState::DepthFunc::L;
|
||||
PipelineState pipeline(material.getPipelineState(mEngine));
|
||||
pipeline.rasterState.depthFunc = RasterState::DepthFunc::L;
|
||||
render(blurred, pipeline, driver);
|
||||
});
|
||||
|
||||
@@ -1657,7 +1653,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
mi->setParameter("coc", inOutCoc, { .filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
|
||||
mi->use(driver);
|
||||
|
||||
auto const pipeline = material.getPipelineState(mEngine, variant);
|
||||
const PipelineState pipeline(material.getPipelineState(mEngine, variant));
|
||||
|
||||
for (size_t level = 0 ; level < mipmapCount - 1u ; level++) {
|
||||
const float w = FTexture::valueForLevel(level, desc.width);
|
||||
@@ -2104,7 +2100,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
mi13->commit(driver);
|
||||
|
||||
// PipelineState for both materials should be the same
|
||||
auto const pipeline = material9.getPipelineState(mEngine);
|
||||
const PipelineState pipeline(material9.getPipelineState(mEngine));
|
||||
|
||||
for (size_t i = 1; i < inoutBloomOptions.levels; i++) {
|
||||
auto hwDstRT = resources.getRenderPassInfo(data.outRT[i]);
|
||||
@@ -2148,9 +2144,9 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST});
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = material.getPipelineState(mEngine);
|
||||
pipeline.first.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.first.rasterState.blendFunctionDstRGB = BlendFunction::ONE;
|
||||
PipelineState pipeline(material.getPipelineState(mEngine));
|
||||
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE;
|
||||
|
||||
for (size_t j = inoutBloomOptions.levels, i = j - 1; i >= 1; i--, j++) {
|
||||
auto hwDstRT = resources.getRenderPassInfo(data.outRT[i - 1]);
|
||||
@@ -2303,11 +2299,8 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver,
|
||||
const Variant::type_t variant = Variant::type_t(colorGradingConfig.translucent ?
|
||||
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE);
|
||||
|
||||
auto const pipeline = material.getPipelineState(mEngine, variant);
|
||||
|
||||
driver.nextSubpass();
|
||||
driver.scissor(pipeline.second);
|
||||
driver.draw(pipeline.first, fullScreenRenderPrimitive, 0, 3, 1);
|
||||
driver.draw(material.getPipelineState(mEngine, variant), fullScreenRenderPrimitive, 1);
|
||||
}
|
||||
|
||||
void PostProcessManager::customResolvePrepareSubpass(DriverApi& driver, CustomResolveOp op) noexcept {
|
||||
@@ -2323,12 +2316,10 @@ void PostProcessManager::customResolveSubpass(DriverApi& driver) noexcept {
|
||||
Handle<HwRenderPrimitive> const& fullScreenRenderPrimitive = engine.getFullScreenRenderPrimitive();
|
||||
auto const& material = getPostProcessMaterial("customResolveAsSubpass");
|
||||
// the UBO has been set and committed in colorGradingPrepareSubpass()
|
||||
auto const pipeline = material.getPipelineState(mEngine);
|
||||
FMaterialInstance* mi = material.getMaterialInstance(mEngine);
|
||||
mi->use(driver);
|
||||
driver.nextSubpass();
|
||||
driver.scissor(pipeline.second);
|
||||
driver.draw(pipeline.first, fullScreenRenderPrimitive, 0, 3, 1);
|
||||
driver.draw(material.getPipelineState(mEngine), fullScreenRenderPrimitive, 1);
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::customResolveUncompressPass(FrameGraph& fg,
|
||||
@@ -2771,10 +2762,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
if (colorGradingConfig.asSubpass) {
|
||||
out.params.subpassMask = 1;
|
||||
}
|
||||
auto const pipeline = material.getPipelineState(mEngine, variant);
|
||||
PipelineState const pipeline(material.getPipelineState(mEngine, variant));
|
||||
driver.beginRenderPass(out.target, out.params);
|
||||
driver.scissor(pipeline.second);
|
||||
driver.draw(pipeline.first, mEngine.getFullScreenRenderPrimitive(), 0, 3, 1);
|
||||
driver.draw(pipeline, mEngine.getFullScreenRenderPrimitive(), 1);
|
||||
if (colorGradingConfig.asSubpass) {
|
||||
colorGradingSubpass(driver, colorGradingConfig);
|
||||
}
|
||||
@@ -2848,7 +2838,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::rcas(
|
||||
const uint8_t variant = uint8_t(
|
||||
translucent ? PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE);
|
||||
|
||||
auto const pipeline = material.getPipelineState(mEngine, variant);
|
||||
PipelineState const pipeline(material.getPipelineState(mEngine, variant));
|
||||
render(out, pipeline, driver);
|
||||
});
|
||||
|
||||
@@ -2990,25 +2980,25 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
|
||||
auto out = resources.getRenderPassInfo();
|
||||
|
||||
if (UTILS_UNLIKELY(twoPassesEASU)) {
|
||||
auto pipeline0 = splitEasuMaterial->getPipelineState(mEngine);
|
||||
auto pipeline1 = easuMaterial->getPipelineState(mEngine);
|
||||
pipeline1.first.rasterState.depthFunc = backend::SamplerCompareFunc::NE;
|
||||
PipelineState pipeline0(splitEasuMaterial->getPipelineState(mEngine));
|
||||
PipelineState pipeline1(easuMaterial->getPipelineState(mEngine));
|
||||
pipeline1.rasterState.depthFunc = backend::SamplerCompareFunc::NE;
|
||||
if (translucent) {
|
||||
enableTranslucentBlending(pipeline0.first);
|
||||
enableTranslucentBlending(pipeline1.first);
|
||||
enableTranslucentBlending(pipeline0);
|
||||
enableTranslucentBlending(pipeline1);
|
||||
}
|
||||
driver.beginRenderPass(out.target, out.params);
|
||||
driver.scissor(pipeline0.second);
|
||||
driver.draw(pipeline0.first, fullScreenRenderPrimitive, 0, 3, 1);
|
||||
driver.scissor(pipeline1.second);
|
||||
driver.draw(pipeline1.first, fullScreenRenderPrimitive, 0, 3, 1);
|
||||
driver.draw(pipeline0, fullScreenRenderPrimitive, 1);
|
||||
driver.draw(pipeline1, fullScreenRenderPrimitive, 1);
|
||||
driver.endRenderPass();
|
||||
} else {
|
||||
auto pipeline = easuMaterial->getPipelineState(mEngine);
|
||||
PipelineState pipeline(easuMaterial->getPipelineState(mEngine));
|
||||
if (translucent) {
|
||||
enableTranslucentBlending(pipeline.first);
|
||||
enableTranslucentBlending(pipeline);
|
||||
}
|
||||
render(out, pipeline, driver);
|
||||
driver.beginRenderPass(out.target, out.params);
|
||||
driver.draw(pipeline, fullScreenRenderPrimitive, 1);
|
||||
driver.endRenderPass();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3077,12 +3067,12 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool tr
|
||||
mi->commit(driver);
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = material.getPipelineState(mEngine);
|
||||
PipelineState pipeline(material.getPipelineState(mEngine));
|
||||
if (translucent) {
|
||||
pipeline.first.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.first.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
|
||||
pipeline.first.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA;
|
||||
pipeline.first.rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA;
|
||||
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);
|
||||
});
|
||||
@@ -3228,8 +3218,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
|
||||
|
||||
// When generating shadow map mip levels, we want to preserve the 1 texel border.
|
||||
// (note clearing never respects the scissor in Filament)
|
||||
auto const [pipeline, _] = material.getPipelineState(mEngine);
|
||||
backend::Viewport const scissor = { 1u, 1u, dim - 2u, dim - 2u };
|
||||
PipelineState pipeline(material.getPipelineState(mEngine));
|
||||
pipeline.scissor = { 1u, 1u, dim - 2u, dim - 2u };
|
||||
|
||||
FMaterialInstance* const mi = material.getMaterialInstance(mEngine);
|
||||
mi->setParameter("color", in, {
|
||||
@@ -3240,7 +3230,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
|
||||
mi->setParameter("uvscale", 1.0f / float(dim));
|
||||
mi->commit(driver);
|
||||
mi->use(driver);
|
||||
render(out, pipeline, scissor, driver);
|
||||
render(out, pipeline, driver);
|
||||
|
||||
if (finalize) {
|
||||
driver.setMinMaxLevels(in, 0, level);
|
||||
@@ -3273,7 +3263,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugShadowCascades(FrameGra
|
||||
auto depth = resources.getTexture(data.depth);
|
||||
auto out = resources.getRenderPassInfo();
|
||||
auto& material = getPostProcessMaterial("debugShadowCascades");
|
||||
auto const pipeline = material.getPipelineState(mEngine);
|
||||
PipelineState const pipeline(material.getPipelineState(mEngine));
|
||||
FMaterialInstance* mi = material.getMaterialInstance(mEngine);
|
||||
mi->setParameter("color", color, {}); // nearest
|
||||
mi->setParameter("depth", depth, {}); // nearest
|
||||
@@ -3286,84 +3276,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugShadowCascades(FrameGra
|
||||
return debugShadowCascadePass->output;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(FrameGraph& fg,
|
||||
bool translucent, FrameGraphId<FrameGraphTexture> input,
|
||||
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
|
||||
SamplerMagFilter filterMag,
|
||||
SamplerMinFilter filterMin) noexcept {
|
||||
|
||||
assert_invariant(fg.getDescriptor(input).depth > 1);
|
||||
assert_invariant(fg.getDescriptor(input).type == SamplerType::SAMPLER_2D_ARRAY);
|
||||
|
||||
// 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>("combining array tex",
|
||||
[&](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 out = resources.getRenderPassInfo();
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
// set uniforms
|
||||
|
||||
PostProcessMaterial const& material = getPostProcessMaterial("blitArray");
|
||||
auto* mi = material.getMaterialInstance(mEngine);
|
||||
mi->setParameter("color", color, {
|
||||
.filterMag = filterMag,
|
||||
.filterMin = filterMin
|
||||
});
|
||||
mi->setParameter("layerIndex", 0);
|
||||
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);
|
||||
|
||||
auto pipeline = material.getPipelineState(mEngine);
|
||||
if (translucent) {
|
||||
pipeline.first.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
|
||||
pipeline.first.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
|
||||
pipeline.first.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA;
|
||||
pipeline.first.rasterState.blendFunctionDstAlpha = BlendFunction::ONE_MINUS_SRC_ALPHA;
|
||||
}
|
||||
|
||||
// Blit the scene rendered to the first layer to the left half of the screen.
|
||||
out.params.viewport.width /= 2;
|
||||
render(out, pipeline, driver);
|
||||
|
||||
// Blit the scene rendered to the second layer to the right half of the screen.
|
||||
// Don't clear or discard the target to keep the previously rendered left half image.
|
||||
mi->setParameter("layerIndex", 1);
|
||||
mi->commit(driver);
|
||||
out.params.flags.clear = filament::backend::TargetBufferFlags::NONE;
|
||||
out.params.flags.discardStart = filament::backend::TargetBufferFlags::NONE;
|
||||
out.params.viewport.left += out.params.viewport.width;
|
||||
render(out, pipeline, driver);
|
||||
});
|
||||
|
||||
return ppQuadBlit->output;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::debugDisplayShadowTexture(
|
||||
FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
|
||||
@@ -288,15 +288,6 @@ public:
|
||||
FrameGraphId<FrameGraphTexture> shadowmap, float scale,
|
||||
uint8_t layer, uint8_t level, uint8_t channel, float power) noexcept;
|
||||
|
||||
// Combine an array texture pointed to by `input` into a single image, then return it.
|
||||
// This is only useful to check the multiview rendered scene as a debugging purpose, thus this
|
||||
// is not expected to be used in normal cases.
|
||||
FrameGraphId<FrameGraphTexture> debugCombineArrayTexture(FrameGraph& fg, bool translucent,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
filament::Viewport const& vp, FrameGraphTexture::Descriptor const& outDesc,
|
||||
backend::SamplerMagFilter filterMag,
|
||||
backend::SamplerMinFilter filterMin) noexcept;
|
||||
|
||||
backend::Handle<backend::HwTexture> getOneTexture() const;
|
||||
backend::Handle<backend::HwTexture> getZeroTexture() const;
|
||||
backend::Handle<backend::HwTexture> getOneTextureArray() const;
|
||||
@@ -320,7 +311,7 @@ public:
|
||||
FMaterial* getMaterial(FEngine& engine) const noexcept;
|
||||
FMaterialInstance* getMaterialInstance(FEngine& engine) const noexcept;
|
||||
|
||||
std::pair<backend::PipelineState, backend::Viewport> getPipelineState(FEngine& engine,
|
||||
backend::PipelineState getPipelineState(FEngine& engine,
|
||||
Variant::type_t variantKey = 0u) const noexcept;
|
||||
|
||||
private:
|
||||
@@ -348,15 +339,9 @@ public:
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
void render(FrameGraphResources::RenderPassInfo const& out,
|
||||
backend::PipelineState const& pipeline, backend::Viewport const& scissor,
|
||||
backend::PipelineState const& pipeline,
|
||||
backend::DriverApi& driver) const noexcept;
|
||||
|
||||
void render(FrameGraphResources::RenderPassInfo const& out,
|
||||
std::pair<backend::PipelineState, backend::Viewport> const& combo,
|
||||
backend::DriverApi& driver) const noexcept {
|
||||
render(out, combo.first, combo.second, driver);
|
||||
}
|
||||
|
||||
private:
|
||||
FEngine& mEngine;
|
||||
|
||||
|
||||
+72
-122
@@ -55,7 +55,6 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace utils;
|
||||
using namespace filament::math;
|
||||
@@ -165,11 +164,6 @@ void RenderPass::appendCommands(FEngine& engine,
|
||||
// trace the number of visible renderables
|
||||
SYSTRACE_VALUE32("visibleRenderables", vr.size());
|
||||
if (UTILS_UNLIKELY(vr.empty())) {
|
||||
// no renderables, we still need the sentinel and the command buffer size should be
|
||||
// exactly 1.
|
||||
assert_invariant(commands.size() == 1);
|
||||
Command* curr = commands.data();
|
||||
curr->key = uint64_t(Pass::SENTINEL);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -214,7 +208,7 @@ void RenderPass::appendCommands(FEngine& engine,
|
||||
// This must be done from the main thread.
|
||||
for (Command const* first = curr, *last = curr + commandCount ; first != last ; ++first) {
|
||||
if (UTILS_LIKELY((first->key & CUSTOM_MASK) == uint64_t(CustomCommand::PASS))) {
|
||||
auto ma = first->primitive.primitive->getMaterialInstance()->getMaterial();
|
||||
auto ma = first->primitive.mi->getMaterial();
|
||||
ma->prepareProgram(first->primitive.materialVariant);
|
||||
}
|
||||
}
|
||||
@@ -296,7 +290,8 @@ void RenderPass::instanceify(FEngine& engine, Arena& arena) noexcept {
|
||||
[lhs = *curr](Command const& rhs) {
|
||||
// primitives must be identical to be instanced. Currently, instancing doesn't support
|
||||
// skinning/morphing.
|
||||
return lhs.primitive.primitive == rhs.primitive.primitive &&
|
||||
return lhs.primitive.mi == rhs.primitive.mi &&
|
||||
lhs.primitive.primitiveHandle == rhs.primitive.primitiveHandle &&
|
||||
lhs.primitive.rasterState == rhs.primitive.rasterState &&
|
||||
lhs.primitive.skinningHandle == rhs.primitive.skinningHandle &&
|
||||
lhs.primitive.skinningOffset == rhs.primitive.skinningOffset &&
|
||||
@@ -425,6 +420,7 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
|
||||
cmdDraw.primitive.rasterState.colorWrite = mi->isColorWriteEnabled();
|
||||
cmdDraw.primitive.rasterState.depthWrite = mi->isDepthWriteEnabled();
|
||||
cmdDraw.primitive.rasterState.depthFunc = mi->getDepthFunc();
|
||||
cmdDraw.primitive.mi = mi;
|
||||
cmdDraw.primitive.materialVariant = variant;
|
||||
// we keep "RasterState::colorWrite" to the value set by material (could be disabled)
|
||||
}
|
||||
@@ -435,7 +431,7 @@ void RenderPass::generateCommands(CommandTypeFlags commandTypeFlags, Command* co
|
||||
FScene::RenderableSoa const& soa, Range<uint32_t> range,
|
||||
Variant variant, RenderFlags renderFlags,
|
||||
FScene::VisibleMaskType visibilityMask, float3 cameraPosition, float3 cameraForward,
|
||||
uint8_t stereoEyeCount) noexcept {
|
||||
uint8_t instancedStereoEyeCount) noexcept {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
|
||||
@@ -467,12 +463,12 @@ void RenderPass::generateCommands(CommandTypeFlags commandTypeFlags, Command* co
|
||||
case CommandTypeFlags::COLOR:
|
||||
curr = generateCommandsImpl<CommandTypeFlags::COLOR>(commandTypeFlags, curr,
|
||||
soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward,
|
||||
stereoEyeCount);
|
||||
instancedStereoEyeCount);
|
||||
break;
|
||||
case CommandTypeFlags::DEPTH:
|
||||
curr = generateCommandsImpl<CommandTypeFlags::DEPTH>(commandTypeFlags, curr,
|
||||
soa, range, variant, renderFlags, visibilityMask, cameraPosition, cameraForward,
|
||||
stereoEyeCount);
|
||||
instancedStereoEyeCount);
|
||||
break;
|
||||
default:
|
||||
// we should never end-up here
|
||||
@@ -495,7 +491,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
Command* UTILS_RESTRICT curr,
|
||||
FScene::RenderableSoa const& UTILS_RESTRICT soa, Range<uint32_t> range,
|
||||
Variant const variant, RenderFlags renderFlags, FScene::VisibleMaskType visibilityMask,
|
||||
float3 cameraPosition, float3 cameraForward, uint8_t stereoEyeCount) noexcept {
|
||||
float3 cameraPosition, float3 cameraForward, uint8_t instancedStereoEyeCount) noexcept {
|
||||
|
||||
// generateCommands() writes both the draw and depth commands simultaneously such that
|
||||
// we go throw the list of renderables just once.
|
||||
@@ -521,7 +517,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
|
||||
const bool hasShadowing = renderFlags & HAS_SHADOWING;
|
||||
const bool viewInverseFrontFaces = renderFlags & HAS_INVERSE_FRONT_FACES;
|
||||
const bool hasStereo = renderFlags & IS_STEREOSCOPIC;
|
||||
const bool hasInstancedStereo = renderFlags & IS_STEREOSCOPIC;
|
||||
|
||||
Command cmdColor;
|
||||
|
||||
@@ -577,12 +573,11 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
// calculate the per-primitive face winding order inversion
|
||||
const bool inverseFrontFaces = viewInverseFrontFaces ^ soaVisibility[i].reversedWindingOrder;
|
||||
const bool hasMorphing = soaVisibility[i].morphing;
|
||||
const bool hasSkinning = soaVisibility[i].skinning;
|
||||
const bool hasSkinningOrMorphing = hasSkinning || hasMorphing;
|
||||
const bool hasSkinningOrMorphing = soaVisibility[i].skinning || hasMorphing;
|
||||
|
||||
cmdColor.key = makeField(soaVisibility[i].priority, PRIORITY_MASK, PRIORITY_SHIFT);
|
||||
cmdColor.key |= makeField(soaVisibility[i].channel, CHANNEL_MASK, CHANNEL_SHIFT);
|
||||
cmdColor.primitive.index = i;
|
||||
cmdColor.primitive.index = (uint16_t)i;
|
||||
cmdColor.primitive.instanceCount =
|
||||
soaInstanceInfo[i].count | PrimitiveInfo::USER_INSTANCE_MASK;
|
||||
cmdColor.primitive.instanceBufferHandle = soaInstanceInfo[i].handle;
|
||||
@@ -590,9 +585,9 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
// soaInstanceInfo[i].count is the number of instances the user has requested, either for
|
||||
// manual or hybrid instancing. Instanced stereo multiplies the number of instances by the
|
||||
// eye count.
|
||||
if (UTILS_UNLIKELY(hasStereo)) {
|
||||
if (UTILS_UNLIKELY(hasInstancedStereo)) {
|
||||
cmdColor.primitive.instanceCount =
|
||||
(soaInstanceInfo[i].count * stereoEyeCount) |
|
||||
(soaInstanceInfo[i].count * instancedStereoEyeCount) |
|
||||
PrimitiveInfo::USER_INSTANCE_MASK;
|
||||
}
|
||||
|
||||
@@ -603,46 +598,36 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
Variant::isSSRVariant(variant) || (soaVisibility[i].receiveShadows & hasShadowing));
|
||||
renderableVariant.setSkinning(hasSkinningOrMorphing);
|
||||
|
||||
const FRenderableManager::SkinningBindingInfo& skinning = soaSkinning[i];
|
||||
const FRenderableManager::MorphingBindingInfo& morphing = soaMorphing[i];
|
||||
|
||||
if constexpr (isDepthPass) {
|
||||
cmdDepth.key = uint64_t(Pass::DEPTH);
|
||||
cmdDepth.key |= uint64_t(CustomCommand::PASS);
|
||||
cmdDepth.key |= makeField(soaVisibility[i].priority, PRIORITY_MASK, PRIORITY_SHIFT);
|
||||
cmdDepth.key |= makeField(soaVisibility[i].channel, CHANNEL_MASK, CHANNEL_SHIFT);
|
||||
cmdDepth.key |= makeField(distanceBits >> 22u, Z_BUCKET_MASK, Z_BUCKET_SHIFT);
|
||||
cmdDepth.primitive.index = i;
|
||||
cmdDepth.primitive.index = (uint16_t)i;
|
||||
cmdDepth.primitive.instanceCount =
|
||||
soaInstanceInfo[i].count | PrimitiveInfo::USER_INSTANCE_MASK;
|
||||
cmdDepth.primitive.instanceBufferHandle = soaInstanceInfo[i].handle;
|
||||
cmdDepth.primitive.materialVariant.setSkinning(hasSkinningOrMorphing);
|
||||
cmdDepth.primitive.rasterState.inverseFrontFaces = inverseFrontFaces;
|
||||
|
||||
cmdDepth.primitive.skinningHandle = skinning.handle;
|
||||
cmdDepth.primitive.skinningOffset = skinning.offset;
|
||||
cmdDepth.primitive.skinningTexture = skinning.handleSampler;
|
||||
cmdDepth.primitive.morphWeightBuffer = morphing.handle;
|
||||
|
||||
if (UTILS_UNLIKELY(hasStereo)) {
|
||||
if (UTILS_UNLIKELY(hasInstancedStereo)) {
|
||||
cmdColor.primitive.instanceCount =
|
||||
(soaInstanceInfo[i].count * stereoEyeCount) |
|
||||
(soaInstanceInfo[i].count * instancedStereoEyeCount) |
|
||||
PrimitiveInfo::USER_INSTANCE_MASK;
|
||||
}
|
||||
}
|
||||
if constexpr (isColorPass) {
|
||||
renderableVariant.setFog(soaVisibility[i].fog && Variant::isFogVariant(variant));
|
||||
|
||||
cmdColor.primitive.skinningHandle = skinning.handle;
|
||||
cmdColor.primitive.skinningOffset = skinning.offset;
|
||||
cmdColor.primitive.skinningTexture = skinning.handleSampler;
|
||||
cmdColor.primitive.morphWeightBuffer = morphing.handle;
|
||||
}
|
||||
|
||||
const bool shadowCaster = soaVisibility[i].castShadows & hasShadowing;
|
||||
const bool writeDepthForShadowCasters = depthContainsShadowCasters & shadowCaster;
|
||||
|
||||
const Slice<FRenderPrimitive>& primitives = soaPrimitives[i];
|
||||
const FRenderableManager::SkinningBindingInfo& skinning = soaSkinning[i];
|
||||
const FRenderableManager::MorphingBindingInfo& morphing = soaMorphing[i];
|
||||
|
||||
/*
|
||||
* This is our hot loop. It's written to avoid branches.
|
||||
* When modifying this code, always ensure it stays efficient.
|
||||
@@ -653,13 +638,15 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
FMaterialInstance const* const mi = primitive.getMaterialInstance();
|
||||
FMaterial const* const ma = mi->getMaterial();
|
||||
|
||||
// TODO: we should disable the SKN variant if this primitive doesn't have either
|
||||
// skinning or morphing.
|
||||
|
||||
if constexpr (isColorPass) {
|
||||
cmdColor.primitive.primitive = &primitive;
|
||||
cmdColor.primitive.primitiveHandle = primitive.getHwHandle();
|
||||
RenderPass::setupColorCommand(cmdColor, renderableVariant, mi, inverseFrontFaces);
|
||||
|
||||
cmdColor.primitive.skinningHandle = skinning.handle;
|
||||
cmdColor.primitive.skinningOffset = skinning.offset;
|
||||
cmdColor.primitive.skinningTexture = skinning.handleSampler;
|
||||
|
||||
cmdColor.primitive.morphWeightBuffer = morphing.handle;
|
||||
cmdColor.primitive.morphTargetBuffer = morphTargets.buffer->getHwHandle();
|
||||
|
||||
const bool blendPass = Pass(cmdColor.key & PASS_MASK) == Pass::BLENDED;
|
||||
@@ -754,14 +741,18 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
const bool translucent = (blendingMode != BlendingMode::OPAQUE
|
||||
&& blendingMode != BlendingMode::MASKED);
|
||||
|
||||
// TODO: we should disable the SKN variant if this primitive doesn't have either
|
||||
// skinning or morphing.
|
||||
|
||||
cmdDepth.key |= mi->getSortingKey(); // already all set-up for direct or'ing
|
||||
|
||||
// unconditionally write the command
|
||||
cmdDepth.primitive.primitive = &primitive;
|
||||
cmdDepth.primitive.primitiveHandle = primitive.getHwHandle();
|
||||
cmdDepth.primitive.mi = mi;
|
||||
cmdDepth.primitive.rasterState.culling = mi->getCullingMode();
|
||||
|
||||
cmdDepth.primitive.skinningHandle = skinning.handle;
|
||||
cmdDepth.primitive.skinningOffset = skinning.offset;
|
||||
cmdDepth.primitive.skinningTexture = skinning.handleSampler;
|
||||
|
||||
cmdDepth.primitive.morphWeightBuffer = morphing.handle;
|
||||
cmdDepth.primitive.morphTargetBuffer = morphTargets.buffer->getHwHandle();
|
||||
|
||||
// FIXME: should writeDepthForShadowCasters take precedence over mi->getDepthWrite()?
|
||||
@@ -819,28 +810,6 @@ void RenderPass::Executor::execute(FEngine& engine, const char*) const noexcept
|
||||
execute(engine, mCommands.begin(), mCommands.end());
|
||||
}
|
||||
|
||||
UTILS_NOINLINE // no need to be inlined
|
||||
backend::Viewport RenderPass::Executor::applyScissorViewport(
|
||||
backend::Viewport const& scissorViewport, backend::Viewport const& scissor) noexcept {
|
||||
// scissor is set, we need to apply the offset/clip
|
||||
// clang vectorizes this!
|
||||
constexpr int32_t maxvali = std::numeric_limits<int32_t>::max();
|
||||
// compute new left/bottom, assume no overflow
|
||||
int32_t l = scissor.left + scissorViewport.left;
|
||||
int32_t b = scissor.bottom + scissorViewport.bottom;
|
||||
// compute right/top without overflowing, scissor.width/height guaranteed
|
||||
// to convert to int32
|
||||
int32_t r = (l > maxvali - int32_t(scissor.width)) ? maxvali : l + int32_t(scissor.width);
|
||||
int32_t t = (b > maxvali - int32_t(scissor.height)) ? maxvali : b + int32_t(scissor.height);
|
||||
// clip to the viewport
|
||||
l = std::max(l, scissorViewport.left);
|
||||
b = std::max(b, scissorViewport.bottom);
|
||||
r = std::min(r, scissorViewport.left + int32_t(scissorViewport.width));
|
||||
t = std::min(t, scissorViewport.bottom + int32_t(scissorViewport.height));
|
||||
assert_invariant(r >= l && t >= b);
|
||||
return { l, b, uint32_t(r - l), uint32_t(t - b) };
|
||||
}
|
||||
|
||||
UTILS_NOINLINE // no need to be inlined
|
||||
void RenderPass::Executor::execute(FEngine& engine,
|
||||
const Command* first, const Command* last) const noexcept {
|
||||
@@ -855,21 +824,16 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
if (first != last) {
|
||||
SYSTRACE_VALUE32("commandCount", last - first);
|
||||
|
||||
bool const scissorOverride = mScissorOverride;
|
||||
if (UTILS_UNLIKELY(scissorOverride)) {
|
||||
// initialize with scissor overide
|
||||
driver.scissor(mScissor);
|
||||
}
|
||||
|
||||
bool const polygonOffsetOverride = mPolygonOffsetOverride;
|
||||
PipelineState pipeline{
|
||||
// initialize with polygon offset override
|
||||
.polygonOffset = mPolygonOffset,
|
||||
};
|
||||
.scissor = mScissor
|
||||
}, dummyPipeline;
|
||||
|
||||
PipelineState currentPipeline{};
|
||||
Handle<HwRenderPrimitive> currentPrimitiveHandle{};
|
||||
bool rebindPipeline = true;
|
||||
auto* const pPipelinePolygonOffset =
|
||||
mPolygonOffsetOverride ? &dummyPipeline.polygonOffset : &pipeline.polygonOffset;
|
||||
|
||||
auto* const pScissor =
|
||||
mScissorOverride ? &dummyPipeline.scissor : &pipeline.scissor;
|
||||
|
||||
FMaterialInstance const* UTILS_RESTRICT mi = nullptr;
|
||||
FMaterial const* UTILS_RESTRICT ma = nullptr;
|
||||
@@ -916,42 +880,51 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
}
|
||||
|
||||
// primitiveHandle may be invalid if no geometry was set on the renderable.
|
||||
if (UTILS_UNLIKELY(!first->primitive.primitive->getHwHandle())) {
|
||||
if (UTILS_UNLIKELY(!first->primitive.primitiveHandle)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// per-renderable uniform
|
||||
PrimitiveInfo const info = first->primitive;
|
||||
const PrimitiveInfo info = first->primitive;
|
||||
pipeline.rasterState = info.rasterState;
|
||||
pipeline.vertexBufferInfo = info.primitive->getVertexBufferInfoHandle();
|
||||
pipeline.primitiveType = info.primitive->getPrimitiveType();
|
||||
assert_invariant(pipeline.vertexBufferInfo);
|
||||
|
||||
if (UTILS_UNLIKELY(mi != info.primitive->getMaterialInstance())) {
|
||||
if (UTILS_UNLIKELY(mi != info.mi)) {
|
||||
// this is always taken the first time
|
||||
mi = info.primitive->getMaterialInstance();
|
||||
mi = info.mi;
|
||||
assert_invariant(mi);
|
||||
|
||||
ma = mi->getMaterial();
|
||||
|
||||
if (UTILS_LIKELY(!scissorOverride)) {
|
||||
backend::Viewport scissor = mi->getScissor();
|
||||
if (UTILS_UNLIKELY(mi->hasScissor())) {
|
||||
scissor = applyScissorViewport(mScissorViewport, scissor);
|
||||
}
|
||||
driver.scissor(scissor);
|
||||
}
|
||||
auto const& scissor = mi->getScissor();
|
||||
if (UTILS_UNLIKELY(mi->hasScissor())) {
|
||||
// scissor is set, we need to apply the offset/clip
|
||||
// clang vectorizes this!
|
||||
constexpr int32_t maxvali = std::numeric_limits<int32_t>::max();
|
||||
const backend::Viewport scissorViewport = mScissorViewport;
|
||||
// compute new left/bottom, assume no overflow
|
||||
int32_t l = scissor.left + scissorViewport.left;
|
||||
int32_t b = scissor.bottom + scissorViewport.bottom;
|
||||
// compute right/top without overflowing, scissor.width/height guaranteed
|
||||
// to convert to int32
|
||||
int32_t r = (l > maxvali - int32_t(scissor.width)) ?
|
||||
maxvali : l + int32_t(scissor.width);
|
||||
int32_t t = (b > maxvali - int32_t(scissor.height)) ?
|
||||
maxvali : b + int32_t(scissor.height);
|
||||
// clip to the viewport
|
||||
l = std::max(l, scissorViewport.left);
|
||||
b = std::max(b, scissorViewport.bottom);
|
||||
r = std::min(r, scissorViewport.left + int32_t(scissorViewport.width));
|
||||
t = std::min(t, scissorViewport.bottom + int32_t(scissorViewport.height));
|
||||
assert_invariant(r >= l && t >= b);
|
||||
*pScissor = { l, b, uint32_t(r - l), uint32_t(t - b) };
|
||||
} else {
|
||||
// no scissor set (common case), 'scissor' has its default value, use that.
|
||||
*pScissor = scissor;
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(!polygonOffsetOverride)) {
|
||||
pipeline.polygonOffset = mi->getPolygonOffset();
|
||||
}
|
||||
*pPipelinePolygonOffset = mi->getPolygonOffset();
|
||||
pipeline.stencilState = mi->getStencilState();
|
||||
mi->use(driver);
|
||||
|
||||
// FIXME: MaterialInstance changed (not necessarily the program though),
|
||||
// however, texture bindings may have changed and currently we need to
|
||||
// rebind the pipeline when that happens.
|
||||
rebindPipeline = true;
|
||||
}
|
||||
|
||||
assert_invariant(ma);
|
||||
@@ -1001,10 +974,6 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
// note: even if only skinning is enabled, binding morphTargetBuffer is needed.
|
||||
driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING,
|
||||
info.morphTargetBuffer);
|
||||
|
||||
// FIXME: Currently we need to rebind the PipelineState when texture or
|
||||
// UBO binding change.
|
||||
rebindPipeline = true;
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(info.morphWeightBuffer)) {
|
||||
@@ -1017,28 +986,9 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
// note: even if only morphing is enabled, binding skinningTexture is needed.
|
||||
driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_SKINNING,
|
||||
info.skinningTexture);
|
||||
|
||||
// FIXME: Currently we need to rebind the PipelineState when texture or
|
||||
// UBO binding change.
|
||||
rebindPipeline = true;
|
||||
}
|
||||
|
||||
if (rebindPipeline ||
|
||||
(memcmp(&pipeline, ¤tPipeline, sizeof(PipelineState)) != 0)) {
|
||||
rebindPipeline = false;
|
||||
currentPipeline = pipeline;
|
||||
driver.bindPipeline(pipeline);
|
||||
}
|
||||
|
||||
if (info.primitive->getHwHandle() != currentPrimitiveHandle) {
|
||||
currentPrimitiveHandle = info.primitive->getHwHandle();
|
||||
driver.bindRenderPrimitive(info.primitive->getHwHandle());
|
||||
}
|
||||
|
||||
driver.draw2(
|
||||
info.primitive->getIndexOffset(),
|
||||
info.primitive->getIndexCount(),
|
||||
instanceCount);
|
||||
driver.draw(pipeline, info.primitiveHandle, instanceCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ class CommandBufferQueue;
|
||||
}
|
||||
|
||||
class FMaterialInstance;
|
||||
class FRenderPrimitive;
|
||||
class RenderPassBuilder;
|
||||
|
||||
class RenderPass {
|
||||
@@ -236,13 +235,13 @@ public:
|
||||
return boolish ? value : uint64_t(0);
|
||||
}
|
||||
|
||||
struct PrimitiveInfo { // 56 bytes
|
||||
struct PrimitiveInfo { // 48 bytes
|
||||
union {
|
||||
FRenderPrimitive const* primitive; // 8 bytes;
|
||||
uint64_t padding = {}; // ensures primitive is 8 bytes on all archs
|
||||
FMaterialInstance const* mi;
|
||||
uint64_t padding = {}; // ensures mi is 8 bytes on all archs
|
||||
}; // 8 bytes
|
||||
uint64_t rfu0; // 8 bytes
|
||||
backend::RasterState rasterState; // 4 bytes
|
||||
backend::Handle<backend::HwRenderPrimitive> primitiveHandle; // 4 bytes
|
||||
backend::Handle<backend::HwBufferObject> skinningHandle; // 4 bytes
|
||||
backend::Handle<backend::HwSamplerGroup> skinningTexture; // 4 bytes
|
||||
backend::Handle<backend::HwBufferObject> morphWeightBuffer; // 4 bytes
|
||||
@@ -252,17 +251,16 @@ public:
|
||||
uint32_t skinningOffset = 0; // 4 bytes
|
||||
uint16_t instanceCount; // 2 bytes [MSb: user]
|
||||
Variant materialVariant; // 1 byte
|
||||
uint8_t rfu1; // 1 byte
|
||||
uint32_t rfu2; // 4 byte
|
||||
|
||||
static const uint16_t USER_INSTANCE_MASK = 0x8000u;
|
||||
static const uint16_t INSTANCE_COUNT_MASK = 0x7fffu;
|
||||
};
|
||||
static_assert(sizeof(PrimitiveInfo) == 56);
|
||||
static_assert(sizeof(PrimitiveInfo) == 48);
|
||||
|
||||
struct alignas(8) Command { // 64 bytes
|
||||
CommandKey key = 0; // 8 bytes
|
||||
PrimitiveInfo primitive; // 56 bytes
|
||||
PrimitiveInfo primitive; // 48 bytes
|
||||
uint64_t reserved[1] = {}; // 8 bytes
|
||||
bool operator < (Command const& rhs) const noexcept { return key < rhs.key; }
|
||||
// placement new declared as "throw" to avoid the compiler's null-check
|
||||
inline void* operator new (size_t, void* ptr) {
|
||||
@@ -331,10 +329,6 @@ public:
|
||||
|
||||
void execute(FEngine& engine, const Command* first, const Command* last) const noexcept;
|
||||
|
||||
static backend::Viewport applyScissorViewport(
|
||||
backend::Viewport const& scissorViewport,
|
||||
backend::Viewport const& scissor) noexcept;
|
||||
|
||||
public:
|
||||
Executor() = default;
|
||||
Executor(Executor const& rhs);
|
||||
@@ -413,6 +407,7 @@ private:
|
||||
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> vr) noexcept;
|
||||
|
||||
|
||||
// Arena where all Commands are allocated. The Arena owns the commands.
|
||||
FScene::RenderableSoa const& mRenderableSoa;
|
||||
utils::Range<uint32_t> const mVisibleRenderables;
|
||||
backend::Handle<backend::HwBufferObject> const mUboHandle;
|
||||
|
||||
@@ -16,20 +16,13 @@
|
||||
|
||||
#include "RenderPrimitive.h"
|
||||
|
||||
#include <filament/RenderableManager.h>
|
||||
#include <filament/MaterialEnums.h>
|
||||
|
||||
#include "details/Engine.h"
|
||||
#include "details/IndexBuffer.h"
|
||||
#include "details/MaterialInstance.h"
|
||||
#include "details/Material.h"
|
||||
#include "details/VertexBuffer.h"
|
||||
|
||||
#include <private/backend/CommandStream.h>
|
||||
#include <backend/DriverApiForward.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
void FRenderPrimitive::init(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver,
|
||||
@@ -43,7 +36,17 @@ void FRenderPrimitive::init(HwRenderPrimitiveFactory& factory, backend::DriverAp
|
||||
if (entry.indices && entry.vertices) {
|
||||
FVertexBuffer* vertexBuffer = downcast(entry.vertices);
|
||||
FIndexBuffer* indexBuffer = downcast(entry.indices);
|
||||
set(factory, driver, entry.type, vertexBuffer, indexBuffer, entry.offset, entry.count);
|
||||
|
||||
AttributeBitset enabledAttributes = vertexBuffer->getDeclaredAttributes();
|
||||
|
||||
auto const& ebh = vertexBuffer->getHwHandle();
|
||||
auto const& ibh = indexBuffer->getHwHandle();
|
||||
|
||||
mHandle = factory.create(driver, ebh, ibh, entry.type, (uint32_t)entry.offset,
|
||||
(uint32_t)entry.minIndex, (uint32_t)entry.maxIndex, (uint32_t)entry.count);
|
||||
|
||||
mPrimitiveType = entry.type;
|
||||
mEnabledAttributes = enabledAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,23 +58,22 @@ void FRenderPrimitive::terminate(HwRenderPrimitiveFactory& factory, backend::Dri
|
||||
|
||||
void FRenderPrimitive::set(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver,
|
||||
RenderableManager::PrimitiveType type,
|
||||
FVertexBuffer* vertexBuffer, FIndexBuffer* indexBuffer,
|
||||
size_t offset, size_t count) noexcept {
|
||||
FVertexBuffer* vertices, FIndexBuffer* indices, size_t offset,
|
||||
size_t minIndex, size_t maxIndex, size_t count) noexcept {
|
||||
|
||||
AttributeBitset enabledAttributes = vertices->getDeclaredAttributes();
|
||||
|
||||
auto const& ebh = vertices->getHwHandle();
|
||||
auto const& ibh = indices->getHwHandle();
|
||||
|
||||
if (mHandle) {
|
||||
factory.destroy(driver, mHandle);
|
||||
}
|
||||
|
||||
AttributeBitset const enabledAttributes = vertexBuffer->getDeclaredAttributes();
|
||||
|
||||
auto const& ebh = vertexBuffer->getHwHandle();
|
||||
auto const& ibh = indexBuffer->getHwHandle();
|
||||
|
||||
mHandle = factory.create(driver, ebh, ibh, type);
|
||||
mVertexBufferInfoHandle = vertexBuffer->getVertexBufferInfoHandle();
|
||||
mHandle = factory.create(driver, ebh, ibh, type,
|
||||
(uint32_t)offset, (uint32_t)minIndex, (uint32_t)maxIndex, (uint32_t)count);
|
||||
|
||||
mPrimitiveType = type;
|
||||
mIndexOffset = offset;
|
||||
mIndexCount = count;
|
||||
mEnabledAttributes = enabledAttributes;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,16 +17,13 @@
|
||||
#ifndef TNT_FILAMENT_DETAILS_RENDERPRIMITIVE_H
|
||||
#define TNT_FILAMENT_DETAILS_RENDERPRIMITIVE_H
|
||||
|
||||
#include <filament/RenderableManager.h>
|
||||
|
||||
#include "components/RenderableManager.h"
|
||||
|
||||
#include "details/MaterialInstance.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <utils/compiler.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -45,18 +42,14 @@ public:
|
||||
|
||||
void set(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver,
|
||||
RenderableManager::PrimitiveType type,
|
||||
FVertexBuffer* vertexBuffer, FIndexBuffer* indexBuffer, size_t offset,
|
||||
size_t count) noexcept;
|
||||
FVertexBuffer* vertices, FIndexBuffer* indices, size_t offset,
|
||||
size_t minIndex, size_t maxIndex, size_t count) noexcept;
|
||||
|
||||
// frees driver resources, object becomes invalid
|
||||
void terminate(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver);
|
||||
|
||||
const FMaterialInstance* getMaterialInstance() const noexcept { return mMaterialInstance; }
|
||||
backend::RenderPrimitiveHandle getHwHandle() const noexcept { return mHandle; }
|
||||
backend::VertexBufferInfoHandle getVertexBufferInfoHandle() const { return mVertexBufferInfoHandle; }
|
||||
uint32_t getIndexOffset() const noexcept { return mIndexOffset; }
|
||||
uint32_t getIndexCount() const noexcept { return mIndexCount; }
|
||||
|
||||
backend::Handle<backend::HwRenderPrimitive> getHwHandle() const noexcept { return mHandle; }
|
||||
backend::PrimitiveType getPrimitiveType() const noexcept { return mPrimitiveType; }
|
||||
AttributeBitset getEnabledAttributes() const noexcept { return mEnabledAttributes; }
|
||||
uint16_t getBlendOrder() const noexcept { return mBlendOrder; }
|
||||
@@ -73,19 +66,13 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
// These first fields are dereferences from PrimitiveInfo, keep them together
|
||||
struct {
|
||||
FMaterialInstance const* mMaterialInstance = nullptr;
|
||||
backend::Handle<backend::HwRenderPrimitive> mHandle = {};
|
||||
backend::Handle<backend::HwVertexBufferInfo> mVertexBufferInfoHandle = {};
|
||||
uint32_t mIndexOffset = 0;
|
||||
uint32_t mIndexCount = 0;
|
||||
};
|
||||
|
||||
FMaterialInstance const* mMaterialInstance = nullptr;
|
||||
backend::Handle<backend::HwRenderPrimitive> mHandle = {};
|
||||
AttributeBitset mEnabledAttributes = {};
|
||||
uint16_t mBlendOrder = 0;
|
||||
bool mGlobalBlendOrderEnabled = false;
|
||||
backend::PrimitiveType mPrimitiveType = backend::PrimitiveType::TRIANGLES;
|
||||
UTILS_UNUSED uint8_t reserved[4] = {};
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user