From 7377684430da050da4e206f31d16645d82bb16b2 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Fri, 4 Sep 2026 22:12:04 -0400 Subject: [PATCH 1/2] WIP: OSMesa support --- .github/workflows/macos.yml | 19 ++++++- .gitignore | 3 ++ CMakeLists.txt | 28 ++++++++-- README.md | 47 +++++++++++++--- cmake/Modules/FindOSMesa.cmake | 97 +++++++++++++++++++++++++++++++++ src/OffscreenContextFactory.cc | 12 +++++ src/OffscreenContextOSMesa.cc | 98 ++++++++++++++++++++++++++++++++++ src/OffscreenContextOSMesa.h | 13 +++++ src/main.cc | 11 ++++ src/system-gl.h | 12 ++--- tools/build-osmesa.sh | 84 +++++++++++++++++++++++++++++ tools/sync-to-openscad.py | 3 ++ 12 files changed, 408 insertions(+), 19 deletions(-) create mode 100644 cmake/Modules/FindOSMesa.cmake create mode 100644 src/OffscreenContextOSMesa.cc create mode 100644 src/OffscreenContextOSMesa.h create mode 100755 tools/build-osmesa.sh diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 3aca854..adc8ed8 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -30,14 +30,29 @@ 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 }} + + - name: Build OSMesa + if: steps.cache-osmesa.outputs.cache-hit != 'true' + run: | + python3 -m pip install mako pyyaml packaging + ./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}} diff --git a/.gitignore b/.gitignore index b9559fe..21560b0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ offscreen offscreen.exe .vscode/ build/ +dependencies/ +.venv/ + diff --git a/CMakeLists.txt b/CMakeLists.txt index fe17aa2..85bfaa1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() @@ -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") @@ -216,9 +230,6 @@ 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) @@ -226,6 +237,16 @@ if(WIN32) 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 "====================================") @@ -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}") diff --git a/README.md b/README.md index c96968d..375d26d 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 diff --git a/cmake/Modules/FindOSMesa.cmake b/cmake/Modules/FindOSMesa.cmake new file mode 100644 index 0000000..c0714ba --- /dev/null +++ b/cmake/Modules/FindOSMesa.cmake @@ -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) diff --git a/src/OffscreenContextFactory.cc b/src/OffscreenContextFactory.cc index aeb172f..3981af0 100644 --- a/src/OffscreenContextFactory.cc +++ b/src/OffscreenContextFactory.cc @@ -14,6 +14,9 @@ #ifdef _WIN32 #include "OffscreenContextWGL.h" #endif +#ifdef HAS_OSMESA +#include "OffscreenContextOSMesa.h" +#endif #ifdef HAS_EGL #include "OffscreenContextEGL.h" #endif @@ -30,6 +33,9 @@ const char *defaultProvider() { #ifdef __APPLE__ return "cgl"; #endif +#if HAS_OSMESA + return "osmesa"; +#endif #if HAS_EGL return "egl"; #endif @@ -65,6 +71,12 @@ std::shared_ptr 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, diff --git a/src/OffscreenContextOSMesa.cc b/src/OffscreenContextOSMesa.cc new file mode 100644 index 0000000..5ef4343 --- /dev/null +++ b/src/OffscreenContextOSMesa.cc @@ -0,0 +1,98 @@ +#include "OffscreenContextOSMesa.h" + +#include +#include +#include +#include +#include + +#ifndef GLAPI +#define GLAPI extern +#endif +#ifndef GLAPIENTRY +#define GLAPIENTRY +#endif +#ifndef APIENTRY +#define APIENTRY GLAPIENTRY +#endif + +#include + +#include "utils/printutils.h" + +class OffscreenContextOSMesa : public OffscreenContext { +public: + OSMesaContext osmesaContext = nullptr; + mutable std::vector buffer; + + OffscreenContextOSMesa(uint32_t width, uint32_t height) + : OffscreenContext(width, height), buffer(width * height * 4, 0) {} + + ~OffscreenContextOSMesa() override { + if (osmesaContext) { + OSMesaDestroyContext(osmesaContext); + osmesaContext = nullptr; + } + } + + std::string getInfo() const override { + std::ostringstream out; + out << "GL context creator: OSMesa\n"; + return out.str(); + } + + bool makeCurrent() const override { + if (!osmesaContext) { + LOG(message_group::Error, "OSMesa context is null"); + return false; + } + if (!OSMesaMakeCurrent(osmesaContext, buffer.data(), + GL_UNSIGNED_BYTE, width_, height_)) { + LOG(message_group::Error, "OSMesaMakeCurrent() failed"); + return false; + } + return true; + } +}; + +std::shared_ptr CreateOffscreenContextOSMesa( + uint32_t width, uint32_t height, + uint32_t majorGLVersion, uint32_t minorGLVersion, + bool compatibilityProfile) +{ + auto ctx = std::make_shared(width, height); + + std::vector attribs; + attribs.push_back(OSMESA_FORMAT); + attribs.push_back(OSMESA_RGBA); + attribs.push_back(OSMESA_DEPTH_BITS); + attribs.push_back(24); + attribs.push_back(OSMESA_STENCIL_BITS); + attribs.push_back(8); + attribs.push_back(OSMESA_ACCUM_BITS); + attribs.push_back(0); + + if (majorGLVersion >= 3) { + attribs.push_back(OSMESA_PROFILE); + attribs.push_back(compatibilityProfile ? OSMESA_COMPAT_PROFILE : OSMESA_CORE_PROFILE); + attribs.push_back(OSMESA_CONTEXT_MAJOR_VERSION); + attribs.push_back(static_cast(majorGLVersion)); + attribs.push_back(OSMESA_CONTEXT_MINOR_VERSION); + attribs.push_back(static_cast(minorGLVersion)); + } + attribs.push_back(0); + + ctx->osmesaContext = OSMesaCreateContextAttribs(attribs.data(), nullptr); + if (!ctx->osmesaContext && majorGLVersion <= 2) { + // Fallback for OpenGL 2.x + ctx->osmesaContext = OSMesaCreateContextExt(OSMESA_RGBA, 24, 8, 0, nullptr); + } + + if (!ctx->osmesaContext) { + LOG(message_group::Error, "OSMesaCreateContext failed for GL %1$d.%2$d", + majorGLVersion, minorGLVersion); + return nullptr; + } + + return ctx; +} diff --git a/src/OffscreenContextOSMesa.h b/src/OffscreenContextOSMesa.h new file mode 100644 index 0000000..a858349 --- /dev/null +++ b/src/OffscreenContextOSMesa.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +#include "OffscreenContext.h" + +std::shared_ptr CreateOffscreenContextOSMesa( + uint32_t width, uint32_t height, + uint32_t majorGLVersion, uint32_t minorGLVersion, + bool compatibilityProfile); + + diff --git a/src/main.cc b/src/main.cc index 2e9ec7a..0794de7 100644 --- a/src/main.cc +++ b/src/main.cc @@ -22,6 +22,9 @@ #include #include "GLFWContext.h" #endif +#ifdef HAS_OSMESA +#include +#endif #include "CommandLine.h" #include "OffscreenContextFactory.h" @@ -94,6 +97,9 @@ int main(int argc, char *argv[]) #if HAS_EGL contextProviders.push_back("egl"); #endif +#ifdef HAS_OSMESA + contextProviders.push_back("osmesa"); +#endif #ifdef ENABLE_GLX contextProviders.push_back("glx"); #endif @@ -214,6 +220,11 @@ int main(int argc, char *argv[]) if (argContextProvider == "glfw") { version = requestGLES ? gladLoadGLES2(glfwGetProcAddress) : gladLoadGL(glfwGetProcAddress); } else +#endif +#ifdef HAS_OSMESA + if (argContextProvider == "osmesa") { + version = gladLoadGL(reinterpret_cast(OSMesaGetProcAddress)); + } else #endif { version = requestGLES ? gladLoaderLoadGLES2() : gladLoaderLoadGL(); diff --git a/src/system-gl.h b/src/system-gl.h index b5f94c3..c835d8e 100644 --- a/src/system-gl.h +++ b/src/system-gl.h @@ -5,17 +5,19 @@ #ifdef USE_GLAD #define GLAD_GLES2 #include "glad/gl.h" +#else +#ifdef __APPLE__ +#define GL_SILENCE_DEPRECATION +#include +#endif #endif #ifdef _WIN32 #include #include -#endif - -#ifdef __APPLE__ +#elif defined(__APPLE__) #define GL_SILENCE_DEPRECATION #include -#include #else #include #endif @@ -91,5 +93,3 @@ bool hasGLESVersion2(); #define GL_DEBUG_CHECKD(...) __VA_ARGS__ #endif - - diff --git a/tools/build-osmesa.sh b/tools/build-osmesa.sh new file mode 100755 index 0000000..6fed5d8 --- /dev/null +++ b/tools/build-osmesa.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +DEPS_DIR="${ROOT_DIR}/dependencies" +MESA_SRC="${DEPS_DIR}/mesa" +MESA_BUILD="${DEPS_DIR}/mesa_build" +MESA_INSTALL="${DEPS_DIR}/mesa_install" +MESA_VERSION="${MESA_VERSION:-24.3.4}" + +mkdir -p "${DEPS_DIR}" + +# Ensure Homebrew is in PATH +if [ -d "/opt/homebrew/bin" ]; then + export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:${PATH}" +elif [ -d "/usr/local/bin" ]; then + export PATH="/usr/local/bin:${PATH}" +fi + +# Ensure Homebrew tools and libraries are found +if command -v brew >/dev/null 2>&1; then + LLVM_PREFIX="$(brew --prefix llvm 2>/dev/null || true)" + BISON_PREFIX="$(brew --prefix bison 2>/dev/null || true)" + HOMEBREW_PREFIX="$(brew --prefix 2>/dev/null || true)" + [ -n "${HOMEBREW_PREFIX}" ] && export PATH="${HOMEBREW_PREFIX}/bin:${PATH}" + [ -n "${LLVM_PREFIX}" ] && export PATH="${LLVM_PREFIX}/bin:${PATH}" + [ -n "${BISON_PREFIX}" ] && export PATH="${BISON_PREFIX}/bin:${PATH}" + [ -n "${HOMEBREW_PREFIX}" ] && export LIBRARY_PATH="${HOMEBREW_PREFIX}/lib:${LLVM_PREFIX}/lib:${LIBRARY_PATH:-}" +fi + +# Ensure Python virtual environment with mako/pyyaml/packaging is in PATH +if [ -d "${ROOT_DIR}/.venv" ]; then + export PATH="${ROOT_DIR}/.venv/bin:${PATH}" +fi + +# Download or clone Mesa source if not present +if [ ! -d "${MESA_SRC}" ]; then + echo "==> Fetching Mesa ${MESA_VERSION}..." + TARBALL="${DEPS_DIR}/mesa-${MESA_VERSION}.tar.xz" + if curl -f -L -o "${TARBALL}" "https://archive.mesa3d.org/mesa-${MESA_VERSION}.tar.xz"; then + echo "==> Extracting ${TARBALL}..." + tar -xf "${TARBALL}" -C "${DEPS_DIR}" + mv "${DEPS_DIR}/mesa-${MESA_VERSION}" "${MESA_SRC}" + rm -f "${TARBALL}" + else + echo "==> Downloading tarball failed, cloning from git repository..." + git clone --recurse-submodules --depth 1 --branch "mesa-${MESA_VERSION}" \ + https://gitlab.freedesktop.org/mesa/mesa.git "${MESA_SRC}" + fi +fi + +echo "==> Configuring Mesa (OSMesa + LLVMpipe)..." +rm -rf "${MESA_BUILD}" + +meson setup "${MESA_BUILD}" "${MESA_SRC}" \ + --prefix="${MESA_INSTALL}" \ + --buildtype=release \ + --libdir=lib \ + -Db_ndebug=true \ + -Dosmesa=true \ + -Dgallium-drivers=llvmpipe \ + -Dvulkan-drivers=[] \ + -Dopengl=true \ + -Dgles1=disabled \ + -Dgles2=disabled \ + -Degl=disabled \ + -Dglx=disabled \ + -Dxlib-lease=disabled \ + -Dxmlconfig=disabled \ + -Dexpat=disabled \ + -Dplatforms=[] \ + -Dshared-glapi=disabled \ + -Dllvm=enabled \ + -Dshared-llvm=disabled + +echo "==> Building and installing Mesa to ${MESA_INSTALL}..." +ninja -C "${MESA_BUILD}" install + +echo "==> OSMesa build complete!" +echo "Installed in: ${MESA_INSTALL}" +ls -la "${MESA_INSTALL}/lib" +ls -la "${MESA_INSTALL}/include/GL" + diff --git a/tools/sync-to-openscad.py b/tools/sync-to-openscad.py index 54e6056..8a2a84d 100755 --- a/tools/sync-to-openscad.py +++ b/tools/sync-to-openscad.py @@ -23,6 +23,8 @@ "OffscreenContextGLX.cc": "src/glview/OffscreenContextGLX.cc", "OffscreenContextWGL.h": "src/glview/OffscreenContextWGL.h", "OffscreenContextWGL.cc": "src/glview/OffscreenContextWGL.cc", + "OffscreenContextOSMesa.h": "src/glview/OffscreenContextOSMesa.h", + "OffscreenContextOSMesa.cc": "src/glview/OffscreenContextOSMesa.cc", "OffscreenContextNULL.h": "src/glview/OffscreenContextNULL.h", "OffscreenContextNULL.cc": "src/glview/OffscreenContextNULL.cc", "OpenGLContext.h": "src/glview/OpenGLContext.h", @@ -37,6 +39,7 @@ (re.compile(r'#include "OffscreenContextEGL\.h"'), '#include "glview/OffscreenContextEGL.h"'), (re.compile(r'#include "OffscreenContextGLX\.h"'), '#include "glview/OffscreenContextGLX.h"'), (re.compile(r'#include "OffscreenContextWGL\.h"'), '#include "glview/OffscreenContextWGL.h"'), + (re.compile(r'#include "OffscreenContextOSMesa\.h"'), '#include "glview/OffscreenContextOSMesa.h"'), (re.compile(r'#include "OffscreenContextNULL\.h"'), '#include "glview/OffscreenContextNULL.h"'), (re.compile(r'#include "OffscreenContextFactory\.h"'), '#include "glview/OffscreenContextFactory.h"'), (re.compile(r'#include "OpenGLContext\.h"'), '#include "glview/OpenGLContext.h"'), From ed1882b65307c660694e4c076617eae74a371009 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Sat, 26 Sep 2026 09:50:31 -0400 Subject: [PATCH 2/2] Fix macOS CI: self-contained Python venv in build-osmesa.sh and robust cache key --- .github/workflows/macos.yml | 6 ++---- tools/build-osmesa.sh | 28 ++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index adc8ed8..64cf370 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -37,13 +37,11 @@ jobs: uses: actions/cache@v4 with: path: dependencies/mesa_install - key: osmesa-24.3.4-${{ matrix.os }} + key: osmesa-24.3.4-${{ matrix.os }}-${{ hashFiles('tools/build-osmesa.sh') }} - name: Build OSMesa if: steps.cache-osmesa.outputs.cache-hit != 'true' - run: | - python3 -m pip install mako pyyaml packaging - ./tools/build-osmesa.sh + run: ./tools/build-osmesa.sh - name: Setup tmate session uses: mxschmitt/action-tmate@v3 diff --git a/tools/build-osmesa.sh b/tools/build-osmesa.sh index 6fed5d8..c4c6e97 100755 --- a/tools/build-osmesa.sh +++ b/tools/build-osmesa.sh @@ -27,20 +27,39 @@ if command -v brew >/dev/null 2>&1; then [ -n "${LLVM_PREFIX}" ] && export PATH="${LLVM_PREFIX}/bin:${PATH}" [ -n "${BISON_PREFIX}" ] && export PATH="${BISON_PREFIX}/bin:${PATH}" [ -n "${HOMEBREW_PREFIX}" ] && export LIBRARY_PATH="${HOMEBREW_PREFIX}/lib:${LLVM_PREFIX}/lib:${LIBRARY_PATH:-}" + [ -n "${HOMEBREW_PREFIX}" ] && export LDFLAGS="-L${HOMEBREW_PREFIX}/lib -L${LLVM_PREFIX}/lib ${LDFLAGS:-}" fi -# Ensure Python virtual environment with mako/pyyaml/packaging is in PATH -if [ -d "${ROOT_DIR}/.venv" ]; then - export PATH="${ROOT_DIR}/.venv/bin:${PATH}" +# Set SDKROOT on macOS if available +if [ -z "${SDKROOT:-}" ] && command -v xcrun >/dev/null 2>&1; then + export SDKROOT="$(xcrun --show-sdk-path)" +fi + +# Ensure Python dependencies (mako, pyyaml, packaging) are available +VENV_DIR="${ROOT_DIR}/.venv" +if ! python3 -c "import mako, yaml, packaging" >/dev/null 2>&1; then + echo "==> Installing Mesa build dependencies in Python virtual environment..." + if [ ! -d "${VENV_DIR}" ]; then + python3 -m venv "${VENV_DIR}" || true + fi + if [ -x "${VENV_DIR}/bin/pip" ]; then + "${VENV_DIR}/bin/pip" install mako pyyaml packaging + export PATH="${VENV_DIR}/bin:${PATH}" + else + python3 -m pip install --break-system-packages mako pyyaml packaging || true + fi +elif [ -d "${VENV_DIR}" ]; then + export PATH="${VENV_DIR}/bin:${PATH}" fi # Download or clone Mesa source if not present if [ ! -d "${MESA_SRC}" ]; then echo "==> Fetching Mesa ${MESA_VERSION}..." TARBALL="${DEPS_DIR}/mesa-${MESA_VERSION}.tar.xz" - if curl -f -L -o "${TARBALL}" "https://archive.mesa3d.org/mesa-${MESA_VERSION}.tar.xz"; then + if curl -f -L --retry 3 --connect-timeout 30 -o "${TARBALL}" "https://archive.mesa3d.org/mesa-${MESA_VERSION}.tar.xz"; then echo "==> Extracting ${TARBALL}..." tar -xf "${TARBALL}" -C "${DEPS_DIR}" + rm -rf "${MESA_SRC}" mv "${DEPS_DIR}/mesa-${MESA_VERSION}" "${MESA_SRC}" rm -f "${TARBALL}" else @@ -82,3 +101,4 @@ echo "Installed in: ${MESA_INSTALL}" ls -la "${MESA_INSTALL}/lib" ls -la "${MESA_INSTALL}/include/GL" +