Compare commits
9 Commits
pf/fix-bac
...
pf/backend
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e32e6cf2a | ||
|
|
1d4f1fe71f | ||
|
|
c93aa4c90d | ||
|
|
499939ed3c | ||
|
|
6be97ee01d | ||
|
|
7267696cbf | ||
|
|
85eb724a90 | ||
|
|
6dd6db89d5 | ||
|
|
c76f67c139 |
32
BUILDING.md
32
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,27 @@ 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 for running software rasterization on the Vulkan backend. To use this,
|
||||
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`.
|
||||
|
||||
## SwiftShader for CI
|
||||
|
||||
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:
|
||||
|
||||
and then set `VK_ICD_FILENAMES` to the ICD json produced in the build. For example,
|
||||
```shell
|
||||
docker pull ghcr.io/filament-assets/swiftshader
|
||||
docker run -it ghcr.io/filament-assets/swiftshader
|
||||
export VK_ICD_FILENAMES=/Users/user/swiftshader/build/Darwin/vk_swiftshader_icd.json
|
||||
```
|
||||
|
||||
To do more with the container, see the helper script at `build/swiftshader/test.sh`.
|
||||
Build Filament as normal and use the vulkan backend.
|
||||
|
||||
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`.
|
||||
|
||||
@@ -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")
|
||||
@@ -678,21 +672,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
|
||||
# ==================================================================================================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
11
build.sh
11
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"
|
||||
@@ -165,8 +163,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"
|
||||
@@ -233,7 +229,6 @@ 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} \
|
||||
@@ -794,7 +789,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:" opt; do
|
||||
case ${opt} in
|
||||
h)
|
||||
print_help
|
||||
@@ -913,10 +908,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."
|
||||
|
||||
@@ -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,6 +417,7 @@ 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_LoadImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -144,8 +144,7 @@ DECL_DRIVER_API_N(setFrameScheduledCallback,
|
||||
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)
|
||||
@@ -502,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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -243,10 +243,10 @@ void MetalDriver::setFrameScheduledCallback(
|
||||
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 {
|
||||
@@ -1637,7 +1637,7 @@ void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::bindPipeline(PipelineState ps) {
|
||||
void MetalDriver::bindPipeline(PipelineState const& ps) {
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
|
||||
"bindPipeline() without a valid command encoder.");
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ public:
|
||||
|
||||
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback);
|
||||
void setFrameCompletedCallback(
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback);
|
||||
|
||||
// For CAMetalLayer-backed SwapChains, presents the drawable or schedules a
|
||||
// FrameScheduledCallback.
|
||||
@@ -119,13 +119,12 @@ private:
|
||||
// PresentCallable object.
|
||||
struct {
|
||||
CallbackHandler* handler = nullptr;
|
||||
FrameScheduledCallback callback = {};
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -224,14 +224,13 @@ void MetalSwapChain::ensureDepthStencilTexture() {
|
||||
void MetalSwapChain::setFrameScheduledCallback(
|
||||
CallbackHandler* handler, FrameScheduledCallback&& callback) {
|
||||
frameScheduled.handler = handler;
|
||||
frameScheduled.callback = std::move(callback);
|
||||
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() {
|
||||
@@ -304,17 +303,17 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
assert_invariant(drawable);
|
||||
|
||||
struct Callback {
|
||||
Callback(FrameScheduledCallback&& callback, id<CAMetalDrawable> drawable,
|
||||
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
|
||||
MetalDriver* driver)
|
||||
: f(std::move(callback)), data(PresentDrawableData::create(drawable, driver)) {}
|
||||
FrameScheduledCallback f;
|
||||
: f(callback), data(PresentDrawableData::create(drawable, 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(presentCallable);
|
||||
c->f->operator()(presentCallable);
|
||||
delete c;
|
||||
}
|
||||
};
|
||||
@@ -322,7 +321,7 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
// 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>(std::move(frameScheduled.callback), drawable, context.driver);
|
||||
std::make_unique<Callback>(frameScheduled.callback, drawable, context.driver);
|
||||
|
||||
backend::CallbackHandler* handler = frameScheduled.handler;
|
||||
MetalDriver* driver = context.driver;
|
||||
@@ -337,13 +336,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);
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ void NoopDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
}
|
||||
|
||||
void NoopDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
|
||||
}
|
||||
|
||||
@@ -359,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) {
|
||||
|
||||
@@ -241,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;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -924,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)) {
|
||||
@@ -949,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]);
|
||||
@@ -3459,7 +3469,7 @@ void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
}
|
||||
|
||||
void OpenGLDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
CallbackHandler* handler, utils::Invocable<void(void)>&& callback) {
|
||||
DEBUG_MARKER()
|
||||
}
|
||||
|
||||
@@ -3837,7 +3847,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);
|
||||
@@ -3875,20 +3885,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)
|
||||
@@ -3907,7 +3932,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) {
|
||||
@@ -3934,7 +3963,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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -398,7 +398,7 @@ void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1763,7 +1763,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");
|
||||
|
||||
@@ -1873,6 +1873,12 @@ void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1963,7 +1969,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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -44,7 +44,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 +84,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 +92,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 +152,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 +197,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 +247,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 +262,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;
|
||||
}
|
||||
|
||||
@@ -283,8 +285,8 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
}
|
||||
|
||||
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 +294,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;
|
||||
@@ -323,12 +325,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.");
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateDevice error=%d.", result);
|
||||
|
||||
return device;
|
||||
}
|
||||
@@ -342,16 +344,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
|
||||
@@ -557,6 +559,7 @@ struct VulkanPlatformPrivate {
|
||||
std::unordered_set<SwapChainPtr> mHeadlessSwapChains;
|
||||
|
||||
bool mSharedContext = false;
|
||||
bool mForceXCBSwapchain = false;
|
||||
};
|
||||
|
||||
void VulkanPlatform::terminate() {
|
||||
@@ -610,7 +613,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());
|
||||
}
|
||||
|
||||
@@ -672,10 +693,8 @@ 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
|
||||
@@ -748,6 +767,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;
|
||||
}
|
||||
|
||||
@@ -138,7 +139,7 @@ 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)
|
||||
#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.");
|
||||
@@ -146,15 +147,11 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
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.");
|
||||
#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) {
|
||||
ASSERT_POSTCONDITION(vkCreateXcbSurfaceKHR,
|
||||
"Unable to load vkCreateXcbSurfaceKHR function.");
|
||||
|
||||
VkXcbSurfaceCreateInfoKHR const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR,
|
||||
.connection = g_x11_vk.connection,
|
||||
@@ -179,6 +179,9 @@ VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWin
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
if (!useXcb) {
|
||||
ASSERT_POSTCONDITION(vkCreateXlibSurfaceKHR,
|
||||
"Unable to load vkCreateXlibSurfaceKHR function.");
|
||||
|
||||
VkXlibSurfaceCreateInfoKHR const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
|
||||
.dpy = g_x11_vk.display,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <fstream>
|
||||
@@ -149,50 +150,59 @@ void BackendTest::renderTriangle(Handle<HwRenderTarget> renderTarget,
|
||||
}
|
||||
|
||||
void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, size_t height,
|
||||
Handle<HwRenderTarget> rt, uint32_t expectedHash, bool exportScreenshot) {
|
||||
void* buffer = calloc(1, width * height * 4);
|
||||
Handle<HwRenderTarget> rt, uint32_t const expectedHash, bool const exportScreenshot) {
|
||||
auto img = getRenderTargetRGB(rt, width, height);
|
||||
uint32_t hash = computeImageHash(img);
|
||||
|
||||
struct Capture {
|
||||
uint32_t expectedHash;
|
||||
char* name;
|
||||
bool exportScreenshot;
|
||||
size_t width, height;
|
||||
};
|
||||
auto* c = new Capture();
|
||||
c->expectedHash = expectedHash;
|
||||
c->name = strdup(testName);
|
||||
c->exportScreenshot = exportScreenshot;
|
||||
c->width = width;
|
||||
c->height = height;
|
||||
|
||||
PixelBufferDescriptor pbd(buffer, width * height * 4, PixelDataFormat::RGBA, PixelDataType::UBYTE,
|
||||
1, 0, 0, width, [](void* buffer, size_t size, void* user) {
|
||||
auto* c = (Capture*)user;
|
||||
|
||||
// Export a screenshot, if requested.
|
||||
if (c->exportScreenshot) {
|
||||
#ifndef IOS
|
||||
LinearImage image(c->width, c->height, 4);
|
||||
image = toLinearWithAlpha<uint8_t>(c->width, c->height, c->width * 4,
|
||||
(uint8_t*) buffer);
|
||||
const std::string png = std::string(c->name) + ".png";
|
||||
std::ofstream outputStream(png.c_str(), std::ios::binary | std::ios::trunc);
|
||||
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "",
|
||||
png);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Hash the contents of the buffer and check that they match.
|
||||
uint32_t hash = utils::hash::murmur3((const uint32_t*) buffer, size / 4, 0);
|
||||
ASSERT_EQ(hash, c->expectedHash) << c->name << " failed: hashes do not match." << std::endl;
|
||||
|
||||
free(buffer);
|
||||
free(c->name);
|
||||
free(c);
|
||||
}, (void*)c);
|
||||
getDriverApi().readPixels(rt, 0, 0, width, height, std::move(pbd));
|
||||
if (exportScreenshot) {
|
||||
writeImage(img, width, height, utils::CString(testName));
|
||||
}
|
||||
ASSERT_EQ(hash, expectedHash) << testName << " failed: hashes do not match." << std::endl;
|
||||
}
|
||||
|
||||
void BackendTest::writeImage(utils::FixedCapacityVector<uint8_t> img, size_t w, size_t h,
|
||||
utils::CString const& fname) {
|
||||
constexpr size_t MAX_FNAME_LEN = 50;
|
||||
constexpr size_t FNAME_EXT_LEN = 4;
|
||||
constexpr size_t TOTAL_LEN = MAX_FNAME_LEN + FNAME_EXT_LEN + 1;
|
||||
char buf[TOTAL_LEN];
|
||||
|
||||
ASSERT_PRECONDITION(fname.size() <= MAX_FNAME_LEN, "File name %s is too long", fname.c_str());
|
||||
|
||||
snprintf(buf, TOTAL_LEN, "%s.png", fname.c_str());
|
||||
|
||||
#ifndef IOS
|
||||
LinearImage image(w, h, 4);
|
||||
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, img.data());
|
||||
std::ofstream pngstrm(buf, std::ios::binary | std::ios::trunc);
|
||||
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", buf);
|
||||
#endif
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<uint8_t> BackendTest::getRenderTarget(
|
||||
Handle<HwRenderTarget> rt, size_t width, size_t height, PixelDataFormat format,
|
||||
PixelDataType dataType) {
|
||||
auto& api = getDriverApi();
|
||||
size_t const size = width * height * 4;
|
||||
utils::FixedCapacityVector<uint8_t> result(size);
|
||||
PixelBufferDescriptor pb(result.data(), size, format, dataType);
|
||||
api.readPixels(rt, 0, 0, width, height, std::move(pb));
|
||||
flushAndWait();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t BackendTest::computeImageHash(utils::FixedCapacityVector<uint8_t> const& img) {
|
||||
return utils::hash::murmur3((uint32_t*) img.data(), img.size() / 4, 0);
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<uint8_t> BackendTest::getRenderTargetRGB(
|
||||
Handle<HwRenderTarget> rt, size_t width, size_t height) {
|
||||
return getRenderTarget(rt, width, height, PixelDataFormat::RGBA,
|
||||
PixelDataType::UBYTE);
|
||||
}
|
||||
|
||||
|
||||
class Environment : public ::testing::Environment {
|
||||
public:
|
||||
virtual void SetUp() override {
|
||||
|
||||
@@ -67,6 +67,17 @@ protected:
|
||||
filament::backend::Driver& getDriver() { return *driver; }
|
||||
|
||||
private:
|
||||
void writeImage(utils::FixedCapacityVector<uint8_t> img, size_t w, size_t h,
|
||||
utils::CString const& fname);
|
||||
utils::FixedCapacityVector<uint8_t> getRenderTarget(
|
||||
filament::backend::Handle<filament::backend::HwRenderTarget> rt, size_t width,
|
||||
size_t height, filament::backend::PixelDataFormat format,
|
||||
filament::backend::PixelDataType dataType);
|
||||
uint32_t computeImageHash(utils::FixedCapacityVector<uint8_t> const& img);
|
||||
|
||||
utils::FixedCapacityVector<uint8_t> getRenderTargetRGB(
|
||||
filament::backend::Handle<filament::backend::HwRenderTarget> rt, size_t width,
|
||||
size_t height);
|
||||
|
||||
filament::backend::Driver* driver = nullptr;
|
||||
filament::backend::CommandBufferQueue commandBufferQueue;
|
||||
|
||||
@@ -60,36 +60,6 @@ struct MaterialParams {
|
||||
float4 scale;
|
||||
};
|
||||
|
||||
struct ScreenshotParams {
|
||||
int width;
|
||||
int height;
|
||||
const char* filename;
|
||||
uint32_t pixelHashResult;
|
||||
};
|
||||
|
||||
#ifdef IOS
|
||||
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt, ScreenshotParams* params) {}
|
||||
#else
|
||||
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt, ScreenshotParams* params) {
|
||||
using namespace image;
|
||||
const size_t size = params->width * params->height * 4;
|
||||
void* buffer = calloc(1, size);
|
||||
auto cb = [](void* buffer, size_t size, void* user) {
|
||||
ScreenshotParams* params = (ScreenshotParams*) user;
|
||||
int w = params->width, h = params->height;
|
||||
const uint32_t* texels = (uint32_t*) buffer;
|
||||
params->pixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
|
||||
LinearImage image(w, h, 4);
|
||||
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
|
||||
std::ofstream pngstrm(params->filename, std::ios::binary | std::ios::trunc);
|
||||
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", params->filename);
|
||||
};
|
||||
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb,
|
||||
(void*) params);
|
||||
dapi.readPixels(rt, 0, 0, params->width, params->height, std::move(pb));
|
||||
}
|
||||
#endif
|
||||
|
||||
static void uploadUniforms(DriverApi& dapi, Handle<HwBufferObject> ubh, MaterialParams params) {
|
||||
MaterialParams* tmp = new MaterialParams(params);
|
||||
auto cb = [](void* buffer, size_t size, void* user) {
|
||||
@@ -243,21 +213,12 @@ TEST_F(BackendTest, ColorMagnify) {
|
||||
api.endFrame(0);
|
||||
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMagnify.png" };
|
||||
api.beginFrame(0, 0, 0);
|
||||
dumpScreenshot(api, dstRenderTargets[0], ¶ms);
|
||||
api.commit(swapChain);
|
||||
constexpr uint32_t expected = 0x410bdd31;
|
||||
readPixelsAndAssertHash("ColorMagnify", kDstTexWidth, kDstTexHeight, dstRenderTargets[0],
|
||||
expected, true);
|
||||
api.endFrame(0);
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
api.finish();
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
|
||||
// Check if the image matches perfectly to our golden run.
|
||||
const uint32_t expected = 0x410bdd31;
|
||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
|
||||
EXPECT_TRUE(params.pixelHashResult == expected);
|
||||
flushAndWait();
|
||||
|
||||
// Cleanup.
|
||||
api.destroyTexture(srcTexture);
|
||||
@@ -315,17 +276,15 @@ TEST_F(BackendTest, ColorMinify) {
|
||||
SamplerMagFilter::LINEAR);
|
||||
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMinify.png" };
|
||||
dumpScreenshot(api, dstRenderTargets[0], ¶ms);
|
||||
api.beginFrame(0, 0, 0);
|
||||
constexpr uint32_t expected = 0xf3d9c53f;
|
||||
readPixelsAndAssertHash("ColorMinify", kDstTexWidth, kDstTexHeight, dstRenderTargets[0],
|
||||
expected, true);
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
flushAndWait();
|
||||
|
||||
// Check if the image matches perfectly to our golden run.
|
||||
const uint32_t expected = 0xf3d9c53f;
|
||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
|
||||
EXPECT_TRUE(params.pixelHashResult == expected);
|
||||
|
||||
// Cleanup.
|
||||
api.destroyTexture(srcTexture);
|
||||
api.destroyTexture(dstTexture);
|
||||
@@ -416,17 +375,13 @@ TEST_F(BackendTest, ColorResolve) {
|
||||
SamplerMagFilter::NEAREST);
|
||||
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams sparams{ kDstTexWidth, kDstTexHeight, "ColorResolve.png" };
|
||||
dumpScreenshot(api, dstRenderTarget, &sparams);
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
api.beginFrame(0, 0, 0);
|
||||
constexpr uint32_t expected = 0xebfac2ef;
|
||||
readPixelsAndAssertHash("ColorResolve", kDstTexWidth, kDstTexHeight, dstRenderTarget,
|
||||
expected, true);
|
||||
api.endFrame(0);
|
||||
flushAndWait();
|
||||
|
||||
// Check if the image matches perfectly to our golden run.
|
||||
const uint32_t expected = 0xebfac2ef;
|
||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sparams.pixelHashResult, expected);
|
||||
EXPECT_TRUE(sparams.pixelHashResult == expected);
|
||||
|
||||
// Cleanup.
|
||||
api.destroyBufferObject(ubuffer);
|
||||
api.destroyProgram(program);
|
||||
@@ -489,21 +444,12 @@ TEST_F(BackendTest, Blit2DTextureArray) {
|
||||
api.endFrame(0);
|
||||
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "Blit2DTextureArray.png" };
|
||||
api.beginFrame(0, 0, 0);
|
||||
dumpScreenshot(api, dstRenderTarget, ¶ms);
|
||||
api.commit(swapChain);
|
||||
constexpr uint32_t expected = 0x8de7d55b;
|
||||
readPixelsAndAssertHash("Blit2DTextureArray", kDstTexWidth, kDstTexHeight, dstRenderTarget,
|
||||
expected, true);
|
||||
api.endFrame(0);
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
api.finish();
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
|
||||
// Check if the image matches perfectly to our golden run.
|
||||
const uint32_t expected = 0x8de7d55b;
|
||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
|
||||
EXPECT_TRUE(params.pixelHashResult == expected);
|
||||
flushAndWait();
|
||||
|
||||
// Cleanup.
|
||||
api.destroyTexture(srcTexture);
|
||||
@@ -578,28 +524,17 @@ TEST_F(BackendTest, BlitRegion) {
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Grab a screenshot.
|
||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "BlitRegion.png" };
|
||||
api.beginFrame(0, 0, 0);
|
||||
dumpScreenshot(api, dstRenderTarget, ¶ms);
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
api.finish();
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
|
||||
// Check if the image matches perfectly to our golden run.
|
||||
//
|
||||
// TODO: for some reason, this test has very, very slight (as in one pixel) differences between
|
||||
// OpenGL and Metal. So disable golden checking for now.
|
||||
// Use the compare tool from ImageMagick to see visual differences:
|
||||
// compare -verbose -metric mae BlitRegion_Metal.png BlitRegion_OpenGL.png difference.png
|
||||
//
|
||||
// const uint32_t expected = 0x74fa34ed;
|
||||
// printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
|
||||
// EXPECT_TRUE(params.pixelHashResult == expected);
|
||||
// api.beginFrame(0, 0, 0);
|
||||
// constexpr uint32_t expected = 0x74fa34ed;
|
||||
// readPixelsAndAssertHash("BlitRegion", kDstTexWidth, kDstTexHeight, dstRenderTarget,
|
||||
// expected, true);
|
||||
// api.endFrame(0);
|
||||
// flushAndWait();
|
||||
|
||||
// Cleanup.
|
||||
api.destroyTexture(srcTexture);
|
||||
@@ -655,23 +590,22 @@ TEST_F(BackendTest, BlitRegionToSwapChain) {
|
||||
.height = kDstTexHeight - 10,
|
||||
};
|
||||
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
|
||||
dstRect, srcRenderTargets[srcLevel],
|
||||
srcRect, SamplerMagFilter::LINEAR);
|
||||
|
||||
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain.png" };
|
||||
dumpScreenshot(api, dstRenderTarget, ¶ms);
|
||||
|
||||
// Push through an empty frame to allow the texture to upload and the blit to execute.
|
||||
api.beginFrame(0, 0, 0);
|
||||
api.commit(swapChain);
|
||||
|
||||
api.endFrame(0);
|
||||
|
||||
// Wait for the ReadPixels result to come back.
|
||||
api.finish();
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
// Grab a screenshot.
|
||||
api.beginFrame(0, 0, 0);
|
||||
constexpr uint32_t expected = 0xebfac2ef;
|
||||
readPixelsAndAssertHash("BlitRegionToSwapChain", kDstTexWidth, kDstTexHeight, dstRenderTarget,
|
||||
expected, true);
|
||||
api.endFrame(0);
|
||||
flushAndWait();
|
||||
|
||||
// Cleanup.
|
||||
api.destroyTexture(srcTexture);
|
||||
|
||||
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
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "BackendTestUtils.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
@@ -64,8 +65,6 @@ void main() {
|
||||
fragColor = textureLod(test_tex, uv, params.sourceLevel);
|
||||
})";
|
||||
|
||||
static uint32_t sPixelHashResult = 0;
|
||||
|
||||
// Selecting a NPOT texture size seems to exacerbate the bug seen with Intel GPU's.
|
||||
// Note that Filament uses a higher precision format (R11F_G11F_B10F) but this does not seem
|
||||
// necessary to trigger the bug.
|
||||
@@ -99,30 +98,13 @@ static void uploadUniforms(DriverApi& dapi, Handle<HwBufferObject> ubh, Material
|
||||
dapi.updateBufferObject(ubh, std::move(bd), 0);
|
||||
}
|
||||
|
||||
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt) {
|
||||
const size_t size = kTexWidth * kTexHeight * 4;
|
||||
void* buffer = calloc(1, size);
|
||||
auto cb = [](void* buffer, size_t size, void* user) {
|
||||
int w = kTexWidth, h = kTexHeight;
|
||||
const uint32_t* texels = (uint32_t*) buffer;
|
||||
sPixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
|
||||
#ifndef IOS
|
||||
LinearImage image(w, h, 4);
|
||||
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
|
||||
std::ofstream pngstrm("feedback.png", std::ios::binary | std::ios::trunc);
|
||||
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", "feedback.png");
|
||||
#endif
|
||||
free(buffer);
|
||||
};
|
||||
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
|
||||
dapi.readPixels(rt, 0, 0, kTexWidth, kTexHeight, std::move(pb));
|
||||
}
|
||||
|
||||
// TODO: This test needs work to get Metal and OpenGL to agree on results.
|
||||
// The problems are caused by both uploading and rendering into the same texture, since the OpenGL
|
||||
// backend's readPixels does not work correctly with textures that have image data uploaded.
|
||||
TEST_F(BackendTest, FeedbackLoops) {
|
||||
auto& api = getDriverApi();
|
||||
auto& driver = getDriver();
|
||||
uint32_t pixelHashResult = 0;
|
||||
|
||||
// The test is executed within this block scope to force destructors to run before
|
||||
// executeCommands().
|
||||
@@ -147,7 +129,7 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
program = api.createProgram(std::move(prog));
|
||||
}
|
||||
|
||||
TrianglePrimitive const triangle(getDriverApi());
|
||||
TrianglePrimitive const triangle(api);
|
||||
|
||||
// Create a texture.
|
||||
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
|
||||
@@ -211,8 +193,8 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
const uint32_t sourceLevel = targetLevel - 1;
|
||||
params.viewport.width = kTexWidth >> targetLevel;
|
||||
params.viewport.height = kTexHeight >> targetLevel;
|
||||
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
|
||||
uploadUniforms(getDriverApi(), ubuffer, {
|
||||
api.setMinMaxLevels(texture, sourceLevel, sourceLevel);
|
||||
uploadUniforms(api, ubuffer, {
|
||||
.fbWidth = float(params.viewport.width),
|
||||
.fbHeight = float(params.viewport.height),
|
||||
.sourceLevel = float(sourceLevel),
|
||||
@@ -230,8 +212,8 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
const uint32_t sourceLevel = targetLevel + 1;
|
||||
params.viewport.width = kTexWidth >> targetLevel;
|
||||
params.viewport.height = kTexHeight >> targetLevel;
|
||||
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
|
||||
uploadUniforms(getDriverApi(), ubuffer, {
|
||||
api.setMinMaxLevels(texture, sourceLevel, sourceLevel);
|
||||
uploadUniforms(api, ubuffer, {
|
||||
.fbWidth = float(params.viewport.width),
|
||||
.fbHeight = float(params.viewport.height),
|
||||
.sourceLevel = float(sourceLevel),
|
||||
@@ -241,14 +223,16 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
api.endRenderPass();
|
||||
}
|
||||
|
||||
getDriverApi().setMinMaxLevels(texture, 0, 0x7f);
|
||||
api.setMinMaxLevels(texture, 0, 0x7f);
|
||||
|
||||
// Read back the render target corresponding to the base level.
|
||||
//
|
||||
// NOTE: Calling glReadPixels on any miplevel other than the base level
|
||||
// seems to be un-reliable on some GPU's.
|
||||
if (frame == kNumFrames - 1) {
|
||||
dumpScreenshot(api, renderTargets[0]);
|
||||
constexpr uint32_t expected = 0x70695aa1;
|
||||
readPixelsAndAssertHash("feedback", kTexWidth, kTexHeight, renderTargets[0],
|
||||
expected, true);
|
||||
}
|
||||
|
||||
api.flush();
|
||||
@@ -256,7 +240,7 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
api.endFrame(0);
|
||||
api.finish();
|
||||
executeCommands();
|
||||
getDriver().purge();
|
||||
driver.purge();
|
||||
}
|
||||
|
||||
api.destroyProgram(program);
|
||||
@@ -264,10 +248,6 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
api.destroyTexture(texture);
|
||||
for (auto rt : renderTargets) api.destroyRenderTarget(rt);
|
||||
}
|
||||
|
||||
const uint32_t expected = 0x70695aa1;
|
||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sPixelHashResult, expected);
|
||||
EXPECT_TRUE(sPixelHashResult == expected);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -79,24 +79,11 @@ bool FSwapChain::isFrameScheduledCallbackSet() const noexcept {
|
||||
return mFrameScheduledCallbackIsSet;
|
||||
}
|
||||
|
||||
void FSwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler,
|
||||
utils::Invocable<void(SwapChain*)>&& callback) noexcept {
|
||||
struct Callback {
|
||||
utils::Invocable<void(SwapChain*)> f;
|
||||
SwapChain* s;
|
||||
static void func(void* user) {
|
||||
auto* const c = reinterpret_cast<Callback*>(user);
|
||||
c->f(c->s);
|
||||
delete c;
|
||||
}
|
||||
};
|
||||
if (callback) {
|
||||
auto* const user = new(std::nothrow) Callback{ std::move(callback), this };
|
||||
mEngine.getDriverApi().setFrameCompletedCallback(
|
||||
mHwSwapChain, handler, &Callback::func, static_cast<void*>(user));
|
||||
} else {
|
||||
mEngine.getDriverApi().setFrameCompletedCallback(mHwSwapChain, nullptr, nullptr, nullptr);
|
||||
}
|
||||
void FSwapChain::setFrameCompletedCallback(
|
||||
backend::CallbackHandler* handler, FrameCompletedCallback&& callback) noexcept {
|
||||
using namespace std::placeholders;
|
||||
auto boundCallback = std::bind(std::move(callback), this);
|
||||
mEngine.getDriverApi().setFrameCompletedCallback(mHwSwapChain, handler, std::move(boundCallback));
|
||||
}
|
||||
|
||||
bool FSwapChain::isSRGBSwapChainSupported(FEngine& engine) noexcept {
|
||||
|
||||
@@ -299,6 +299,16 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void shrink_to_fit() {
|
||||
if (size() < capacity()) {
|
||||
FixedCapacityVector t(construct_with_capacity, size(), allocator());
|
||||
t.mSize = size();
|
||||
std::uninitialized_move(begin(), end(), t.begin());
|
||||
this->swap(t);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
enum construct_with_capacity_tag{ construct_with_capacity };
|
||||
|
||||
|
||||
@@ -60,6 +60,11 @@ public:
|
||||
std::fill(std::begin(storage), std::end(storage), 0);
|
||||
}
|
||||
|
||||
template<typename U, typename = typename std::enable_if_t<N == 1, U>>
|
||||
explicit bitset(U value) noexcept {
|
||||
storage[0] = value;
|
||||
}
|
||||
|
||||
T getBitsAt(size_t n) const noexcept {
|
||||
assert_invariant(n<N);
|
||||
return storage[n];
|
||||
@@ -94,6 +99,8 @@ public:
|
||||
|
||||
size_t size() const noexcept { return N * BITS_PER_WORD; }
|
||||
|
||||
bool empty() const noexcept { return none(); }
|
||||
|
||||
bool test(size_t bit) const noexcept { return operator[](bit); }
|
||||
|
||||
void set(size_t b) noexcept {
|
||||
@@ -117,11 +124,14 @@ public:
|
||||
storage[b / BITS_PER_WORD] ^= T(1) << (b % BITS_PER_WORD);
|
||||
}
|
||||
|
||||
|
||||
void reset() noexcept {
|
||||
std::fill(std::begin(storage), std::end(storage), 0);
|
||||
}
|
||||
|
||||
void clear() noexcept {
|
||||
reset();
|
||||
}
|
||||
|
||||
bool operator[](size_t b) const noexcept {
|
||||
assert_invariant(b / BITS_PER_WORD < N);
|
||||
return bool(storage[b / BITS_PER_WORD] & (T(1) << (b % BITS_PER_WORD)));
|
||||
|
||||
Reference in New Issue
Block a user