gl: add PlatformOSMesa as an offscreen context

For GL+Linux, PlatformGLX will try to open an X11 window
regardless of whether we are doing headless/offscreen rendering
or not.

Here we add an OSMesa platform, which will allow us to avoid
opening any window on Linux. This is particularly useful for
situation where a display is not available, like for CI.

One important detail is that even though we are displaying through
a window, we keep the SDL2 dependency in tact for gltf_viewer.
This is due to the fact that gltf_viewer is built upon
FilamentApp, which is heavily integrated with SDL2. This is mostly
ok since we won't be hitting any path for opening a window due to
gltf_viewer's existing support for headless mode.
This commit is contained in:
Powei Feng
2024-09-25 15:03:53 -07:00
parent b97221b8de
commit 7da09a7f45
8 changed files with 212 additions and 2 deletions

View File

@@ -45,6 +45,8 @@ option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON)
option(FILAMENT_ENABLE_MULTIVIEW "Enable multiview for Filament" OFF)
option(FILAMENT_SUPPORTS_OSMESA "Enable OSMesa (headless GL context) for Filament" OFF)
set(FILAMENT_NDK_VERSION "" CACHE STRING
"Android NDK version or version prefix to be used when building for Android."
)
@@ -73,6 +75,10 @@ set(FILAMENT_BACKEND_DEBUG_FLAG "" CACHE STRING
"A debug flag meant for enabling/disabling backend debugging paths"
)
set(FILAMENT_OSMESA_PATH "" CACHE STRING
"Path to the OSMesa header and lib"
)
# Enable exceptions by default in spirv-cross.
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS OFF)
@@ -132,12 +138,22 @@ else()
endif()
if (LINUX)
if (NOT FILAMENT_OSMESA_PATH STREQUAL "")
if (NOT EXISTS ${FILAMENT_OSMESA_PATH}/)
message(FATAL_ERROR "Cannot find specified OSMesa build directory: ${FILAMENT_OSMESA_PATH}")
endif()
set(FILAMENT_SUPPORTS_OSMESA TRUE)
endif()
if (FILAMENT_SUPPORTS_WAYLAND)
add_definitions(-DFILAMENT_SUPPORTS_WAYLAND)
set(FILAMENT_SUPPORTS_X11 FALSE)
elseif (FILAMENT_SUPPORTS_EGL_ON_LINUX)
add_definitions(-DFILAMENT_SUPPORTS_EGL_ON_LINUX)
set(FILAMENT_SUPPORTS_X11 FALSE)
elseif (FILAMENT_SUPPORTS_OSMESA)
set(FILAMENT_SUPPORTS_X11 FALSE)
add_definitions(-DFILAMENT_SUPPORTS_OSMESA)
else ()
if (FILAMENT_SUPPORTS_XCB)
add_definitions(-DFILAMENT_SUPPORTS_XCB)

View File

@@ -64,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 " -X osmesa_path"
echo " Indicates a path to the path of a completed OSMesa build. OSMesa is used to create an"
echo " offscreen context for software rasterization"
echo " -S type"
echo " Enable stereoscopic rendering where type is one of [instanced|multiview]. This is only"
echo " meant for building the samples."
@@ -180,6 +183,8 @@ BACKEND_DEBUG_FLAG_OPTION=""
STEREOSCOPIC_OPTION=""
OSMESA_OPTION=""
IOS_BUILD_SIMULATOR=false
BUILD_UNIVERSAL_LIBRARIES=false
@@ -240,6 +245,7 @@ function build_desktop_target {
${ASAN_UBSAN_OPTION} \
${BACKEND_DEBUG_FLAG_OPTION} \
${STEREOSCOPIC_OPTION} \
${OSMESA_OPTION} \
${architectures} \
../..
ln -sf "out/cmake-${lc_target}/compile_commands.json" \
@@ -796,7 +802,7 @@ function check_debug_release_build {
pushd "$(dirname "$0")" > /dev/null
while getopts ":hacCfgijmp:q:uvslwedk:bx:S:" opt; do
while getopts ":hacCfgijmp:q:uvslwedk:bx:S:X:" opt; do
case ${opt} in
h)
print_help
@@ -950,6 +956,8 @@ while getopts ":hacCfgijmp:q:uvslwedk:bx:S:" opt; do
exit 1
esac
;;
X) OSMESA_OPTION="-DFILAMENT_OSMESA_PATH=${OPTARG}"
;;
\?)
echo "Invalid option: -${OPTARG}" >&2
echo ""

