Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,27 @@ jobs:
submodules: 'recursive'

- name: Install dependencies
run: brew install glfw
run: brew install glfw llvm meson ninja pkgconf bison

- name: Cache OSMesa
id: cache-osmesa
uses: actions/cache@v4
with:
path: dependencies/mesa_install
key: osmesa-24.3.4-${{ matrix.os }}-${{ hashFiles('tools/build-osmesa.sh') }}

- name: Build OSMesa
if: steps.cache-osmesa.outputs.cache-hit != 'true'
run: ./tools/build-osmesa.sh

- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && inputs.enable_ssh }}

- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
run: >
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
-DCMAKE_PREFIX_PATH="${{github.workspace}}/dependencies/mesa_install"

- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ offscreen
offscreen.exe
.vscode/
build/
dependencies/
.venv/

28 changes: 25 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,27 @@ option(USE_GLAD "Use GLAD for OpenGL function wrangling" ${USE_GLAD_DEFAULT})
option(ENABLE_GLFW "Enable on-screen rendering using glfw" ON)
option(ENABLE_EGL "Enable EGL offscreen context" ON)
option(ENABLE_GLX "Enable GLX offscreen context" ON)
option(ENABLE_OSMESA "Enable OSMesa offscreen rendering" ON)

set(HAS_GLVND FALSE)
set(HAS_GLX FALSE)
set(HAS_EGL FALSE)
set(HAS_OSMESA FALSE)
set(HAS_CGL FALSE)
set(HAS_NSOPENGL FALSE)
set(HAS_WGL FALSE)

if(ENABLE_OSMESA)
find_package(OSMesa)
if(OSMESA_FOUND)
set(HAS_OSMESA TRUE)
target_link_libraries(offscreen OSMesa::OSMesa)
target_compile_definitions(offscreen PRIVATE HAS_OSMESA)
set(SRCS_OSMESA src/OffscreenContextOSMesa.cc)
set(USE_GLAD ON)
endif()
endif()

if(USE_GLAD)
target_compile_definitions(offscreen PRIVATE USE_GLAD)
endif()
Expand Down Expand Up @@ -147,6 +160,7 @@ set(SRCS
${SRCS_GLX}
${SRCS_APPLE}
${SRCS_WINDOWS}
${SRCS_OSMESA}
)
target_sources(offscreen PRIVATE ${SRCS})
target_include_directories(offscreen PUBLIC "${CMAKE_SOURCE_DIR}/src")
Expand Down Expand Up @@ -216,16 +230,23 @@ if(HAS_GLX)
set_tests_properties(check_glx_file_exists PROPERTIES DEPENDS glx_save_framebuffer)
endif()




if(WIN32)
add_test(NAME wgl_opengl2_immediate COMMAND offscreen --context wgl --opengl 2 --mode immediate)
add_test(NAME wgl_opengl2_modern COMMAND offscreen --context wgl --opengl 2 --mode modern)
add_test(NAME wgl_opengl3_core COMMAND offscreen --context wgl --opengl 3.3 --profile core)
add_test(NAME wgl_opengl3_compat COMMAND offscreen --context wgl --opengl 3.3 --profile compatibility)
endif(WIN32)

if(HAS_OSMESA)
add_test(NAME osmesa_opengl2_immediate COMMAND offscreen --context osmesa --opengl 2 --mode immediate)
add_test(NAME osmesa_opengl2_modern COMMAND offscreen --context osmesa --opengl 2 --mode modern)
add_test(NAME osmesa_opengl3.3_core COMMAND offscreen --context osmesa --opengl 3.3 --profile core)
add_test(NAME osmesa_opengl3.3_compat COMMAND offscreen --context osmesa --opengl 3.3 --profile compatibility)
add_test(NAME osmesa_opengl4.5_core COMMAND offscreen --context osmesa --opengl 4.5 --profile core)
add_test(NAME osmesa_save_framebuffer COMMAND offscreen --context osmesa -o out_osmesa.png)
add_test(NAME check_osmesa_file_exists COMMAND ${CMAKE_COMMAND} -E cat out_osmesa.png)
set_tests_properties(check_osmesa_file_exists PROPERTIES DEPENDS osmesa_save_framebuffer)
endif(HAS_OSMESA)

