Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbd4177dd0 | ||
|
|
5d5f53e6e3 | ||
|
|
ec44c4a157 | ||
|
|
74751a0971 | ||
|
|
28069e43dc | ||
|
|
3c46788e06 | ||
|
|
51d749f451 | ||
|
|
343be60eb3 | ||
|
|
3603202cc5 | ||
|
|
a8596ae9c9 | ||
|
|
783c35e85b | ||
|
|
3fb9521c10 | ||
|
|
278e706d20 | ||
|
|
cf91e42847 | ||
|
|
17f32d198a | ||
|
|
11ecaa2fbf | ||
|
|
3ec2249b2a | ||
|
|
a52ae3a7ef | ||
|
|
b4b702b977 | ||
|
|
75158847f7 | ||
|
|
ddf1d422bc | ||
|
|
bbb2e1a454 | ||
|
|
d56f769d4d | ||
|
|
a46ca78f41 | ||
|
|
c7202c575a | ||
|
|
8cfdab0c28 | ||
|
|
180c326bb7 | ||
|
|
7d80975c3c | ||
|
|
7ba437b2c6 | ||
|
|
b4c33d2ab2 | ||
|
|
813e6f805b | ||
|
|
450644ccd5 | ||
|
|
979421c019 | ||
|
|
455025349d | ||
|
|
3fa4aab02a | ||
|
|
18ccf0cd8d | ||
|
|
5485ef238f | ||
|
|
5b80407f6c | ||
|
|
a9f3971989 | ||
|
|
ab12984912 | ||
|
|
a5541de84d | ||
|
|
1d4f1fe71f | ||
|
|
c93aa4c90d | ||
|
|
499939ed3c | ||
|
|
6be97ee01d | ||
|
|
7267696cbf | ||
|
|
85eb724a90 | ||
|
|
6dd6db89d5 | ||
|
|
c76f67c139 | ||
|
|
c2e3a97705 | ||
|
|
6fc16bdcda | ||
|
|
4f021583f1 | ||
|
|
ef15a29c0c | ||
|
|
a0472d3c9f | ||
|
|
54a800a25d | ||
|
|
7f8fbe586c | ||
|
|
6f2c45c76d | ||
|
|
ad60008b6a | ||
|
|
d87f9b621b | ||
|
|
1b38cda0d5 | ||
|
|
a1dea7b1fa | ||
|
|
744708b5ca | ||
|
|
e901837317 |
61
BUILDING.md
61
BUILDING.md
@@ -73,7 +73,6 @@ The following CMake options are boolean options specific to Filament:
|
||||
- `FILAMENT_SUPPORTS_VULKAN`: Include the Vulkan backend
|
||||
- `FILAMENT_INSTALL_BACKEND_TEST`: Install the backend test library so it can be consumed on iOS
|
||||
- `FILAMENT_USE_EXTERNAL_GLES3`: Experimental: Compile Filament against OpenGL ES 3
|
||||
- `FILAMENT_USE_SWIFTSHADER`: Compile Filament against SwiftShader
|
||||
- `FILAMENT_SKIP_SAMPLES`: Don't build sample apps
|
||||
|
||||
To turn an option on or off:
|
||||
@@ -426,7 +425,7 @@ value is the desired roughness between 0 and 1.
|
||||
|
||||
## Generating C++ documentation
|
||||
|
||||
To generate the documentation you must first install `doxygen` and `graphviz`, then run the
|
||||
To generate the documentation you must first install `doxygen` and `graphviz`, then run the
|
||||
following commands:
|
||||
|
||||
```shell
|
||||
@@ -436,32 +435,62 @@ doxygen docs/doxygen/filament.doxygen
|
||||
|
||||
Finally simply open `docs/html/index.html` in your web browser.
|
||||
|
||||
## SwiftShader
|
||||
## Software Rasterization
|
||||
|
||||
To try out Filament's Vulkan support with SwiftShader, first build SwiftShader and set the
|
||||
`SWIFTSHADER_LD_LIBRARY_PATH` variable to the folder that contains `libvk_swiftshader.dylib`:
|
||||
We have tested swiftshader and Mesa for software rasterization on the Vulkan/GL backends.
|
||||
|
||||
To use this for Vulkan, please first make sure that the [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) is
|
||||
installed on your machine. If you are doing a manual installation of the SDK on Linux, you will have
|
||||
to source `setup-env.sh` in the SDK's root folder to make sure the Vulkan loader is the first lib loaded.
|
||||
|
||||
### Swiftshader (Vulkan) [tested on macOS and Linux]
|
||||
|
||||
First, build SwiftShader
|
||||
|
||||
```shell
|
||||
git clone https://github.com/google/swiftshader.git
|
||||
cd swiftshader/build
|
||||
cmake .. && make -j
|
||||
export SWIFTSHADER_LD_LIBRARY_PATH=`pwd`
|
||||
```
|
||||
|
||||
Next, go to your Filament repo and use the [easy build](#easy-build) script with `-t`.
|
||||
and then set `VK_ICD_FILENAMES` to the ICD json produced in the build. For example,
|
||||
```shell
|
||||
export VK_ICD_FILENAMES=/Users/user/swiftshader/build/Darwin/vk_swiftshader_icd.json
|
||||
```
|
||||
|
||||
## SwiftShader for CI
|
||||
Build and run Filament as usual and specify the Vulkan backend when creating the Engine.
|
||||
|
||||
Continuous testing turnaround can be quite slow if you need to build SwiftShader from scratch, so we
|
||||
provide an Ubuntu-based Docker image that has it already built. The Docker image also includes
|
||||
everything necessary for building Filament. You can fetch and run the image as follows:
|
||||
### Mesa's LLVMPipe (GL) and Lavapipe (Vulkan) [tested on Linux]
|
||||
|
||||
We will only cover steps that build Mesa from source. The official documentation of Mesa mentioned
|
||||
that in general precompiled libraries [are **not** made available](https://docs.mesa3d.org/precompiled.html).
|
||||
|
||||
Download the repo and make sure you have the build depedencies. For example (assuming an Ubuntu/Debian distro),
|
||||
```shell
|
||||
git clone https://gitlab.freedesktop.org/mesa/mesa.git
|
||||
sudo apt-get build-dep mesa
|
||||
```
|
||||
|
||||
To build both the GL and Vulkan rasterizers,
|
||||
|
||||
```shell
|
||||
docker pull ghcr.io/filament-assets/swiftshader
|
||||
docker run -it ghcr.io/filament-assets/swiftshader
|
||||
cd mesa
|
||||
mkdir -p out
|
||||
meson setup builddir/ -Dprefix=$(pwd)/out -Dglx=xlib -Dgallium-drivers=swrast -Dvulkan-drivers=swrast
|
||||
meson install -C builddir/
|
||||
```
|
||||
|
||||
To do more with the container, see the helper script at `build/swiftshader/test.sh`.
|
||||
For GL, we need to ensure that we load the GL lib from the mesa output directory. For example, to run
|
||||
the debug `gltf_viewer`, we would execute
|
||||
```shell
|
||||
LD_LIBRARY_PATH=/Users/user/mesa/out/lib/x86_64-linux-gnu \
|
||||
./out/cmake-debug/samples/gltf_viewer -a opengl
|
||||
```
|
||||
|
||||
If you are a team member, you can update the public image to the latest SwiftShader by
|
||||
following the instructions at the top of `build/swiftshader/Dockerfile`.
|
||||
For Vulkan, we need to set the path to the ICD json, which tells the loader where to find the driver
|
||||
library. To run `gltf_viewer`, we would execute
|
||||
```shell
|
||||
VK_ICD_FILENAMES=/Users/user/mesa/out/share/vulkan/icd.d/lvp_icd.x86_64.json \
|
||||
./out/cmake-debug/samples/gltf_viewer -a vulkan
|
||||
|
||||
```
|
||||
|
||||
@@ -21,8 +21,6 @@ project(TNT)
|
||||
# ==================================================================================================
|
||||
option(FILAMENT_USE_EXTERNAL_GLES3 "Experimental: Compile Filament against OpenGL ES 3" OFF)
|
||||
|
||||
option(FILAMENT_USE_SWIFTSHADER "Compile Filament against SwiftShader" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_LTO "Enable link-time optimizations if supported by the compiler" OFF)
|
||||
|
||||
option(FILAMENT_SKIP_SAMPLES "Don't build samples" OFF)
|
||||
@@ -145,11 +143,6 @@ if (LINUX)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_XCB)
|
||||
endif()
|
||||
|
||||
# Default Swiftshader build does not enable the xlib extension
|
||||
if (FILAMENT_SUPPORTS_XLIB AND FILAMENT_USE_SWIFTSHADER)
|
||||
set(FILAMENT_SUPPORTS_XLIB OFF)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_XLIB)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_XLIB)
|
||||
endif()
|
||||
@@ -327,10 +320,6 @@ if (FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
set(EGL TRUE)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_USE_SWIFTSHADER)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DFILAMENT_USE_SWIFTSHADER")
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_USE_MATH_DEFINES=1")
|
||||
endif()
|
||||
@@ -451,8 +440,13 @@ endif()
|
||||
if (NOT WEBGL)
|
||||
set(GC_SECTIONS "-Wl,--gc-sections")
|
||||
endif()
|
||||
|
||||
set(B_SYMBOLIC_FUNCTIONS "-Wl,-Bsymbolic-functions")
|
||||
|
||||
if (ANDROID)
|
||||
set(BINARY_ALIGNMENT "-Wl,-z,max-page-size=16384")
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
set(GC_SECTIONS "-Wl,-dead_strip")
|
||||
set(B_SYMBOLIC_FUNCTIONS "")
|
||||
@@ -466,7 +460,7 @@ if (APPLE)
|
||||
endif()
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GC_SECTIONS}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GC_SECTIONS} ${B_SYMBOLIC_FUNCTIONS}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GC_SECTIONS} ${B_SYMBOLIC_FUNCTIONS} ${BINARY_ALIGNMENT}")
|
||||
|
||||
if (WEBGL_PTHREADS)
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pthread")
|
||||
@@ -530,13 +524,15 @@ else()
|
||||
endif()
|
||||
|
||||
# This only affects the prebuilt shader files in gltfio and samples, not filament library.
|
||||
# The value can be either "instanced" or "multiview".
|
||||
set(FILAMENT_SAMPLES_STEREO_TYPE "instanced" CACHE STRING
|
||||
# The value can be either "instanced", "multiview", or "none"
|
||||
set(FILAMENT_SAMPLES_STEREO_TYPE "none" CACHE STRING
|
||||
"Stereoscopic type that shader files in gltfio and samples are built for."
|
||||
)
|
||||
string(TOLOWER "${FILAMENT_SAMPLES_STEREO_TYPE}" FILAMENT_SAMPLES_STEREO_TYPE)
|
||||
if (NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced" AND NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview")
|
||||
message(FATAL_ERROR "Invalid stereo type: \"${FILAMENT_SAMPLES_STEREO_TYPE}\" choose either \"instanced\" or \"multiview\" ")
|
||||
if (NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced"
|
||||
AND NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview"
|
||||
AND NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "none")
|
||||
message(FATAL_ERROR "Invalid stereo type: \"${FILAMENT_SAMPLES_STEREO_TYPE}\" choose either \"instanced\", \"multiview\", or \"none\" ")
|
||||
endif ()
|
||||
|
||||
# Compiling samples for multiview implies enabling multiview feature as well.
|
||||
@@ -678,21 +674,6 @@ else()
|
||||
set(IMPORT_EXECUTABLES ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-${CMAKE_BUILD_TYPE}.cmake)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Try to find Vulkan if the SDK is installed, otherwise fall back to the bundled version.
|
||||
# This needs to stay in our top-level CMakeLists because it sets up variables that are used by the
|
||||
# "bluevk" and "samples" targets.
|
||||
# ==================================================================================================
|
||||
|
||||
if (FILAMENT_USE_SWIFTSHADER)
|
||||
if (NOT FILAMENT_SUPPORTS_VULKAN)
|
||||
message(ERROR "SwiftShader is only useful when Vulkan is enabled.")
|
||||
endif()
|
||||
find_library(SWIFTSHADER_VK NAMES vk_swiftshader HINTS "$ENV{SWIFTSHADER_LD_LIBRARY_PATH}")
|
||||
message(STATUS "Found SwiftShader VK library in: ${SWIFTSHADER_VK}.")
|
||||
add_definitions(-DFILAMENT_VKLIBRARY_PATH=\"${SWIFTSHADER_VK}\")
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Common Functions
|
||||
# ==================================================================================================
|
||||
@@ -754,7 +735,6 @@ add_subdirectory(${FILAMENT}/filament)
|
||||
add_subdirectory(${FILAMENT}/shaders)
|
||||
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
||||
add_subdirectory(${EXTERNAL}/civetweb/tnt)
|
||||
add_subdirectory(${EXTERNAL}/hat-trie/tnt)
|
||||
add_subdirectory(${EXTERNAL}/imgui/tnt)
|
||||
add_subdirectory(${EXTERNAL}/robin-map/tnt)
|
||||
add_subdirectory(${EXTERNAL}/smol-v/tnt)
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.51.8'
|
||||
implementation 'com.google.android.filament:filament-android:1.52.3'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.51.8'
|
||||
pod 'Filament', '~> 1.52.3'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -7,6 +7,19 @@ 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.52.3
|
||||
|
||||
|
||||
## v1.52.2
|
||||
|
||||
|
||||
## v1.52.1
|
||||
|
||||
- Add instructions for using Mesa for software rasterization
|
||||
|
||||
## v1.51.9
|
||||
|
||||
|
||||
## v1.51.8
|
||||
|
||||
- filagui: Fix regression which broke WebGL
|
||||
|
||||
@@ -38,6 +38,7 @@ set(FILAMAT_INCLUDE_DIRS
|
||||
include_directories(${FILAMENT_DIR}/include)
|
||||
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_SOURCE_DIR}/libfilamat-jni.map")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
|
||||
add_library(filamat-jni SHARED src/main/cpp/MaterialBuilder.cpp)
|
||||
target_include_directories(filamat-jni PRIVATE ${FILAMAT_INCLUDE_DIRS})
|
||||
|
||||
@@ -59,6 +59,7 @@ endif()
|
||||
|
||||
set(VERSION_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/libfilament-jni.map")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${VERSION_SCRIPT}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
|
||||
add_library(filament-jni SHARED
|
||||
src/main/cpp/BufferObject.cpp
|
||||
|
||||
@@ -518,6 +518,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
jint stereoscopicType, jlong stereoscopicEyeCount,
|
||||
jlong resourceAllocatorCacheSizeMB, jlong resourceAllocatorCacheMaxAge,
|
||||
jboolean disableHandleUseAfterFreeCheck,
|
||||
jint preferredShaderLanguage,
|
||||
jboolean forceGLES2Context) {
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
Engine::Config config = {
|
||||
@@ -534,7 +535,8 @@ extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_Engine_nSetBu
|
||||
.resourceAllocatorCacheSizeMB = (uint32_t) resourceAllocatorCacheSizeMB,
|
||||
.resourceAllocatorCacheMaxAge = (uint8_t) resourceAllocatorCacheMaxAge,
|
||||
.disableHandleUseAfterFreeCheck = (bool) disableHandleUseAfterFreeCheck,
|
||||
.forceGLES2Context = (bool) forceGLES2Context
|
||||
.preferredShaderLanguage = (Engine::Config::ShaderLanguage) preferredShaderLanguage,
|
||||
.forceGLES2Context = (bool) forceGLES2Context,
|
||||
};
|
||||
builder->config(&config);
|
||||
}
|
||||
|
||||
@@ -244,13 +244,25 @@ Java_com_google_android_filament_RenderableManager_nBuilderMorphing(JNIEnv*, jcl
|
||||
builder->morphing(targetCount);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nBuilderMorphingStandard(JNIEnv*, jclass,
|
||||
jlong nativeBuilder, jlong nativeMorphTargetBuffer) {
|
||||
RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder;
|
||||
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
|
||||
builder->morphing(morphTargetBuffer);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferAt(JNIEnv*, jclass,
|
||||
jlong nativeBuilder, int level, int primitiveIndex, jlong nativeMorphTargetBuffer,
|
||||
int offset, int count) {
|
||||
RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder;
|
||||
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
|
||||
builder->morphing(level, primitiveIndex, morphTargetBuffer, offset, count);
|
||||
if (nativeMorphTargetBuffer) {
|
||||
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
|
||||
builder->morphing(level, primitiveIndex, morphTargetBuffer, offset, count);
|
||||
} else {
|
||||
builder->morphing(level, primitiveIndex, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
@@ -326,9 +338,14 @@ Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferAt(JNIEn
|
||||
jclass, jlong nativeRenderableManager, jint i, int level, jint primitiveIndex,
|
||||
jlong nativeMorphTargetBuffer, jint offset, jint count) {
|
||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
||||
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
|
||||
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
|
||||
(size_t) primitiveIndex, morphTargetBuffer, (size_t) offset, (size_t) count);
|
||||
if (nativeMorphTargetBuffer) {
|
||||
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
|
||||
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
|
||||
(size_t) primitiveIndex, morphTargetBuffer, (size_t) offset, (size_t) count);
|
||||
} else {
|
||||
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
|
||||
(size_t) primitiveIndex, (size_t) offset, (size_t) count);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
|
||||
@@ -159,9 +159,12 @@ public class Engine {
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of technique for stereoscopic rendering
|
||||
* The type of technique for stereoscopic rendering. (Note that the materials used will need to be
|
||||
* compatible with the chosen technique.)
|
||||
*/
|
||||
public enum StereoscopicType {
|
||||
/** No stereoscopic rendering. */
|
||||
NONE,
|
||||
/** Stereoscopic rendering is performed using instanced rendering technique. */
|
||||
INSTANCED,
|
||||
/** Stereoscopic rendering is performed using the multiview feature from the graphics backend. */
|
||||
@@ -226,6 +229,7 @@ public class Engine {
|
||||
config.stereoscopicType.ordinal(), config.stereoscopicEyeCount,
|
||||
config.resourceAllocatorCacheSizeMB, config.resourceAllocatorCacheMaxAge,
|
||||
config.disableHandleUseAfterFreeCheck,
|
||||
config.preferredShaderLanguage.ordinal(),
|
||||
config.forceGLES2Context);
|
||||
return this;
|
||||
}
|
||||
@@ -404,7 +408,7 @@ public class Engine {
|
||||
*
|
||||
* @see View#setStereoscopicOptions
|
||||
*/
|
||||
public StereoscopicType stereoscopicType = StereoscopicType.INSTANCED;
|
||||
public StereoscopicType stereoscopicType = StereoscopicType.NONE;
|
||||
|
||||
/**
|
||||
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
|
||||
@@ -430,6 +434,28 @@ public class Engine {
|
||||
*/
|
||||
public boolean disableHandleUseAfterFreeCheck = false;
|
||||
|
||||
/*
|
||||
* Sets a preferred shader language for Filament to use.
|
||||
*
|
||||
* The Metal backend supports two shader languages: MSL (Metal Shading Language) and
|
||||
* METAL_LIBRARY (precompiled .metallib). This option controls which shader language is
|
||||
* used when materials contain both.
|
||||
*
|
||||
* By default, when preferredShaderLanguage is unset, Filament will prefer METAL_LIBRARY
|
||||
* shaders if present within a material, falling back to MSL. Setting
|
||||
* preferredShaderLanguage to ShaderLanguage::MSL will instead instruct Filament to check
|
||||
* for the presence of MSL in a material first, falling back to METAL_LIBRARY if MSL is not
|
||||
* present.
|
||||
*
|
||||
* When using a non-Metal backend, setting this has no effect.
|
||||
*/
|
||||
public enum ShaderLanguage {
|
||||
DEFAULT,
|
||||
MSL,
|
||||
METAL_LIBRARY,
|
||||
};
|
||||
public ShaderLanguage preferredShaderLanguage = ShaderLanguage.DEFAULT;
|
||||
|
||||
/*
|
||||
* When the OpenGL ES backend is used, setting this value to true will force a GLES2.0
|
||||
* context if supported by the Platform, or if not, will have the backend pretend
|
||||
@@ -1362,6 +1388,7 @@ public class Engine {
|
||||
int stereoscopicType, long stereoscopicEyeCount,
|
||||
long resourceAllocatorCacheSizeMB, long resourceAllocatorCacheMaxAge,
|
||||
boolean disableHandleUseAfterFreeCheck,
|
||||
int preferredShaderLanguage,
|
||||
boolean forceGLES2Context);
|
||||
private static native void nSetBuilderFeatureLevel(long nativeBuilder, int ordinal);
|
||||
private static native void nSetBuilderSharedContext(long nativeBuilder, long sharedContext);
|
||||
|
||||
@@ -524,14 +524,7 @@ public class RenderableManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls if the renderable has vertex morphing targets, zero by default. This is
|
||||
* required to enable GPU morphing.
|
||||
*
|
||||
* <p>Filament supports two morphing modes: standard (default) and legacy.</p>
|
||||
*
|
||||
* <p>For standard morphing, A {@link MorphTargetBuffer} must be created and provided via
|
||||
* {@link RenderableManager#setMorphTargetBufferAt}. Standard morphing supports up to
|
||||
* <code>CONFIG_MAX_MORPH_TARGET_COUNT</code> morph targets.</p>
|
||||
* Controls if the renderable has legacy vertex morphing targets, zero by default.
|
||||
*
|
||||
* For legacy morphing, the attached {@link VertexBuffer} must provide data in the
|
||||
* appropriate {@link VertexBuffer.VertexAttribute} slots (<code>MORPH_POSITION_0</code> etc).
|
||||
@@ -549,6 +542,22 @@ public class RenderableManager {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls if the renderable has vertex morphing targets, zero by default.
|
||||
*
|
||||
* <p>For standard morphing, A {@link MorphTargetBuffer} must be provided.
|
||||
* Standard morphing supports up to
|
||||
* <code>CONFIG_MAX_MORPH_TARGET_COUNT</code> morph targets.</p>
|
||||
*
|
||||
* <p>See also {@link RenderableManager#setMorphWeights}, which can be called on a per-frame basis
|
||||
* to advance the animation.</p>
|
||||
*/
|
||||
@NonNull
|
||||
public Builder morphing(@NonNull MorphTargetBuffer morphTargetBuffer) {
|
||||
nBuilderMorphingStandard(mNativeBuilder, morphTargetBuffer.getNativeObject());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the morph target buffer for a primitive.
|
||||
*
|
||||
@@ -565,6 +574,17 @@ public class RenderableManager {
|
||||
* @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3)
|
||||
*/
|
||||
@NonNull
|
||||
public Builder morphing(@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@IntRange(from = 0) int offset,
|
||||
@IntRange(from = 0) int count) {
|
||||
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex, 0, offset, count);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Builder morphing(@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@NonNull MorphTargetBuffer morphTargetBuffer,
|
||||
@@ -575,10 +595,8 @@ public class RenderableManager {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to specify morph target buffer for a primitive.
|
||||
* For details, see the {@link RenderableManager.Builder#morphing}.
|
||||
*/
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Builder morphing(@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@@ -687,6 +705,16 @@ public class RenderableManager {
|
||||
*
|
||||
* @see Builder#morphing
|
||||
*/
|
||||
public void setMorphTargetBufferAt(@EntityInstance int i,
|
||||
@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@IntRange(from = 0) int offset,
|
||||
@IntRange(from = 0) int count) {
|
||||
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex, 0, offset, count);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
public void setMorphTargetBufferAt(@EntityInstance int i,
|
||||
@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@@ -697,10 +725,8 @@ public class RenderableManager {
|
||||
morphTargetBuffer.getNativeObject(), offset, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to change morph target buffer for the given primitive.
|
||||
* For details, see the {@link RenderableManager#setMorphTargetBufferAt}.
|
||||
*/
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
public void setMorphTargetBufferAt(@EntityInstance int i,
|
||||
@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@@ -1006,6 +1032,7 @@ public class RenderableManager {
|
||||
private static native int nBuilderSkinningBones(long nativeBuilder, int boneCount, Buffer bones, int remaining);
|
||||
private static native void nBuilderSkinningBuffer(long nativeBuilder, long nativeSkinningBuffer, int boneCount, int offset);
|
||||
private static native void nBuilderMorphing(long nativeBuilder, int targetCount);
|
||||
private static native void nBuilderMorphingStandard(long nativeBuilder, long nativeMorphTargetBuffer);
|
||||
private static native void nBuilderSetMorphTargetBufferAt(long nativeBuilder, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count);
|
||||
private static native void nBuilderEnableSkinningBuffers(long nativeBuilder, boolean enabled);
|
||||
private static native void nBuilderFog(long nativeBuilder, boolean enabled);
|
||||
|
||||
@@ -31,6 +31,7 @@ set_target_properties(iblprefilter PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilament-iblprefilter.a)
|
||||
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libfilament-utils-jni.map")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
|
||||
add_library(filament-utils-jni SHARED
|
||||
src/main/cpp/AutomationEngine.cpp
|
||||
|
||||
@@ -44,6 +44,7 @@ set_target_properties(uberarchive PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libuberarchive.a)
|
||||
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.map")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
|
||||
|
||||
set(GLTFIO_SRCS
|
||||
${GLTFIO_DIR}/include/gltfio/Animator.h
|
||||
@@ -119,7 +120,6 @@ set(GLTFIO_INCLUDE_DIRS
|
||||
../../third_party/cgltf
|
||||
../../third_party/meshoptimizer/src
|
||||
../../third_party/robin-map
|
||||
../../third_party/hat-trie
|
||||
../../third_party/stb
|
||||
../../libs/utils/include
|
||||
../../libs/ktxreader/include
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.51.8
|
||||
VERSION_NAME=1.52.3
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
34
build.sh
34
build.sh
@@ -44,8 +44,6 @@ function print_help {
|
||||
echo " Exclude Vulkan support from the Android build."
|
||||
echo " -s"
|
||||
echo " Add iOS simulator support to the iOS build."
|
||||
echo " -t"
|
||||
echo " Enable SwiftShader support for Vulkan in desktop builds."
|
||||
echo " -e"
|
||||
echo " Enable EGL on Linux support for desktop builds."
|
||||
echo " -l"
|
||||
@@ -66,6 +64,9 @@ function print_help {
|
||||
echo " enabling debug paths in the backend from the build script. For example, make a"
|
||||
echo " systrace-enabled build without directly changing #defines. Remember to add -f when"
|
||||
echo " changing this option."
|
||||
echo " -S type"
|
||||
echo " Enable stereoscopic rendering where type is one of [instanced|multiview]. This is only"
|
||||
echo " meant for building the samples."
|
||||
echo ""
|
||||
echo "Build types:"
|
||||
echo " release"
|
||||
@@ -165,8 +166,6 @@ INSTALL_COMMAND=
|
||||
VULKAN_ANDROID_OPTION="-DFILAMENT_SUPPORTS_VULKAN=ON"
|
||||
VULKAN_ANDROID_GRADLE_OPTION=""
|
||||
|
||||
SWIFTSHADER_OPTION="-DFILAMENT_USE_SWIFTSHADER=OFF"
|
||||
|
||||
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=OFF"
|
||||
|
||||
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=OFF"
|
||||
@@ -179,6 +178,8 @@ ASAN_UBSAN_OPTION=""
|
||||
|
||||
BACKEND_DEBUG_FLAG_OPTION=""
|
||||
|
||||
STEREOSCOPIC_OPTION=""
|
||||
|
||||
IOS_BUILD_SIMULATOR=false
|
||||
BUILD_UNIVERSAL_LIBRARIES=false
|
||||
|
||||
@@ -233,12 +234,12 @@ function build_desktop_target {
|
||||
-DIMPORT_EXECUTABLES_DIR=out \
|
||||
-DCMAKE_BUILD_TYPE="$1" \
|
||||
-DCMAKE_INSTALL_PREFIX="../${lc_target}/filament" \
|
||||
${SWIFTSHADER_OPTION} \
|
||||
${EGL_ON_LINUX_OPTION} \
|
||||
${MATDBG_OPTION} \
|
||||
${MATOPT_OPTION} \
|
||||
${ASAN_UBSAN_OPTION} \
|
||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||
${STEREOSCOPIC_OPTION} \
|
||||
${architectures} \
|
||||
../..
|
||||
ln -sf "out/cmake-${lc_target}/compile_commands.json" \
|
||||
@@ -373,6 +374,7 @@ function build_android_target {
|
||||
${MATOPT_OPTION} \
|
||||
${VULKAN_ANDROID_OPTION} \
|
||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||
${STEREOSCOPIC_OPTION} \
|
||||
../..
|
||||
ln -sf "out/cmake-android-${lc_target}-${arch}/compile_commands.json" \
|
||||
../../compile_commands.json
|
||||
@@ -607,7 +609,7 @@ function build_ios_target {
|
||||
-DCMAKE_TOOLCHAIN_FILE=../../third_party/clang/iOS.cmake \
|
||||
${MATDBG_OPTION} \
|
||||
${MATOPT_OPTION} \
|
||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||
${STEREOSCOPIC_OPTION} \
|
||||
../..
|
||||
ln -sf "out/cmake-ios-${lc_target}-${arch}/compile_commands.json" \
|
||||
../../compile_commands.json
|
||||
@@ -794,7 +796,7 @@ function check_debug_release_build {
|
||||
|
||||
pushd "$(dirname "$0")" > /dev/null
|
||||
|
||||
while getopts ":hacCfgijmp:q:uvslwtedk:bx:" opt; do
|
||||
while getopts ":hacCfgijmp:q:uvslwedk:bx:S:" opt; do
|
||||
case ${opt} in
|
||||
h)
|
||||
print_help
|
||||
@@ -913,10 +915,6 @@ while getopts ":hacCfgijmp:q:uvslwtedk:bx:" opt; do
|
||||
IOS_BUILD_SIMULATOR=true
|
||||
echo "iOS simulator support enabled."
|
||||
;;
|
||||
t)
|
||||
SWIFTSHADER_OPTION="-DFILAMENT_USE_SWIFTSHADER=ON"
|
||||
echo "SwiftShader support enabled."
|
||||
;;
|
||||
e)
|
||||
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=ON -DFILAMENT_SKIP_SDL2=ON -DFILAMENT_SKIP_SAMPLES=ON"
|
||||
echo "EGL on Linux support enabled; skipping SDL2."
|
||||
@@ -938,6 +936,20 @@ while getopts ":hacCfgijmp:q:uvslwtedk:bx:" opt; do
|
||||
;;
|
||||
x) BACKEND_DEBUG_FLAG_OPTION="-DFILAMENT_BACKEND_DEBUG_FLAG=${OPTARG}"
|
||||
;;
|
||||
S) case $(echo "${OPTARG}" | tr '[:upper:]' '[:lower:]') in
|
||||
instanced)
|
||||
STEREOSCOPIC_OPTION="-DFILAMENT_SAMPLES_STEREO_TYPE=instanced"
|
||||
;;
|
||||
multiview)
|
||||
STEREOSCOPIC_OPTION="-DFILAMENT_SAMPLES_STEREO_TYPE=multiview"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown stereoscopic type ${OPTARG}"
|
||||
echo "Type must be one of [instanced|multiview]"
|
||||
echo ""
|
||||
exit 1
|
||||
esac
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -${OPTARG}" >&2
|
||||
echo ""
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# Build the image:
|
||||
# docker build --no-cache --tag ssfilament -f build/swiftshader/Dockerfile .
|
||||
# docker tag ssfilament ghcr.io/filament-assets/swiftshader
|
||||
#
|
||||
# Publish the image:
|
||||
# docker login ghcr.io --username <user> --password <token>
|
||||
# docker push ghcr.io/filament-assets/swiftshader
|
||||
#
|
||||
# Run the image and mount the current directory:
|
||||
# docker run -it -v `pwd`:/trees/filament -t ssfilament
|
||||
|
||||
FROM ubuntu:focal
|
||||
WORKDIR /trees
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ENV SWIFTSHADER_LD_LIBRARY_PATH=/trees/swiftshader/build
|
||||
ENV CXXFLAGS='-fno-builtin -Wno-pass-failed'
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get --no-install-recommends install -y \
|
||||
apt-transport-https \
|
||||
apt-utils \
|
||||
build-essential \
|
||||
cmake \
|
||||
ca-certificates \
|
||||
git \
|
||||
ninja-build \
|
||||
python \
|
||||
python3 \
|
||||
xorg-dev \
|
||||
clang-7 \
|
||||
libc++-7-dev \
|
||||
libc++abi-7-dev \
|
||||
lldb
|
||||
|
||||
# Ensure that clang is used instead of gcc.
|
||||
RUN set -eux ;\
|
||||
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-7 100 ;\
|
||||
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-7 100 ;\
|
||||
update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100 ;\
|
||||
update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
|
||||
|
||||
# Get patch files from the local Filament tree.
|
||||
COPY build/swiftshader/*.diff .
|
||||
|
||||
# Clone SwiftShader, apply patches, and build it.
|
||||
RUN set -eux ;\
|
||||
git clone https://swiftshader.googlesource.com/SwiftShader swiftshader ;\
|
||||
cd swiftshader ;\
|
||||
git checkout 139f5c3 ;\
|
||||
git apply /trees/*.diff ;\
|
||||
cd build ;\
|
||||
cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release ;\
|
||||
ninja
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
spath = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
path = Path(spath)
|
||||
|
||||
folder = "../../results/"
|
||||
|
||||
images = list(path.glob(folder + '*.png'))
|
||||
|
||||
images.sort()
|
||||
|
||||
gallery = open(path.absolute().joinpath(folder + 'index.html'), 'w')
|
||||
|
||||
gallery.write("""<html>
|
||||
<head>
|
||||
<script type="module" src="https://unpkg.com/img-comparison-slider@latest/dist/component/component.esm.js"></script>
|
||||
<script nomodule="" src="https://unpkg.com/img-comparison-slider@latest/dist/component/component.js"></script>
|
||||
<link rel="stylesheet" href="https://unpkg.com/img-comparison-slider@latest/dist/collection/styles/initial.css"/>
|
||||
<style>
|
||||
h2 {
|
||||
font-weight: normal;
|
||||
margin-top: 150px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
color: blue;
|
||||
}
|
||||
a:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
""")
|
||||
|
||||
tag = ''
|
||||
|
||||
for image in images:
|
||||
group = image.stem.rstrip('0123456789')
|
||||
before = f'https://filament-assets.github.io/golden/{group}/{image.name}'
|
||||
after = image.name
|
||||
gallery.write('\n')
|
||||
gallery.write(f'<h2><a href="{image.stem}.json">{image.stem}.json</a></h2>\n')
|
||||
gallery.write('<img-comparison-slider>\n')
|
||||
gallery.write(f'<img slot="before" src="{before}" /> <img slot="after" src="{after}" />\n')
|
||||
gallery.write('</img-comparison-slider>\n')
|
||||
|
||||
gallery.write("""</body>
|
||||
</html>
|
||||
""")
|
||||
@@ -1,62 +0,0 @@
|
||||
diff --git a/src/Vulkan/VkPipeline.cpp b/src/Vulkan/VkPipeline.cpp
|
||||
index 86913ec72..3b35345af 100644
|
||||
--- a/src/Vulkan/VkPipeline.cpp
|
||||
+++ b/src/Vulkan/VkPipeline.cpp
|
||||
@@ -71,7 +71,56 @@ std::vector<uint32_t> preprocessSpirv(
|
||||
if(optimize)
|
||||
{
|
||||
// Full optimization list taken from spirv-opt.
|
||||
- opt.RegisterPerformancePasses();
|
||||
+
|
||||
+ // We have removed CreateRedundancyEliminationPass because it segfaults when encountering:
|
||||
+ // %389 = OpCompositeConstruct %7 %386 %387 %388 %86
|
||||
+ // When inserting an entry into instruction_to_value_ (which is an unordered_map)
|
||||
+ // This could perhaps be investigated further with help from asan.
|
||||
+
|
||||
+ using namespace spvtools;
|
||||
+ opt.RegisterPass(CreateWrapOpKillPass())
|
||||
+ .RegisterPass(CreateDeadBranchElimPass())
|
||||
+ .RegisterPass(CreateMergeReturnPass())
|
||||
+ .RegisterPass(CreateInlineExhaustivePass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreatePrivateToLocalPass())
|
||||
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
||||
+ .RegisterPass(CreateLocalSingleStoreElimPass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateScalarReplacementPass())
|
||||
+ .RegisterPass(CreateLocalAccessChainConvertPass())
|
||||
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
||||
+ .RegisterPass(CreateLocalSingleStoreElimPass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateLocalMultiStoreElimPass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateCCPPass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateLoopUnrollPass(true))
|
||||
+ .RegisterPass(CreateDeadBranchElimPass())
|
||||
+ .RegisterPass(CreateRedundancyEliminationPass()) // workaround for SEGFAULT
|
||||
+ .RegisterPass(CreateCombineAccessChainsPass())
|
||||
+ .RegisterPass(CreateSimplificationPass())
|
||||
+ .RegisterPass(CreateScalarReplacementPass())
|
||||
+ .RegisterPass(CreateLocalAccessChainConvertPass())
|
||||
+ .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
||||
+ .RegisterPass(CreateLocalSingleStoreElimPass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateSSARewritePass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateVectorDCEPass())
|
||||
+ .RegisterPass(CreateDeadInsertElimPass())
|
||||
+ .RegisterPass(CreateDeadBranchElimPass())
|
||||
+ .RegisterPass(CreateSimplificationPass())
|
||||
+ .RegisterPass(CreateIfConversionPass())
|
||||
+ .RegisterPass(CreateCopyPropagateArraysPass())
|
||||
+ .RegisterPass(CreateReduceLoadSizePass())
|
||||
+ .RegisterPass(CreateAggressiveDCEPass())
|
||||
+ .RegisterPass(CreateBlockMergePass())
|
||||
+ .RegisterPass(CreateRedundancyEliminationPass()) // workaround for SEGFAULT
|
||||
+ .RegisterPass(CreateDeadBranchElimPass())
|
||||
+ .RegisterPass(CreateBlockMergePass())
|
||||
+ .RegisterPass(CreateSimplificationPass());
|
||||
}
|
||||
|
||||
std::vector<uint32_t> optimized;
|
||||
@@ -1,127 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
function print_help {
|
||||
local self_name=$(basename "$0")
|
||||
echo "This script issues docker commands for testing Filament with SwiftShader."
|
||||
echo "The usual sequence of commands is: fetch, start, build filament release, and run."
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $self_name [command]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " build filament [debug | release]"
|
||||
echo " Use the container to build Filament."
|
||||
echo " build swiftshader [debug | release]"
|
||||
echo " Use the container to do a clean rebuild of SwiftShader."
|
||||
echo " (Note that the container already has SwiftShader built.)"
|
||||
echo " fetch"
|
||||
echo " Download the docker image from the central repository."
|
||||
echo " help"
|
||||
echo " Print this help message."
|
||||
echo " logs"
|
||||
echo " Print messages from the container's kernel ring buffer."
|
||||
echo " This is useful for diagnosing OOM issues."
|
||||
echo " run [lldb]"
|
||||
echo " Launch a test inside the container, optionally via lldb."
|
||||
echo " shell"
|
||||
echo " Interact with a bash prompt in the container."
|
||||
echo " start"
|
||||
echo " Start a container from the image."
|
||||
echo " stop"
|
||||
echo " Stop the container."
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Change the current working directory to the Filament root.
|
||||
pushd "$(dirname "$0")/../.." > /dev/null
|
||||
|
||||
if [[ "$1" == "build" ]] && [[ "$2" == "filament" ]]; then
|
||||
docker exec runner filament/build.sh -t $3 gltf_viewer
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "build" ]] && [[ "$2" == "swiftshader" ]]; then
|
||||
BUILD_TYPE="$3"
|
||||
BUILD_TYPE="$(tr '[:lower:]' '[:upper:]' <<< ${BUILD_TYPE:0:1})${BUILD_TYPE:1}"
|
||||
docker exec --workdir /trees/swiftshader runner rm -rf build
|
||||
docker exec --workdir /trees/swiftshader runner mkdir build
|
||||
docker exec --workdir /trees/swiftshader/build runner cmake -GNinja -DCMAKE_BUILD_TYPE="$BUILD_TYPE" ..
|
||||
docker exec --workdir /trees/swiftshader/build runner ninja
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "fetch" ]]; then
|
||||
docker pull ghcr.io/filament-assets/swiftshader:latest
|
||||
docker tag ghcr.io/filament-assets/swiftshader:latest ssfilament
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "help" ]]; then
|
||||
print_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$1" == "logs" ]]; then
|
||||
docker exec runner dmesg --human --read-clear
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "run" ]] && [[ "$2" == "lldb" ]]; then
|
||||
docker exec -i --workdir /trees/filament/results runner \
|
||||
lldb --batch -o run -o bt -- \
|
||||
../out/cmake-release/samples/gltf_viewer \
|
||||
--headless \
|
||||
--batch ../libs/viewer/tests/basic.json \
|
||||
--api vulkan
|
||||
docker exec runner /trees/filament/build/swiftshader/gallery.py
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "run" ]]; then
|
||||
docker exec --tty --workdir /trees/filament/results runner \
|
||||
/usr/bin/catchsegv \
|
||||
../out/cmake-release/samples/gltf_viewer \
|
||||
--headless \
|
||||
--batch ../libs/viewer/tests/basic.json \
|
||||
--api vulkan
|
||||
docker exec runner /trees/filament/build/swiftshader/gallery.py
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "shell" ]]; then
|
||||
docker exec --interactive --tty runner /bin/bash
|
||||
exit $?
|
||||
fi
|
||||
|
||||
# Notes on options being passed to docker's run command:
|
||||
#
|
||||
# - The memory constraint seems to prevent an OOM signal in GitHub Actions.
|
||||
# - The cap / security args allow use of lldb and creation of core dumps.
|
||||
# - The privileged arg allows use of dmesg for examining OOM logs.
|
||||
#
|
||||
# Currently, a GitHub Actions VM has 2 CPUs, 7 GB RAM, and 14 GB of SSD disk space.
|
||||
#
|
||||
# Please be aware that Docker Desktop might impose additional resource constraints, and that those
|
||||
# settings can only be controlled with its GUI. We recommend at least 7 GB of memory and 2 GB swap.
|
||||
if [[ "$1" == "start" ]]; then
|
||||
mkdir -p results
|
||||
docker run --tty --rm --detach --privileged \
|
||||
--memory 6.5g \
|
||||
--name runner \
|
||||
--cap-add=SYS_PTRACE \
|
||||
--security-opt seccomp=unconfined \
|
||||
--security-opt apparmor=unconfined \
|
||||
--volume `pwd`:/trees/filament \
|
||||
--workdir /trees \
|
||||
ssfilament
|
||||
exit $?
|
||||
fi
|
||||
|
||||
if [[ "$1" == "stop" ]]; then
|
||||
docker container rm runner --force
|
||||
exit $?
|
||||
fi
|
||||
|
||||
print_help
|
||||
exit 1
|
||||
@@ -66,7 +66,7 @@ set(PRIVATE_HDRS
|
||||
# OpenGL / OpenGL ES Sources
|
||||
# ==================================================================================================
|
||||
|
||||
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMENT_USE_SWIFTSHADER)
|
||||
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/OpenGLPlatform.h
|
||||
src/opengl/gl_headers.cpp
|
||||
@@ -417,7 +417,9 @@ if (APPLE OR LINUX)
|
||||
test/test_MissingRequiredAttributes.cpp
|
||||
test/test_ReadPixels.cpp
|
||||
test/test_BufferUpdates.cpp
|
||||
test/test_Callbacks.cpp
|
||||
test/test_MRT.cpp
|
||||
test/test_PushConstants.cpp
|
||||
test/test_LoadImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
@@ -478,6 +480,10 @@ if (APPLE)
|
||||
# linker from removing "unused" symbols.
|
||||
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
|
||||
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
|
||||
|
||||
# This is needed after XCode 15.3
|
||||
set_target_properties(backend_test_mac PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set_target_properties(backend_test_mac PROPERTIES INSTALL_RPATH /usr/local/lib)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -22,14 +22,17 @@
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/unwindows.h> // Because we define ERROR in the FenceStatus enum.
|
||||
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/PresentCallable.h>
|
||||
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <array> // FIXME: STL headers are not allowed in public headers
|
||||
#include <type_traits> // FIXME: STL headers are not allowed in public headers
|
||||
#include <variant> // FIXME: STL headers are not allowed in public headers
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -90,12 +93,15 @@ static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = SWAP_CHAIN_CON
|
||||
*/
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_PROTECTED_CONTENT = 0x40;
|
||||
|
||||
|
||||
static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3.
|
||||
static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects.
|
||||
static constexpr size_t MAX_SSBO_COUNT = 4; // This is guaranteed by OpenGL ES.
|
||||
|
||||
static constexpr size_t MAX_PUSH_CONSTANT_COUNT = 32; // Vulkan 1.1 spec allows for 128-byte
|
||||
// of push constant (we assume 4-byte
|
||||
// types).
|
||||
|
||||
// Per feature level caps
|
||||
// Use (int)FeatureLevel to index this array
|
||||
static constexpr struct {
|
||||
@@ -112,7 +118,7 @@ static_assert(MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_ATTRIBUTE_COUNT,
|
||||
"The number of buffer objects that can be attached to a VertexBuffer must be "
|
||||
"less than or equal to the maximum number of vertex attributes.");
|
||||
|
||||
static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 10; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t CONFIG_UNIFORM_BINDING_COUNT = 9; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t CONFIG_SAMPLER_BINDING_COUNT = 4; // This is guaranteed by OpenGL ES.
|
||||
|
||||
/**
|
||||
@@ -331,7 +337,7 @@ enum class UniformType : uint8_t {
|
||||
/**
|
||||
* Supported constant parameter types
|
||||
*/
|
||||
enum class ConstantType : uint8_t {
|
||||
enum class ConstantType : uint8_t {
|
||||
INT,
|
||||
FLOAT,
|
||||
BOOL
|
||||
@@ -1218,13 +1224,15 @@ struct StencilState {
|
||||
uint8_t padding = 0;
|
||||
};
|
||||
|
||||
using PushConstantVariant = std::variant<int32_t, float, bool>;
|
||||
|
||||
static_assert(sizeof(StencilState::StencilOperations) == 5u,
|
||||
"StencilOperations size not what was intended");
|
||||
|
||||
static_assert(sizeof(StencilState) == 12u,
|
||||
"StencilState size not what was intended");
|
||||
|
||||
using FrameScheduledCallback = void(*)(PresentCallable callable, void* user);
|
||||
using FrameScheduledCallback = utils::Invocable<void(backend::PresentCallable)>;
|
||||
|
||||
enum class Workaround : uint16_t {
|
||||
// The EASU pass must split because shader compiler flattens early-exit branch
|
||||
@@ -1243,13 +1251,7 @@ enum class Workaround : uint16_t {
|
||||
POWER_VR_SHADER_WORKAROUNDS,
|
||||
};
|
||||
|
||||
//! 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,
|
||||
};
|
||||
using StereoscopicType = backend::Platform::StereoscopicType;
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
@@ -41,6 +41,26 @@ public:
|
||||
struct Fence {};
|
||||
struct Stream {};
|
||||
|
||||
/**
|
||||
* The type of technique for stereoscopic rendering. (Note that the materials used will need to
|
||||
* be compatible with the chosen technique.)
|
||||
*/
|
||||
enum class StereoscopicType : uint8_t {
|
||||
/**
|
||||
* No stereoscopic rendering
|
||||
*/
|
||||
NONE,
|
||||
/**
|
||||
* Stereoscopic rendering is performed using instanced rendering technique.
|
||||
*/
|
||||
INSTANCED,
|
||||
/**
|
||||
* Stereoscopic rendering is performed using the multiview feature from the graphics
|
||||
* backend.
|
||||
*/
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
struct DriverConfig {
|
||||
/**
|
||||
* Size of handle arena in bytes. Setting to 0 indicates default value is to be used.
|
||||
@@ -71,6 +91,11 @@ public:
|
||||
* GLES 3.x backends.
|
||||
*/
|
||||
bool forceGLES2Context = false;
|
||||
|
||||
/**
|
||||
* Sets the technique for stereoscopic rendering.
|
||||
*/
|
||||
StereoscopicType stereoscopicType = StereoscopicType::NONE;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace filament::backend {
|
||||
* and optional user data:
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* swapChain->setFrameScheduledCallback(myFrameScheduledCallback, nullptr);
|
||||
* swapChain->setFrameScheduledCallback(nullptr, myFrameScheduledCallback);
|
||||
* if (renderer->beginFrame(swapChain)) {
|
||||
* renderer->render(view);
|
||||
* renderer->endFrame();
|
||||
@@ -58,8 +58,6 @@ namespace filament::backend {
|
||||
* @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other
|
||||
* backends ignore the callback (which will never be called) and proceed normally.
|
||||
*
|
||||
* @remark The SwapChain::FrameScheduledCallback is called on an arbitrary thread.
|
||||
*
|
||||
* Applications *must* call each PresentCallable they receive. Each PresentCallable represents a
|
||||
* frame that is waiting to be presented. If an application fails to call a PresentCallable, a
|
||||
* memory leak could occur. To "cancel" the presentation of a frame, pass false to the
|
||||
|
||||
@@ -117,6 +117,14 @@ public:
|
||||
Program& specializationConstants(
|
||||
utils::FixedCapacityVector<SpecializationConstant> specConstants) noexcept;
|
||||
|
||||
struct PushConstant {
|
||||
utils::CString name;
|
||||
ConstantType type;
|
||||
};
|
||||
|
||||
Program& pushConstants(ShaderStage stage,
|
||||
utils::FixedCapacityVector<PushConstant> constants) noexcept;
|
||||
|
||||
Program& cacheId(uint64_t cacheId) noexcept;
|
||||
|
||||
Program& multiview(bool multiview) noexcept;
|
||||
@@ -148,6 +156,15 @@ public:
|
||||
return mSpecializationConstants;
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<PushConstant> const& getPushConstants(
|
||||
ShaderStage stage) const noexcept {
|
||||
return mPushConstants[static_cast<uint8_t>(stage)];
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<PushConstant>& getPushConstants(ShaderStage stage) noexcept {
|
||||
return mPushConstants[static_cast<uint8_t>(stage)];
|
||||
}
|
||||
|
||||
uint64_t getCacheId() const noexcept { return mCacheId; }
|
||||
|
||||
bool isMultiview() const noexcept { return mMultiview; }
|
||||
@@ -165,6 +182,7 @@ private:
|
||||
uint64_t mCacheId{};
|
||||
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)> mLogger;
|
||||
utils::FixedCapacityVector<SpecializationConstant> mSpecializationConstants;
|
||||
std::array<utils::FixedCapacityVector<PushConstant>, SHADER_TYPE_COUNT> mPushConstants;
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> mAttributes;
|
||||
std::array<UniformInfo, Program::UNIFORM_BINDING_COUNT> mBindingUniformInfo;
|
||||
CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH;
|
||||
|
||||
@@ -29,17 +29,36 @@ namespace filament::backend {
|
||||
//! \privatesection
|
||||
|
||||
struct TargetBufferInfo {
|
||||
// note: the parameters of this constructor are not in the order of this structure's fields
|
||||
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level, uint16_t layer, uint8_t baseViewIndex) noexcept
|
||||
: handle(handle), baseViewIndex(baseViewIndex), level(level), layer(layer) {
|
||||
}
|
||||
|
||||
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
|
||||
: handle(handle), level(level), layer(layer) {
|
||||
}
|
||||
|
||||
TargetBufferInfo(Handle<HwTexture> handle, uint8_t level) noexcept
|
||||
: handle(handle), level(level) {
|
||||
}
|
||||
|
||||
TargetBufferInfo(Handle<HwTexture> handle) noexcept // NOLINT(*-explicit-constructor)
|
||||
: handle(handle) {
|
||||
}
|
||||
|
||||
TargetBufferInfo() noexcept = default;
|
||||
|
||||
// texture to be used as render target
|
||||
Handle<HwTexture> handle;
|
||||
|
||||
// starting layer index for multiview. This value is only used when the `layerCount` for the
|
||||
// Starting layer index for multiview. This value is only used when the `layerCount` for the
|
||||
// render target is greater than 1.
|
||||
uint8_t baseViewIndex = 0;
|
||||
|
||||
// level to be used
|
||||
uint8_t level = 0;
|
||||
|
||||
// for cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping
|
||||
// For cubemaps and 3D textures. See TextureCubemapFace for the face->layer mapping
|
||||
uint16_t layer = 0;
|
||||
};
|
||||
|
||||
@@ -64,7 +83,7 @@ public:
|
||||
|
||||
MRT() noexcept = default;
|
||||
|
||||
MRT(TargetBufferInfo const& color) noexcept // NOLINT(hicpp-explicit-conversions)
|
||||
MRT(TargetBufferInfo const& color) noexcept // NOLINT(hicpp-explicit-conversions, *-explicit-constructor)
|
||||
: mInfos{ color } {
|
||||
}
|
||||
|
||||
@@ -84,7 +103,7 @@ public:
|
||||
|
||||
// this is here for backward compatibility
|
||||
MRT(Handle<HwTexture> handle, uint8_t level, uint16_t layer) noexcept
|
||||
: mInfos{{ handle, 0, level, layer }} {
|
||||
: mInfos{{ handle, level, layer, 0 }} {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/PrivateImplementation.h>
|
||||
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -47,6 +47,14 @@ struct VulkanPlatformPrivate;
|
||||
class VulkanPlatform : public Platform, utils::PrivateImplementation<VulkanPlatformPrivate> {
|
||||
public:
|
||||
|
||||
struct ExtensionHashFn {
|
||||
std::size_t operator()(utils::CString const& s) const noexcept {
|
||||
return std::hash<std::string>{}(s.data());
|
||||
}
|
||||
};
|
||||
// Utility for managing device or instance extensions during initialization.
|
||||
using ExtensionSet = std::unordered_set<utils::CString, ExtensionHashFn>;
|
||||
|
||||
/**
|
||||
* A collection of handles to objects and metadata that comprises a Vulkan context. The client
|
||||
* can instantiate this struct and pass to Engine::Builder::sharedContext if they wishes to
|
||||
@@ -192,6 +200,13 @@ public:
|
||||
virtual SwapChainPtr createSwapChain(void* nativeWindow, uint64_t flags = 0,
|
||||
VkExtent2D extent = {0, 0});
|
||||
|
||||
/**
|
||||
* Allows implementers to provide instance extensions that they'd like to include in the
|
||||
* instance creation.
|
||||
* @return A set of extensions to enable for the instance.
|
||||
*/
|
||||
virtual ExtensionSet getRequiredInstanceExtensions() { return {}; }
|
||||
|
||||
/**
|
||||
* Destroy the swapchain.
|
||||
* @param handle The handle returned by createSwapChain()
|
||||
@@ -236,10 +251,9 @@ public:
|
||||
VkQueue getGraphicsQueue() const noexcept;
|
||||
|
||||
private:
|
||||
// Platform dependent helper methods
|
||||
using ExtensionSet = std::unordered_set<std::string_view>;
|
||||
static ExtensionSet getRequiredInstanceExtensions();
|
||||
static ExtensionSet getSwapchainInstanceExtensions();
|
||||
|
||||
// Platform dependent helper methods
|
||||
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
|
||||
static SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) noexcept;
|
||||
|
||||
@@ -138,14 +138,13 @@ DECL_DRIVER_API_N(beginFrame,
|
||||
|
||||
DECL_DRIVER_API_N(setFrameScheduledCallback,
|
||||
backend::SwapChainHandle, sch,
|
||||
backend::FrameScheduledCallback, callback,
|
||||
void*, user)
|
||||
backend::CallbackHandler*, handler,
|
||||
backend::FrameScheduledCallback&&, callback)
|
||||
|
||||
DECL_DRIVER_API_N(setFrameCompletedCallback,
|
||||
backend::SwapChainHandle, sch,
|
||||
backend::CallbackHandler*, handler,
|
||||
backend::CallbackHandler::Callback, callback,
|
||||
void*, user)
|
||||
utils::Invocable<void(void)>&&, callback)
|
||||
|
||||
DECL_DRIVER_API_N(setPresentationTime,
|
||||
int64_t, monotonic_clock_ns)
|
||||
@@ -301,7 +300,7 @@ 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_N(bool, isDepthStencilBlitSupported, backend::TextureFormat, format)
|
||||
@@ -434,6 +433,11 @@ DECL_DRIVER_API_N(bindSamplers,
|
||||
uint32_t, index,
|
||||
backend::SamplerGroupHandle, sbh)
|
||||
|
||||
DECL_DRIVER_API_N(setPushConstant,
|
||||
backend::ShaderStage, stage,
|
||||
uint8_t, index,
|
||||
backend::PushConstantVariant, value)
|
||||
|
||||
DECL_DRIVER_API_N(insertEventMarker,
|
||||
const char*, string,
|
||||
uint32_t, len = 0)
|
||||
@@ -497,7 +501,7 @@ DECL_DRIVER_API_N(blit,
|
||||
math::uint2, size)
|
||||
|
||||
DECL_DRIVER_API_N(bindPipeline,
|
||||
backend::PipelineState, state)
|
||||
backend::PipelineState const&, state)
|
||||
|
||||
DECL_DRIVER_API_N(bindRenderPrimitive,
|
||||
backend::RenderPrimitiveHandle, rph)
|
||||
|
||||
@@ -173,8 +173,8 @@ public:
|
||||
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());
|
||||
FILAMENT_CHECK_POSTCONDITION(expectedAge == age) <<
|
||||
"use-after-free of Handle with id=" << handle.getId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,8 +240,8 @@ private:
|
||||
Node* const pNode = static_cast<Node*>(p);
|
||||
uint8_t& expectedAge = pNode[-1].age;
|
||||
if (UTILS_UNLIKELY(!mUseAfterFreeCheckDisabled)) {
|
||||
ASSERT_POSTCONDITION(expectedAge == age,
|
||||
"double-free of Handle of size %d at %p", size, p);
|
||||
FILAMENT_CHECK_POSTCONDITION(expectedAge == age) <<
|
||||
"double-free of Handle of size " << size << " at " << p;
|
||||
}
|
||||
expectedAge = (expectedAge + 1) & 0xF; // fixme
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ PresentCallable::PresentCallable(PresentFn fn, void* user) noexcept
|
||||
}
|
||||
|
||||
void PresentCallable::operator()(bool presentFrame) noexcept {
|
||||
ASSERT_PRECONDITION(mPresentFn, "This PresentCallable was already called. " \
|
||||
"PresentCallables should be called exactly once.");
|
||||
FILAMENT_CHECK_PRECONDITION(mPresentFn) << "This PresentCallable was already called. "
|
||||
"PresentCallables should be called exactly once.";
|
||||
mPresentFn(presentFrame, mUser);
|
||||
// Set mPresentFn to nullptr to denote that the callable has been called.
|
||||
mPresentFn = nullptr;
|
||||
|
||||
@@ -119,9 +119,9 @@ void* CircularBuffer::alloc(size_t size) noexcept {
|
||||
data = mmap(nullptr, size * 2 + BLOCK_SIZE,
|
||||
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
|
||||
ASSERT_POSTCONDITION(data,
|
||||
"couldn't allocate %u KiB of virtual address space for the command buffer",
|
||||
(size * 2 / 1024));
|
||||
FILAMENT_CHECK_POSTCONDITION(data) <<
|
||||
"couldn't allocate " << (size * 2 / 1024) <<
|
||||
" KiB of virtual address space for the command buffer";
|
||||
|
||||
slog.d << "WARNING: Using soft CircularBuffer (" << (size * 2 / 1024) << " KiB)"
|
||||
<< io::endl;
|
||||
|
||||
@@ -74,8 +74,6 @@ void CommandBufferQueue::setPaused(bool paused) {
|
||||
|
||||
bool CommandBufferQueue::isExitRequested() const {
|
||||
std::lock_guard<utils::Mutex> const lock(mLock);
|
||||
ASSERT_PRECONDITION( mExitRequested == 0 || mExitRequested == EXIT_REQUESTED,
|
||||
"mExitRequested is corrupted (value = 0x%08x)!", mExitRequested);
|
||||
return (bool)mExitRequested;
|
||||
}
|
||||
|
||||
@@ -108,11 +106,11 @@ void CommandBufferQueue::flush() noexcept {
|
||||
mCondition.notify_one();
|
||||
|
||||
// circular buffer is too small, we corrupted the stream
|
||||
ASSERT_POSTCONDITION(used <= mFreeSpace,
|
||||
FILAMENT_CHECK_POSTCONDITION(used <= mFreeSpace) <<
|
||||
"Backend CommandStream overflow. Commands are corrupted and unrecoverable.\n"
|
||||
"Please increase minCommandBufferSizeMB inside the Config passed to Engine::create.\n"
|
||||
"Space used at this time: %u bytes, overflow: %u bytes",
|
||||
(unsigned)used, unsigned(used - mFreeSpace));
|
||||
"Space used at this time: " << used <<
|
||||
" bytes, overflow: " << used - mFreeSpace << " bytes";
|
||||
|
||||
// wait until there is enough space in the buffer
|
||||
mFreeSpace -= used;
|
||||
@@ -131,9 +129,11 @@ void CommandBufferQueue::flush() noexcept {
|
||||
#endif
|
||||
|
||||
SYSTRACE_NAME("waiting: CircularBuffer::flush()");
|
||||
ASSERT_POSTCONDITION(!mPaused,
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(!mPaused) <<
|
||||
"CommandStream is full, but since the rendering thread is paused, "
|
||||
"the buffer cannot flush and we will deadlock. Instead, abort.");
|
||||
"the buffer cannot flush and we will deadlock. Instead, abort.";
|
||||
|
||||
mCondition.wait(lock, [this, requiredSize]() -> bool {
|
||||
// TODO: on macOS, we need to call pumpEvents from time to time
|
||||
return mFreeSpace >= requiredSize;
|
||||
@@ -149,10 +149,6 @@ std::vector<CommandBufferQueue::Range> CommandBufferQueue::waitForCommands() con
|
||||
while ((mCommandBuffersToExecute.empty() || mPaused) && !mExitRequested) {
|
||||
mCondition.wait(lock);
|
||||
}
|
||||
|
||||
ASSERT_PRECONDITION( mExitRequested == 0 || mExitRequested == EXIT_REQUESTED,
|
||||
"mExitRequested is corrupted (value = 0x%08x)!", mExitRequested);
|
||||
|
||||
return std::move(mCommandBuffersToExecute);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,9 +113,9 @@ HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size
|
||||
|
||||
HandleBase::HandleId id = (++mId) | HANDLE_HEAP_FLAG;
|
||||
|
||||
ASSERT_POSTCONDITION(mId < HANDLE_HEAP_FLAG,
|
||||
FILAMENT_CHECK_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");
|
||||
" for a while. Please increase FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB";
|
||||
|
||||
mOverflowMap.emplace(id, p);
|
||||
lock.unlock();
|
||||
|
||||
@@ -29,21 +29,21 @@
|
||||
#include "backend/platforms/PlatformCocoaTouchGL.h"
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include <backend/platforms/PlatformCocoaGL.h>
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
#if defined(FILAMENT_SUPPORTS_X11)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include "backend/platforms/PlatformGLX.h"
|
||||
#endif
|
||||
#elif defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include "backend/platforms/PlatformEGLHeadless.h"
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3) && !defined(FILAMENT_USE_SWIFTSHADER)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include "backend/platforms/PlatformWGL.h"
|
||||
#endif
|
||||
#elif defined(__EMSCRIPTEN__)
|
||||
@@ -111,8 +111,7 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
|
||||
}
|
||||
assert_invariant(*backend == Backend::OPENGL);
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL)
|
||||
#if defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(FILAMENT_USE_SWIFTSHADER)
|
||||
// Swiftshader OpenGLES support is deprecated and incomplete
|
||||
#if defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
return nullptr;
|
||||
#elif defined(__ANDROID__)
|
||||
return new PlatformEGLAndroid();
|
||||
|
||||
@@ -91,6 +91,12 @@ Program& Program::specializationConstants(
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::pushConstants(ShaderStage stage,
|
||||
utils::FixedCapacityVector<PushConstant> constants) noexcept {
|
||||
mPushConstants[static_cast<uint8_t>(stage)] = std::move(constants);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::cacheId(uint64_t cacheId) noexcept {
|
||||
mCacheId = cacheId;
|
||||
return *this;
|
||||
|
||||
@@ -109,9 +109,8 @@ inline bool MTLSizeEqual(T a, T b) noexcept {
|
||||
MetalBlitter::MetalBlitter(MetalContext& context) noexcept : mContext(context) { }
|
||||
|
||||
void MetalBlitter::blit(id<MTLCommandBuffer> cmdBuffer, const BlitArgs& args, const char* label) {
|
||||
|
||||
ASSERT_PRECONDITION(args.source.region.size.depth == args.destination.region.size.depth,
|
||||
"Blitting requires the source and destination regions to have the same depth.");
|
||||
FILAMENT_CHECK_PRECONDITION(args.source.region.size.depth == args.destination.region.size.depth)
|
||||
<< "Blitting requires the source and destination regions to have the same depth.";
|
||||
|
||||
// Determine if the blit for color or depth are eligible to use a MTLBlitCommandEncoder.
|
||||
// blitFastPath returns true upon success.
|
||||
@@ -327,7 +326,8 @@ id<MTLFunction> MetalBlitter::compileFragmentFunction(BlitFunctionKey key) const
|
||||
utils::slog.e << description << utils::io::endl;
|
||||
}
|
||||
}
|
||||
ASSERT_POSTCONDITION(library && function, "Unable to compile fragment shader for MetalBlitter.");
|
||||
FILAMENT_CHECK_POSTCONDITION(library && function)
|
||||
<< "Unable to compile fragment shader for MetalBlitter.";
|
||||
|
||||
return function;
|
||||
}
|
||||
@@ -352,7 +352,8 @@ id<MTLFunction> MetalBlitter::getBlitVertexFunction() {
|
||||
utils::slog.e << description << utils::io::endl;
|
||||
}
|
||||
}
|
||||
ASSERT_POSTCONDITION(library && function, "Unable to compile vertex shader for MetalBlitter.");
|
||||
FILAMENT_CHECK_POSTCONDITION(library && function)
|
||||
<< "Unable to compile vertex shader for MetalBlitter.";
|
||||
|
||||
mVertexFunction = function;
|
||||
|
||||
|
||||
@@ -65,12 +65,9 @@ private:
|
||||
const char* mName;
|
||||
};
|
||||
|
||||
#ifndef FILAMENT_METAL_BUFFER_TRACKING
|
||||
#define FILAMENT_METAL_BUFFER_TRACKING 0
|
||||
#endif
|
||||
|
||||
class MetalBufferTracking {
|
||||
class TrackedMetalBuffer {
|
||||
public:
|
||||
|
||||
static constexpr size_t EXCESS_BUFFER_COUNT = 30000;
|
||||
|
||||
enum class Type {
|
||||
@@ -94,57 +91,66 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
#if FILAMENT_METAL_BUFFER_TRACKING
|
||||
static void initialize() {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
for (size_t i = 0; i < TypeCount; i++) {
|
||||
aliveBuffers[i] = [NSHashTable weakObjectsHashTable];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void setPlatform(MetalPlatform* p) { platform = p; }
|
||||
|
||||
static void track(id<MTLBuffer> buffer, Type type) {
|
||||
TrackedMetalBuffer() noexcept : mBuffer(nil) {}
|
||||
TrackedMetalBuffer(nullptr_t) noexcept : mBuffer(nil) {}
|
||||
TrackedMetalBuffer(id<MTLBuffer> buffer, Type type) : mBuffer(buffer), mType(type) {
|
||||
assert_invariant(type != Type::NONE);
|
||||
if (UTILS_UNLIKELY(getAliveBuffers() >= EXCESS_BUFFER_COUNT)) {
|
||||
if (platform && platform->hasDebugUpdateStatFunc()) {
|
||||
platform->debugUpdateStat("filament.metal.excess_buffers_allocated",
|
||||
MetalBufferTracking::getAliveBuffers());
|
||||
if (buffer) {
|
||||
aliveBuffers[toIndex(type)]++;
|
||||
mType = type;
|
||||
if (getAliveBuffers() >= EXCESS_BUFFER_COUNT) {
|
||||
if (platform && platform->hasDebugUpdateStatFunc()) {
|
||||
platform->debugUpdateStat("filament.metal.excess_buffers_allocated",
|
||||
TrackedMetalBuffer::getAliveBuffers());
|
||||
}
|
||||
}
|
||||
}
|
||||
[aliveBuffers[toIndex(type)] addObject:buffer];
|
||||
}
|
||||
|
||||
~TrackedMetalBuffer() {
|
||||
if (mBuffer) {
|
||||
assert_invariant(mType != Type::NONE);
|
||||
aliveBuffers[toIndex(mType)]--;
|
||||
}
|
||||
}
|
||||
|
||||
TrackedMetalBuffer(TrackedMetalBuffer&&) = delete;
|
||||
TrackedMetalBuffer(TrackedMetalBuffer const&) = delete;
|
||||
TrackedMetalBuffer& operator=(TrackedMetalBuffer const&) = delete;
|
||||
|
||||
TrackedMetalBuffer& operator=(TrackedMetalBuffer&& rhs) noexcept {
|
||||
swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
id<MTLBuffer> get() const noexcept { return mBuffer; }
|
||||
operator bool() const noexcept { return bool(mBuffer); }
|
||||
|
||||
static uint64_t getAliveBuffers() {
|
||||
uint64_t sum = 0;
|
||||
for (size_t i = 1; i < TypeCount; i++) {
|
||||
sum += getAliveBuffers(static_cast<Type>(i));
|
||||
for (const auto& v : aliveBuffers) {
|
||||
sum += v;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
static uint64_t getAliveBuffers(Type type) {
|
||||
assert_invariant(type != Type::NONE);
|
||||
NSHashTable* hashTable = aliveBuffers[toIndex(type)];
|
||||
// Caution! We can't simply use hashTable.count here, which is inaccurate.
|
||||
// See http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/
|
||||
return hashTable.objectEnumerator.allObjects.count;
|
||||
return aliveBuffers[toIndex(type)];
|
||||
}
|
||||
#else
|
||||
static void initialize() {}
|
||||
static void setPlatform(MetalPlatform* p) {}
|
||||
static id<MTLBuffer> track(id<MTLBuffer> buffer, Type type) { return buffer; }
|
||||
static uint64_t getAliveBuffers() { return 0; }
|
||||
static uint64_t getAliveBuffers(Type type) { return 0; }
|
||||
#endif
|
||||
static void setPlatform(MetalPlatform* p) { platform = p; }
|
||||
|
||||
private:
|
||||
#if FILAMENT_METAL_BUFFER_TRACKING
|
||||
static std::array<NSHashTable<id<MTLBuffer>>*, TypeCount> aliveBuffers;
|
||||
void swap(TrackedMetalBuffer& other) noexcept {
|
||||
std::swap(mBuffer, other.mBuffer);
|
||||
std::swap(mType, other.mType);
|
||||
}
|
||||
|
||||
id<MTLBuffer> mBuffer;
|
||||
Type mType = Type::NONE;
|
||||
|
||||
static MetalPlatform* platform;
|
||||
#endif
|
||||
static std::array<uint64_t, TypeCount> aliveBuffers;
|
||||
};
|
||||
|
||||
class MetalBuffer {
|
||||
@@ -198,7 +204,7 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
id<MTLBuffer> mBuffer;
|
||||
TrackedMetalBuffer mBuffer;
|
||||
size_t mBufferSize = 0;
|
||||
void* mCpuBuffer = nullptr;
|
||||
MetalContext& mContext;
|
||||
@@ -247,11 +253,9 @@ public:
|
||||
mBufferOptions(options),
|
||||
mSlotSizeBytes(computeSlotSize(layout)),
|
||||
mSlotCount(slotCount) {
|
||||
{
|
||||
ScopedAllocationTimer timer("ring");
|
||||
mBuffer = [device newBufferWithLength:mSlotSizeBytes * mSlotCount options:mBufferOptions];
|
||||
}
|
||||
MetalBufferTracking::track(mBuffer, MetalBufferTracking::Type::RING);
|
||||
ScopedAllocationTimer timer("ring");
|
||||
mBuffer = { [device newBufferWithLength:mSlotSizeBytes * mSlotCount options:mBufferOptions],
|
||||
TrackedMetalBuffer::Type::RING };
|
||||
assert_invariant(mBuffer);
|
||||
}
|
||||
|
||||
@@ -271,11 +275,11 @@ public:
|
||||
// finishes executing.
|
||||
{
|
||||
ScopedAllocationTimer timer("ring");
|
||||
mAuxBuffer = [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions];
|
||||
mAuxBuffer = { [mDevice newBufferWithLength:mSlotSizeBytes options:mBufferOptions],
|
||||
TrackedMetalBuffer::Type::RING };
|
||||
}
|
||||
MetalBufferTracking::track(mAuxBuffer, MetalBufferTracking::Type::RING);
|
||||
assert_invariant(mAuxBuffer);
|
||||
return { mAuxBuffer, 0 };
|
||||
return { mAuxBuffer.get(), 0 };
|
||||
}
|
||||
mCurrentSlot = (mCurrentSlot + 1) % mSlotCount;
|
||||
mOccupiedSlots->fetch_add(1, std::memory_order_relaxed);
|
||||
@@ -304,9 +308,9 @@ public:
|
||||
*/
|
||||
std::pair<id<MTLBuffer>, NSUInteger> getCurrentAllocation() const {
|
||||
if (UTILS_UNLIKELY(mAuxBuffer)) {
|
||||
return { mAuxBuffer, 0 };
|
||||
return { mAuxBuffer.get(), 0 };
|
||||
}
|
||||
return { mBuffer, mCurrentSlot * mSlotSizeBytes };
|
||||
return { mBuffer.get(), mCurrentSlot * mSlotSizeBytes };
|
||||
}
|
||||
|
||||
bool canAccomodateLayout(MTLSizeAndAlign layout) const {
|
||||
@@ -315,8 +319,8 @@ public:
|
||||
|
||||
private:
|
||||
id<MTLDevice> mDevice;
|
||||
id<MTLBuffer> mBuffer;
|
||||
id<MTLBuffer> mAuxBuffer;
|
||||
TrackedMetalBuffer mBuffer;
|
||||
TrackedMetalBuffer mAuxBuffer;
|
||||
|
||||
MTLResourceOptions mBufferOptions;
|
||||
|
||||
|
||||
@@ -22,14 +22,10 @@
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
std::array<uint64_t, TrackedMetalBuffer::TypeCount> TrackedMetalBuffer::aliveBuffers = { 0 };
|
||||
MetalPlatform* TrackedMetalBuffer::platform = nullptr;
|
||||
MetalPlatform* ScopedAllocationTimer::platform = nullptr;
|
||||
|
||||
#if FILAMENT_METAL_BUFFER_TRACKING
|
||||
std::array<NSHashTable<id<MTLBuffer>>*, MetalBufferTracking::TypeCount>
|
||||
MetalBufferTracking::aliveBuffers;
|
||||
MetalPlatform* MetalBufferTracking::platform = nullptr;
|
||||
#endif
|
||||
|
||||
MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
|
||||
size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) {
|
||||
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit
|
||||
@@ -45,10 +41,11 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
|
||||
// Otherwise, we allocate a private GPU buffer.
|
||||
{
|
||||
ScopedAllocationTimer timer("generic");
|
||||
mBuffer = [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate];
|
||||
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
|
||||
TrackedMetalBuffer::Type::GENERIC };
|
||||
}
|
||||
MetalBufferTracking::track(mBuffer, MetalBufferTracking::Type::GENERIC);
|
||||
ASSERT_POSTCONDITION(mBuffer, "Could not allocate Metal buffer of size %zu.", size);
|
||||
FILAMENT_CHECK_POSTCONDITION(mBuffer)
|
||||
<< "Could not allocate Metal buffer of size " << size << ".";
|
||||
}
|
||||
|
||||
MetalBuffer::~MetalBuffer() {
|
||||
@@ -61,9 +58,9 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
|
||||
if (size <= 0) {
|
||||
return;
|
||||
}
|
||||
ASSERT_PRECONDITION(size + byteOffset <= mBufferSize,
|
||||
"Attempting to copy %zu bytes into a buffer of size %zu at offset %zu",
|
||||
size, mBufferSize, byteOffset);
|
||||
FILAMENT_CHECK_PRECONDITION(size + byteOffset <= mBufferSize)
|
||||
<< "Attempting to copy " << size << " bytes into a buffer of size " << mBufferSize
|
||||
<< " at offset " << byteOffset;
|
||||
|
||||
// Either copy into the Metal buffer or into our cpu buffer.
|
||||
if (mCpuBuffer) {
|
||||
@@ -74,18 +71,18 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
|
||||
// Acquire a staging buffer to hold the contents of this update.
|
||||
MetalBufferPool* bufferPool = mContext.bufferPool;
|
||||
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
|
||||
memcpy(staging->buffer.contents, src, size);
|
||||
memcpy(staging->buffer.get().contents, src, size);
|
||||
|
||||
// The blit below requires that byteOffset be a multiple of 4.
|
||||
ASSERT_PRECONDITION(!(byteOffset & 0x3u), "byteOffset must be a multiple of 4");
|
||||
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3u)) << "byteOffset must be a multiple of 4";
|
||||
|
||||
// Encode a blit from the staging buffer into the private GPU buffer.
|
||||
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @"Buffer upload blit";
|
||||
[blitEncoder copyFromBuffer:staging->buffer
|
||||
[blitEncoder copyFromBuffer:staging->buffer.get()
|
||||
sourceOffset:0
|
||||
toBuffer:mBuffer
|
||||
toBuffer:mBuffer.get()
|
||||
destinationOffset:byteOffset
|
||||
size:size];
|
||||
[blitEncoder endEncoding];
|
||||
@@ -106,7 +103,7 @@ id<MTLBuffer> MetalBuffer::getGpuBufferForDraw(id<MTLCommandBuffer> cmdBuffer) n
|
||||
return nil;
|
||||
}
|
||||
assert_invariant(mBuffer);
|
||||
return mBuffer;
|
||||
return mBuffer.get();
|
||||
}
|
||||
|
||||
void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncoder> encoder,
|
||||
|
||||
@@ -32,7 +32,7 @@ struct MetalContext;
|
||||
|
||||
// Immutable POD representing a shared CPU-GPU buffer.
|
||||
struct MetalBufferPoolEntry {
|
||||
id<MTLBuffer> buffer;
|
||||
TrackedMetalBuffer buffer;
|
||||
size_t capacity;
|
||||
mutable uint64_t lastAccessed;
|
||||
mutable uint32_t referenceCount;
|
||||
|
||||
@@ -48,10 +48,10 @@ MetalBufferPoolEntry const* MetalBufferPool::acquireBuffer(size_t numBytes) {
|
||||
buffer = [mContext.device newBufferWithLength:numBytes
|
||||
options:MTLResourceStorageModeShared];
|
||||
}
|
||||
MetalBufferTracking::track(buffer, MetalBufferTracking::Type::STAGING);
|
||||
ASSERT_POSTCONDITION(buffer, "Could not allocate Metal staging buffer of size %zu.", numBytes);
|
||||
FILAMENT_CHECK_POSTCONDITION(buffer)
|
||||
<< "Could not allocate Metal staging buffer of size " << numBytes << ".";
|
||||
MetalBufferPoolEntry* stage = new MetalBufferPoolEntry {
|
||||
.buffer = buffer,
|
||||
.buffer = { buffer, TrackedMetalBuffer::Type::STAGING },
|
||||
.capacity = numBytes,
|
||||
.lastAccessed = mCurrentFrame,
|
||||
.referenceCount = 1
|
||||
|
||||
@@ -55,6 +55,18 @@ struct MetalVertexBuffer;
|
||||
|
||||
constexpr static uint8_t MAX_SAMPLE_COUNT = 8; // Metal devices support at most 8 MSAA samples
|
||||
|
||||
class MetalPushConstantBuffer {
|
||||
public:
|
||||
void setPushConstant(PushConstantVariant value, uint8_t index);
|
||||
bool isDirty() const { return mDirty; }
|
||||
void setBytes(id<MTLCommandEncoder> encoder, ShaderStage stage);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
std::vector<PushConstantVariant> mPushConstants;
|
||||
bool mDirty = false;
|
||||
};
|
||||
|
||||
struct MetalContext {
|
||||
explicit MetalContext(size_t metalFreedTextureListSize)
|
||||
: texturesToDestroy(metalFreedTextureListSize) {}
|
||||
@@ -109,6 +121,8 @@ struct MetalContext {
|
||||
|
||||
PolygonOffset currentPolygonOffset = {0.0f, 0.0f};
|
||||
|
||||
std::array<MetalPushConstantBuffer, Program::SHADER_TYPE_COUNT> currentPushConstants;
|
||||
|
||||
MetalSamplerGroup* samplerBindings[Program::SAMPLER_BINDING_COUNT] = {};
|
||||
|
||||
// Keeps track of sampler groups we've finalized for the current render pass.
|
||||
|
||||
@@ -113,7 +113,8 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
|
||||
}
|
||||
}
|
||||
}];
|
||||
ASSERT_POSTCONDITION(context->pendingCommandBuffer, "Could not obtain command buffer.");
|
||||
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
|
||||
<< "Could not obtain command buffer.";
|
||||
return context->pendingCommandBuffer;
|
||||
}
|
||||
|
||||
@@ -153,5 +154,68 @@ bool isInRenderPass(MetalContext* context) {
|
||||
return context->currentRenderPassEncoder != nil;
|
||||
}
|
||||
|
||||
void MetalPushConstantBuffer::setPushConstant(PushConstantVariant value, uint8_t index) {
|
||||
if (mPushConstants.size() <= index) {
|
||||
mPushConstants.resize(index + 1);
|
||||
mDirty = true;
|
||||
}
|
||||
if (UTILS_LIKELY(mPushConstants[index] != value)) {
|
||||
mDirty = true;
|
||||
mPushConstants[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void MetalPushConstantBuffer::setBytes(id<MTLCommandEncoder> encoder, ShaderStage stage) {
|
||||
constexpr size_t PUSH_CONSTANT_SIZE_BYTES = 4;
|
||||
constexpr size_t PUSH_CONSTANT_BUFFER_INDEX = 26;
|
||||
|
||||
static char buffer[MAX_PUSH_CONSTANT_COUNT * PUSH_CONSTANT_SIZE_BYTES];
|
||||
assert_invariant(mPushConstants.size() <= MAX_PUSH_CONSTANT_COUNT);
|
||||
|
||||
size_t bufferSize = PUSH_CONSTANT_SIZE_BYTES * mPushConstants.size();
|
||||
for (size_t i = 0; i < mPushConstants.size(); i++) {
|
||||
const auto& constant = mPushConstants[i];
|
||||
std::visit(
|
||||
[i](auto arg) {
|
||||
if constexpr (std::is_same_v<decltype(arg), bool>) {
|
||||
// bool push constants are converted to uints in MSL.
|
||||
// We must ensure we write all the bytes for boolean values to work
|
||||
// correctly.
|
||||
uint32_t boolAsUint = arg ? 0x00000001 : 0x00000000;
|
||||
*(reinterpret_cast<uint32_t*>(buffer + PUSH_CONSTANT_SIZE_BYTES * i)) =
|
||||
boolAsUint;
|
||||
} else {
|
||||
*(decltype(arg)*)(buffer + PUSH_CONSTANT_SIZE_BYTES * i) = arg;
|
||||
}
|
||||
},
|
||||
constant);
|
||||
}
|
||||
|
||||
switch (stage) {
|
||||
case ShaderStage::VERTEX:
|
||||
[(id<MTLRenderCommandEncoder>)encoder setVertexBytes:buffer
|
||||
length:bufferSize
|
||||
atIndex:PUSH_CONSTANT_BUFFER_INDEX];
|
||||
break;
|
||||
case ShaderStage::FRAGMENT:
|
||||
[(id<MTLRenderCommandEncoder>)encoder setFragmentBytes:buffer
|
||||
length:bufferSize
|
||||
atIndex:PUSH_CONSTANT_BUFFER_INDEX];
|
||||
break;
|
||||
case ShaderStage::COMPUTE:
|
||||
[(id<MTLComputeCommandEncoder>)encoder setBytes:buffer
|
||||
length:bufferSize
|
||||
atIndex:PUSH_CONSTANT_BUFFER_INDEX];
|
||||
break;
|
||||
}
|
||||
|
||||
mDirty = false;
|
||||
}
|
||||
|
||||
void MetalPushConstantBuffer::clear() {
|
||||
mPushConstants.clear();
|
||||
mDirty = false;
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef TNT_FILAMENT_DRIVER_METALDRIVER_H
|
||||
#define TNT_FILAMENT_DRIVER_METALDRIVER_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include "private/backend/Driver.h"
|
||||
#include "DriverBase.h"
|
||||
|
||||
@@ -140,6 +141,7 @@ private:
|
||||
void enumerateBoundBuffers(BufferObjectBinding bindingType,
|
||||
const std::function<void(const BufferState&, MetalBuffer*, uint32_t)>& f);
|
||||
|
||||
backend::StereoscopicType const mStereoscopicType;
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
|
||||
@@ -102,12 +102,12 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
|
||||
mContext(new MetalContext(driverConfig.textureUseAfterFreePoolSize)),
|
||||
mHandleAllocator("Handles",
|
||||
driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck) {
|
||||
driverConfig.disableHandleUseAfterFreeCheck),
|
||||
mStereoscopicType(driverConfig.stereoscopicType) {
|
||||
mContext->driver = this;
|
||||
|
||||
TrackedMetalBuffer::setPlatform(platform);
|
||||
ScopedAllocationTimer::setPlatform(platform);
|
||||
MetalBufferTracking::initialize();
|
||||
MetalBufferTracking::setPlatform(platform);
|
||||
|
||||
mContext->device = mPlatform.createDevice();
|
||||
assert_invariant(mContext->device);
|
||||
@@ -181,7 +181,8 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
|
||||
|
||||
CVReturn success = CVMetalTextureCacheCreate(kCFAllocatorDefault, nullptr, mContext->device,
|
||||
nullptr, &mContext->textureCache);
|
||||
ASSERT_POSTCONDITION(success == kCVReturnSuccess, "Could not create Metal texture cache.");
|
||||
FILAMENT_CHECK_POSTCONDITION(success == kCVReturnSuccess)
|
||||
<< "Could not create Metal texture cache.";
|
||||
|
||||
if (@available(iOS 12, *)) {
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
|
||||
@@ -202,7 +203,7 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
|
||||
}
|
||||
|
||||
MetalDriver::~MetalDriver() noexcept {
|
||||
MetalBufferTracking::setPlatform(nullptr);
|
||||
TrackedMetalBuffer::setPlatform(nullptr);
|
||||
ScopedAllocationTimer::setPlatform(nullptr);
|
||||
mContext->device = nil;
|
||||
mContext->emptyTexture = nil;
|
||||
@@ -224,29 +225,26 @@ void MetalDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
os_signpost_interval_begin(mContext->log, mContext->signpostId, "Frame encoding", "%{public}d", frameId);
|
||||
#endif
|
||||
if (mPlatform.hasDebugUpdateStatFunc()) {
|
||||
#if FILAMENT_METAL_BUFFER_TRACKING
|
||||
const uint64_t generic = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::GENERIC);
|
||||
const uint64_t ring = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::RING);
|
||||
const uint64_t staging = MetalBufferTracking::getAliveBuffers(MetalBufferTracking::Type::STAGING);
|
||||
const uint64_t total = generic + ring + staging;
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers", total);
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers.generic", generic);
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers.ring", ring);
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers.staging", staging);
|
||||
#endif
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers", TrackedMetalBuffer::getAliveBuffers());
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers.generic",
|
||||
TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::GENERIC));
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers.ring",
|
||||
TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::RING));
|
||||
mPlatform.debugUpdateStat("filament.metal.alive_buffers.staging",
|
||||
TrackedMetalBuffer::getAliveBuffers(TrackedMetalBuffer::Type::STAGING));
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
FrameScheduledCallback callback, void* user) {
|
||||
void MetalDriver::setFrameScheduledCallback(
|
||||
Handle<HwSwapChain> sch, CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
auto* swapChain = handle_cast<MetalSwapChain>(sch);
|
||||
swapChain->setFrameScheduledCallback(callback, user);
|
||||
swapChain->setFrameScheduledCallback(handler, std::move(callback));
|
||||
}
|
||||
|
||||
void MetalDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
void MetalDriver::setFrameCompletedCallback(
|
||||
Handle<HwSwapChain> sch, CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
auto* swapChain = handle_cast<MetalSwapChain>(sch);
|
||||
swapChain->setFrameCompletedCallback(handler, callback, user);
|
||||
swapChain->setFrameCompletedCallback(handler, std::move(callback));
|
||||
}
|
||||
|
||||
void MetalDriver::execute(std::function<void(void)> const& fn) noexcept {
|
||||
@@ -292,14 +290,14 @@ void MetalDriver::endFrame(uint32_t frameId) {
|
||||
}
|
||||
|
||||
void MetalDriver::flush(int) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"flush must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "flush must be called outside of a render pass.";
|
||||
submitPendingCommands(mContext);
|
||||
}
|
||||
|
||||
void MetalDriver::finish(int) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"finish must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "finish must be called outside of a render pass.";
|
||||
// Wait for all frames to finish by submitting and waiting on a dummy command buffer.
|
||||
submitPendingCommands(mContext);
|
||||
id<MTLCommandBuffer> oneOffBuffer = [mContext->commandQueue commandBuffer];
|
||||
@@ -360,19 +358,19 @@ void MetalDriver::importTextureR(Handle<HwTexture> th, intptr_t i,
|
||||
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height,
|
||||
uint32_t depth, TextureUsage usage) {
|
||||
id<MTLTexture> metalTexture = (id<MTLTexture>) CFBridgingRelease((void*) i);
|
||||
ASSERT_PRECONDITION(metalTexture.width == width,
|
||||
"Imported id<MTLTexture> width (%d) != Filament texture width (%d)",
|
||||
metalTexture.width, width);
|
||||
ASSERT_PRECONDITION(metalTexture.height == height,
|
||||
"Imported id<MTLTexture> height (%d) != Filament texture height (%d)",
|
||||
metalTexture.height, height);
|
||||
ASSERT_PRECONDITION(metalTexture.mipmapLevelCount == levels,
|
||||
"Imported id<MTLTexture> levels (%d) != Filament texture levels (%d)",
|
||||
metalTexture.mipmapLevelCount, levels);
|
||||
FILAMENT_CHECK_PRECONDITION(metalTexture.width == width)
|
||||
<< "Imported id<MTLTexture> width (" << metalTexture.width
|
||||
<< ") != Filament texture width (" << width << ")";
|
||||
FILAMENT_CHECK_PRECONDITION(metalTexture.height == height)
|
||||
<< "Imported id<MTLTexture> height (" << metalTexture.height
|
||||
<< ") != Filament texture height (" << height << ")";
|
||||
FILAMENT_CHECK_PRECONDITION(metalTexture.mipmapLevelCount == levels)
|
||||
<< "Imported id<MTLTexture> levels (" << metalTexture.mipmapLevelCount
|
||||
<< ") != Filament texture levels (" << levels << ")";
|
||||
MTLTextureType filamentMetalType = getMetalType(target);
|
||||
ASSERT_PRECONDITION(metalTexture.textureType == filamentMetalType,
|
||||
"Imported id<MTLTexture> type (%d) != Filament texture type (%d)",
|
||||
metalTexture.textureType, filamentMetalType);
|
||||
FILAMENT_CHECK_PRECONDITION(metalTexture.textureType == filamentMetalType)
|
||||
<< "Imported id<MTLTexture> type (" << metalTexture.textureType
|
||||
<< ") != Filament texture type (" << filamentMetalType << ")";
|
||||
mContext->textures.insert(construct_handle<MetalTexture>(th, *mContext,
|
||||
target, levels, format, samples, width, height, depth, usage, metalTexture));
|
||||
}
|
||||
@@ -401,8 +399,8 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
TargetBufferFlags targetBufferFlags, uint32_t width, uint32_t height,
|
||||
uint8_t samples, uint8_t layerCount, MRT color,
|
||||
TargetBufferInfo depth, TargetBufferInfo stencil) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"createRenderTarget must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "createRenderTarget must be called outside of a render pass.";
|
||||
// Clamp sample count to what the device supports.
|
||||
auto& sc = mContext->sampleCountLookup;
|
||||
samples = sc[std::min(MAX_SAMPLE_COUNT, samples)];
|
||||
@@ -413,33 +411,33 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
continue;
|
||||
}
|
||||
const auto& buffer = color[i];
|
||||
ASSERT_PRECONDITION(buffer.handle,
|
||||
"The COLOR%u flag was specified, but invalid color handle provided.", i);
|
||||
FILAMENT_CHECK_PRECONDITION(buffer.handle)
|
||||
<< "The COLOR" << i << " flag was specified, but invalid color handle provided.";
|
||||
auto colorTexture = handle_cast<MetalTexture>(buffer.handle);
|
||||
ASSERT_PRECONDITION(colorTexture->getMtlTextureForWrite(),
|
||||
"Color texture passed to render target has no texture allocation");
|
||||
FILAMENT_CHECK_PRECONDITION(colorTexture->getMtlTextureForWrite())
|
||||
<< "Color texture passed to render target has no texture allocation";
|
||||
colorTexture->extendLodRangeTo(buffer.level);
|
||||
colorAttachments[i] = { colorTexture, color[i].level, color[i].layer };
|
||||
}
|
||||
|
||||
MetalRenderTarget::Attachment depthAttachment = {};
|
||||
if (any(targetBufferFlags & TargetBufferFlags::DEPTH)) {
|
||||
ASSERT_PRECONDITION(depth.handle,
|
||||
"The DEPTH flag was specified, but invalid depth handle provided.");
|
||||
FILAMENT_CHECK_PRECONDITION(depth.handle)
|
||||
<< "The DEPTH flag was specified, but invalid depth handle provided.";
|
||||
auto depthTexture = handle_cast<MetalTexture>(depth.handle);
|
||||
ASSERT_PRECONDITION(depthTexture->getMtlTextureForWrite(),
|
||||
"Depth texture passed to render target has no texture allocation.");
|
||||
FILAMENT_CHECK_PRECONDITION(depthTexture->getMtlTextureForWrite())
|
||||
<< "Depth texture passed to render target has no texture allocation.";
|
||||
depthTexture->extendLodRangeTo(depth.level);
|
||||
depthAttachment = { depthTexture, depth.level, depth.layer };
|
||||
}
|
||||
|
||||
MetalRenderTarget::Attachment stencilAttachment = {};
|
||||
if (any(targetBufferFlags & TargetBufferFlags::STENCIL)) {
|
||||
ASSERT_PRECONDITION(stencil.handle,
|
||||
"The STENCIL flag was specified, but invalid stencil handle provided.");
|
||||
FILAMENT_CHECK_PRECONDITION(stencil.handle)
|
||||
<< "The STENCIL flag was specified, but invalid stencil handle provided.";
|
||||
auto stencilTexture = handle_cast<MetalTexture>(stencil.handle);
|
||||
ASSERT_PRECONDITION(stencilTexture->getMtlTextureForWrite(),
|
||||
"Stencil texture passed to render target has no texture allocation.");
|
||||
FILAMENT_CHECK_PRECONDITION(stencilTexture->getMtlTextureForWrite())
|
||||
<< "Stencil texture passed to render target has no texture allocation.";
|
||||
stencilTexture->extendLodRangeTo(stencil.level);
|
||||
stencilAttachment = { stencilTexture, stencil.level, stencil.layer };
|
||||
}
|
||||
@@ -803,13 +801,15 @@ bool MetalDriver::isProtectedContentSupported() {
|
||||
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() {
|
||||
switch (mStereoscopicType) {
|
||||
case backend::StereoscopicType::INSTANCED:
|
||||
return true;
|
||||
case backend::StereoscopicType::MULTIVIEW:
|
||||
// TODO: implement multiview feature in Metal.
|
||||
return false;
|
||||
case backend::StereoscopicType::NONE:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,8 +882,8 @@ void MetalDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&
|
||||
|
||||
void MetalDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& data,
|
||||
uint32_t byteOffset) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"updateBufferObject must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "updateBufferObject must be called outside of a render pass.";
|
||||
auto* bo = handle_cast<MetalBufferObject>(boh);
|
||||
bo->updateBuffer(data.buffer, data.size, byteOffset);
|
||||
scheduleDestroy(std::move(data));
|
||||
@@ -922,8 +922,8 @@ void MetalDriver::update3DImage(Handle<HwTexture> th, uint32_t level,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
PixelBufferDescriptor&& data) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"update3DImage must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "update3DImage must be called outside of a render pass.";
|
||||
auto tex = handle_cast<MetalTexture>(th);
|
||||
tex->loadImage(level, MTLRegionMake3D(xoffset, yoffset, zoffset, width, height, depth), data);
|
||||
scheduleDestroy(std::move(data));
|
||||
@@ -939,15 +939,15 @@ void MetalDriver::setupExternalImage(void* image) {
|
||||
}
|
||||
|
||||
void MetalDriver::setExternalImage(Handle<HwTexture> th, void* image) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"setExternalImage must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "setExternalImage must be called outside of a render pass.";
|
||||
auto texture = handle_cast<MetalTexture>(th);
|
||||
texture->externalImage.set((CVPixelBufferRef) image);
|
||||
}
|
||||
|
||||
void MetalDriver::setExternalImagePlane(Handle<HwTexture> th, void* image, uint32_t plane) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"setExternalImagePlane must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "setExternalImagePlane must be called outside of a render pass.";
|
||||
auto texture = handle_cast<MetalTexture>(th);
|
||||
texture->externalImage.set((CVPixelBufferRef) image, plane);
|
||||
}
|
||||
@@ -962,15 +962,15 @@ TimerQueryResult MetalDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint6
|
||||
}
|
||||
|
||||
void MetalDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"generateMipmaps must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "generateMipmaps must be called outside of a render pass.";
|
||||
auto tex = handle_cast<MetalTexture>(th);
|
||||
tex->generateMipmaps();
|
||||
}
|
||||
|
||||
void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescriptor&& data) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"updateSamplerGroup must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "updateSamplerGroup must be called outside of a render pass.";
|
||||
|
||||
auto sb = handle_cast<MetalSamplerGroup>(sbh);
|
||||
assert_invariant(sb->size == data.size / sizeof(SamplerDescriptor));
|
||||
@@ -1109,6 +1109,10 @@ void MetalDriver::beginRenderPass(Handle<HwRenderTarget> rth,
|
||||
mContext->currentPolygonOffset = {0.0f, 0.0f};
|
||||
|
||||
mContext->finalizedSamplerGroups.clear();
|
||||
|
||||
for (auto& pc : mContext->currentPushConstants) {
|
||||
pc.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::nextSubpass(int dummy) {}
|
||||
@@ -1256,6 +1260,16 @@ void MetalDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||
mContext->samplerBindings[index] = sb;
|
||||
}
|
||||
|
||||
void MetalDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
FILAMENT_CHECK_PRECONDITION(isInRenderPass(mContext))
|
||||
<< "setPushConstant must be called inside a render pass.";
|
||||
assert_invariant(static_cast<size_t>(stage) < mContext->currentPushConstants.size());
|
||||
MetalPushConstantBuffer& pushConstants =
|
||||
mContext->currentPushConstants[static_cast<size_t>(stage)];
|
||||
pushConstants.setPushConstant(value, index);
|
||||
}
|
||||
|
||||
void MetalDriver::insertEventMarker(const char* string, uint32_t len) {
|
||||
|
||||
}
|
||||
@@ -1306,8 +1320,8 @@ void MetalDriver::stopCapture(int) {
|
||||
|
||||
void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y, uint32_t width,
|
||||
uint32_t height, PixelBufferDescriptor&& data) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"readPixels must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "readPixels must be called outside of a render pass.";
|
||||
|
||||
auto srcTarget = handle_cast<MetalRenderTarget>(src);
|
||||
// We always readPixels from the COLOR0 attachment.
|
||||
@@ -1321,17 +1335,19 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
|
||||
width = std::min(static_cast<uint32_t>(srcTextureSize.width), width);
|
||||
|
||||
const MTLPixelFormat format = getMetalFormat(data.format, data.type);
|
||||
ASSERT_PRECONDITION(format != MTLPixelFormatInvalid,
|
||||
"The chosen combination of PixelDataFormat (%d) and PixelDataType (%d) is not supported for "
|
||||
"readPixels.", (int) data.format, (int) data.type);
|
||||
FILAMENT_CHECK_PRECONDITION(format != MTLPixelFormatInvalid)
|
||||
<< "The chosen combination of PixelDataFormat (" << (int)data.format
|
||||
<< ") and PixelDataType (" << (int)data.type
|
||||
<< ") is not supported for "
|
||||
"readPixels.";
|
||||
|
||||
const bool formatConversionNecessary = srcTexture.pixelFormat != format;
|
||||
|
||||
// TODO: MetalBlitter does not currently support format conversions to integer types.
|
||||
// The format and type must match the source pixel format exactly.
|
||||
ASSERT_PRECONDITION(!formatConversionNecessary || !isMetalFormatInteger(format),
|
||||
"readPixels does not support integer format conversions from MTLPixelFormat (%d) to (%d).",
|
||||
(int) srcTexture.pixelFormat, (int) format);
|
||||
FILAMENT_CHECK_PRECONDITION(!formatConversionNecessary || !isMetalFormatInteger(format))
|
||||
<< "readPixels does not support integer format conversions from MTLPixelFormat ("
|
||||
<< (int)srcTexture.pixelFormat << ") to (" << (int)format << ").";
|
||||
|
||||
MTLTextureDescriptor* textureDescriptor =
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
|
||||
@@ -1397,31 +1413,31 @@ void MetalDriver::resolve(
|
||||
assert_invariant(srcTexture);
|
||||
assert_invariant(dstTexture);
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"resolve() cannot be invoked inside a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder == nil)
|
||||
<< "resolve() cannot be invoked inside a render pass.";
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height,
|
||||
"invalid resolve: src and dst sizes don't match");
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height)
|
||||
<< "invalid resolve: src and dst sizes don't match";
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1,
|
||||
"invalid resolve: src.samples=%u, dst.samples=%u",
|
||||
+srcTexture->samples, +dstTexture->samples);
|
||||
FILAMENT_CHECK_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1)
|
||||
<< "invalid resolve: src.samples=" << +srcTexture->samples
|
||||
<< ", dst.samples=" << +dstTexture->samples;
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
FILAMENT_CHECK_PRECONDITION(srcTexture->format == dstTexture->format)
|
||||
<< "src and dst texture format don't match";
|
||||
|
||||
ASSERT_PRECONDITION(!isDepthFormat(srcTexture->format),
|
||||
"can't resolve depth formats");
|
||||
FILAMENT_CHECK_PRECONDITION(!isDepthFormat(srcTexture->format))
|
||||
<< "can't resolve depth formats";
|
||||
|
||||
ASSERT_PRECONDITION(!isStencilFormat(srcTexture->format),
|
||||
"can't resolve stencil formats");
|
||||
FILAMENT_CHECK_PRECONDITION(!isStencilFormat(srcTexture->format))
|
||||
<< "can't resolve stencil formats";
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
FILAMENT_CHECK_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST))
|
||||
<< "texture doesn't have BLIT_DST";
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
FILAMENT_CHECK_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC))
|
||||
<< "texture doesn't have BLIT_SRC";
|
||||
|
||||
// FIXME: on metal the blit() call below always take the slow path (using a shader)
|
||||
|
||||
@@ -1446,21 +1462,22 @@ void MetalDriver::blit(
|
||||
assert_invariant(srcTexture);
|
||||
assert_invariant(dstTexture);
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"blit() cannot be invoked inside a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder == nil)
|
||||
<< "blit() cannot be invoked inside a render pass.";
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
FILAMENT_CHECK_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST))
|
||||
<< "texture doesn't have BLIT_DST";
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
FILAMENT_CHECK_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC))
|
||||
<< "texture doesn't have BLIT_SRC";
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
FILAMENT_CHECK_PRECONDITION(srcTexture->format == dstTexture->format)
|
||||
<< "src and dst texture format don't match";
|
||||
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcTexture->getMtlTextureForRead().textureType) &&
|
||||
isBlitableTextureType(dstTexture->getMtlTextureForWrite().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
isBlitableTextureType(srcTexture->getMtlTextureForRead().textureType) &&
|
||||
isBlitableTextureType(dstTexture->getMtlTextureForWrite().textureType))
|
||||
<< "Metal does not support blitting to/from non-2D textures.";
|
||||
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = SamplerMagFilter::NEAREST;
|
||||
@@ -1498,18 +1515,18 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
// It is called between beginFrame and endFrame, but should never be called in the middle of
|
||||
// a render pass.
|
||||
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder == nil,
|
||||
"blitDEPRECATED() cannot be invoked inside a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder == nil)
|
||||
<< "blitDEPRECATED() cannot be invoked inside a render pass.";
|
||||
|
||||
auto srcTarget = handle_cast<MetalRenderTarget>(src);
|
||||
auto dstTarget = handle_cast<MetalRenderTarget>(dst);
|
||||
|
||||
ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0,
|
||||
"blitDEPRECATED only supports COLOR0");
|
||||
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
|
||||
<< "blitDEPRECATED only supports COLOR0";
|
||||
|
||||
ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 &&
|
||||
dstRect.left >= 0 && dstRect.bottom >= 0,
|
||||
"Source and destination rects must be positive.");
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
srcRect.left >= 0 && srcRect.bottom >= 0 && dstRect.left >= 0 && dstRect.bottom >= 0)
|
||||
<< "Source and destination rects must be positive.";
|
||||
|
||||
auto isBlitableTextureType = [](MTLTextureType t) {
|
||||
return t == MTLTextureType2D || t == MTLTextureType2DMultisample ||
|
||||
@@ -1521,9 +1538,10 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
MetalRenderTarget::Attachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
|
||||
|
||||
if (srcColorAttachment && dstColorAttachment) {
|
||||
ASSERT_PRECONDITION(isBlitableTextureType(srcColorAttachment.getTexture().textureType) &&
|
||||
isBlitableTextureType(dstColorAttachment.getTexture().textureType),
|
||||
"Metal does not support blitting to/from non-2D textures.");
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
isBlitableTextureType(srcColorAttachment.getTexture().textureType) &&
|
||||
isBlitableTextureType(dstColorAttachment.getTexture().textureType))
|
||||
<< "Metal does not support blitting to/from non-2D textures.";
|
||||
|
||||
MetalBlitter::BlitArgs args{};
|
||||
args.filter = filter;
|
||||
@@ -1634,9 +1652,9 @@ void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::bindPipeline(PipelineState ps) {
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
|
||||
"bindPipeline() without a valid command encoder.");
|
||||
void MetalDriver::bindPipeline(PipelineState const& ps) {
|
||||
FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr)
|
||||
<< "bindPipeline() without a valid command encoder.";
|
||||
|
||||
MetalVertexBufferInfo const* const vbi =
|
||||
handle_cast<MetalVertexBufferInfo>(ps.vertexBufferInfo);
|
||||
@@ -1808,8 +1826,8 @@ void MetalDriver::bindPipeline(PipelineState ps) {
|
||||
}
|
||||
|
||||
void MetalDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
|
||||
"bindRenderPrimitive() without a valid command encoder.");
|
||||
FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr)
|
||||
<< "bindRenderPrimitive() without a valid command encoder.";
|
||||
|
||||
// Bind the user vertex buffers.
|
||||
MetalBuffer* vertexBuffers[MAX_VERTEX_BUFFER_COUNT] = {};
|
||||
@@ -1845,8 +1863,8 @@ void MetalDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
}
|
||||
|
||||
void MetalDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
|
||||
"draw() without a valid command encoder.");
|
||||
FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr)
|
||||
<< "draw() without a valid command encoder.";
|
||||
|
||||
// Bind uniform buffers.
|
||||
MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil };
|
||||
@@ -1862,6 +1880,14 @@ void MetalDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t inst
|
||||
UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT,
|
||||
uniformsToBind, offsets, Program::UNIFORM_BINDING_COUNT);
|
||||
|
||||
// Update push constants.
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
auto& pushConstants = mContext->currentPushConstants[i];
|
||||
if (UTILS_UNLIKELY(pushConstants.isDirty())) {
|
||||
pushConstants.setBytes(mContext->currentRenderPassEncoder, static_cast<ShaderStage>(i));
|
||||
}
|
||||
}
|
||||
|
||||
auto primitive = handle_cast<MetalRenderPrimitive>(mContext->currentRenderPrimitive);
|
||||
|
||||
MetalIndexBuffer* indexBuffer = primitive->indexBuffer;
|
||||
@@ -1887,8 +1913,8 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph,
|
||||
}
|
||||
|
||||
void MetalDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"dispatchCompute must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "dispatchCompute must be called outside of a render pass.";
|
||||
|
||||
auto mtlProgram = handle_cast<MetalProgram>(program);
|
||||
|
||||
@@ -1988,15 +2014,15 @@ void MetalDriver::scissor(Viewport scissorBox) {
|
||||
}
|
||||
|
||||
void MetalDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"beginTimerQuery must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "beginTimerQuery must be called outside of a render pass.";
|
||||
auto* tq = handle_cast<MetalTimerQuery>(tqh);
|
||||
mContext->timerQueryImpl->beginTimeElapsedQuery(tq);
|
||||
}
|
||||
|
||||
void MetalDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"endTimerQuery must be called outside of a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||
<< "endTimerQuery must be called outside of a render pass.";
|
||||
auto* tq = handle_cast<MetalTimerQuery>(tqh);
|
||||
mContext->timerQueryImpl->endTimeElapsedQuery(tq);
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ constexpr inline MTLIndexType getIndexType(size_t elementSize) noexcept {
|
||||
} else if (elementSize == 4) {
|
||||
return MTLIndexTypeUInt32;
|
||||
}
|
||||
ASSERT_POSTCONDITION(false, "Index element size not supported.");
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Index element size not supported.";
|
||||
}
|
||||
|
||||
constexpr inline MTLVertexFormat getMetalFormat(ElementType type, bool normalized) noexcept {
|
||||
@@ -100,7 +100,7 @@ constexpr inline MTLVertexFormat getMetalFormat(ElementType type, bool normalize
|
||||
case ElementType::SHORT4: return MTLVertexFormatShort4Normalized;
|
||||
case ElementType::USHORT4: return MTLVertexFormatUShort4Normalized;
|
||||
default:
|
||||
ASSERT_POSTCONDITION(false, "Normalized format does not exist.");
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Normalized format does not exist.";
|
||||
return MTLVertexFormatInvalid;
|
||||
}
|
||||
}
|
||||
@@ -326,7 +326,8 @@ constexpr inline MTLCullMode getMetalCullMode(CullingMode cullMode) noexcept {
|
||||
case CullingMode::FRONT: return MTLCullModeFront;
|
||||
case CullingMode::BACK: return MTLCullModeBack;
|
||||
case CullingMode::FRONT_AND_BACK:
|
||||
ASSERT_POSTCONDITION(false, "FRONT_AND_BACK culling is not supported in Metal.");
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "FRONT_AND_BACK culling is not supported in Metal.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
auto description = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding]; \
|
||||
utils::slog.e << description << utils::io::endl; \
|
||||
} \
|
||||
ASSERT_POSTCONDITION(error == nil, message);
|
||||
FILAMENT_CHECK_POSTCONDITION(error == nil) << message;
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
@@ -86,14 +86,14 @@ void MetalExternalImage::set(CVPixelBufferRef image) noexcept {
|
||||
}
|
||||
|
||||
OSType formatType = CVPixelBufferGetPixelFormatType(image);
|
||||
ASSERT_POSTCONDITION(formatType == kCVPixelFormatType_32BGRA ||
|
||||
formatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
|
||||
"Metal external images must be in either 32BGRA or 420f format.");
|
||||
FILAMENT_CHECK_POSTCONDITION(formatType == kCVPixelFormatType_32BGRA ||
|
||||
formatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
|
||||
<< "Metal external images must be in either 32BGRA or 420f format.";
|
||||
|
||||
size_t planeCount = CVPixelBufferGetPlaneCount(image);
|
||||
ASSERT_POSTCONDITION(planeCount == 0 || planeCount == 2,
|
||||
"The Metal backend does not support images with plane counts of %d.", planeCount);
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(planeCount == 0 || planeCount == 2)
|
||||
<< "The Metal backend does not support images with plane counts of " << planeCount
|
||||
<< ".";
|
||||
|
||||
if (planeCount == 0) {
|
||||
mImage = image;
|
||||
@@ -138,8 +138,8 @@ void MetalExternalImage::set(CVPixelBufferRef image, size_t plane) noexcept {
|
||||
}
|
||||
|
||||
const OSType formatType = CVPixelBufferGetPixelFormatType(image);
|
||||
ASSERT_POSTCONDITION(formatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
|
||||
"Metal planar external images must be in the 420f format.");
|
||||
FILAMENT_CHECK_POSTCONDITION(formatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
|
||||
<< "Metal planar external images must be in the 420f format.";
|
||||
|
||||
mImage = image;
|
||||
|
||||
@@ -191,8 +191,8 @@ CVMetalTextureRef MetalExternalImage::createTextureFromImage(CVPixelBufferRef im
|
||||
CVMetalTextureRef texture;
|
||||
CVReturn result = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
|
||||
mContext.textureCache, image, nullptr, format, width, height, plane, &texture);
|
||||
ASSERT_POSTCONDITION(result == kCVReturnSuccess,
|
||||
"Could not create a CVMetalTexture from CVPixelBuffer.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == kCVReturnSuccess)
|
||||
<< "Could not create a CVMetalTexture from CVPixelBuffer.";
|
||||
|
||||
return texture;
|
||||
}
|
||||
@@ -203,8 +203,8 @@ void MetalExternalImage::shutdown(MetalContext& context) noexcept {
|
||||
|
||||
void MetalExternalImage::assertWritableImage(CVPixelBufferRef image) {
|
||||
OSType formatType = CVPixelBufferGetPixelFormatType(image);
|
||||
ASSERT_PRECONDITION(formatType == kCVPixelFormatType_32BGRA,
|
||||
"Metal SwapChain images must be in the 32BGRA format.");
|
||||
FILAMENT_CHECK_PRECONDITION(formatType == kCVPixelFormatType_32BGRA)
|
||||
<< "Metal SwapChain images must be in the 32BGRA format.";
|
||||
}
|
||||
|
||||
void MetalExternalImage::unset() {
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
@@ -71,9 +73,9 @@ public:
|
||||
|
||||
void releaseDrawable();
|
||||
|
||||
void setFrameScheduledCallback(FrameScheduledCallback callback, void* user);
|
||||
void setFrameCompletedCallback(CallbackHandler* handler,
|
||||
CallbackHandler::Callback callback, void* user);
|
||||
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback);
|
||||
void setFrameCompletedCallback(
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback);
|
||||
|
||||
// For CAMetalLayer-backed SwapChains, presents the drawable or schedules a
|
||||
// FrameScheduledCallback.
|
||||
@@ -107,22 +109,23 @@ private:
|
||||
NSUInteger headlessWidth = 0;
|
||||
NSUInteger headlessHeight = 0;
|
||||
CAMetalLayer* layer = nullptr;
|
||||
std::mutex layerDrawableMutex;
|
||||
MetalExternalImage externalImage;
|
||||
SwapChainType type;
|
||||
|
||||
// These two fields store a callback and user data to notify the client that a frame is ready
|
||||
// for presentation.
|
||||
// If frameScheduledCallback is nullptr, then the Metal backend automatically calls
|
||||
// presentDrawable when the frame is committed.
|
||||
// Otherwise, the Metal backend will not automatically present the frame. Instead, clients bear
|
||||
// the responsibility of presenting the frame by calling the PresentCallable object.
|
||||
FrameScheduledCallback frameScheduledCallback = nullptr;
|
||||
void* frameScheduledUserData = nullptr;
|
||||
// These fields store a callback to notify the client that a frame is ready for presentation. If
|
||||
// !frameScheduled.callback, then the Metal backend automatically calls presentDrawable when the
|
||||
// frame is committed. Otherwise, the Metal backend will not automatically present the frame.
|
||||
// Instead, clients bear the responsibility of presenting the frame by calling the
|
||||
// PresentCallable object.
|
||||
struct {
|
||||
CallbackHandler* handler = nullptr;
|
||||
std::shared_ptr<FrameScheduledCallback> callback = nullptr;
|
||||
} frameScheduled;
|
||||
|
||||
struct {
|
||||
CallbackHandler* handler = nullptr;
|
||||
CallbackHandler::Callback callback = {};
|
||||
void* user = nullptr;
|
||||
std::shared_ptr<utils::Invocable<void(void)>> callback = nullptr;
|
||||
} frameCompleted;
|
||||
};
|
||||
|
||||
|
||||
@@ -174,13 +174,21 @@ id<MTLTexture> MetalSwapChain::acquireDrawable() {
|
||||
}
|
||||
|
||||
assert_invariant(isCaMetalLayer());
|
||||
drawable = [layer nextDrawable];
|
||||
|
||||
ASSERT_POSTCONDITION(drawable != nil, "Could not obtain drawable.");
|
||||
// CAMetalLayer's drawable pool is not thread safe. Use a mutex when
|
||||
// calling -nextDrawable, or when releasing the last known reference
|
||||
// to any CAMetalDrawable returned from a previous -nextDrawable.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(layerDrawableMutex);
|
||||
drawable = [layer nextDrawable];
|
||||
}
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable.";
|
||||
return drawable.texture;
|
||||
}
|
||||
|
||||
void MetalSwapChain::releaseDrawable() {
|
||||
std::lock_guard<std::mutex> lock(layerDrawableMutex);
|
||||
drawable = nil;
|
||||
}
|
||||
|
||||
@@ -221,16 +229,16 @@ void MetalSwapChain::ensureDepthStencilTexture() {
|
||||
depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
|
||||
}
|
||||
|
||||
void MetalSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void* user) {
|
||||
frameScheduledCallback = callback;
|
||||
frameScheduledUserData = user;
|
||||
void MetalSwapChain::setFrameScheduledCallback(
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
frameScheduled.handler = handler;
|
||||
frameScheduled.callback = std::make_shared<FrameScheduledCallback>(std::move(callback));
|
||||
}
|
||||
|
||||
void MetalSwapChain::setFrameCompletedCallback(CallbackHandler* handler,
|
||||
CallbackHandler::Callback callback, void* user) {
|
||||
void MetalSwapChain::setFrameCompletedCallback(
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
frameCompleted.handler = handler;
|
||||
frameCompleted.callback = callback;
|
||||
frameCompleted.user = user;
|
||||
frameCompleted.callback = std::make_shared<utils::Invocable<void(void)>>(std::move(callback));
|
||||
}
|
||||
|
||||
void MetalSwapChain::present() {
|
||||
@@ -238,7 +246,7 @@ void MetalSwapChain::present() {
|
||||
scheduleFrameCompletedCallback();
|
||||
}
|
||||
if (drawable) {
|
||||
if (frameScheduledCallback) {
|
||||
if (frameScheduled.callback) {
|
||||
scheduleFrameScheduledCallback();
|
||||
} else {
|
||||
[getPendingCommandBuffer(&context) presentDrawable:drawable];
|
||||
@@ -256,9 +264,11 @@ public:
|
||||
PresentDrawableData(const PresentDrawableData&) = delete;
|
||||
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
|
||||
|
||||
static PresentDrawableData* create(id<CAMetalDrawable> drawable, MetalDriver* driver) {
|
||||
static PresentDrawableData* create(id<CAMetalDrawable> drawable,
|
||||
std::mutex* drawableMutex, MetalDriver* driver) {
|
||||
assert_invariant(drawableMutex);
|
||||
assert_invariant(driver);
|
||||
return new PresentDrawableData(drawable, driver);
|
||||
return new PresentDrawableData(drawable, drawableMutex, driver);
|
||||
}
|
||||
|
||||
static void maybePresentAndDestroyAsync(PresentDrawableData* that, bool shouldPresent) {
|
||||
@@ -277,16 +287,22 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
PresentDrawableData(id<CAMetalDrawable> drawable, MetalDriver* driver)
|
||||
: mDrawable(drawable), mDriver(driver) {}
|
||||
PresentDrawableData(id<CAMetalDrawable> drawable, std::mutex* drawableMutex,
|
||||
MetalDriver* driver)
|
||||
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver) {}
|
||||
|
||||
static void cleanupAndDestroy(PresentDrawableData *that) {
|
||||
that->mDrawable = nil;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*(that->mDrawableMutex));
|
||||
that->mDrawable = nil;
|
||||
}
|
||||
that->mDrawableMutex = nullptr;
|
||||
that->mDriver = nullptr;
|
||||
delete that;
|
||||
}
|
||||
|
||||
id<CAMetalDrawable> mDrawable;
|
||||
std::mutex* mDrawableMutex = nullptr;
|
||||
MetalDriver* mDriver = nullptr;
|
||||
};
|
||||
|
||||
@@ -296,21 +312,38 @@ void presentDrawable(bool presentFrame, void* user) {
|
||||
}
|
||||
|
||||
void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
if (!frameScheduledCallback) {
|
||||
if (!frameScheduled.callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert_invariant(drawable);
|
||||
|
||||
// Destroy this by calling maybePresentAndDestroyAsync() later.
|
||||
auto* presentData = PresentDrawableData::create(drawable, context.driver);
|
||||
struct Callback {
|
||||
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
|
||||
std::mutex* drawableMutex, MetalDriver* driver)
|
||||
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver)) {}
|
||||
std::shared_ptr<FrameScheduledCallback> f;
|
||||
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
|
||||
std::unique_ptr<PresentDrawableData> data;
|
||||
static void func(void* user) {
|
||||
auto* const c = reinterpret_cast<Callback*>(user);
|
||||
PresentDrawableData* presentDrawableData = c->data.release();
|
||||
PresentCallable presentCallable(presentDrawable, presentDrawableData);
|
||||
c->f->operator()(presentCallable);
|
||||
delete c;
|
||||
}
|
||||
};
|
||||
|
||||
FrameScheduledCallback userCallback = frameScheduledCallback;
|
||||
void* userData = frameScheduledUserData;
|
||||
// This callback pointer will be captured by the block. Even if the scheduled handler is never
|
||||
// called, the unique_ptr will still ensure we don't leak memory.
|
||||
__block auto callback = std::make_unique<Callback>(
|
||||
frameScheduled.callback, drawable, &layerDrawableMutex, context.driver);
|
||||
|
||||
backend::CallbackHandler* handler = frameScheduled.handler;
|
||||
MetalDriver* driver = context.driver;
|
||||
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
|
||||
PresentCallable callable(presentDrawable, static_cast<void*>(presentData));
|
||||
userCallback(callable, userData);
|
||||
Callback* user = callback.release();
|
||||
driver->scheduleCallback(handler, user, &Callback::func);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -319,13 +352,25 @@ void MetalSwapChain::scheduleFrameCompletedCallback() {
|
||||
return;
|
||||
}
|
||||
|
||||
CallbackHandler* handler = frameCompleted.handler;
|
||||
void* user = frameCompleted.user;
|
||||
CallbackHandler::Callback callback = frameCompleted.callback;
|
||||
struct Callback {
|
||||
Callback(std::shared_ptr<utils::Invocable<void(void)>> callback) : f(callback) {}
|
||||
std::shared_ptr<utils::Invocable<void(void)>> f;
|
||||
static void func(void* user) {
|
||||
auto* const c = reinterpret_cast<Callback*>(user);
|
||||
c->f->operator()();
|
||||
delete c;
|
||||
}
|
||||
};
|
||||
|
||||
// This callback pointer will be captured by the block. Even if the completed handler is never
|
||||
// called, the unique_ptr will still ensure we don't leak memory.
|
||||
__block auto callback = std::make_unique<Callback>(frameCompleted.callback);
|
||||
|
||||
CallbackHandler* handler = frameCompleted.handler;
|
||||
MetalDriver* driver = context.driver;
|
||||
[getPendingCommandBuffer(&context) addCompletedHandler:^(id<MTLCommandBuffer> cb) {
|
||||
driver->scheduleCallback(handler, user, callback);
|
||||
Callback* user = callback.release();
|
||||
driver->scheduleCallback(handler, user, &Callback::func);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -464,15 +509,16 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
externalImage(context, r, g, b, a) {
|
||||
|
||||
devicePixelFormat = decidePixelFormat(&context, format);
|
||||
ASSERT_POSTCONDITION(devicePixelFormat != MTLPixelFormatInvalid, "Texture format not supported.");
|
||||
FILAMENT_CHECK_POSTCONDITION(devicePixelFormat != MTLPixelFormatInvalid)
|
||||
<< "Texture format not supported.";
|
||||
|
||||
const BOOL mipmapped = levels > 1;
|
||||
const BOOL multisampled = samples > 1;
|
||||
|
||||
#if defined(IOS)
|
||||
const BOOL textureArray = target == SamplerType::SAMPLER_2D_ARRAY;
|
||||
ASSERT_PRECONDITION(!textureArray || !multisampled,
|
||||
"iOS does not support multisampled texture arrays.");
|
||||
FILAMENT_CHECK_PRECONDITION(!textureArray || !multisampled)
|
||||
<< "iOS does not support multisampled texture arrays.";
|
||||
#endif
|
||||
|
||||
const auto get2DTextureType = [](SamplerType target, bool isMultisampled) {
|
||||
@@ -507,12 +553,14 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
descriptor.usage = getMetalTextureUsage(usage);
|
||||
descriptor.storageMode = MTLStorageModePrivate;
|
||||
texture = [context.device newTextureWithDescriptor:descriptor];
|
||||
ASSERT_POSTCONDITION(texture != nil, "Could not create Metal texture. Out of memory?");
|
||||
FILAMENT_CHECK_POSTCONDITION(texture != nil)
|
||||
<< "Could not create Metal texture. Out of memory?";
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
ASSERT_POSTCONDITION(!multisampled, "Multisampled cubemap faces not supported.");
|
||||
ASSERT_POSTCONDITION(width == height, "Cubemap faces must be square.");
|
||||
FILAMENT_CHECK_POSTCONDITION(!multisampled)
|
||||
<< "Multisampled cubemap faces not supported.";
|
||||
FILAMENT_CHECK_POSTCONDITION(width == height) << "Cubemap faces must be square.";
|
||||
descriptor = [MTLTextureDescriptor textureCubeDescriptorWithPixelFormat:devicePixelFormat
|
||||
size:width
|
||||
mipmapped:mipmapped];
|
||||
@@ -521,7 +569,8 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
descriptor.usage = getMetalTextureUsage(usage);
|
||||
descriptor.storageMode = MTLStorageModePrivate;
|
||||
texture = [context.device newTextureWithDescriptor:descriptor];
|
||||
ASSERT_POSTCONDITION(texture != nil, "Could not create Metal texture. Out of memory?");
|
||||
FILAMENT_CHECK_POSTCONDITION(texture != nil)
|
||||
<< "Could not create Metal texture. Out of memory?";
|
||||
break;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
descriptor = [MTLTextureDescriptor new];
|
||||
@@ -534,7 +583,8 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
descriptor.usage = getMetalTextureUsage(usage);
|
||||
descriptor.storageMode = MTLStorageModePrivate;
|
||||
texture = [context.device newTextureWithDescriptor:descriptor];
|
||||
ASSERT_POSTCONDITION(texture != nil, "Could not create Metal texture. Out of memory?");
|
||||
FILAMENT_CHECK_POSTCONDITION(texture != nil)
|
||||
<< "Could not create Metal texture. Out of memory?";
|
||||
break;
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
// If we're using external textures (CVPixelBufferRefs), we don't need to make any
|
||||
@@ -736,9 +786,9 @@ void MetalTexture::loadSlice(uint32_t level, MTLRegion region, uint32_t byteOffs
|
||||
PixelBufferDescriptor const& data) noexcept {
|
||||
const PixelBufferShape shape = PixelBufferShape::compute(data, format, region.size, byteOffset);
|
||||
|
||||
ASSERT_PRECONDITION(data.size >= shape.totalBytes,
|
||||
"Expected buffer size of at least %d but "
|
||||
"received PixelBufferDescriptor with size %d.", shape.totalBytes, data.size);
|
||||
FILAMENT_CHECK_PRECONDITION(data.size >= shape.totalBytes)
|
||||
<< "Expected buffer size of at least " << shape.totalBytes
|
||||
<< " but received PixelBufferDescriptor with size " << data.size << ".";
|
||||
|
||||
// Earlier versions of iOS don't have the maxBufferLength query, but 256 MB is a safe bet.
|
||||
NSUInteger deviceMaxBufferLength = 256 * 1024 * 1024; // 256 MB
|
||||
@@ -771,13 +821,13 @@ void MetalTexture::loadWithCopyBuffer(uint32_t level, uint32_t slice, MTLRegion
|
||||
PixelBufferDescriptor const& data, const PixelBufferShape& shape) {
|
||||
const size_t stagingBufferSize = shape.totalBytes;
|
||||
auto entry = context.bufferPool->acquireBuffer(stagingBufferSize);
|
||||
memcpy(entry->buffer.contents,
|
||||
memcpy(entry->buffer.get().contents,
|
||||
static_cast<uint8_t*>(data.buffer) + shape.sourceOffset,
|
||||
stagingBufferSize);
|
||||
id<MTLCommandBuffer> blitCommandBuffer = getPendingCommandBuffer(&context);
|
||||
id<MTLBlitCommandEncoder> blitCommandEncoder = [blitCommandBuffer blitCommandEncoder];
|
||||
blitCommandEncoder.label = @"Texture upload buffer blit";
|
||||
[blitCommandEncoder copyFromBuffer:entry->buffer
|
||||
[blitCommandEncoder copyFromBuffer:entry->buffer.get()
|
||||
sourceOffset:0
|
||||
sourceBytesPerRow:shape.bytesPerRow
|
||||
sourceBytesPerImage:shape.bytesPerSlice
|
||||
@@ -959,9 +1009,9 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
|
||||
}
|
||||
color[i] = colorAttachments[i];
|
||||
|
||||
ASSERT_PRECONDITION(color[i].getSampleCount() <= samples,
|
||||
"MetalRenderTarget was initialized with a MSAA COLOR%d texture, but sample count is %d.",
|
||||
i, samples);
|
||||
FILAMENT_CHECK_PRECONDITION(color[i].getSampleCount() <= samples)
|
||||
<< "MetalRenderTarget was initialized with a MSAA COLOR" << i
|
||||
<< " texture, but sample count is " << samples << ".";
|
||||
|
||||
auto t = color[i].metalTexture;
|
||||
const auto twidth = std::max(1u, t->width >> color[i].level);
|
||||
@@ -984,9 +1034,10 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
|
||||
if (depthAttachment) {
|
||||
depth = depthAttachment;
|
||||
|
||||
ASSERT_PRECONDITION(depth.getSampleCount() <= samples,
|
||||
"MetalRenderTarget was initialized with a MSAA DEPTH texture, but sample count is %d.",
|
||||
samples);
|
||||
FILAMENT_CHECK_PRECONDITION(depth.getSampleCount() <= samples)
|
||||
<< "MetalRenderTarget was initialized with a MSAA DEPTH texture, but sample count "
|
||||
"is "
|
||||
<< samples << ".";
|
||||
|
||||
auto t = depth.metalTexture;
|
||||
const auto twidth = std::max(1u, t->width >> depth.level);
|
||||
@@ -1009,9 +1060,10 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
|
||||
if (stencilAttachment) {
|
||||
stencil = stencilAttachment;
|
||||
|
||||
ASSERT_PRECONDITION(stencil.getSampleCount() <= samples,
|
||||
"MetalRenderTarget was initialized with a MSAA STENCIL texture, but sample count is %d.",
|
||||
samples);
|
||||
FILAMENT_CHECK_PRECONDITION(stencil.getSampleCount() <= samples)
|
||||
<< "MetalRenderTarget was initialized with a MSAA STENCIL texture, but sample "
|
||||
"count is "
|
||||
<< samples << ".";
|
||||
|
||||
auto t = stencil.metalTexture;
|
||||
const auto twidth = std::max(1u, t->width >> stencil.level);
|
||||
|
||||
@@ -43,7 +43,8 @@ inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) {
|
||||
// ------------------------------------------------------
|
||||
// 0 Zero buffer (placeholder vertex buffer) 1
|
||||
// 1-16 Filament vertex buffers 16 limited by MAX_VERTEX_BUFFER_COUNT
|
||||
// 17-26 Uniform buffers 10 Program::UNIFORM_BINDING_COUNT
|
||||
// 17-25 Uniform buffers 9 Program::UNIFORM_BINDING_COUNT
|
||||
// 26 Push constants 1
|
||||
// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT
|
||||
//
|
||||
// Total 31
|
||||
@@ -53,7 +54,8 @@ inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) {
|
||||
// Bindings Buffer name Count
|
||||
// ------------------------------------------------------
|
||||
// 0-3 SSBO buffers 4 MAX_SSBO_COUNT
|
||||
// 17-26 Uniform buffers 10 Program::UNIFORM_BINDING_COUNT
|
||||
// 17-25 Uniform buffers 9 Program::UNIFORM_BINDING_COUNT
|
||||
// 26 Push constants 1
|
||||
// 27-30 Sampler groups (argument buffers) 4 Program::SAMPLER_BINDING_COUNT
|
||||
//
|
||||
// Total 18
|
||||
|
||||
@@ -90,11 +90,17 @@ id<MTLRenderPipelineState> PipelineStateCreator::operator()(id<MTLDevice> device
|
||||
NSError* error = nullptr;
|
||||
id<MTLRenderPipelineState> pipeline = [device newRenderPipelineStateWithDescriptor:descriptor
|
||||
error:&error];
|
||||
if (error) {
|
||||
auto description = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
if (UTILS_UNLIKELY(pipeline == nil)) {
|
||||
NSString *errorMessage =
|
||||
[NSString stringWithFormat:@"Could not create Metal pipeline state: %@",
|
||||
error ? error.localizedDescription : @"unknown error"];
|
||||
auto description = [errorMessage cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
utils::slog.e << description << utils::io::endl;
|
||||
[[NSException exceptionWithName:@"MetalRenderPipelineFailure"
|
||||
reason:errorMessage
|
||||
userInfo:nil] raise];
|
||||
}
|
||||
ASSERT_POSTCONDITION(error == nil, "Could not create Metal pipeline state.");
|
||||
FILAMENT_CHECK_POSTCONDITION(error == nil) << "Could not create Metal pipeline state.";
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -54,12 +54,12 @@ void NoopDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
}
|
||||
|
||||
void NoopDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
FrameScheduledCallback callback, void* user) {
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
|
||||
}
|
||||
|
||||
void NoopDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ bool NoopDriver::isProtectedContentSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isStereoSupported(backend::StereoscopicType) {
|
||||
bool NoopDriver::isStereoSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -312,6 +312,10 @@ void NoopDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
|
||||
void NoopDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||
}
|
||||
|
||||
void NoopDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
}
|
||||
|
||||
void NoopDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
}
|
||||
|
||||
@@ -355,7 +359,7 @@ void NoopDriver::blit(
|
||||
math::uint2 size) {
|
||||
}
|
||||
|
||||
void NoopDriver::bindPipeline(PipelineState pipelineState) {
|
||||
void NoopDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
}
|
||||
|
||||
void NoopDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
|
||||
@@ -66,7 +66,8 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
|
||||
OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
|
||||
Platform::DriverConfig const& driverConfig) noexcept
|
||||
: mPlatform(platform),
|
||||
mSamplerMap(32) {
|
||||
mSamplerMap(32),
|
||||
mDriverConfig(driverConfig) {
|
||||
|
||||
state.vao.p = &mDefaultVAO;
|
||||
|
||||
@@ -366,7 +367,8 @@ void OpenGLContext::setDefaultState() noexcept {
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ext.EXT_clip_cull_distance) {
|
||||
if (ext.EXT_clip_cull_distance
|
||||
&& mDriverConfig.stereoscopicType == StereoscopicType::INSTANCED) {
|
||||
glEnable(GL_CLIP_DISTANCE0);
|
||||
glEnable(GL_CLIP_DISTANCE1);
|
||||
}
|
||||
@@ -894,12 +896,16 @@ void OpenGLContext::unbindTexture(
|
||||
// unbind this texture from all the units it might be bound to
|
||||
// no need unbind the texture from FBOs because we're not tracking that state (and there is
|
||||
// no need to).
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
|
||||
if (state.textures.units[unit].id == texture_id) {
|
||||
// if this texture is bound, it should be at the same target
|
||||
assert_invariant(state.textures.units[unit].target == target);
|
||||
unbindTextureUnit(unit);
|
||||
// Never attempt to unbind texture 0. This could happen with external textures w/ streaming if
|
||||
// never populated.
|
||||
if (texture_id) {
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint unit = 0; unit < MAX_TEXTURE_UNIT_COUNT; unit++) {
|
||||
if (state.textures.units[unit].id == texture_id) {
|
||||
// if this texture is bound, it should be at the same target
|
||||
assert_invariant(state.textures.units[unit].target == target);
|
||||
unbindTextureUnit(unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,6 +511,8 @@ private:
|
||||
mutable tsl::robin_map<SamplerParams, GLuint,
|
||||
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
|
||||
|
||||
Platform::DriverConfig const mDriverConfig;
|
||||
|
||||
void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept;
|
||||
|
||||
const std::array<std::tuple<bool const&, char const*, char const*>, sizeof(bugs)> mBugDatabase{{
|
||||
|
||||
@@ -212,7 +212,8 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
|
||||
mHandleAllocator("Handles",
|
||||
driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck),
|
||||
mDriverConfig(driverConfig) {
|
||||
mDriverConfig(driverConfig),
|
||||
mCurrentPushConstants(new(std::nothrow) PushConstantBundle{}) {
|
||||
|
||||
std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr);
|
||||
|
||||
@@ -240,7 +241,15 @@ OpenGLDriver::~OpenGLDriver() noexcept { // NOLINT(modernize-use-equals-default)
|
||||
}
|
||||
|
||||
Dispatcher OpenGLDriver::getDispatcher() const noexcept {
|
||||
return ConcreteDispatcher<OpenGLDriver>::make();
|
||||
auto dispatcher = ConcreteDispatcher<OpenGLDriver>::make();
|
||||
if (mContext.isES2()) {
|
||||
dispatcher.draw2_ = +[](Driver& driver, CommandBase* base, intptr_t* next){
|
||||
using Cmd = COMMAND_TYPE(draw2);
|
||||
OpenGLDriver& concreteDriver = static_cast<OpenGLDriver&>(driver);
|
||||
Cmd::execute(&OpenGLDriver::draw2GLES2, concreteDriver, base, next);
|
||||
};
|
||||
}
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -269,6 +278,9 @@ void OpenGLDriver::terminate() {
|
||||
assert_invariant(mGpuCommandCompleteOps.empty());
|
||||
#endif
|
||||
|
||||
delete mCurrentPushConstants;
|
||||
mCurrentPushConstants = nullptr;
|
||||
|
||||
mContext.terminate();
|
||||
|
||||
mPlatform.terminate();
|
||||
@@ -289,6 +301,39 @@ void OpenGLDriver::bindSampler(GLuint unit, GLuint sampler) noexcept {
|
||||
mContext.bindSampler(unit, sampler);
|
||||
}
|
||||
|
||||
void OpenGLDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
assert_invariant(stage == ShaderStage::VERTEX || stage == ShaderStage::FRAGMENT);
|
||||
utils::Slice<std::pair<GLint, ConstantType>> constants;
|
||||
if (stage == ShaderStage::VERTEX) {
|
||||
constants = mCurrentPushConstants->vertexConstants;
|
||||
} else if (stage == ShaderStage::FRAGMENT) {
|
||||
constants = mCurrentPushConstants->fragmentConstants;
|
||||
}
|
||||
|
||||
assert_invariant(index < constants.size());
|
||||
auto const& [location, type] = constants[index];
|
||||
|
||||
// This push constant wasn't found in the shader. It's ok to return without error-ing here.
|
||||
if (location < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::holds_alternative<bool>(value)) {
|
||||
assert_invariant(type == ConstantType::BOOL);
|
||||
bool const bval = std::get<bool>(value);
|
||||
glUniform1i(location, bval ? 1 : 0);
|
||||
} else if (std::holds_alternative<float>(value)) {
|
||||
assert_invariant(type == ConstantType::FLOAT);
|
||||
float const fval = std::get<float>(value);
|
||||
glUniform1f(location, fval);
|
||||
} else {
|
||||
assert_invariant(type == ConstantType::INT);
|
||||
int const ival = std::get<int>(value);
|
||||
glUniform1i(location, ival);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
|
||||
assert_invariant(t != nullptr);
|
||||
mContext.bindTexture(unit, t->gl.target, t->gl.id);
|
||||
@@ -887,16 +932,18 @@ 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.
|
||||
// NOTE: this is called often and must be as efficient as possible.
|
||||
|
||||
auto& gl = mContext;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (UTILS_LIKELY(gl.ext.OES_vertex_array_object)) {
|
||||
// The VAO for the given render primitive must already be bound.
|
||||
GLint vaoBinding;
|
||||
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vaoBinding);
|
||||
assert_invariant(vaoBinding == (GLint)rp->gl.vao[gl.contextIndex]);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (UTILS_LIKELY(rp->gl.vertexBufferVersion == vb->bufferObjectsVersion &&
|
||||
rp->gl.stateVersion == gl.state.age)) {
|
||||
@@ -912,7 +959,7 @@ void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer
|
||||
// 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
|
||||
// if we're on ES2, the user shouldn't use FLAG_INTEGER_TARGET
|
||||
assert_invariant(!(gl.isES2() && (attribute.flags & Attribute::FLAG_INTEGER_TARGET)));
|
||||
|
||||
gl.bindBuffer(GL_ARRAY_BUFFER, vb->gl.buffers[bi]);
|
||||
@@ -1447,9 +1494,8 @@ void OpenGLDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
||||
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
// note: in practice this should never happen on Android
|
||||
ASSERT_POSTCONDITION(sc->swapChain,
|
||||
"createSwapChain(%p, 0x%lx) failed. See logs for details.",
|
||||
nativeWindow, flags);
|
||||
FILAMENT_CHECK_POSTCONDITION(sc->swapChain) << "createSwapChain(" << nativeWindow << ", "
|
||||
<< flags << ") failed. See logs for details.";
|
||||
#endif
|
||||
|
||||
// See if we need the emulated rec709 output conversion
|
||||
@@ -1468,9 +1514,9 @@ void OpenGLDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch,
|
||||
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
// note: in practice this should never happen on Android
|
||||
ASSERT_POSTCONDITION(sc->swapChain,
|
||||
"createSwapChainHeadless(%u, %u, 0x%lx) failed. See logs for details.",
|
||||
width, height, flags);
|
||||
FILAMENT_CHECK_POSTCONDITION(sc->swapChain)
|
||||
<< "createSwapChainHeadless(" << width << ", " << height << ", " << flags
|
||||
<< ") failed. See logs for details.";
|
||||
#endif
|
||||
|
||||
// See if we need the emulated rec709 output conversion
|
||||
@@ -2003,19 +2049,19 @@ bool OpenGLDriver::isProtectedContentSupported() {
|
||||
return mPlatform.isProtectedContextSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isStereoSupported(backend::StereoscopicType stereoscopicType) {
|
||||
bool OpenGLDriver::isStereoSupported() {
|
||||
// Instanced-stereo requires instancing and EXT_clip_cull_distance.
|
||||
// Multiview-stereo requires ES 3.0 and OVR_multiview2.
|
||||
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;
|
||||
switch (mDriverConfig.stereoscopicType) {
|
||||
case backend::StereoscopicType::INSTANCED:
|
||||
return mContext.ext.EXT_clip_cull_distance;
|
||||
case backend::StereoscopicType::MULTIVIEW:
|
||||
return mContext.ext.OVR_multiview2;
|
||||
case backend::StereoscopicType::NONE:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3417,12 +3463,12 @@ void OpenGLDriver::beginFrame(
|
||||
}
|
||||
|
||||
void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
FrameScheduledCallback callback, void* user) {
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
DEBUG_MARKER()
|
||||
}
|
||||
|
||||
void OpenGLDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
DEBUG_MARKER()
|
||||
}
|
||||
|
||||
@@ -3559,13 +3605,11 @@ void OpenGLDriver::resolve(
|
||||
assert_invariant(s);
|
||||
assert_invariant(d);
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
d->width == s->width && d->height == s->height,
|
||||
"invalid resolve: src and dst sizes don't match");
|
||||
FILAMENT_CHECK_PRECONDITION(d->width == s->width && d->height == s->height)
|
||||
<< "invalid resolve: src and dst sizes don't match";
|
||||
|
||||
ASSERT_PRECONDITION(s->samples > 1 && d->samples == 1,
|
||||
"invalid resolve: src.samples=%u, dst.samples=%u",
|
||||
+s->samples, +d->samples);
|
||||
FILAMENT_CHECK_PRECONDITION(s->samples > 1 && d->samples == 1)
|
||||
<< "invalid resolve: src.samples=" << +s->samples << ", dst.samples=" << +d->samples;
|
||||
|
||||
blit( dst, dstLevel, dstLayer, {},
|
||||
src, srcLevel, srcLayer, {},
|
||||
@@ -3721,12 +3765,12 @@ void OpenGLDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
UTILS_UNUSED_IN_RELEASE auto& gl = mContext;
|
||||
assert_invariant(!gl.isES2());
|
||||
|
||||
ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0,
|
||||
"blitDEPRECATED only supports COLOR0");
|
||||
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
|
||||
<< "blitDEPRECATED only supports COLOR0";
|
||||
|
||||
ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 &&
|
||||
dstRect.left >= 0 && dstRect.bottom >= 0,
|
||||
"Source and destination rects must be positive.");
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
srcRect.left >= 0 && srcRect.bottom >= 0 && dstRect.left >= 0 && dstRect.bottom >= 0)
|
||||
<< "Source and destination rects must be positive.";
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
|
||||
@@ -3800,7 +3844,7 @@ void OpenGLDriver::updateTextureLodRange(GLTexture* texture, int8_t targetLevel)
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindPipeline(PipelineState state) {
|
||||
void OpenGLDriver::bindPipeline(PipelineState const& state) {
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
setRasterState(state.rasterState);
|
||||
@@ -3808,6 +3852,7 @@ void OpenGLDriver::bindPipeline(PipelineState state) {
|
||||
gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant);
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
|
||||
mValidProgram = useProgram(p);
|
||||
(*mCurrentPushConstants) = p->getPushConstants();
|
||||
}
|
||||
|
||||
void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
@@ -3837,20 +3882,35 @@ void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
return;
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(instanceCount <= 1)) {
|
||||
glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize));
|
||||
} 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),
|
||||
(GLsizei)instanceCount);
|
||||
assert_invariant(!mContext.isES2());
|
||||
glDrawElementsInstanced(GLenum(rp->type), (GLsizei)indexCount,
|
||||
rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize),
|
||||
(GLsizei)instanceCount);
|
||||
#endif
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
|
||||
#else
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
|
||||
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef FILAMENT_ENABLE_MATDBG
|
||||
assert_invariant(mContext.isES2());
|
||||
assert_invariant(instanceCount == 1);
|
||||
|
||||
glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(),
|
||||
reinterpret_cast<const void*>(indexOffset * rp->gl.indicesSize));
|
||||
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
|
||||
#else
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
@@ -3869,7 +3929,11 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
|
||||
state.vertexBufferInfo = rp->vbih;
|
||||
bindPipeline(state);
|
||||
bindRenderPrimitive(rph);
|
||||
draw2(indexOffset, indexCount, instanceCount);
|
||||
if (UTILS_UNLIKELY(mContext.isES2())) {
|
||||
draw2GLES2(indexOffset, indexCount, instanceCount);
|
||||
} else {
|
||||
draw2(indexOffset, indexCount, instanceCount);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
@@ -3896,7 +3960,7 @@ void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGr
|
||||
glDispatchCompute(workGroupCount.x, workGroupCount.y, workGroupCount.z);
|
||||
#endif // BACKEND_OPENGL_LEVEL_GLES31
|
||||
|
||||
#ifdef FILAMENT_ENABLE_MATDBG
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
|
||||
#else
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
|
||||
@@ -66,9 +66,9 @@ namespace filament::backend {
|
||||
class OpenGLPlatform;
|
||||
class PixelBufferDescriptor;
|
||||
struct TargetBufferInfo;
|
||||
|
||||
class OpenGLProgram;
|
||||
class TimerQueryFactoryInterface;
|
||||
struct PushConstantBundle;
|
||||
|
||||
class OpenGLDriver final : public DriverBase {
|
||||
inline explicit OpenGLDriver(OpenGLPlatform* platform,
|
||||
@@ -336,6 +336,8 @@ private:
|
||||
|
||||
void setScissor(Viewport const& scissor) noexcept;
|
||||
|
||||
void draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount);
|
||||
|
||||
// ES2 only. Uniform buffer emulation binding points
|
||||
GLuint mLastAssignedEmulatedUboId = 0;
|
||||
|
||||
@@ -375,6 +377,8 @@ private:
|
||||
// for ES2 sRGB support
|
||||
GLSwapChain* mCurrentDrawSwapChain = nullptr;
|
||||
bool mRec709OutputColorspace = false;
|
||||
|
||||
PushConstantBundle* mCurrentPushConstants = nullptr;
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -46,6 +46,8 @@ struct OpenGLProgram::LazyInitializationData {
|
||||
Program::UniformBlockInfo uniformBlockInfo;
|
||||
Program::SamplerGroupInfo samplerGroupInfo;
|
||||
std::array<Program::UniformInfo, Program::UNIFORM_BINDING_COUNT> bindingUniformInfo;
|
||||
utils::FixedCapacityVector<Program::PushConstant> vertexPushConstants;
|
||||
utils::FixedCapacityVector<Program::PushConstant> fragmentPushConstants;
|
||||
};
|
||||
|
||||
|
||||
@@ -53,7 +55,6 @@ OpenGLProgram::OpenGLProgram() noexcept = default;
|
||||
|
||||
OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept
|
||||
: HwProgram(std::move(program.getName())) {
|
||||
|
||||
auto* const lazyInitializationData = new(std::nothrow) LazyInitializationData();
|
||||
lazyInitializationData->samplerGroupInfo = std::move(program.getSamplerGroupInfo());
|
||||
if (UTILS_UNLIKELY(gld.getContext().isES2())) {
|
||||
@@ -61,6 +62,8 @@ OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept
|
||||
} else {
|
||||
lazyInitializationData->uniformBlockInfo = std::move(program.getUniformBlockBindings());
|
||||
}
|
||||
lazyInitializationData->vertexPushConstants = std::move(program.getPushConstants(ShaderStage::VERTEX));
|
||||
lazyInitializationData->fragmentPushConstants = std::move(program.getPushConstants(ShaderStage::FRAGMENT));
|
||||
|
||||
ShaderCompilerService& compiler = gld.getShaderCompilerService();
|
||||
mToken = compiler.createProgram(name, std::move(program));
|
||||
@@ -203,6 +206,21 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
|
||||
}
|
||||
}
|
||||
mUsedBindingsCount = usedBindingCount;
|
||||
|
||||
auto& vertexConstants = lazyInitializationData.vertexPushConstants;
|
||||
auto& fragmentConstants = lazyInitializationData.fragmentPushConstants;
|
||||
|
||||
size_t const totalConstantCount = vertexConstants.size() + fragmentConstants.size();
|
||||
if (totalConstantCount > 0) {
|
||||
mPushConstants.reserve(totalConstantCount);
|
||||
mPushConstantFragmentStageOffset = vertexConstants.size();
|
||||
auto const transformAndAdd = [&](Program::PushConstant const& constant) {
|
||||
GLint const loc = glGetUniformLocation(program, constant.name.c_str());
|
||||
mPushConstants.push_back({loc, constant.type});
|
||||
};
|
||||
std::for_each(vertexConstants.cbegin(), vertexConstants.cend(), transformAndAdd);
|
||||
std::for_each(fragmentConstants.cbegin(), fragmentConstants.cend(), transformAndAdd);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Slice.h>
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
@@ -38,6 +39,11 @@ namespace filament::backend {
|
||||
|
||||
class OpenGLDriver;
|
||||
|
||||
struct PushConstantBundle {
|
||||
utils::Slice<std::pair<GLint, ConstantType>> vertexConstants;
|
||||
utils::Slice<std::pair<GLint, ConstantType>> fragmentConstants;
|
||||
};
|
||||
|
||||
class OpenGLProgram : public HwProgram {
|
||||
public:
|
||||
|
||||
@@ -78,6 +84,14 @@ public:
|
||||
GLuint program = 0;
|
||||
} gl; // 4 bytes
|
||||
|
||||
PushConstantBundle getPushConstants() {
|
||||
auto fragBegin = mPushConstants.begin() + mPushConstantFragmentStageOffset;
|
||||
return {
|
||||
.vertexConstants = utils::Slice(mPushConstants.begin(), fragBegin),
|
||||
.fragmentConstants = utils::Slice(fragBegin, mPushConstants.end()),
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
// keep these away from of other class attributes
|
||||
struct LazyInitializationData;
|
||||
@@ -95,11 +109,14 @@ private:
|
||||
ShaderCompilerService::program_token_t mToken{}; // 16 bytes
|
||||
|
||||
uint8_t mUsedBindingsCount = 0u; // 1 byte
|
||||
UTILS_UNUSED uint8_t padding[3] = {}; // 3 bytes
|
||||
UTILS_UNUSED uint8_t padding[2] = {}; // 2 byte
|
||||
|
||||
// Push constant array offset for fragment stage constants.
|
||||
uint8_t mPushConstantFragmentStageOffset = 0u; // 1 byte
|
||||
|
||||
// only needed for ES2
|
||||
GLint mRec709Location = -1; // 4 bytes
|
||||
GLint mRec709Location = -1; // 4 bytes
|
||||
|
||||
using LocationInfo = utils::FixedCapacityVector<GLint>;
|
||||
struct UniformsRecord {
|
||||
Program::UniformInfo uniforms;
|
||||
@@ -107,11 +124,15 @@ private:
|
||||
mutable GLuint id = 0;
|
||||
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
|
||||
};
|
||||
UniformsRecord const* mUniformsRecords = nullptr;
|
||||
UniformsRecord const* mUniformsRecords = nullptr; // 8 bytes
|
||||
|
||||
// Note that this can be replaced with a raw pointer and an uint8_t (for size) to reduce the
|
||||
// size of the container to 9 bytes if there is a need in the future.
|
||||
utils::FixedCapacityVector<std::pair<GLint, ConstantType>> mPushConstants;// 16 bytes
|
||||
};
|
||||
|
||||
// if OpenGLProgram is larger tha 64 bytes, it'll fall in a larger Handle bucket.
|
||||
static_assert(sizeof(OpenGLProgram) <= 64); // currently 48 bytes
|
||||
static_assert(sizeof(OpenGLProgram) <= 64); // currently 64 bytes
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
@@ -111,8 +111,8 @@ bool CocoaExternalImage::set(CVPixelBufferRef image) noexcept {
|
||||
}
|
||||
|
||||
OSType formatType = CVPixelBufferGetPixelFormatType(image);
|
||||
ASSERT_POSTCONDITION(formatType == kCVPixelFormatType_32BGRA,
|
||||
"macOS external images must be 32BGRA format.");
|
||||
FILAMENT_CHECK_POSTCONDITION(formatType == kCVPixelFormatType_32BGRA)
|
||||
<< "macOS external images must be 32BGRA format.";
|
||||
|
||||
// The pixel buffer must be locked whenever we do rendering with it. We'll unlock it before
|
||||
// releasing.
|
||||
|
||||
@@ -135,13 +135,14 @@ bool CocoaTouchExternalImage::set(CVPixelBufferRef image) noexcept {
|
||||
}
|
||||
|
||||
OSType formatType = CVPixelBufferGetPixelFormatType(image);
|
||||
ASSERT_POSTCONDITION(formatType == kCVPixelFormatType_32BGRA ||
|
||||
formatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
|
||||
"iOS external images must be in either 32BGRA or 420f format.");
|
||||
FILAMENT_CHECK_POSTCONDITION(formatType == kCVPixelFormatType_32BGRA ||
|
||||
formatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
|
||||
<< "iOS external images must be in either 32BGRA or 420f format.";
|
||||
|
||||
size_t planeCount = CVPixelBufferGetPlaneCount(image);
|
||||
ASSERT_POSTCONDITION(planeCount == 0 || planeCount == 2,
|
||||
"The OpenGL backend does not support images with plane counts of %d.", planeCount);
|
||||
FILAMENT_CHECK_POSTCONDITION(planeCount == 0 || planeCount == 2)
|
||||
<< "The OpenGL backend does not support images with plane counts of " << planeCount
|
||||
<< ".";
|
||||
|
||||
// The pixel buffer must be locked whenever we do rendering with it. We'll unlock it before
|
||||
// releasing.
|
||||
|
||||
@@ -162,7 +162,7 @@ Driver* PlatformCocoaGL::createDriver(void* sharedContext, const Platform::Drive
|
||||
pImpl->mGLContext = nsOpenGLContext;
|
||||
|
||||
int result = bluegl::bind();
|
||||
ASSERT_POSTCONDITION(!result, "Unable to load OpenGL entry points.");
|
||||
FILAMENT_CHECK_POSTCONDITION(!result) << "Unable to load OpenGL entry points.";
|
||||
|
||||
UTILS_UNUSED_IN_RELEASE CVReturn success = CVOpenGLTextureCacheCreate(kCFAllocatorDefault, nullptr,
|
||||
[pImpl->mGLContext CGLContextObj], [pImpl->mGLContext.pixelFormat CGLPixelFormatObj], nullptr,
|
||||
|
||||
@@ -61,7 +61,7 @@ Driver* PlatformCocoaTouchGL::createDriver(void* const sharedGLContext, const Pl
|
||||
EAGLSharegroup* sharegroup = (__bridge EAGLSharegroup*) sharedGLContext;
|
||||
|
||||
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3 sharegroup:sharegroup];
|
||||
ASSERT_POSTCONDITION(context, "Unable to create OpenGL ES context.");
|
||||
FILAMENT_CHECK_POSTCONDITION(context) << "Unable to create OpenGL ES context.";
|
||||
|
||||
[EAGLContext setCurrentContext:context];
|
||||
|
||||
@@ -103,7 +103,7 @@ void PlatformCocoaTouchGL::createContext(bool shared) {
|
||||
EAGLContext* const context = [[EAGLContext alloc]
|
||||
initWithAPI:kEAGLRenderingAPIOpenGLES3
|
||||
sharegroup:sharegroup];
|
||||
ASSERT_POSTCONDITION(context, "Unable to create extra OpenGL ES context.");
|
||||
FILAMENT_CHECK_POSTCONDITION(context) << "Unable to create extra OpenGL ES context.";
|
||||
[EAGLContext setCurrentContext:context];
|
||||
pImpl->mAdditionalContexts.push_back(context);
|
||||
}
|
||||
@@ -180,7 +180,8 @@ bool PlatformCocoaTouchGL::makeCurrent(ContextType type, SwapChain* drawSwapChai
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFramebuffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, pImpl->mDefaultFramebuffer);
|
||||
GLenum const status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
ASSERT_POSTCONDITION(status == GL_FRAMEBUFFER_COMPLETE, "Incomplete framebuffer.");
|
||||
FILAMENT_CHECK_POSTCONDITION(status == GL_FRAMEBUFFER_COMPLETE)
|
||||
<< "Incomplete framebuffer.";
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, oldFramebuffer);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -226,7 +226,7 @@ Driver* PlatformGLX::createDriver(void* const sharedGLContext,
|
||||
g_glx.setCurrentContext(mGLXDisplay, mDummySurface, mDummySurface, mGLXContext);
|
||||
|
||||
int result = bluegl::bind();
|
||||
ASSERT_POSTCONDITION(!result, "Unable to load OpenGL entry points.");
|
||||
FILAMENT_CHECK_POSTCONDITION(!result) << "Unable to load OpenGL entry points.";
|
||||
|
||||
return OpenGLPlatform::createDefaultDriver(this, sharedGLContext, driverConfig);
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext,
|
||||
}
|
||||
|
||||
result = bluegl::bind();
|
||||
ASSERT_POSTCONDITION(!result, "Unable to load OpenGL entry points.");
|
||||
FILAMENT_CHECK_POSTCONDITION(!result) << "Unable to load OpenGL entry points.";
|
||||
|
||||
return OpenGLPlatform::createDefaultDriver(this, sharedGLContext, driverConfig);
|
||||
|
||||
|
||||
@@ -410,6 +410,7 @@ io::ostream& operator<<(io::ostream& out, const RasterState& rs) {
|
||||
io::ostream& operator<<(io::ostream& out, const TargetBufferInfo& tbi) {
|
||||
return out << "TargetBufferInfo{"
|
||||
<< "handle=" << tbi.handle
|
||||
<< ", baseViewIndex=" << tbi.baseViewIndex
|
||||
<< ", level=" << tbi.level
|
||||
<< ", layer=" << tbi.layer << "}";
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ VulkanTimestamps::VulkanTimestamps(VkDevice device) : mDevice(device) {
|
||||
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||
tqpCreateInfo.queryCount = mUsed.size() * 2;
|
||||
VkResult result = vkCreateQueryPool(mDevice, &tqpCreateInfo, VKALLOC, &mPool);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateQueryPool error.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateQueryPool error.";
|
||||
mUsed.reset();
|
||||
}
|
||||
|
||||
@@ -134,8 +134,8 @@ VulkanTimestamps::QueryResult VulkanTimestamps::getResult(VulkanTimerQuery const
|
||||
VkResult vkresult =
|
||||
vkGetQueryPoolResults(mDevice, mPool, index, 2, dataSize, (void*) result.data(),
|
||||
stride, VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
|
||||
ASSERT_POSTCONDITION(vkresult == VK_SUCCESS || vkresult == VK_NOT_READY,
|
||||
"vkGetQueryPoolResults error: %d", static_cast<int32_t>(vkresult));
|
||||
FILAMENT_CHECK_POSTCONDITION(vkresult == VK_SUCCESS || vkresult == VK_NOT_READY)
|
||||
<< "vkGetQueryPoolResults error: " << static_cast<int32_t>(vkresult);
|
||||
if (vkresult == VK_NOT_READY) {
|
||||
return {0, 0, 0, 0};
|
||||
}
|
||||
|
||||
@@ -120,16 +120,21 @@ public:
|
||||
}
|
||||
|
||||
inline bool isImageCubeArraySupported() const noexcept {
|
||||
return mPhysicalDeviceFeatures.imageCubeArray;
|
||||
return mPhysicalDeviceFeatures.imageCubeArray == VK_TRUE;
|
||||
}
|
||||
|
||||
inline bool isDebugMarkersSupported() const noexcept {
|
||||
return mDebugMarkersSupported;
|
||||
}
|
||||
|
||||
inline bool isDebugUtilsSupported() const noexcept {
|
||||
return mDebugUtilsSupported;
|
||||
}
|
||||
|
||||
inline bool isClipDistanceSupported() const noexcept {
|
||||
return mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE;
|
||||
}
|
||||
|
||||
private:
|
||||
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
|
||||
VkPhysicalDeviceProperties mPhysicalDeviceProperties = {};
|
||||
|
||||
@@ -173,7 +173,8 @@ DebugUtils::DebugUtils(VkInstance instance, VkDevice device, VulkanContext const
|
||||
};
|
||||
VkResult result = vkCreateDebugUtilsMessengerEXT(instance, &createInfo,
|
||||
VKALLOC, &mDebugMessenger);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan debug messenger.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create Vulkan debug messenger.";
|
||||
}
|
||||
#endif // FVK_EANBLED(FVK_DEBUG_VALIDATION)
|
||||
}
|
||||
@@ -228,7 +229,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
|
||||
mReadPixels(mPlatform->getDevice()),
|
||||
mDescriptorSetManager(mPlatform->getDevice(), &mResourceAllocator),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported) {
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
||||
mStereoscopicType(driverConfig.stereoscopicType) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
DebugUtils::mSingleton =
|
||||
@@ -246,7 +248,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
};
|
||||
VkResult result = createDebugReportCallback(mPlatform->getInstance(), &cbinfo, VKALLOC,
|
||||
&mDebugCallback);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan debug callback.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create Vulkan debug callback.";
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -259,8 +262,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mDescriptorSetManager.setPlaceHolders(mSamplerCache.getSampler({}), mEmptyTexture,
|
||||
mEmptyBufferObject);
|
||||
|
||||
mGetPipelineFunction = [this](VulkanDescriptorSetLayoutList const& layouts) {
|
||||
return mPipelineLayoutCache.getLayout(layouts);
|
||||
mGetPipelineFunction = [this](VulkanDescriptorSetLayoutList const& layouts, VulkanProgram* program) {
|
||||
return mPipelineLayoutCache.getLayout(layouts, program);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -322,6 +325,10 @@ ShaderModel VulkanDriver::getShaderModel() const noexcept {
|
||||
}
|
||||
|
||||
void VulkanDriver::terminate() {
|
||||
// Flush and wait here to make sure all queued commands are executed and resources that are tied
|
||||
// to those commands are no longer referenced.
|
||||
finish(0);
|
||||
|
||||
delete mEmptyBufferObject;
|
||||
delete mEmptyTexture;
|
||||
|
||||
@@ -394,11 +401,11 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
}
|
||||
|
||||
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
FrameScheduledCallback callback, void* user) {
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
}
|
||||
|
||||
void VulkanDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
}
|
||||
|
||||
void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||
@@ -899,13 +906,14 @@ bool VulkanDriver::isProtectedContentSupported() {
|
||||
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() {
|
||||
switch (mStereoscopicType) {
|
||||
case backend::StereoscopicType::INSTANCED:
|
||||
return mContext.isClipDistanceSupported();
|
||||
case backend::StereoscopicType::MULTIVIEW:
|
||||
// TODO: implement multiview feature in Vulkan.
|
||||
case backend::StereoscopicType::NONE:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1088,7 +1096,8 @@ TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint
|
||||
return TimerQueryResult::NOT_READY;
|
||||
}
|
||||
|
||||
ASSERT_POSTCONDITION(timestamp1 >= timestamp0, "Timestamps are not monotonically increasing.");
|
||||
FILAMENT_CHECK_POSTCONDITION(timestamp1 >= timestamp0)
|
||||
<< "Timestamps are not monotonically increasing.";
|
||||
|
||||
// NOTE: MoltenVK currently writes system time so the following delta will always be zero.
|
||||
// However there are plans for implementing this properly. See the following GitHub ticket.
|
||||
@@ -1493,8 +1502,8 @@ void VulkanDriver::endRenderPass(int) {
|
||||
}
|
||||
|
||||
void VulkanDriver::nextSubpass(int) {
|
||||
ASSERT_PRECONDITION(mCurrentRenderPass.currentSubpass == 0,
|
||||
"Only two subpasses are currently supported.");
|
||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.currentSubpass == 0)
|
||||
<< "Only two subpasses are currently supported.";
|
||||
|
||||
VulkanRenderTarget* renderTarget = mCurrentRenderPass.renderTarget;
|
||||
assert_invariant(renderTarget);
|
||||
@@ -1572,6 +1581,14 @@ void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||
mSamplerBindings[index] = hwsb;
|
||||
}
|
||||
|
||||
void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants");
|
||||
VulkanCommands* commands = &mCommands;
|
||||
mBoundPipeline.program->writePushConstant(commands, mBoundPipeline.pipelineLayout, stage, index,
|
||||
value);
|
||||
}
|
||||
|
||||
void VulkanDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
mCommands.insertEventMarker(string, len);
|
||||
@@ -1626,36 +1643,36 @@ void VulkanDriver::resolve(
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("resolve");
|
||||
|
||||
ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE,
|
||||
"resolve() cannot be invoked inside a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
||||
<< "resolve() cannot be invoked inside a render pass.";
|
||||
|
||||
auto* const srcTexture = mResourceAllocator.handle_cast<VulkanTexture*>(src);
|
||||
auto* const dstTexture = mResourceAllocator.handle_cast<VulkanTexture*>(dst);
|
||||
assert_invariant(srcTexture);
|
||||
assert_invariant(dstTexture);
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height,
|
||||
"invalid resolve: src and dst sizes don't match");
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
dstTexture->width == srcTexture->width && dstTexture->height == srcTexture->height)
|
||||
<< "invalid resolve: src and dst sizes don't match";
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1,
|
||||
"invalid resolve: src.samples=%u, dst.samples=%u",
|
||||
+srcTexture->samples, +dstTexture->samples);
|
||||
FILAMENT_CHECK_PRECONDITION(srcTexture->samples > 1 && dstTexture->samples == 1)
|
||||
<< "invalid resolve: src.samples=" << +srcTexture->samples
|
||||
<< ", dst.samples=" << +dstTexture->samples;
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
FILAMENT_CHECK_PRECONDITION(srcTexture->format == dstTexture->format)
|
||||
<< "src and dst texture format don't match";
|
||||
|
||||
ASSERT_PRECONDITION(!isDepthFormat(srcTexture->format),
|
||||
"can't resolve depth formats");
|
||||
FILAMENT_CHECK_PRECONDITION(!isDepthFormat(srcTexture->format))
|
||||
<< "can't resolve depth formats";
|
||||
|
||||
ASSERT_PRECONDITION(!isStencilFormat(srcTexture->format),
|
||||
"can't resolve stencil formats");
|
||||
FILAMENT_CHECK_PRECONDITION(!isStencilFormat(srcTexture->format))
|
||||
<< "can't resolve stencil formats";
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
FILAMENT_CHECK_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST))
|
||||
<< "texture doesn't have BLIT_DST";
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
FILAMENT_CHECK_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC))
|
||||
<< "texture doesn't have BLIT_SRC";
|
||||
|
||||
mBlitter.resolve(
|
||||
{ .texture = dstTexture, .level = dstLevel, .layer = dstLayer },
|
||||
@@ -1671,20 +1688,20 @@ void VulkanDriver::blit(
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("blit");
|
||||
|
||||
ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE,
|
||||
"blit() cannot be invoked inside a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
||||
<< "blit() cannot be invoked inside a render pass.";
|
||||
|
||||
auto* const srcTexture = mResourceAllocator.handle_cast<VulkanTexture*>(src);
|
||||
auto* const dstTexture = mResourceAllocator.handle_cast<VulkanTexture*>(dst);
|
||||
|
||||
ASSERT_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST),
|
||||
"texture doesn't have BLIT_DST");
|
||||
FILAMENT_CHECK_PRECONDITION(any(dstTexture->usage & TextureUsage::BLIT_DST))
|
||||
<< "texture doesn't have BLIT_DST";
|
||||
|
||||
ASSERT_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC),
|
||||
"texture doesn't have BLIT_SRC");
|
||||
FILAMENT_CHECK_PRECONDITION(any(srcTexture->usage & TextureUsage::BLIT_SRC))
|
||||
<< "texture doesn't have BLIT_SRC";
|
||||
|
||||
ASSERT_PRECONDITION(srcTexture->format == dstTexture->format,
|
||||
"src and dst texture format don't match");
|
||||
FILAMENT_CHECK_PRECONDITION(srcTexture->format == dstTexture->format)
|
||||
<< "src and dst texture format don't match";
|
||||
|
||||
// The Y inversion below makes it so that Vk matches GL and Metal.
|
||||
|
||||
@@ -1716,15 +1733,15 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
|
||||
// Note: blitDEPRECATED is only used for Renderer::copyFrame()
|
||||
|
||||
ASSERT_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE,
|
||||
"blitDEPRECATED() cannot be invoked inside a render pass.");
|
||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
||||
<< "blitDEPRECATED() cannot be invoked inside a render pass.";
|
||||
|
||||
ASSERT_PRECONDITION(buffers == TargetBufferFlags::COLOR0,
|
||||
"blitDEPRECATED only supports COLOR0");
|
||||
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
|
||||
<< "blitDEPRECATED only supports COLOR0";
|
||||
|
||||
ASSERT_PRECONDITION(srcRect.left >= 0 && srcRect.bottom >= 0 &&
|
||||
dstRect.left >= 0 && dstRect.bottom >= 0,
|
||||
"Source and destination rects must be positive.");
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
srcRect.left >= 0 && srcRect.bottom >= 0 && dstRect.left >= 0 && dstRect.bottom >= 0)
|
||||
<< "Source and destination rects must be positive.";
|
||||
|
||||
VulkanRenderTarget* dstTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(dst);
|
||||
VulkanRenderTarget* srcTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(src);
|
||||
@@ -1755,7 +1772,7 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("draw");
|
||||
|
||||
@@ -1857,8 +1874,20 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
mDescriptorSetManager.updateSampler({}, binding, texture, vksampler);
|
||||
}
|
||||
|
||||
mPipelineCache.bindLayout(mDescriptorSetManager.bind(commands, program, mGetPipelineFunction));
|
||||
auto const pipelineLayout = mDescriptorSetManager.bind(commands, program, mGetPipelineFunction);
|
||||
mBoundPipeline = {
|
||||
.program = program,
|
||||
.pipelineLayout = pipelineLayout,
|
||||
};
|
||||
|
||||
mPipelineCache.bindLayout(pipelineLayout);
|
||||
mPipelineCache.bindPipeline(commands);
|
||||
|
||||
// Since we don't statically define scissor as part of the pipeline, we need to call scissor at
|
||||
// least once. Context: VUID-vkCmdDrawIndexed-None-07832.
|
||||
auto const& extent = rt->getExtent();
|
||||
scissor({0, 0, extent.width, extent.height});
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
@@ -1949,7 +1978,7 @@ void VulkanDriver::scissor(Viewport scissorBox) {
|
||||
|
||||
const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
||||
rt->transformClientRectToPlatform(&scissor);
|
||||
mPipelineCache.bindScissor(cmdbuffer, scissor);
|
||||
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
|
||||
}
|
||||
|
||||
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanStagePool.h"
|
||||
#include "VulkanUtility.h"
|
||||
#include "backend/DriverEnums.h"
|
||||
#include "caching/VulkanDescriptorSetManager.h"
|
||||
#include "caching/VulkanPipelineLayoutCache.h"
|
||||
|
||||
@@ -160,9 +161,16 @@ private:
|
||||
|
||||
VulkanDescriptorSetManager::GetPipelineLayoutFunction mGetPipelineFunction;
|
||||
|
||||
// This is necessary for us to write to push constants after binding a pipeline.
|
||||
struct BoundPipeline {
|
||||
VulkanProgram* program;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
};
|
||||
BoundPipeline mBoundPipeline = {};
|
||||
RenderPassFboBundle mRenderPassFboInfo;
|
||||
|
||||
bool const mIsSRGBSwapChainSupported;
|
||||
backend::StereoscopicType const mStereoscopicType;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -64,8 +64,8 @@ VulkanFboCache::VulkanFboCache(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VulkanFboCache::~VulkanFboCache() {
|
||||
ASSERT_POSTCONDITION(mFramebufferCache.empty() && mRenderPassCache.empty(),
|
||||
"Please explicitly call terminate() while the VkDevice is still alive.");
|
||||
FILAMENT_CHECK_POSTCONDITION(mFramebufferCache.empty() && mRenderPassCache.empty())
|
||||
<< "Please explicitly call terminate() while the VkDevice is still alive.";
|
||||
}
|
||||
|
||||
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config) noexcept {
|
||||
@@ -115,7 +115,7 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config) noexcept {
|
||||
mRenderPassRefCount[info.renderPass]++;
|
||||
VkFramebuffer framebuffer;
|
||||
VkResult error = vkCreateFramebuffer(mDevice, &info, VKALLOC, &framebuffer);
|
||||
ASSERT_POSTCONDITION(!error, "Unable to create framebuffer.");
|
||||
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to create framebuffer.";
|
||||
mFramebufferCache[config] = {framebuffer, mCurrentTime};
|
||||
return framebuffer;
|
||||
}
|
||||
@@ -306,7 +306,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
// Finally, create the VkRenderPass.
|
||||
VkRenderPass renderPass;
|
||||
VkResult error = vkCreateRenderPass(mDevice, &renderPassInfo, VKALLOC, &renderPass);
|
||||
ASSERT_POSTCONDITION(!error, "Unable to create render pass.");
|
||||
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to create render pass.";
|
||||
mRenderPassCache[config] = {renderPass, mCurrentTime};
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
||||
|
||||
@@ -112,27 +112,82 @@ inline VkDescriptorSetLayout createDescriptorSetLayout(VkDevice device,
|
||||
return layout;
|
||||
}
|
||||
|
||||
inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) {
|
||||
switch(stage) {
|
||||
case backend::ShaderStage::VERTEX:
|
||||
return VK_SHADER_STAGE_VERTEX_BIT;
|
||||
case backend::ShaderStage::FRAGMENT:
|
||||
return VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
case backend::ShaderStage::COMPUTE:
|
||||
PANIC_POSTCONDITION("Unsupported stage");
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device, VkDescriptorSetLayoutCreateInfo const& info,
|
||||
Bitmask const& bitmask)
|
||||
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VkDevice device,
|
||||
VkDescriptorSetLayoutCreateInfo const& info, Bitmask const& bitmask)
|
||||
: VulkanResource(VulkanResourceType::DESCRIPTOR_SET_LAYOUT),
|
||||
mDevice(device),
|
||||
vklayout(createDescriptorSetLayout(device, info)),
|
||||
bitmask(bitmask),
|
||||
bindings(getBindings(bitmask)),
|
||||
count(Count::fromLayoutBitmask(bitmask)) {
|
||||
}
|
||||
count(Count::fromLayoutBitmask(bitmask)) {}
|
||||
|
||||
VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() {
|
||||
vkDestroyDescriptorSetLayout(mDevice, vklayout, VKALLOC);
|
||||
}
|
||||
|
||||
PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept {
|
||||
mRangeCount = 0;
|
||||
for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) {
|
||||
auto const& constants = program.getPushConstants(stage);
|
||||
if (constants.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We store the type of the constant for type-checking when writing.
|
||||
auto& types = mTypes[(uint8_t) stage];
|
||||
types.reserve(constants.size());
|
||||
std::for_each(constants.cbegin(), constants.cend(), [&types] (Program::PushConstant t) {
|
||||
types.push_back(t.type);
|
||||
});
|
||||
|
||||
mRanges[mRangeCount++] = {
|
||||
.stageFlags = getVkStage(stage),
|
||||
.offset = 0,
|
||||
.size = (uint32_t) constants.size() * ENTRY_SIZE,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void PushConstantDescription::write(VulkanCommands* commands, VkPipelineLayout layout,
|
||||
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
|
||||
VulkanCommandBuffer* cmdbuf = &(commands->get());
|
||||
uint32_t binaryValue = 0;
|
||||
UTILS_UNUSED_IN_RELEASE auto const& types = mTypes[(uint8_t) stage];
|
||||
if (std::holds_alternative<bool>(value)) {
|
||||
assert_invariant(types[index] == ConstantType::BOOL);
|
||||
bool const bval = std::get<bool>(value);
|
||||
binaryValue = static_cast<uint32_t const>(bval ? VK_TRUE : VK_FALSE);
|
||||
} else if (std::holds_alternative<float>(value)) {
|
||||
assert_invariant(types[index] == ConstantType::FLOAT);
|
||||
float const fval = std::get<float>(value);
|
||||
binaryValue = *reinterpret_cast<uint32_t const*>(&fval);
|
||||
} else {
|
||||
assert_invariant(types[index] == ConstantType::INT);
|
||||
int const ival = std::get<int>(value);
|
||||
binaryValue = *reinterpret_cast<uint32_t const*>(&ival);
|
||||
}
|
||||
vkCmdPushConstants(cmdbuf->buffer(), layout, getVkStage(stage), index * ENTRY_SIZE, ENTRY_SIZE,
|
||||
&binaryValue);
|
||||
}
|
||||
|
||||
VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
: HwProgram(builder.getName()),
|
||||
VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mInfo(new PipelineInfo()),
|
||||
mInfo(new(std::nothrow) PipelineInfo(builder)),
|
||||
mDevice(device) {
|
||||
|
||||
constexpr uint8_t UBO_MODULE_OFFSET = (sizeof(UniformBufferBitmask) * 8) / MAX_SHADER_MODULES;
|
||||
@@ -182,7 +237,7 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
.pCode = data,
|
||||
};
|
||||
VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, &module);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create shader module.");
|
||||
FILAMENT_CHECK_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() };
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
#include "utils/FixedCapacityVector.h"
|
||||
#include "vulkan/vulkan_core.h"
|
||||
#include <private/backend/SamplerGroup.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/StructureOfArrays.h>
|
||||
|
||||
@@ -180,6 +180,28 @@ private:
|
||||
using VulkanDescriptorSetList = std::array<Handle<VulkanDescriptorSet>,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
using PushConstantNameArray = utils::FixedCapacityVector<char const*>;
|
||||
using PushConstantNameByStage = std::array<PushConstantNameArray, Program::SHADER_TYPE_COUNT>;
|
||||
|
||||
struct PushConstantDescription {
|
||||
|
||||
explicit PushConstantDescription(backend::Program const& program) noexcept;
|
||||
|
||||
VkPushConstantRange const* getVkRanges() const noexcept { return mRanges; }
|
||||
|
||||
uint32_t getVkRangeCount() const noexcept { return mRangeCount; }
|
||||
|
||||
void write(VulkanCommands* commands, VkPipelineLayout layout, backend::ShaderStage stage,
|
||||
uint8_t index, backend::PushConstantVariant const& value);
|
||||
|
||||
private:
|
||||
static constexpr uint32_t ENTRY_SIZE = sizeof(uint32_t);
|
||||
|
||||
utils::FixedCapacityVector<backend::ConstantType> mTypes[Program::SHADER_TYPE_COUNT];
|
||||
VkPushConstantRange mRanges[Program::SHADER_TYPE_COUNT];
|
||||
uint32_t mRangeCount;
|
||||
};
|
||||
|
||||
struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
|
||||
using BindingList = CappedArray<uint16_t, MAX_SAMPLER_COUNT>;
|
||||
@@ -212,6 +234,19 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
inline LayoutDescriptionList const& getLayoutDescriptionList() const { return mInfo->layouts; }
|
||||
|
||||
inline uint32_t getPushConstantRangeCount() const {
|
||||
return mInfo->pushConstantDescription.getVkRangeCount();
|
||||
}
|
||||
|
||||
inline VkPushConstantRange const* getPushConstantRanges() const {
|
||||
return mInfo->pushConstantDescription.getVkRanges();
|
||||
}
|
||||
|
||||
inline void writePushConstant(VulkanCommands* commands, VkPipelineLayout layout,
|
||||
backend::ShaderStage stage, uint8_t index, backend::PushConstantVariant const& value) {
|
||||
mInfo->pushConstantDescription.write(commands, layout, stage, index, value);
|
||||
}
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
inline utils::FixedCapacityVector<std::string> const& getBindingToName() const {
|
||||
return mInfo->bindingToName;
|
||||
@@ -224,8 +259,9 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
|
||||
private:
|
||||
struct PipelineInfo {
|
||||
PipelineInfo()
|
||||
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff)
|
||||
explicit PipelineInfo(backend::Program const& program) noexcept
|
||||
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff),
|
||||
pushConstantDescription(program)
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
, bindingToName(MAX_SAMPLER_COUNT, "")
|
||||
#endif
|
||||
@@ -241,6 +277,8 @@ private:
|
||||
// descset::DescriptorSetLayout layout;
|
||||
LayoutDescriptionList layouts;
|
||||
|
||||
PushConstantDescription pushConstantDescription;
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
// We store the sampler name mapped from binding index (only for debug purposes).
|
||||
utils::FixedCapacityVector<std::string> bindingToName;
|
||||
|
||||
@@ -79,10 +79,6 @@ void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
|
||||
commands->setPipeline(cacheEntry->handle);
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept {
|
||||
vkCmdSetScissor(cmdbuffer, 0, 1, &scissor);
|
||||
}
|
||||
|
||||
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {
|
||||
assert_invariant(mPipelineRequirements.shaders[0] && "Vertex shader is not bound.");
|
||||
assert_invariant(mPipelineRequirements.layout && "No pipeline layout specified");
|
||||
@@ -306,7 +302,6 @@ void VulkanPipelineCache::gc() noexcept {
|
||||
// The Vulkan spec says: "When a command buffer begins recording, all state in that command
|
||||
// buffer is undefined." Therefore, we need to clear all bindings at this time.
|
||||
mBoundPipeline = {};
|
||||
mCurrentScissor = {};
|
||||
|
||||
// NOTE: Due to robin_map restrictions, we cannot use auto or range-based loops.
|
||||
|
||||
|
||||
@@ -120,9 +120,6 @@ public:
|
||||
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
|
||||
void bindPipeline(VulkanCommandBuffer* commands);
|
||||
|
||||
// Sets up a new scissor rectangle if it has been dirtied.
|
||||
void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept;
|
||||
|
||||
// Each of the following methods are fast and do not make Vulkan calls.
|
||||
void bindProgram(VulkanProgram* program) noexcept;
|
||||
void bindRasterState(const RasterState& rasterState) noexcept;
|
||||
@@ -263,9 +260,6 @@ private:
|
||||
|
||||
// Current bindings for the pipeline and descriptor sets.
|
||||
PipelineKey mBoundPipeline = {};
|
||||
|
||||
// Current state for scissoring.
|
||||
VkRect2D mCurrentScissor = {};
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -71,8 +71,8 @@ void TaskHandler::shutdown() {
|
||||
}
|
||||
mHasTaskCondition.notify_one();
|
||||
mThread.join();
|
||||
ASSERT_POSTCONDITION(mTaskQueue.empty(),
|
||||
"ReadPixels handler has tasks in the queue after shutdown");
|
||||
FILAMENT_CHECK_POSTCONDITION(mTaskQueue.empty())
|
||||
<< "ReadPixels handler has tasks in the queue after shutdown";
|
||||
}
|
||||
|
||||
void TaskHandler::loop() {
|
||||
@@ -190,8 +190,8 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
|
||||
<< utils::io::endl;
|
||||
}
|
||||
|
||||
ASSERT_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES,
|
||||
"VulkanReadPixels: unable to find a memory type that meets requirements.");
|
||||
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
|
||||
<< "VulkanReadPixels: unable to find a memory type that meets requirements.";
|
||||
|
||||
VkMemoryAllocateInfo const allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
|
||||
@@ -122,7 +122,7 @@ VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept {
|
||||
};
|
||||
VkSampler sampler;
|
||||
VkResult error = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
|
||||
ASSERT_POSTCONDITION(!error, "Unable to create sampler.");
|
||||
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to create sampler.";
|
||||
mCache.insert({params, sampler});
|
||||
return sampler;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
|
||||
mAcquired(false),
|
||||
mIsFirstRenderPass(true) {
|
||||
swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent);
|
||||
ASSERT_POSTCONDITION(swapChain, "Unable to create swapchain");
|
||||
FILAMENT_CHECK_POSTCONDITION(swapChain) << "Unable to create swapchain";
|
||||
|
||||
VkSemaphoreCreateInfo const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
@@ -53,8 +53,9 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
|
||||
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");
|
||||
}
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Failed to create semaphore";
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
@@ -112,9 +113,9 @@ void VulkanSwapChain::present() {
|
||||
if (!mHeadless) {
|
||||
VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal();
|
||||
VkResult const result = mPlatform->present(swapChain, mCurrentSwapIndex, finishedDrawing);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR ||
|
||||
result == VK_ERROR_OUT_OF_DATE_KHR,
|
||||
"Cannot present in swapchain.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR ||
|
||||
result == VK_ERROR_OUT_OF_DATE_KHR)
|
||||
<< "Cannot present in swapchain.";
|
||||
}
|
||||
|
||||
// We presented the last acquired buffer.
|
||||
@@ -141,8 +142,8 @@ void VulkanSwapChain::acquire(bool& resized) {
|
||||
mCurrentImageReadyIndex = (mCurrentImageReadyIndex + 1) % IMAGE_READY_SEMAPHORE_COUNT;
|
||||
const VkSemaphore imageReady = mImageReady[mCurrentImageReadyIndex];
|
||||
VkResult const result = mPlatform->acquire(swapChain, imageReady, &mCurrentSwapIndex);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR,
|
||||
"Cannot acquire in swapchain.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
|
||||
<< "Cannot acquire in swapchain.";
|
||||
if (imageReady != VK_NULL_HANDLE) {
|
||||
mCommands->injectDependency(imageReady);
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
<< "target = " << static_cast<int>(target) <<", "
|
||||
<< "format = " << mVkFormat << utils::io::endl;
|
||||
}
|
||||
ASSERT_POSTCONDITION(!error, "Unable to create image.");
|
||||
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to create image.";
|
||||
|
||||
// Allocate memory for the VkImage and bind it.
|
||||
VkMemoryRequirements memReqs = {};
|
||||
@@ -186,8 +186,8 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
uint32_t memoryTypeIndex
|
||||
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
|
||||
ASSERT_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES,
|
||||
"VulkanTexture: unable to find a memory type that meets requirements.");
|
||||
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
|
||||
<< "VulkanTexture: unable to find a memory type that meets requirements.";
|
||||
|
||||
VkMemoryAllocateInfo allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
@@ -195,9 +195,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
};
|
||||
error = vkAllocateMemory(mDevice, &allocInfo, nullptr, &mTextureImageMemory);
|
||||
ASSERT_POSTCONDITION(!error, "Unable to allocate image memory.");
|
||||
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to allocate image memory.";
|
||||
error = vkBindImageMemory(mDevice, mTextureImage, mTextureImageMemory, 0);
|
||||
ASSERT_POSTCONDITION(!error, "Unable to bind image.");
|
||||
FILAMENT_CHECK_POSTCONDITION(!error) << "Unable to bind image.";
|
||||
|
||||
uint32_t layerCount = 0;
|
||||
if (target == SamplerType::SAMPLER_CUBEMAP) {
|
||||
|
||||
@@ -51,7 +51,7 @@ VkFormat getVkFormat(ElementType type, bool normalized, bool integer) {
|
||||
case ElementType::SHORT4: return VK_FORMAT_R16G16B16A16_SNORM;
|
||||
case ElementType::USHORT4: return VK_FORMAT_R16G16B16A16_UNORM;
|
||||
default:
|
||||
ASSERT_POSTCONDITION(false, "Normalized format does not exist.");
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Normalized format does not exist.";
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,13 +56,13 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask);
|
||||
// considered, but because the "variadic" part of the vk methods (i.e. the inputs) are before the
|
||||
// non-variadic parts, this breaks the template type matching logic. Hence, we use a macro approach
|
||||
// here.
|
||||
#define EXPAND_ENUM(...)\
|
||||
uint32_t size = 0;\
|
||||
VkResult result = func(__VA_ARGS__, nullptr);\
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "enumerate size error");\
|
||||
utils::FixedCapacityVector<OutType> ret(size);\
|
||||
result = func(__VA_ARGS__, ret.data());\
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "enumerate error");\
|
||||
#define EXPAND_ENUM(...) \
|
||||
uint32_t size = 0; \
|
||||
VkResult result = func(__VA_ARGS__, nullptr); \
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "enumerate size error"; \
|
||||
utils::FixedCapacityVector<OutType> ret(size); \
|
||||
result = func(__VA_ARGS__, ret.data()); \
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "enumerate error"; \
|
||||
return std::move(ret);
|
||||
|
||||
#define EXPAND_ENUM_NO_ARGS() EXPAND_ENUM(&size)
|
||||
|
||||
@@ -170,9 +170,9 @@ public:
|
||||
};
|
||||
VkDescriptorSet vkSet;
|
||||
UTILS_UNUSED VkResult result = vkAllocateDescriptorSets(mDevice, &allocInfo, &vkSet);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS,
|
||||
"Failed to allocate descriptor set code=%d size=%d capacity=%d count=%s", result,
|
||||
mSize, mCapacity);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Failed to allocate descriptor set code=" << result << " size=" << mSize
|
||||
<< " capacity=" << mCapacity << " count=" << "%s";
|
||||
mSize++;
|
||||
return createSet(layout->bitmask, vkSet);
|
||||
}
|
||||
@@ -879,7 +879,7 @@ public:
|
||||
vkUpdateDescriptorSets(mDevice, nwrites, descriptorWrites, 0, nullptr);
|
||||
}
|
||||
|
||||
VkPipelineLayout const pipelineLayout = getPipelineLayoutFn(outLayouts);
|
||||
VkPipelineLayout const pipelineLayout = getPipelineLayoutFn(outLayouts, program);
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
|
||||
BoundState state{};
|
||||
|
||||
@@ -45,8 +45,8 @@ class VulkanDescriptorSetManager {
|
||||
public:
|
||||
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT =
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
|
||||
using GetPipelineLayoutFunction =
|
||||
std::function<VkPipelineLayout(VulkanDescriptorSetLayoutList const&)>;
|
||||
using GetPipelineLayoutFunction = std::function<VkPipelineLayout(
|
||||
VulkanDescriptorSetLayoutList const&, VulkanProgram* program)>;
|
||||
|
||||
VulkanDescriptorSetManager(VkDevice device, VulkanResourceAllocator* resourceAllocator);
|
||||
|
||||
@@ -108,3 +108,4 @@ private:
|
||||
}// namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
|
||||
|
||||
|
||||
@@ -21,13 +21,34 @@
|
||||
namespace filament::backend {
|
||||
|
||||
VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
VulkanDescriptorSetLayoutList const& descriptorSetLayouts) {
|
||||
PipelineLayoutKey key = {VK_NULL_HANDLE};
|
||||
VulkanDescriptorSetLayoutList const& descriptorSetLayouts, VulkanProgram* program) {
|
||||
PipelineLayoutKey key = {};
|
||||
uint8_t descSetLayoutCount = 0;
|
||||
for (auto layoutHandle: descriptorSetLayouts) {
|
||||
if (layoutHandle) {
|
||||
auto layout = mAllocator->handle_cast<VulkanDescriptorSetLayout*>(layoutHandle);
|
||||
key[descSetLayoutCount++] = layout->vklayout;
|
||||
key.descSetLayouts[descSetLayoutCount++] = layout->vklayout;
|
||||
}
|
||||
}
|
||||
|
||||
// build the push constant layout key
|
||||
uint32_t pushConstantRangeCount = program->getPushConstantRangeCount();
|
||||
auto const& pushCostantRanges = program->getPushConstantRanges();
|
||||
if (pushConstantRangeCount > 0) {
|
||||
assert_invariant(pushConstantRangeCount <= Program::SHADER_TYPE_COUNT);
|
||||
for (uint8_t i = 0; i < pushConstantRangeCount; ++i) {
|
||||
auto const& range = pushCostantRanges[i];
|
||||
auto& pushConstant = key.pushConstant[i];
|
||||
if (range.stageFlags & VK_SHADER_STAGE_VERTEX_BIT) {
|
||||
pushConstant.stage = static_cast<uint8_t>(ShaderStage::VERTEX);
|
||||
}
|
||||
if (range.stageFlags & VK_SHADER_STAGE_FRAGMENT_BIT) {
|
||||
pushConstant.stage = static_cast<uint8_t>(ShaderStage::FRAGMENT);
|
||||
}
|
||||
if (range.stageFlags & VK_SHADER_STAGE_COMPUTE_BIT) {
|
||||
pushConstant.stage = static_cast<uint8_t>(ShaderStage::COMPUTE);
|
||||
}
|
||||
pushConstant.size = range.size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +63,11 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.setLayoutCount = (uint32_t) descSetLayoutCount,
|
||||
.pSetLayouts = key.data(),
|
||||
.pushConstantRangeCount = 0,
|
||||
.pSetLayouts = key.descSetLayouts.data(),
|
||||
.pushConstantRangeCount = pushConstantRangeCount,
|
||||
.pPushConstantRanges = pushCostantRanges,
|
||||
};
|
||||
|
||||
VkPipelineLayout layout;
|
||||
vkCreatePipelineLayout(mDevice, &info, VKALLOC, &layout);
|
||||
|
||||
|
||||
@@ -35,13 +35,29 @@ public:
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
using PipelineLayoutKey = std::array<VkDescriptorSetLayout,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
struct PushConstantKey {
|
||||
uint8_t stage;// We have one set of push constant per shader stage (fragment, vertex, etc).
|
||||
uint8_t size;
|
||||
// Note that there is also an offset parameter for push constants, but
|
||||
// we always assume our update range will have the offset 0.
|
||||
};
|
||||
|
||||
struct PipelineLayoutKey {
|
||||
using DescriptorSetLayoutArray = std::array<VkDescriptorSetLayout,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 3
|
||||
PushConstantKey pushConstant[Program::SHADER_TYPE_COUNT] = {}; // 2 * 3
|
||||
uint16_t padding = 0;
|
||||
};
|
||||
static_assert(sizeof(PipelineLayoutKey) == 32);
|
||||
|
||||
VulkanPipelineLayoutCache(VulkanPipelineLayoutCache const&) = delete;
|
||||
VulkanPipelineLayoutCache& operator=(VulkanPipelineLayoutCache const&) = delete;
|
||||
|
||||
VkPipelineLayout getLayout(VulkanDescriptorSetLayoutList const& descriptorSetLayouts);
|
||||
// A pipeline layout depends on the descriptor set layout and the push constant ranges, which
|
||||
// are described in the program.
|
||||
VkPipelineLayout getLayout(VulkanDescriptorSetLayoutList const& descriptorSetLayouts,
|
||||
VulkanProgram* program);
|
||||
|
||||
private:
|
||||
using Timestamp = uint64_t;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#include "backend/platforms/VulkanPlatform.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include "vulkan/platform/VulkanPlatformSwapChainImpl.h"
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "vulkan/VulkanDriver.h"
|
||||
@@ -44,7 +46,11 @@ namespace {
|
||||
|
||||
constexpr uint32_t const INVALID_VK_INDEX = 0xFFFFFFFF;
|
||||
|
||||
typedef std::unordered_set<std::string_view> ExtensionSet;
|
||||
using ExtensionSet = VulkanPlatform::ExtensionSet;
|
||||
|
||||
inline bool setContains(ExtensionSet const& set, utils::CString const& extension) {
|
||||
return set.find(extension) != set.end();
|
||||
};
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
// These strings need to be allocated outside a function stack
|
||||
@@ -80,7 +86,7 @@ FixedCapacityVector<const char*> getEnabledLayers() {
|
||||
|
||||
void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
|
||||
// Print some driver or MoltenVK information if it is available.
|
||||
if (vkGetPhysicalDeviceProperties2KHR) {
|
||||
if (vkGetPhysicalDeviceProperties2) {
|
||||
VkPhysicalDeviceDriverProperties driverProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES,
|
||||
};
|
||||
@@ -88,7 +94,7 @@ void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
||||
.pNext = &driverProperties,
|
||||
};
|
||||
vkGetPhysicalDeviceProperties2KHR(device, &physicalDeviceProperties2);
|
||||
vkGetPhysicalDeviceProperties2(device, &physicalDeviceProperties2);
|
||||
utils::slog.i << "Vulkan device driver: " << driverProperties.driverName << " "
|
||||
<< driverProperties.driverInfo << utils::io::endl;
|
||||
}
|
||||
@@ -148,38 +154,37 @@ void printDepthFormats(VkPhysicalDevice device) {
|
||||
}
|
||||
#endif
|
||||
|
||||
ExtensionSet getInstanceExtensions() {
|
||||
std::string_view const TARGET_EXTS[] = {
|
||||
// Request all cross-platform extensions.
|
||||
VK_KHR_SURFACE_EXTENSION_NAME,
|
||||
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
|
||||
ExtensionSet getInstanceExtensions(ExtensionSet const& externallyRequiredExts = {}) {
|
||||
ExtensionSet const TARGET_EXTS = {
|
||||
// Request all cross-platform extensions.
|
||||
VK_KHR_SURFACE_EXTENSION_NAME,
|
||||
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
|
||||
|
||||
// Request these if available.
|
||||
// Request these if available.
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
|
||||
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
|
||||
#endif
|
||||
VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
|
||||
VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
|
||||
VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
|
||||
#endif
|
||||
};
|
||||
ExtensionSet exts;
|
||||
FixedCapacityVector<VkExtensionProperties> const availableExts
|
||||
= filament::backend::enumerate(vkEnumerateInstanceExtensionProperties,
|
||||
FixedCapacityVector<VkExtensionProperties> const availableExts =
|
||||
filament::backend::enumerate(vkEnumerateInstanceExtensionProperties,
|
||||
static_cast<char const*>(nullptr) /* pLayerName */);
|
||||
for (auto const& extProps: availableExts) {
|
||||
for (auto const& targetExt: TARGET_EXTS) {
|
||||
if (targetExt == extProps.extensionName) {
|
||||
exts.insert(targetExt);
|
||||
}
|
||||
utils::CString name { extProps.extensionName };
|
||||
if (setContains(TARGET_EXTS, name) || setContains(externallyRequiredExts, name)) {
|
||||
exts.insert(name);
|
||||
}
|
||||
}
|
||||
return exts;
|
||||
}
|
||||
|
||||
ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
|
||||
std::string_view const TARGET_EXTS[] = {
|
||||
ExtensionSet const TARGET_EXTS = {
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
VK_EXT_DEBUG_MARKER_EXTENSION_NAME,
|
||||
#endif
|
||||
@@ -194,10 +199,9 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
|
||||
= filament::backend::enumerate(vkEnumerateDeviceExtensionProperties, device,
|
||||
static_cast<const char*>(nullptr) /* pLayerName */);
|
||||
for (auto const& extension: extensions) {
|
||||
for (auto const& targetExt: TARGET_EXTS) {
|
||||
if (targetExt == extension.extensionName) {
|
||||
exts.insert(targetExt);
|
||||
}
|
||||
utils::CString name { extension.extensionName };
|
||||
if (setContains(TARGET_EXTS, name)) {
|
||||
exts.insert(name);
|
||||
}
|
||||
}
|
||||
return exts;
|
||||
@@ -245,7 +249,7 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
ppEnabledExtensions[enabledExtensionCount++] = VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME;
|
||||
}
|
||||
// Request platform-specific extensions.
|
||||
for (auto const requiredExt: requiredExts) {
|
||||
for (auto const& requiredExt: requiredExts) {
|
||||
assert_invariant(enabledExtensionCount < MAX_INSTANCE_EXTENSION_COUNT);
|
||||
ppEnabledExtensions[enabledExtensionCount++] = requiredExt.data();
|
||||
}
|
||||
@@ -260,7 +264,7 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
instanceCreateInfo.pApplicationInfo = &appInfo;
|
||||
instanceCreateInfo.enabledExtensionCount = enabledExtensionCount;
|
||||
instanceCreateInfo.ppEnabledExtensionNames = ppEnabledExtensions;
|
||||
if (requiredExts.find(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) != requiredExts.end()) {
|
||||
if (setContains(requiredExts, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
|
||||
instanceCreateInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
}
|
||||
|
||||
@@ -277,14 +281,14 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
}
|
||||
|
||||
VkResult result = vkCreateInstance(&instanceCreateInfo, VKALLOC, &instance);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create Vulkan instance. Result=%d",
|
||||
result);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create Vulkan instance. Result=" << result;
|
||||
return instance;
|
||||
}
|
||||
|
||||
VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
const VkPhysicalDeviceFeatures& features, uint32_t graphicsQueueFamilyIndex,
|
||||
const ExtensionSet& deviceExtensions) {
|
||||
VkPhysicalDeviceFeatures const& features, uint32_t graphicsQueueFamilyIndex,
|
||||
ExtensionSet const& deviceExtensions) {
|
||||
VkDevice device;
|
||||
VkDeviceQueueCreateInfo deviceQueueCreateInfo[1] = {};
|
||||
const float queuePriority[] = {1.0f};
|
||||
@@ -292,9 +296,9 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
FixedCapacityVector<const char*> requestExtensions;
|
||||
requestExtensions.reserve(deviceExtensions.size() + 1);
|
||||
|
||||
// TODO:We don't really need this if we only ever expect headless swapchains.
|
||||
// TODO: We don't really need this if we only ever expect headless swapchains.
|
||||
requestExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
for (auto ext: deviceExtensions) {
|
||||
for (auto const& ext: deviceExtensions) {
|
||||
requestExtensions.push_back(ext.data());
|
||||
}
|
||||
deviceQueueCreateInfo->sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
@@ -311,6 +315,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
.samplerAnisotropy = features.samplerAnisotropy,
|
||||
.textureCompressionETC2 = features.textureCompressionETC2,
|
||||
.textureCompressionBC = features.textureCompressionBC,
|
||||
.shaderClipDistance = features.shaderClipDistance,
|
||||
};
|
||||
|
||||
deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
|
||||
@@ -323,12 +328,12 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
.imageViewFormatSwizzle = VK_TRUE,
|
||||
.mutableComparisonSamplers = VK_TRUE,
|
||||
};
|
||||
if (deviceExtensions.find(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) != deviceExtensions.end()) {
|
||||
if (setContains(deviceExtensions, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) {
|
||||
deviceCreateInfo.pNext = &portability;
|
||||
}
|
||||
|
||||
VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, VKALLOC, &device);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateDevice error.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateDevice error=" << result << ".";
|
||||
|
||||
return device;
|
||||
}
|
||||
@@ -342,16 +347,16 @@ std::tuple<ExtensionSet, ExtensionSet> pruneExtensions(VkPhysicalDevice device,
|
||||
|
||||
#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()) {
|
||||
if (setContains(newInstExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) &&
|
||||
setContains(newInstExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME)) {
|
||||
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()) {
|
||||
if (setContains(newInstExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME) &&
|
||||
!setContains(newInstExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME)) {
|
||||
newDeviceExts.erase(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
|
||||
}
|
||||
#endif
|
||||
@@ -462,9 +467,9 @@ VkPhysicalDevice selectPhysicalDevice(VkInstance instance,
|
||||
deviceList[deviceInd].name = targetDeviceProperties.deviceName;
|
||||
}
|
||||
|
||||
ASSERT_PRECONDITION(gpuPreference.index < static_cast<int32_t>(deviceList.size()),
|
||||
"Provided GPU index=%d >= the number of GPUs=%d", gpuPreference.index,
|
||||
static_cast<int32_t>(deviceList.size()));
|
||||
FILAMENT_CHECK_PRECONDITION(gpuPreference.index < static_cast<int32_t>(deviceList.size()))
|
||||
<< "Provided GPU index=" << gpuPreference.index
|
||||
<< " >= the number of GPUs=" << static_cast<int32_t>(deviceList.size());
|
||||
|
||||
// Sort the found devices
|
||||
std::sort(deviceList.begin(), deviceList.end(),
|
||||
@@ -492,7 +497,7 @@ VkPhysicalDevice selectPhysicalDevice(VkInstance instance,
|
||||
return deviceTypeOrder(a.deviceType) < deviceTypeOrder(b.deviceType);
|
||||
});
|
||||
auto device = deviceList.back().device;
|
||||
ASSERT_POSTCONDITION(device != VK_NULL_HANDLE, "Unable to find suitable device.");
|
||||
FILAMENT_CHECK_POSTCONDITION(device != VK_NULL_HANDLE) << "Unable to find suitable device.";
|
||||
return device;
|
||||
}
|
||||
|
||||
@@ -557,6 +562,7 @@ struct VulkanPlatformPrivate {
|
||||
std::unordered_set<SwapChainPtr> mHeadlessSwapChains;
|
||||
|
||||
bool mSharedContext = false;
|
||||
bool mForceXCBSwapchain = false;
|
||||
};
|
||||
|
||||
void VulkanPlatform::terminate() {
|
||||
@@ -580,21 +586,21 @@ void VulkanPlatform::terminate() {
|
||||
Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
// Load Vulkan entry points.
|
||||
ASSERT_POSTCONDITION(bluevk::initialize(), "BlueVK is unable to load entry points.");
|
||||
FILAMENT_CHECK_POSTCONDITION(bluevk::initialize()) << "BlueVK is unable to load entry points.";
|
||||
|
||||
if (sharedContext) {
|
||||
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
|
||||
// All fields of VulkanSharedContext should be present.
|
||||
ASSERT_PRECONDITION(scontext->instance != VK_NULL_HANDLE,
|
||||
"Client needs to provide VkInstance");
|
||||
ASSERT_PRECONDITION(scontext->physicalDevice != VK_NULL_HANDLE,
|
||||
"Client needs to provide VkPhysicalDevice");
|
||||
ASSERT_PRECONDITION(scontext->logicalDevice != VK_NULL_HANDLE,
|
||||
"Client needs to provide VkDevice");
|
||||
ASSERT_PRECONDITION(scontext->graphicsQueueFamilyIndex != INVALID_VK_INDEX,
|
||||
"Client needs to provide graphics queue family index");
|
||||
ASSERT_PRECONDITION(scontext->graphicsQueueIndex != INVALID_VK_INDEX,
|
||||
"Client needs to provide graphics queue index");
|
||||
FILAMENT_CHECK_PRECONDITION(scontext->instance != VK_NULL_HANDLE)
|
||||
<< "Client needs to provide VkInstance";
|
||||
FILAMENT_CHECK_PRECONDITION(scontext->physicalDevice != VK_NULL_HANDLE)
|
||||
<< "Client needs to provide VkPhysicalDevice";
|
||||
FILAMENT_CHECK_PRECONDITION(scontext->logicalDevice != VK_NULL_HANDLE)
|
||||
<< "Client needs to provide VkDevice";
|
||||
FILAMENT_CHECK_PRECONDITION(scontext->graphicsQueueFamilyIndex != INVALID_VK_INDEX)
|
||||
<< "Client needs to provide graphics queue family index";
|
||||
FILAMENT_CHECK_PRECONDITION(scontext->graphicsQueueIndex != INVALID_VK_INDEX)
|
||||
<< "Client needs to provide graphics queue index";
|
||||
|
||||
mImpl->mInstance = scontext->instance;
|
||||
mImpl->mPhysicalDevice = scontext->physicalDevice;
|
||||
@@ -610,7 +616,25 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
ExtensionSet instExts;
|
||||
// If using a shared context, we do not assume any extensions.
|
||||
if (!mImpl->mSharedContext) {
|
||||
instExts = getInstanceExtensions();
|
||||
// This constains instance extensions that are required for the platform, which includes
|
||||
// swapchain surface extensions.
|
||||
auto const& swapchainExts = getSwapchainInstanceExtensions();
|
||||
instExts = getInstanceExtensions(swapchainExts);
|
||||
|
||||
#if defined(FILAMENT_SUPPORTS_XCB) && defined(FILAMENT_SUPPORTS_XLIB)
|
||||
// For the special case where we're on linux and both xcb and xlib are "required", then we
|
||||
// check if the set of supported extensions contain both of them. If only xcb is supported,
|
||||
// we force XCB surface creation. This workaround is needed for the default swiftshader
|
||||
// build where only XCB is available.
|
||||
if (setContains(swapchainExts, VK_KHR_XCB_SURFACE_EXTENSION_NAME) &&
|
||||
setContains(swapchainExts, VK_KHR_XLIB_SURFACE_EXTENSION_NAME)) {
|
||||
// Assume only XCB is left, then we force the XCB path in the swapchain creation.
|
||||
mImpl->mForceXCBSwapchain = !setContains(instExts, VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
|
||||
assert_invariant(!mImpl->mForceXCBSwapchain ||
|
||||
setContains(instExts, VK_KHR_XCB_SURFACE_EXTENSION_NAME));
|
||||
}
|
||||
#endif
|
||||
|
||||
instExts.merge(getRequiredInstanceExtensions());
|
||||
}
|
||||
|
||||
@@ -622,8 +646,8 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
|
||||
VulkanPlatform::Customization::GPUPreference const pref = getCustomization().gpu;
|
||||
bool const hasGPUPreference = pref.index >= 0 || !pref.deviceName.empty();
|
||||
ASSERT_PRECONDITION(!(hasGPUPreference && sharedContext),
|
||||
"Cannot both share context and indicate GPU preference");
|
||||
FILAMENT_CHECK_PRECONDITION(!(hasGPUPreference && sharedContext))
|
||||
<< "Cannot both share context and indicate GPU preference";
|
||||
|
||||
mImpl->mPhysicalDevice = mImpl->mPhysicalDevice == VK_NULL_HANDLE
|
||||
? selectPhysicalDevice(mImpl->mInstance, pref)
|
||||
@@ -644,6 +668,12 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
: mImpl->mGraphicsQueueFamilyIndex;
|
||||
assert_invariant(mImpl->mGraphicsQueueFamilyIndex != INVALID_VK_INDEX);
|
||||
|
||||
// Only enable shaderClipDistance if we are doing instanced stereoscopic rendering.
|
||||
if (context.mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE
|
||||
&& driverConfig.stereoscopicType != StereoscopicType::INSTANCED) {
|
||||
context.mPhysicalDeviceFeatures.shaderClipDistance = VK_FALSE;
|
||||
}
|
||||
|
||||
// At this point, we should have a family index that points to a family that has > 0 queues for
|
||||
// graphics. In which case, we will allocate one queue for all of Filament (and assumes at least
|
||||
// one has been allocated by the client if context was shared). If the index of the target queue
|
||||
@@ -672,15 +702,13 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
assert_invariant(mImpl->mGraphicsQueue != VK_NULL_HANDLE);
|
||||
|
||||
// Store the extension support in the context
|
||||
context.mDebugUtilsSupported
|
||||
= instExts.find(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) != instExts.end();
|
||||
context.mDebugMarkersSupported
|
||||
= deviceExts.find(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) != deviceExts.end();
|
||||
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
|
||||
|
||||
#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.");
|
||||
FILAMENT_CHECK_POSTCONDITION(!context.mDebugUtilsSupported && !context.mDebugMarkersSupported)
|
||||
<< "Debug utils should not be enabled in release build.";
|
||||
#endif
|
||||
|
||||
context.mDepthStencilFormats = findAttachmentDepthStencilFormats(mImpl->mPhysicalDevice);
|
||||
@@ -748,6 +776,10 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
|
||||
return swapchain;
|
||||
}
|
||||
|
||||
if (mImpl->mForceXCBSwapchain) {
|
||||
flags |= SWAP_CHAIN_CONFIG_ENABLE_XCB;
|
||||
}
|
||||
|
||||
auto [surface, fallbackExtent] = createVkSurfaceKHR(nativeWindow, mImpl->mInstance, flags);
|
||||
// The VulkanPlatformSurfaceSwapChain now `owns` the surface.
|
||||
VulkanPlatformSurfaceSwapChain* swapchain = new VulkanPlatformSurfaceSwapChain(mImpl->mContext,
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
uint32_t height;
|
||||
} wl;
|
||||
}// anonymous namespace
|
||||
#elif LINUX_OR_FREEBSD && defined(FILAMENT_SUPPORTS_X11)
|
||||
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
|
||||
// TODO: we should allow for headless on Linux explicitly. Right now this is the headless path
|
||||
// (with no FILAMENT_SUPPORTS_XCB or FILAMENT_SUPPORTS_XLIB).
|
||||
#include <dlfcn.h>
|
||||
@@ -86,22 +86,23 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getRequiredInstanceExtensions() {
|
||||
VulkanPlatform::ExtensionSet ret;
|
||||
#if defined(__ANDROID__)
|
||||
ret.insert("VK_KHR_android_surface");
|
||||
#elif defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
ret.insert("VK_KHR_wayland_surface");
|
||||
#elif LINUX_OR_FREEBSD && defined(FILAMENT_SUPPORTS_X11)
|
||||
#if defined(FILAMENT_SUPPORTS_XCB)
|
||||
ret.insert("VK_KHR_xcb_surface");
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
ret.insert("VK_KHR_xlib_surface");
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
ret.insert("VK_KHR_win32_surface");
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() {
|
||||
VulkanPlatform::ExtensionSet const ret = {
|
||||
#if defined(__ANDROID__)
|
||||
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
|
||||
#elif defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
|
||||
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
|
||||
#if defined(FILAMENT_SUPPORTS_XCB)
|
||||
VK_KHR_XCB_SURFACE_EXTENSION_NAME,
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
|
||||
#endif
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -122,7 +123,7 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
};
|
||||
VkResult const result = vkCreateAndroidSurfaceKHR(instance, &createInfo, VKALLOC,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateAndroidSurfaceKHR error.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateAndroidSurfaceKHR error.";
|
||||
#elif defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
wl* ptrval = reinterpret_cast<wl*>(nativeWindow);
|
||||
extent.width = ptrval->width;
|
||||
@@ -137,24 +138,20 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
};
|
||||
VkResult const result = vkCreateWaylandSurfaceKHR(instance, &createInfo, VKALLOC,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateWaylandSurfaceKHR error.");
|
||||
#elif LINUX_OR_FREEBSD && defined(FILAMENT_SUPPORTS_X11)
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateWaylandSurfaceKHR error.";
|
||||
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
|
||||
if (g_x11_vk.library == nullptr) {
|
||||
g_x11_vk.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW);
|
||||
ASSERT_PRECONDITION(g_x11_vk.library, "Unable to open X11 library.");
|
||||
FILAMENT_CHECK_PRECONDITION(g_x11_vk.library) << "Unable to open X11 library.";
|
||||
#if defined(FILAMENT_SUPPORTS_XCB)
|
||||
g_x11_vk.xcbConnect = (XCB_CONNECT) dlsym(g_x11_vk.library, "xcb_connect");
|
||||
int screen;
|
||||
g_x11_vk.connection = g_x11_vk.xcbConnect(nullptr, &screen);
|
||||
ASSERT_POSTCONDITION(vkCreateXcbSurfaceKHR,
|
||||
"Unable to load vkCreateXcbSurfaceKHR function.");
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
g_x11_vk.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11_vk.library, "XOpenDisplay");
|
||||
g_x11_vk.display = g_x11_vk.openDisplay(NULL);
|
||||
ASSERT_PRECONDITION(g_x11_vk.display, "Unable to open X11 display.");
|
||||
ASSERT_POSTCONDITION(vkCreateXlibSurfaceKHR,
|
||||
"Unable to load vkCreateXlibSurfaceKHR function.");
|
||||
FILAMENT_CHECK_PRECONDITION(g_x11_vk.display) << "Unable to open X11 display.";
|
||||
#endif
|
||||
}
|
||||
#if defined(FILAMENT_SUPPORTS_XCB) || defined(FILAMENT_SUPPORTS_XLIB)
|
||||
@@ -167,6 +164,9 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
useXcb = true;
|
||||
#endif
|
||||
if (useXcb) {
|
||||
FILAMENT_CHECK_POSTCONDITION(vkCreateXcbSurfaceKHR)
|
||||
<< "Unable to load vkCreateXcbSurfaceKHR function.";
|
||||
|
||||
VkXcbSurfaceCreateInfoKHR const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR,
|
||||
.connection = g_x11_vk.connection,
|
||||
@@ -174,11 +174,15 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
};
|
||||
VkResult const result = vkCreateXcbSurfaceKHR(instance, &createInfo, VKALLOC,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateXcbSurfaceKHR error.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateXcbSurfaceKHR error.";
|
||||
}
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
if (!useXcb) {
|
||||
FILAMENT_CHECK_POSTCONDITION(vkCreateXlibSurfaceKHR)
|
||||
<< "Unable to load vkCreateXlibSurfaceKHR function.";
|
||||
|
||||
VkXlibSurfaceCreateInfoKHR const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
|
||||
.dpy = g_x11_vk.display,
|
||||
@@ -186,7 +190,8 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
};
|
||||
VkResult const result = vkCreateXlibSurfaceKHR(instance, &createInfo, VKALLOC,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateXlibSurfaceKHR error.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateXlibSurfaceKHR error.";
|
||||
}
|
||||
#endif
|
||||
#elif defined(WIN32)
|
||||
@@ -197,7 +202,7 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
};
|
||||
VkResult const result = vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateWin32SurfaceKHR error.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateWin32SurfaceKHR error.";
|
||||
#endif
|
||||
return std::make_tuple(surface, extent);
|
||||
}
|
||||
|
||||
@@ -52,13 +52,14 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getRequiredInstanceExtensions() {
|
||||
ExtensionSet ret;
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() {
|
||||
ExtensionSet const ret = {
|
||||
#if defined(__APPLE__)
|
||||
ret.insert("VK_MVK_macos_surface"); // TODO: replace with VK_EXT_metal_surface
|
||||
#elif defined(IOS)
|
||||
ret.insert("VK_MVK_ios_surface");
|
||||
VK_MVK_MACOS_SURFACE_EXTENSION_NAME, // TODO: replace with VK_EXT_metal_surface
|
||||
#elif defined(IOS) && defined(METAL_AVAILABLE)
|
||||
VK_MVK_IOS_SURFACE_EXTENSION_NAME,
|
||||
#endif
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -67,20 +68,22 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
VkSurfaceKHR surface;
|
||||
#if defined(__APPLE__)
|
||||
NSView* nsview = (__bridge NSView*) nativeWindow;
|
||||
ASSERT_POSTCONDITION(nsview, "Unable to obtain Metal-backed NSView.");
|
||||
FILAMENT_CHECK_POSTCONDITION(nsview) << "Unable to obtain Metal-backed NSView.";
|
||||
|
||||
// Create the VkSurface.
|
||||
ASSERT_POSTCONDITION(vkCreateMacOSSurfaceMVK, "Unable to load vkCreateMacOSSurfaceMVK.");
|
||||
FILAMENT_CHECK_POSTCONDITION(vkCreateMacOSSurfaceMVK)
|
||||
<< "Unable to load vkCreateMacOSSurfaceMVK.";
|
||||
VkMacOSSurfaceCreateInfoMVK createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
||||
createInfo.pView = (__bridge void*) nsview;
|
||||
VkResult result = vkCreateMacOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateMacOSSurfaceMVK error.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateMacOSSurfaceMVK error.";
|
||||
#elif defined(IOS) && defined(METAL_AVAILABLE)
|
||||
CAMetalLayer* metalLayer = (CAMetalLayer*) nativeWindow;
|
||||
// Create the VkSurface.
|
||||
ASSERT_POSTCONDITION(vkCreateIOSSurfaceMVK, "Unable to load vkCreateIOSSurfaceMVK function.");
|
||||
FILAMENT_CHECK_POSTCONDITION(vkCreateIOSSurfaceMVK)
|
||||
<< "Unable to load vkCreateIOSSurfaceMVK function.";
|
||||
VkIOSSurfaceCreateInfoMVK createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
|
||||
createInfo.pNext = NULL;
|
||||
@@ -88,7 +91,7 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
createInfo.pView = metalLayer;
|
||||
VkResult result = vkCreateIOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateIOSSurfaceMVK error.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateIOSSurfaceMVK error.";
|
||||
#endif
|
||||
return std::make_tuple(surface, VkExtent2D {});
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
|
||||
};
|
||||
VkImage image;
|
||||
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &image);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS,
|
||||
"Unable to create image: ", static_cast<int32_t>(result));
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create image: " << static_cast<int32_t>(result);
|
||||
|
||||
// Allocate memory for the VkImage and bind it.
|
||||
VkDeviceMemory imageMemory;
|
||||
@@ -61,8 +61,8 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
|
||||
uint32_t memoryTypeIndex
|
||||
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
|
||||
ASSERT_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES,
|
||||
"VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements.");
|
||||
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES)
|
||||
<< "VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements.";
|
||||
|
||||
VkMemoryAllocateInfo allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
@@ -70,9 +70,9 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
};
|
||||
result = vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to allocate image memory.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to allocate image memory.";
|
||||
result = vkBindImageMemory(device, image, imageMemory, 0);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to bind image.");
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to bind image.";
|
||||
return std::tuple(image, imageMemory);
|
||||
}
|
||||
|
||||
@@ -178,8 +178,8 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_POSTCONDITION(surfaceFormat.format != VK_FORMAT_UNDEFINED,
|
||||
"Cannot find suitable swapchain format");
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceFormat.format != VK_FORMAT_UNDEFINED)
|
||||
<< "Cannot find suitable swapchain format";
|
||||
|
||||
// Verify that our chosen present mode is supported. In practice all devices support the FIFO
|
||||
// mode, but we check for it anyway for completeness. (and to avoid validation warnings)
|
||||
@@ -193,8 +193,8 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_POSTCONDITION(foundSuitablePresentMode,
|
||||
"Desired present mode is not supported by this device.");
|
||||
FILAMENT_CHECK_POSTCONDITION(foundSuitablePresentMode)
|
||||
<< "Desired present mode is not supported by this device.";
|
||||
|
||||
// Create the low-level swap chain.
|
||||
if (caps.currentExtent.width == VULKAN_UNDEFINED_EXTENT
|
||||
@@ -237,8 +237,8 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
.oldSwapchain = mSwapchain,
|
||||
};
|
||||
VkResult result = vkCreateSwapchainKHR(mDevice, &createInfo, VKALLOC, &mSwapchain);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkGetPhysicalDeviceSurfaceFormatsKHR error: %d",
|
||||
static_cast<int32_t>(result));
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkGetPhysicalDeviceSurfaceFormatsKHR error: " << static_cast<int32_t>(result);
|
||||
|
||||
mSwapChainBundle.colors = enumerate(vkGetSwapchainImagesKHR, mDevice, mSwapchain);
|
||||
mSwapChainBundle.colorFormat = surfaceFormat.format;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
122
filament/backend/test/test_Callbacks.cpp
Normal file
122
filament/backend/test/test_Callbacks.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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 "BackendTest.h"
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::backend;
|
||||
|
||||
namespace test {
|
||||
|
||||
TEST_F(BackendTest, FrameScheduledCallback) {
|
||||
auto& api = getDriverApi();
|
||||
|
||||
// Create a SwapChain.
|
||||
// In order for the frameScheduledCallback to be called, this must be a real SwapChain (not
|
||||
// headless) so we obtain a drawable.
|
||||
auto swapChain = createSwapChain();
|
||||
|
||||
Handle<HwRenderTarget> renderTarget = api.createDefaultRenderTarget();
|
||||
|
||||
int callbackCountA = 0;
|
||||
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountA](PresentCallable callable) {
|
||||
callable();
|
||||
callbackCountA++;
|
||||
});
|
||||
|
||||
// Render the first frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.beginRenderPass(renderTarget, {});
|
||||
api.endRenderPass(0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Render the next frame. The same callback should be called.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.beginRenderPass(renderTarget, {});
|
||||
api.endRenderPass(0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Now switch out the callback.
|
||||
int callbackCountB = 0;
|
||||
api.setFrameScheduledCallback(swapChain, nullptr, [&callbackCountB](PresentCallable callable) {
|
||||
callable();
|
||||
callbackCountB++;
|
||||
});
|
||||
|
||||
// Render one final frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.beginRenderPass(renderTarget, {});
|
||||
api.endRenderPass(0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.finish();
|
||||
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
|
||||
EXPECT_EQ(callbackCountA, 2);
|
||||
EXPECT_EQ(callbackCountB, 1);
|
||||
}
|
||||
|
||||
TEST_F(BackendTest, FrameCompletedCallback) {
|
||||
auto& api = getDriverApi();
|
||||
|
||||
// Create a SwapChain.
|
||||
auto swapChain = api.createSwapChainHeadless(256, 256, 0);
|
||||
|
||||
int callbackCountA = 0;
|
||||
api.setFrameCompletedCallback(swapChain, nullptr,
|
||||
[&callbackCountA]() { callbackCountA++; });
|
||||
|
||||
// Render the first frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Render the next frame. The same callback should be called.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Now switch out the callback.
|
||||
int callbackCountB = 0;
|
||||
api.setFrameCompletedCallback(swapChain, nullptr,
|
||||
[&callbackCountB]() { callbackCountB++; });
|
||||
|
||||
// Render one final frame.
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.finish();
|
||||
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
|
||||
EXPECT_EQ(callbackCountA, 2);
|
||||
EXPECT_EQ(callbackCountB, 1);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -265,7 +265,7 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
for (auto rt : renderTargets) api.destroyRenderTarget(rt);
|
||||
}
|
||||
|
||||
const uint32_t expected = 0xe93a4a07;
|
||||
const uint32_t expected = 0x70695aa1;
|
||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sPixelHashResult, expected);
|
||||
EXPECT_TRUE(sPixelHashResult == expected);
|
||||
}
|
||||
|
||||
168
filament/backend/test/test_PushConstants.cpp
Normal file
168
filament/backend/test/test_PushConstants.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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 "BackendTest.h"
|
||||
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
|
||||
namespace test {
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::backend;
|
||||
|
||||
static constexpr struct {
|
||||
size_t TRIANGLE_HIDE = 0;
|
||||
size_t TRIANGLE_SCALE = 1;
|
||||
size_t TRIANGLE_OFFSET_X = 2;
|
||||
size_t TRIANGLE_OFFSET_Y = 3;
|
||||
|
||||
size_t RED = 0;
|
||||
size_t GREEN = 2;
|
||||
size_t BLUE = 3;
|
||||
} pushConstantIndex;
|
||||
|
||||
static const char* const triangleVs = R"(#version 450 core
|
||||
|
||||
layout(push_constant) uniform Constants {
|
||||
bool hideTriangle;
|
||||
float triangleScale;
|
||||
float triangleOffsetX;
|
||||
float triangleOffsetY;
|
||||
} pushConstants;
|
||||
|
||||
layout(location = 0) in vec4 mesh_position;
|
||||
void main() {
|
||||
if (pushConstants.hideTriangle) {
|
||||
// Test that bools are written correctly. All bits must be 0 if the bool is false.
|
||||
gl_Position = vec4(0.0);
|
||||
return;
|
||||
}
|
||||
gl_Position = vec4(mesh_position.xy * pushConstants.triangleScale +
|
||||
vec2(pushConstants.triangleOffsetX, pushConstants.triangleOffsetY), 0.0, 1.0);
|
||||
#if defined(TARGET_VULKAN_ENVIRONMENT)
|
||||
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
|
||||
gl_Position.y = -gl_Position.y;
|
||||
#endif
|
||||
})";
|
||||
|
||||
static const char* const triangleFs = R"(#version 450 core
|
||||
|
||||
layout(push_constant) uniform Constants {
|
||||
float red;
|
||||
bool padding; // test correct bool padding
|
||||
float green;
|
||||
float blue;
|
||||
} pushConstants;
|
||||
|
||||
precision mediump int; precision highp float;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = vec4(pushConstants.red, pushConstants.green, pushConstants.blue, 1.0);
|
||||
})";
|
||||
|
||||
TEST_F(BackendTest, PushConstants) {
|
||||
auto& api = getDriverApi();
|
||||
|
||||
api.startCapture(0);
|
||||
|
||||
// The test is executed within this block scope to force destructors to run before
|
||||
// executeCommands().
|
||||
{
|
||||
// Create a SwapChain and make it current.
|
||||
auto swapChain = createSwapChain();
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a program.
|
||||
ShaderGenerator shaderGen(triangleVs, triangleFs, sBackend, sIsMobilePlatform);
|
||||
Program p = shaderGen.getProgram(api);
|
||||
ProgramHandle program = api.createProgram(std::move(p));
|
||||
|
||||
Handle<HwRenderTarget> renderTarget = api.createDefaultRenderTarget();
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
|
||||
RenderPassParams params = {};
|
||||
params.flags.clear = TargetBufferFlags::COLOR0;
|
||||
params.viewport = { 0, 0, 512, 512 };
|
||||
params.clearColor = math::float4(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
params.flags.discardStart = TargetBufferFlags::ALL;
|
||||
params.flags.discardEnd = TargetBufferFlags::NONE;
|
||||
|
||||
PipelineState ps = {};
|
||||
ps.program = program;
|
||||
ps.rasterState.colorWrite = true;
|
||||
ps.rasterState.depthWrite = false;
|
||||
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
|
||||
// Set the push constants to scale the triangle in half
|
||||
api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_HIDE, false);
|
||||
api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_SCALE, 0.5f);
|
||||
api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_X, 0.0f);
|
||||
api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_Y, 0.0f);
|
||||
api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.RED, 0.25f);
|
||||
api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.GREEN, 0.5f);
|
||||
api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.BLUE, 1.0f);
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
|
||||
// Draw another triangle, transposed to the upper-right.
|
||||
api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_X, 0.5f);
|
||||
api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_Y, 0.5f);
|
||||
|
||||
api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.RED, 1.00f);
|
||||
api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.GREEN, 0.5f);
|
||||
api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.BLUE, 0.25f);
|
||||
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
|
||||
// Draw a final triangle, transposed to the lower-left.
|
||||
api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_X, -0.5f);
|
||||
api.setPushConstant(ShaderStage::VERTEX, pushConstantIndex.TRIANGLE_OFFSET_Y, -0.5f);
|
||||
|
||||
api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.RED, 0.5f);
|
||||
api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.GREEN, 0.25f);
|
||||
api.setPushConstant(ShaderStage::FRAGMENT, pushConstantIndex.BLUE, 1.00f);
|
||||
|
||||
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
|
||||
|
||||
api.endRenderPass();
|
||||
|
||||
readPixelsAndAssertHash("pushConstants", 512, 512, renderTarget, 1957275826, true);
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Cleanup.
|
||||
api.destroySwapChain(swapChain);
|
||||
api.destroyRenderTarget(renderTarget);
|
||||
}
|
||||
|
||||
api.stopCapture(0);
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
api.finish();
|
||||
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -315,7 +315,7 @@ public:
|
||||
*
|
||||
* @see View::setStereoscopicOptions
|
||||
*/
|
||||
StereoscopicType stereoscopicType = StereoscopicType::INSTANCED;
|
||||
StereoscopicType stereoscopicType = StereoscopicType::NONE;
|
||||
|
||||
/*
|
||||
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
|
||||
|
||||
@@ -464,16 +464,11 @@ public:
|
||||
Builder& boneIndicesAndWeights(size_t primitiveIndex,
|
||||
utils::FixedCapacityVector<
|
||||
utils::FixedCapacityVector<math::float2>> indicesAndWeightsVector) noexcept;
|
||||
|
||||
/**
|
||||
* Controls if the renderable has vertex morphing targets, zero by default. This is
|
||||
* Controls if the renderable has legacy vertex morphing targets, zero by default. This is
|
||||
* required to enable GPU morphing.
|
||||
*
|
||||
* Filament supports two morphing modes: standard (default) and legacy.
|
||||
*
|
||||
* For standard morphing, A MorphTargetBuffer must be created and provided via
|
||||
* RenderableManager::setMorphTargetBufferAt(). Standard morphing supports up to
|
||||
* \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets.
|
||||
*
|
||||
* For legacy morphing, the attached VertexBuffer must provide data in the
|
||||
* appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only
|
||||
* supports up to 4 morph targets and will be deprecated in the future. Legacy morphing must
|
||||
@@ -486,26 +481,52 @@ public:
|
||||
Builder& morphing(size_t targetCount) noexcept;
|
||||
|
||||
/**
|
||||
* Specifies the morph target buffer for a primitive.
|
||||
* Controls if the renderable has vertex morphing targets, zero by default. This is
|
||||
* required to enable GPU morphing.
|
||||
*
|
||||
* The morph target buffer must have an associated renderable and geometry. Two conditions
|
||||
* must be met:
|
||||
* 1. The number of morph targets in the buffer must equal the renderable's morph target
|
||||
* count.
|
||||
* 2. The vertex count of each morph target must equal the geometry's vertex count.
|
||||
* Filament supports two morphing modes: standard (default) and legacy.
|
||||
*
|
||||
* @param level the level of detail (lod), only 0 can be specified
|
||||
* @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor
|
||||
* @param morphTargetBuffer specifies the morph target buffer
|
||||
* @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices)
|
||||
* @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3)
|
||||
* For standard morphing, A MorphTargetBuffer must be provided.
|
||||
* Standard morphing supports up to \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets.
|
||||
*
|
||||
* For legacy morphing, the attached VertexBuffer must provide data in the
|
||||
* appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only
|
||||
* supports up to 4 morph targets and will be deprecated in the future. Legacy morphing must
|
||||
* be enabled on the material definition: either via the legacyMorphing material attribute
|
||||
* or by calling filamat::MaterialBuilder::useLegacyMorphing().
|
||||
*
|
||||
* See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis
|
||||
* to advance the animation.
|
||||
*/
|
||||
Builder& morphing(MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept;
|
||||
|
||||
/**
|
||||
* @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead
|
||||
*/
|
||||
Builder& morphing(uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer,
|
||||
size_t offset, size_t count) noexcept;
|
||||
|
||||
/**
|
||||
* @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead
|
||||
*/
|
||||
inline Builder& morphing(uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept;
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept {
|
||||
return morphing(level, primitiveIndex, morphTargetBuffer, 0,
|
||||
morphTargetBuffer->getVertexCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the the range of the MorphTargetBuffer to use with this primitive.
|
||||
*
|
||||
* @param level the level of detail (lod), only 0 can be specified
|
||||
* @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor
|
||||
* @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices)
|
||||
* @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3)
|
||||
*/
|
||||
Builder& morphing(uint8_t level, size_t primitiveIndex,
|
||||
size_t offset, size_t count) noexcept;
|
||||
|
||||
|
||||
/**
|
||||
* Sets the drawing order for blended primitives. The drawing order is either global or
|
||||
@@ -765,14 +786,19 @@ public:
|
||||
/**
|
||||
* Associates a MorphTargetBuffer to the given primitive.
|
||||
*/
|
||||
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
size_t offset, size_t count);
|
||||
|
||||
/** @deprecated */
|
||||
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, size_t offset, size_t count);
|
||||
|
||||
/**
|
||||
* Utility method to change a MorphTargetBuffer to the given primitive
|
||||
*/
|
||||
/** @deprecated */
|
||||
inline void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer);
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) {
|
||||
setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0,
|
||||
morphTargetBuffer->getVertexCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a MorphTargetBuffer to the given primitive or null if it doesn't exist.
|
||||
@@ -906,20 +932,6 @@ protected:
|
||||
~RenderableManager() = default;
|
||||
};
|
||||
|
||||
RenderableManager::Builder& RenderableManager::Builder::morphing(
|
||||
uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept {
|
||||
return morphing(level, primitiveIndex, morphTargetBuffer, 0,
|
||||
morphTargetBuffer->getVertexCount());
|
||||
}
|
||||
|
||||
void RenderableManager::setMorphTargetBufferAt(
|
||||
Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) {
|
||||
setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0,
|
||||
morphTargetBuffer->getVertexCount());
|
||||
}
|
||||
|
||||
template<typename VECTOR, typename INDEX, typename, typename>
|
||||
Box RenderableManager::computeAABB(
|
||||
VECTOR const* UTILS_NONNULL vertices,
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <backend/CallbackHandler.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/PresentCallable.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Invocable.h>
|
||||
@@ -115,7 +116,7 @@ class Engine;
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* SDL_SysWMinfo wmi;
|
||||
* SDL_VERSION(&wmi.version);
|
||||
* ASSERT_POSTCONDITION(SDL_GetWindowWMInfo(sdlWindow, &wmi), "SDL version unsupported!");
|
||||
* FILAMENT_CHECK_POSTCONDITION(SDL_GetWindowWMInfo(sdlWindow, &wmi)) << "SDL version unsupported!";
|
||||
* HDC nativeWindow = (HDC) wmi.info.win.hdc;
|
||||
*
|
||||
* using namespace filament;
|
||||
@@ -264,13 +265,22 @@ public:
|
||||
* backend.
|
||||
*
|
||||
* A FrameScheduledCallback can be set on an individual SwapChain through
|
||||
* SwapChain::setFrameScheduledCallback. If the callback is set, then the SwapChain will *not*
|
||||
* automatically schedule itself for presentation. Instead, the application must call the
|
||||
* PresentCallable passed to the FrameScheduledCallback.
|
||||
* SwapChain::setFrameScheduledCallback. If the callback is set for a given frame, then the
|
||||
* SwapChain will *not* 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.
|
||||
* Each SwapChain can have only one FrameScheduledCallback set per frame. If
|
||||
* setFrameScheduledCallback is called multiple times on the same SwapChain before
|
||||
* Renderer::endFrame(), the most recent call effectively overwrites any previously set
|
||||
* callback. This allows the callback to be updated as needed before the frame has finished
|
||||
* encoding.
|
||||
*
|
||||
* The "last" callback set by setFrameScheduledCallback gets "latched" when Renderer::endFrame()
|
||||
* is executed. At this point, the state of the callback is fixed and is the one used for the
|
||||
* frame that was just encoded. Subsequent changes to the callback using
|
||||
* setFrameScheduledCallback after endFrame() apply to the next frame.
|
||||
*
|
||||
* Use \c setFrameScheduledCallback() (with default arguments) to unset the callback.
|
||||
*
|
||||
* 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
|
||||
@@ -278,28 +288,26 @@ public:
|
||||
* Engine::shutdown. This is necessary to ensure the Filament Engine has had a chance to clean
|
||||
* up all memory related to frame presentation.
|
||||
*
|
||||
* @param callback A callback, or nullptr to unset.
|
||||
* @param user An optional pointer to user data passed to the callback function.
|
||||
* @param handler Handler to dispatch the callback or nullptr for the default handler.
|
||||
* @param callback Callback called when the frame is scheduled.
|
||||
*
|
||||
* @remark Only Filament's Metal backend supports PresentCallables and frame callbacks. Other
|
||||
* backends ignore the callback (which will never be called) and proceed normally.
|
||||
*
|
||||
* @remark The SwapChain::FrameScheduledCallback is called on an arbitrary thread.
|
||||
*
|
||||
* @see CallbackHandler
|
||||
* @see PresentCallable
|
||||
*/
|
||||
void setFrameScheduledCallback(FrameScheduledCallback UTILS_NULLABLE callback,
|
||||
void* UTILS_NULLABLE user = nullptr);
|
||||
void setFrameScheduledCallback(backend::CallbackHandler* UTILS_NULLABLE handler = nullptr,
|
||||
FrameScheduledCallback&& callback = {});
|
||||
|
||||
/**
|
||||
* Returns the SwapChain::FrameScheduledCallback that was previously set with
|
||||
* SwapChain::setFrameScheduledCallback, or nullptr if one is not set.
|
||||
* Returns whether or not this SwapChain currently has a FrameScheduledCallback set.
|
||||
*
|
||||
* @return the previously-set FrameScheduledCallback, or nullptr
|
||||
* @return true, if the last call to setFrameScheduledCallback set a callback
|
||||
*
|
||||
* @see SwapChain::setFrameCompletedCallback
|
||||
*/
|
||||
UTILS_NULLABLE FrameScheduledCallback getFrameScheduledCallback() const noexcept;
|
||||
bool isFrameScheduledCallbackSet() const noexcept;
|
||||
|
||||
/**
|
||||
* FrameCompletedCallback is a callback function that notifies an application when a frame's
|
||||
|
||||
@@ -299,7 +299,8 @@ void* Engine::streamAlloc(size_t size, size_t alignment) noexcept {
|
||||
// The external-facing execute does a flush, and is meant only for single-threaded environments.
|
||||
// It also discards the boolean return value, which would otherwise indicate a thread exit.
|
||||
void Engine::execute() {
|
||||
ASSERT_PRECONDITION(!UTILS_HAS_THREADING, "Execute is meant for single-threaded platforms.");
|
||||
FILAMENT_CHECK_PRECONDITION(!UTILS_HAS_THREADING)
|
||||
<< "Execute is meant for single-threaded platforms.";
|
||||
downcast(this)->flush();
|
||||
downcast(this)->execute();
|
||||
}
|
||||
@@ -309,12 +310,14 @@ utils::JobSystem& Engine::getJobSystem() noexcept {
|
||||
}
|
||||
|
||||
bool Engine::isPaused() const noexcept {
|
||||
ASSERT_PRECONDITION(UTILS_HAS_THREADING, "Pause is meant for multi-threaded platforms.");
|
||||
FILAMENT_CHECK_PRECONDITION(UTILS_HAS_THREADING)
|
||||
<< "Pause is meant for multi-threaded platforms.";
|
||||
return downcast(this)->isPaused();
|
||||
}
|
||||
|
||||
void Engine::setPaused(bool paused) {
|
||||
ASSERT_PRECONDITION(UTILS_HAS_THREADING, "Pause is meant for multi-threaded platforms.");
|
||||
FILAMENT_CHECK_PRECONDITION(UTILS_HAS_THREADING)
|
||||
<< "Pause is meant for multi-threaded platforms.";
|
||||
downcast(this)->setPaused(paused);
|
||||
}
|
||||
|
||||
@@ -355,7 +358,7 @@ const Engine::Config& Engine::getConfig() const noexcept {
|
||||
}
|
||||
|
||||
bool Engine::isStereoSupported(StereoscopicType stereoscopicType) const noexcept {
|
||||
return downcast(this)->isStereoSupported(stereoscopicType);
|
||||
return downcast(this)->isStereoSupported();
|
||||
}
|
||||
|
||||
size_t Engine::getMaxStereoscopicEyes() noexcept {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <private/filament/SubpassInfo.h>
|
||||
#include <private/filament/Variant.h>
|
||||
#include <private/filament/ConstantInfo.h>
|
||||
#include <private/filament/PushConstantInfo.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
@@ -225,6 +226,14 @@ bool MaterialParser::getConstants(utils::FixedCapacityVector<MaterialConstant>*
|
||||
return ChunkMaterialConstants::unflatten(unflattener, value);
|
||||
}
|
||||
|
||||
bool MaterialParser::getPushConstants(utils::CString* structVarName,
|
||||
utils::FixedCapacityVector<MaterialPushConstant>* value) const noexcept {
|
||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(filamat::MaterialPushConstants);
|
||||
if (start == end) return false;
|
||||
Unflattener unflattener(start, end);
|
||||
return ChunkMaterialPushConstants::unflatten(unflattener, structVarName, value);
|
||||
}
|
||||
|
||||
bool MaterialParser::getDepthWriteSet(bool* value) const noexcept {
|
||||
return mImpl.getFromSimpleChunk(ChunkType::MaterialDepthWriteSet, value);
|
||||
}
|
||||
@@ -709,4 +718,46 @@ bool ChunkMaterialConstants::unflatten(filaflat::Unflattener& unflattener,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChunkMaterialPushConstants::unflatten(filaflat::Unflattener& unflattener,
|
||||
utils::CString* structVarName,
|
||||
utils::FixedCapacityVector<MaterialPushConstant>* materialPushConstants) {
|
||||
assert_invariant(materialPushConstants);
|
||||
|
||||
if (!unflattener.read(structVarName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read number of constants.
|
||||
uint64_t numConstants = 0;
|
||||
if (!unflattener.read(&numConstants)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
materialPushConstants->reserve(numConstants);
|
||||
materialPushConstants->resize(numConstants);
|
||||
|
||||
for (uint64_t i = 0; i < numConstants; i++) {
|
||||
CString constantName;
|
||||
uint8_t constantType = 0;
|
||||
uint8_t shaderStage = 0;
|
||||
|
||||
if (!unflattener.read(&constantName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!unflattener.read(&constantType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!unflattener.read(&shaderStage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(*materialPushConstants)[i].name = constantName;
|
||||
(*materialPushConstants)[i].type = static_cast<backend::ConstantType>(constantType);
|
||||
(*materialPushConstants)[i].stage = static_cast<backend::ShaderStage>(shaderStage);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -47,6 +47,7 @@ class BufferInterfaceBlock;
|
||||
class SamplerInterfaceBlock;
|
||||
struct SubpassInfo;
|
||||
struct MaterialConstant;
|
||||
struct MaterialPushConstant;
|
||||
|
||||
class MaterialParser {
|
||||
public:
|
||||
@@ -79,6 +80,8 @@ public:
|
||||
bool getSamplerBlockBindings(SamplerGroupBindingInfoList* pSamplerGroupInfoList,
|
||||
SamplerBindingToNameMap* pSamplerBindingToNameMap) const noexcept;
|
||||
bool getConstants(utils::FixedCapacityVector<MaterialConstant>* value) const noexcept;
|
||||
bool getPushConstants(utils::CString* structVarName,
|
||||
utils::FixedCapacityVector<MaterialPushConstant>* value) const noexcept;
|
||||
|
||||
using BindingUniformInfoContainer = utils::FixedCapacityVector<
|
||||
std::pair<filament::UniformBindingPoints, backend::Program::UniformInfo>>;
|
||||
@@ -214,6 +217,11 @@ struct ChunkMaterialConstants {
|
||||
utils::FixedCapacityVector<MaterialConstant>* materialConstants);
|
||||
};
|
||||
|
||||
struct ChunkMaterialPushConstants {
|
||||
static bool unflatten(filaflat::Unflattener& unflattener, utils::CString* structVarName,
|
||||
utils::FixedCapacityVector<MaterialPushConstant>* materialPushConstants);
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_FILAMENT_MATERIALPARSER_H
|
||||
|
||||
@@ -80,7 +80,8 @@ RenderPassBuilder& RenderPassBuilder::customCommand(
|
||||
}
|
||||
|
||||
RenderPass RenderPassBuilder::build(FEngine& engine) {
|
||||
ASSERT_POSTCONDITION(mRenderableSoa, "RenderPassBuilder::geometry() hasn't been called");
|
||||
FILAMENT_CHECK_POSTCONDITION(mRenderableSoa)
|
||||
<< "RenderPassBuilder::geometry() hasn't been called";
|
||||
assert_invariant(mScissorViewport.width <= std::numeric_limits<int32_t>::max());
|
||||
assert_invariant(mScissorViewport.height <= std::numeric_limits<int32_t>::max());
|
||||
return RenderPass{ engine, *this };
|
||||
@@ -684,6 +685,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
cmd.info.indexCount = primitive.getIndexCount();
|
||||
cmd.info.type = primitive.getPrimitiveType();
|
||||
cmd.info.morphTargetBuffer = morphTargets.buffer->getHwHandle();
|
||||
cmd.info.morphingOffset = morphTargets.offset;
|
||||
|
||||
if constexpr (isColorPass) {
|
||||
RenderPass::setupColorCommand(cmd, renderableVariant, mi, inverseFrontFaces);
|
||||
@@ -1029,6 +1031,9 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
rebindPipeline = false;
|
||||
currentPipeline = pipeline;
|
||||
driver.bindPipeline(pipeline);
|
||||
|
||||
driver.setPushConstant(ShaderStage::VERTEX,
|
||||
+PushConstantIds::MORPHING_BUFFER_OFFSET, int32_t(info.morphingOffset));
|
||||
}
|
||||
|
||||
if (info.rph != currentPrimitiveHandle) {
|
||||
|
||||
@@ -251,6 +251,7 @@ public:
|
||||
uint32_t indexCount; // 4 bytes
|
||||
uint32_t index = 0; // 4 bytes
|
||||
backend::SamplerGroupHandle morphTargetBuffer; // 4 bytes
|
||||
uint32_t morphingOffset = 0; // 4 bytes
|
||||
|
||||
backend::RasterState rasterState; // 4 bytes
|
||||
|
||||
@@ -261,7 +262,7 @@ public:
|
||||
bool hasMorphing : 1; // 1 bit
|
||||
bool hasHybridInstancing : 1; // 1 bit
|
||||
|
||||
uint32_t rfu[3]; // 16 bytes
|
||||
uint32_t rfu[2]; // 16 bytes
|
||||
};
|
||||
static_assert(sizeof(PrimitiveInfo) == 56);
|
||||
|
||||
|
||||
@@ -164,6 +164,11 @@ void RenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level,
|
||||
downcast(morphTargetBuffer), offset, count);
|
||||
}
|
||||
|
||||
void RenderableManager::setMorphTargetBufferAt(
|
||||
Instance instance, uint8_t level, size_t primitiveIndex, size_t offset, size_t count) {
|
||||
downcast(this)->setMorphTargetBufferAt(instance, level, primitiveIndex, offset, count);
|
||||
}
|
||||
|
||||
MorphTargetBuffer* RenderableManager::getMorphTargetBufferAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex) const noexcept {
|
||||
return downcast(this)->getMorphTargetBufferAt(instance, level, primitiveIndex);
|
||||
|
||||
@@ -353,14 +353,12 @@ UTILS_NOINLINE
|
||||
void RendererUtils::readPixels(backend::DriverApi& driver, Handle<HwRenderTarget> renderTargetHandle,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
|
||||
backend::PixelBufferDescriptor&& buffer) {
|
||||
ASSERT_PRECONDITION(
|
||||
buffer.type != PixelDataType::COMPRESSED,
|
||||
"buffer.format cannot be COMPRESSED");
|
||||
FILAMENT_CHECK_PRECONDITION(buffer.type != PixelDataType::COMPRESSED)
|
||||
<< "buffer.format cannot be COMPRESSED";
|
||||
|
||||
ASSERT_PRECONDITION(
|
||||
buffer.alignment > 0 && buffer.alignment <= 8 &&
|
||||
!(buffer.alignment & (buffer.alignment - 1u)),
|
||||
"buffer.alignment must be 1, 2, 4 or 8");
|
||||
FILAMENT_CHECK_PRECONDITION(buffer.alignment > 0 && buffer.alignment <= 8 &&
|
||||
!(buffer.alignment & (buffer.alignment - 1u)))
|
||||
<< "buffer.alignment must be 1, 2, 4 or 8";
|
||||
|
||||
// It's not really possible to know here which formats will be supported because
|
||||
// it can vary depending on the RenderTarget, in GL the following are ALWAYS supported though:
|
||||
@@ -373,8 +371,9 @@ void RendererUtils::readPixels(backend::DriverApi& driver, Handle<HwRenderTarget
|
||||
buffer.top + height,
|
||||
buffer.alignment);
|
||||
|
||||
ASSERT_PRECONDITION(buffer.size >= sizeNeeded,
|
||||
"Pixel buffer too small: has %u bytes, needs %u bytes", buffer.size, sizeNeeded);
|
||||
FILAMENT_CHECK_PRECONDITION(buffer.size >= sizeNeeded)
|
||||
<< "Pixel buffer too small: has " << buffer.size << " bytes, needs " << sizeNeeded
|
||||
<< " bytes";
|
||||
|
||||
driver.readPixels(renderTargetHandle, xoffset, yoffset, width, height, std::move(buffer));
|
||||
}
|
||||
|
||||
@@ -28,12 +28,13 @@ void* SwapChain::getNativeWindow() const noexcept {
|
||||
return downcast(this)->getNativeWindow();
|
||||
}
|
||||
|
||||
void SwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void* user) {
|
||||
downcast(this)->setFrameScheduledCallback(callback, user);
|
||||
void SwapChain::setFrameScheduledCallback(
|
||||
backend::CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
downcast(this)->setFrameScheduledCallback(handler, std::move(callback));
|
||||
}
|
||||
|
||||
SwapChain::FrameScheduledCallback SwapChain::getFrameScheduledCallback() const noexcept {
|
||||
return downcast(this)->getFrameScheduledCallback();
|
||||
bool SwapChain::isFrameScheduledCallbackSet() const noexcept {
|
||||
return downcast(this)->isFrameScheduledCallbackSet();
|
||||
}
|
||||
|
||||
void SwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user