View File

@@ -114,6 +114,8 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
list(APPEND SRCS src/opengl/platforms/PlatformGLX.cpp)
elseif (FILAMENT_SUPPORTS_EGL_ON_LINUX)
list(APPEND SRCS src/opengl/platforms/PlatformEGLHeadless.cpp)
elseif (FILAMENT_SUPPORTS_OSMESA)
list(APPEND SRCS src/opengl/platforms/PlatformOSMesa.cpp)
endif()
elseif (WIN32)
list(APPEND SRCS src/opengl/platforms/PlatformWGL.cpp)
@@ -361,6 +363,15 @@ set(LINUX_LINKER_OPTIMIZATION_FLAGS
-Wl,--exclude-libs,bluegl
)
if (LINUX AND FILAMENT_SUPPORTS_OSMESA)
set(OSMESA_COMPILE_FLAGS
-I${FILAMENT_OSMESA_PATH}/include/GL)
set(OSMESA_LINKER_FLAGS
-Wl,-L${FILAMENT_OSMESA_PATH}/lib/x86_64-linux-gnu/
-lOSMesa
)
endif()
if (MSVC)
set(FILAMENT_WARNINGS /W3)
else()
@@ -381,6 +392,7 @@ endif()
target_compile_options(${TARGET} PRIVATE
${FILAMENT_WARNINGS}
${OSMESA_COMPILE_FLAGS}
$<$<CONFIG:Release>:${OPTIMIZATION_FLAGS}>
$<$<AND:$<PLATFORM_ID:Darwin>,$<CONFIG:Release>>:${DARWIN_OPTIMIZATION_FLAGS}>
)
@@ -390,6 +402,7 @@ if (FILAMENT_SUPPORTS_METAL)
endif()
target_link_libraries(${TARGET} PRIVATE
${OSMESA_LINKER_FLAGS}
$<$<AND:$<PLATFORM_ID:Linux>,$<CONFIG:Release>>:${LINUX_LINKER_OPTIMIZATION_FLAGS}>
)

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H
#include <stdint.h>
#include "bluegl/BlueGL.h"
#include "osmesa.h"
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/DriverEnums.h>
namespace filament::backend {
/**
* A concrete implementation of OpenGLPlatform that uses OSMesa, which is an offscreen
* context that can be used in conjunction with Mesa for software rasterization.
* See https://docs.mesa3d.org/osmesa.html for more information.
*/
class PlatformOSMesa : public OpenGLPlatform {
protected:
// --------------------------------------------------------------------------------------------
// Platform Interface
Driver* createDriver(void* sharedGLContext, const DriverConfig& driverConfig) noexcept override;
int getOSVersion() const noexcept final override { return 0; }
// --------------------------------------------------------------------------------------------
// OpenGLPlatform Interface
void terminate() noexcept override;
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept override;
void commit(SwapChain* swapChain) noexcept override;
private:
OSMesaContext mContext;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_OSMESA_H

View File

@@ -41,6 +41,10 @@
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include "backend/platforms/PlatformEGLHeadless.h"
#endif
#elif defined(FILAMENT_SUPPORTS_OSMESA)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
#include "backend/platforms/PlatformOSMesa.h"
#endif
#endif
#elif defined(WIN32)
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
@@ -124,6 +128,8 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
return new PlatformGLX();
#elif defined(FILAMENT_SUPPORTS_EGL_ON_LINUX)
return new PlatformEGLHeadless();
#elif defined(FILAMENT_SUPPORTS_OSMESA)
return new PlatformOSMesa();
#else
return nullptr;
#endif

View File

@@ -0,0 +1,100 @@
/*
* 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 <backend/platforms/PlatformOSMesa.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <memory>
namespace filament::backend {
using namespace backend;
namespace {
using BackingType = GLfloat;
#define BACKING_GL_TYPE GL_FLOAT
struct OSMesaSwapchain {
OSMesaSwapchain(uint32_t width, uint32_t height)
: width(width),
height(height),
buffer(new uint8_t[width * height * 4 * sizeof(BACKING_GL_TYPE)]) {}
uint32_t width = 0;
uint32_t height = 0;
std::unique_ptr<uint8_t[]> buffer;
};
} // anonymous namespace
Driver* PlatformOSMesa::createDriver(void* const sharedGLContext,
const DriverConfig& driverConfig) noexcept {
FILAMENT_CHECK_PRECONDITION(sharedGLContext == nullptr)
<< "shared GL context is not supported with PlatformOSMesa";
mContext = OSMesaCreateContext(GL_RGBA, NULL);
// We need to do a no-op makecurrent here so that the context will be in a correct state before
// any GL calls.
auto chain = createSwapChain(1, 1, 0);
makeCurrent(ContextType::UNPROTECTED, chain, nullptr);
destroySwapChain(chain);
int result = bluegl::bind();
FILAMENT_CHECK_POSTCONDITION(!result) << "Unable to load OpenGL entry points.";
return OpenGLPlatform::createDefaultDriver(this, sharedGLContext, driverConfig);
}
void PlatformOSMesa::terminate() noexcept {
OSMesaDestroyContext(mContext);
bluegl::unbind();
}
Platform::SwapChain* PlatformOSMesa::createSwapChain(void* nativeWindow, uint64_t flags) noexcept {
FILAMENT_CHECK_POSTCONDITION(false) << "Cannot create non-headless swapchain";
return (SwapChain*) nativeWindow;
}
Platform::SwapChain* PlatformOSMesa::createSwapChain(uint32_t width, uint32_t height,
uint64_t flags) noexcept {
OSMesaSwapchain* swapchain = new OSMesaSwapchain(width, height);
return (SwapChain*) swapchain;
}
void PlatformOSMesa::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
OSMesaSwapchain* impl = (OSMesaSwapchain*) swapChain;
delete impl;
}
bool PlatformOSMesa::makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept {
OSMesaSwapchain* impl = (OSMesaSwapchain*) drawSwapChain;
auto result = OSMesaMakeCurrent(mContext, (BackingType*) impl->buffer.get(), BACKING_GL_TYPE,
impl->width, impl->height);
FILAMENT_CHECK_POSTCONDITION(result) << "OSMesaMakeCurrent failed!";
return true;
}
void PlatformOSMesa::commit(Platform::SwapChain* swapChain) noexcept {
// No-op since we are not scanning out to a display.
}
} // namespace filament::backend

View File

@@ -46,6 +46,10 @@
#include "SDL_config_linux_wayland.h"
#elif defined(FILAMENT_SUPPORTS_X11)
#include "SDL_config_linux_x11.h"
#elif defined(FILAMENT_SUPPORTS_OSMESA)
// We still want the same defines and libs as x11 but we won't actually
// open any windows.
#include "SDL_config_linux_x11.h"
#endif
#else
/* This is a minimal configuration just to get SDL running on new platforms */

View File

@@ -103,7 +103,7 @@ if (LINUX)
if (FILAMENT_SUPPORTS_WAYLAND)
file(GLOB SRCS_LINUX_WAYLAND ${SRC_DIR}/video/wayland/*.c)
list(APPEND SRCS ${SRCS_LINUX_WAYLAND})
elseif(FILAMENT_SUPPORTS_X11)
elseif(FILAMENT_SUPPORTS_X11 OR FILAMENT_SUPPORTS_OSMESA)
file(GLOB SRCS_LINUX_X11 ${SRC_DIR}/video/x11/*.c)
list(APPEND SRCS ${SRCS_LINUX_X11})
endif()