message(STATUS " ")
message(STATUS "====================================")
Expand All @@ -237,6 +258,7 @@ message(STATUS "GLFW: ${ENABLE_GLFW}")
message(STATUS "Has GLVND: ${HAS_GLVND}")
message(STATUS "GLX: ${HAS_GLX}")
message(STATUS "EGL: ${HAS_EGL}")
message(STATUS "OSMesa: ${HAS_OSMESA}")
message(STATUS "CGL: ${HAS_CGL}")
message(STATUS "NSOpenGL: ${HAS_NSOPENGL}")
message(STATUS "WGL: ${HAS_WGL}")
47 changes: 39 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,23 @@ make

* macOS
* Defaults to non-wrangled OpenGL
* Supports: NSOpenGL, CGL
* OpenGL 2, OpenGL 4
* Supports: NSOpenGL, CGL, OSMesa (via Mesa llvmpipe)
* OpenGL 2, OpenGL 3.2-4.1 (Native), OpenGL 2.1-4.5 (OSMesa)
* Linux
* Supports: EGL
* Supports: EGL, OSMesa
* Supports multi-GPU
* OpenGL 2-4
* GLES2-3
* Raspberry Pi
* Only OpenGL 2 for now
* Windows
* Supports: WGL
* OpenGL2-4
* Supports: WGL, OSMesa
* OpenGL 2-4

## TODO

* Use EGL by default, and fall back to GLX. This is apparently needed in some places. Google it and look into it.
* Qt-backed GL context
* macOS: Look into "warning gl.h and gl3.h are both included"
* Support forward-compatible contexts
* Windows: Support GLES
* WASM
Expand All @@ -56,6 +55,29 @@ make
sudo apt install cmake libglfw3-dev libgl1-mesa-dev libegl1-mesa-dev libgbm-dev libglu1-mesa-dev
```

**macOS (Homebrew)**

```bash
brew install cmake glfw
```

**macOS OSMesa (Mesa Software Renderer)**

To build OSMesa using Homebrew-provided dependencies:

```bash
brew install llvm meson ninja pkgconf bison
python3 -m pip install mako pyyaml packaging
./tools/build-osmesa.sh
```

Then configure `offscreen` with:

```bash
cmake -B build -DCMAKE_PREFIX_PATH="$(pwd)/dependencies/mesa_install"
cmake --build build
```

## Examples

### macOS
Expand All @@ -71,20 +93,29 @@ Furthermore, macOS uses weak linking for its OpenGL library, which essentially e
./offscreen --width 640 --height 480 --context glfw --invisible --opengl 3.2 --mode modern
```

**OpenGL 2**
**OpenGL 2 (Native)**

```bash
./offscreen --width 640 --height 480 --context cgl --opengl 2 --mode modern -o out.png
./offscreen --width 640 --height 480 --context nsopengl --opengl 2 --mode immediate -o out.png
```

**OpenGL 3+**
**OpenGL 3+ (Native)**

```bash
./offscreen --width 640 --height 480 --context cgl --opengl 3.2 --mode modern -o out.png
./offscreen --width 640 --height 480 --context nsopengl --opengl 3.2 --mode modern -o out.png
```

**OSMesa (Mesa LLVMpipe - Headless & CI Friendly)**

```bash
./offscreen --context osmesa --opengl 2.1 --mode immediate -o out.png
./offscreen --context osmesa --opengl 3.3 --profile core --mode modern -o out.png
./offscreen --context osmesa --opengl 3.3 --profile compatibility --mode immediate -o out.png
./offscreen --context osmesa --opengl 4.5 --profile core --mode modern -o out.png
```

### Linux OpenGL 3 compatibility mode

```bash
Expand Down
97 changes: 97 additions & 0 deletions cmake/Modules/FindOSMesa.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#[=======================================================================[.rst:
FindOSMesa
----------

Find the Off-Screen Mesa (OSMesa) library and headers.

Imported Targets
^^^^^^^^^^^^^^^^
``OSMesa::OSMesa``
The OSMesa library, if found.

Result Variables
^^^^^^^^^^^^^^^^
``OSMESA_FOUND``
True if OSMesa was found.
``OSMESA_INCLUDE_DIRS``
Path to OSMesa include directories.
``OSMESA_LIBRARIES``
Path to OSMesa libraries.
#]=======================================================================]

find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
set(_OSMESA_HINTS
${OSMesa_ROOT}
${OSMESA_ROOT}
${CMAKE_PREFIX_PATH}
"${CMAKE_SOURCE_DIR}/dependencies/mesa_install"
"${CMAKE_BINARY_DIR}/dependencies/mesa_install"
)
set(_OSMESA_SAVED_PKG_CONFIG_PATH "$ENV{PKG_CONFIG_PATH}")
foreach(_hint ${_OSMESA_HINTS})
if(EXISTS "${_hint}/lib/pkgconfig")
set(ENV{PKG_CONFIG_PATH} "${_hint}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")
endif()
endforeach()
pkg_check_modules(PC_OSMESA QUIET osmesa)
set(ENV{PKG_CONFIG_PATH} "${_OSMESA_SAVED_PKG_CONFIG_PATH}")
endif()

find_path(OSMESA_INCLUDE_DIR
NAMES GL/osmesa.h
HINTS
${OSMesa_ROOT}
${OSMESA_ROOT}
${CMAKE_PREFIX_PATH}
"${CMAKE_SOURCE_DIR}/dependencies/mesa_install"
"${CMAKE_BINARY_DIR}/dependencies/mesa_install"
${PC_OSMESA_INCLUDEDIR}
${PC_OSMESA_INCLUDE_DIRS}
PATH_SUFFIXES
include
PATHS
/opt/homebrew/include
/usr/local/include
/usr/include
)

find_library(OSMESA_LIBRARY
NAMES OSMesa osmesa OSMesa32
HINTS
${OSMesa_ROOT}
${OSMESA_ROOT}
${CMAKE_PREFIX_PATH}
"${CMAKE_SOURCE_DIR}/dependencies/mesa_install"
"${CMAKE_BINARY_DIR}/dependencies/mesa_install"
${PC_OSMESA_LIBDIR}
${PC_OSMESA_LIBRARY_DIRS}
PATH_SUFFIXES
lib
PATHS
/opt/homebrew/lib
/usr/local/lib
/usr/lib
)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(OSMesa
FOUND_VAR OSMESA_FOUND
REQUIRED_VARS OSMESA_LIBRARY OSMESA_INCLUDE_DIR
VERSION_VAR PC_OSMESA_VERSION
)

if(OSMESA_FOUND)
set(OSMESA_INCLUDE_DIRS "${OSMESA_INCLUDE_DIR}")
set(OSMESA_LIBRARIES "${OSMESA_LIBRARY}")

if(NOT TARGET OSMesa::OSMesa)
add_library(OSMesa::OSMesa UNKNOWN IMPORTED)
set_target_properties(OSMesa::OSMesa PROPERTIES
IMPORTED_LOCATION "${OSMESA_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${OSMESA_INCLUDE_DIR}"
)
endif()
endif()

mark_as_advanced(OSMESA_INCLUDE_DIR OSMESA_LIBRARY)
12 changes: 12 additions & 0 deletions src/OffscreenContextFactory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
#ifdef _WIN32
#include "OffscreenContextWGL.h"
#endif
#ifdef HAS_OSMESA
#include "OffscreenContextOSMesa.h"
#endif
#ifdef HAS_EGL
#include "OffscreenContextEGL.h"
#endif
Expand All @@ -30,6 +33,9 @@ const char *defaultProvider() {
#ifdef __APPLE__
return "cgl";
#endif
#if HAS_OSMESA
return "osmesa";
#endif
#if HAS_EGL
return "egl";
#endif
Expand Down Expand Up @@ -65,6 +71,12 @@ std::shared_ptr<OpenGLContext> create(const std::string& provider, const Context
return CreateOffscreenContextCGL(attrib.width, attrib.height, attrib.majorGLVersion, attrib.minorGLVersion);
}
#endif
#if HAS_OSMESA
if (provider == "osmesa") {
return CreateOffscreenContextOSMesa(attrib.width, attrib.height, attrib.majorGLVersion, attrib.minorGLVersion,
attrib.compatibilityProfile);
}
#endif
#if HAS_EGL
if (provider == "egl") {
return CreateOffscreenContextEGL(attrib.width, attrib.height, attrib.majorGLVersion, attrib.minorGLVersion,
Expand Down
Loading
Loading