Skip to content
Open
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
14 changes: 8 additions & 6 deletions .github/workflows/fedora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
container: fedora:latest

steps:
- run: yum install -y g++ cmake git glfw-devel
- run: dnf install -y gcc-c++ cmake git glfw-devel mesa-libGL-devel mesa-libEGL-devel mesa-dri-drivers xorg-x11-server-Xvfb

- uses: actions/checkout@v6
with:
Expand All @@ -44,14 +44,16 @@ jobs:

- name: Test
working-directory: /__w/offscreen/offscreen/build
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest -C ${{env.BUILD_TYPE}}
env:
LIBGL_ALWAYS_SOFTWARE: "1"
run: xvfb-run ctest --output-on-failure -C ${{env.BUILD_TYPE}}

- name: Upload Test Results
uses: actions/upload-artifact@v6
if: ${{ always() }}
with:
name: Test Results(${{ matrix.os }})
name: Test Results(Fedora)
path: |
build/Testing/Temporary/LastTest.log
build/Testing/Temporary/LastTest.log
build/out.png
build/out_320x240.png
24 changes: 19 additions & 5 deletions .github/workflows/offscreen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, macos-14]
os: [ubuntu-latest, macos-13, macos-latest, windows-latest]


steps:
- uses: actions/checkout@v6
Expand All @@ -31,11 +33,15 @@ jobs:

- uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: libglvnd-dev libglfw3-dev libglu1-mesa-dev
packages: libglvnd-dev libglfw3-dev libglu1-mesa-dev libegl1-mesa-dev libgbm-dev xvfb
version: 1.0
if: runner.os == 'Linux'
- run: brew install glfw
if: runner.os == 'macOS'
- name: Install Mesa (Windows)
uses: ssciwr/setup-mesa-dist-win@v3
if: runner.os == 'Windows'


- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
Expand All @@ -52,9 +58,15 @@ jobs:

- name: Test
working-directory: ${{github.workspace}}/build
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest -C ${{env.BUILD_TYPE}}
env:
LIBGL_ALWAYS_SOFTWARE: "1"
run: |
if [ "$RUNNER_OS" = "Linux" ]; then
xvfb-run ctest --output-on-failure -C ${{env.BUILD_TYPE}}
else
ctest --output-on-failure -C ${{env.BUILD_TYPE}}
fi
shell: bash

- name: Upload Test Results
uses: actions/upload-artifact@v6
Expand All @@ -64,3 +76,5 @@ jobs:
path: |
build/Testing/Temporary/LastTest.log
build/out.png
build/out_320x240.png

95 changes: 77 additions & 18 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ endif()

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)

set(HAS_GLVND FALSE)
set(HAS_GLX FALSE)
Expand All @@ -37,7 +39,7 @@ else()
target_link_libraries(offscreen OpenGL::GL)
endif()
target_link_libraries(offscreen OpenGL::GLU)
if (OpenGL_EGL_FOUND)
if (ENABLE_EGL AND OpenGL_EGL_FOUND)
if (TARGET OpenGL::EGL)
# GLVND systems
target_link_libraries(offscreen OpenGL::EGL)
Expand All @@ -53,7 +55,7 @@ if (OpenGL_EGL_FOUND)
target_link_libraries(offscreen GBM::GBM)
endif()
endif()
if(OpenGL_GLX_FOUND)
if(ENABLE_GLX AND OpenGL_GLX_FOUND)
find_package(X11 REQUIRED)
if(X11_FOUND)
if(TARGET OpenGL::OpenGL)
Expand All @@ -62,18 +64,29 @@ if(OpenGL_GLX_FOUND)
endif()
set(HAS_GLX TRUE)
target_link_libraries(offscreen X11::X11)
target_compile_definitions(offscreen PRIVATE ENABLE_GLX)
endif()
endif()


if(ENABLE_GLFW)
find_package(glfw3 REQUIRED)
target_link_libraries(offscreen glfw)
target_compile_definitions(offscreen PRIVATE ENABLE_GLFW)
find_package(glfw3 QUIET)
if(TARGET glfw OR TARGET glfw3 OR glfw3_FOUND)
if(TARGET glfw)
target_link_libraries(offscreen glfw)
else()
target_link_libraries(offscreen glfw3)
endif()
target_compile_definitions(offscreen PRIVATE ENABLE_GLFW)
else()
message(STATUS "GLFW not found, disabling on-screen GLFW backend")
set(ENABLE_GLFW OFF)
endif()
endif()

if(CMAKE_DL_LIBS)
target_link_libraries(offscreen ${CMAKE_DL_LIBS})
endif()

# Needed for Raspberry pi:
target_link_libraries(offscreen dl)

if(APPLE)
set(HAS_NSOPENGL TRUE)
Expand All @@ -96,16 +109,17 @@ if(HAS_EGL)
src/OffscreenContextEGL.cc
src/egl_utils.cc
)
add_compile_definitions(HAS_EGL)
add_compile_definitions(HAS_EGL ENABLE_EGL)
endif()

if(HAS_GLX)
set(SRCS_GLX
src/OffscreenContextGLX.cc
)
add_compile_definitions(HAS_GLX)
add_compile_definitions(HAS_GLX ENABLE_GLX)
endif()


if(ENABLE_GLFW)
set(SRCS_GLFW src/GLFWContext.cc)
endif()
Expand All @@ -130,30 +144,75 @@ set(SRCS
)
target_sources(offscreen PRIVATE ${SRCS})
target_include_directories(offscreen PUBLIC "${CMAKE_SOURCE_DIR}/src")
# Using C++17 as my RaspberryPi runs gcc=8.3 which doesn't implement <numbers>
set_property(TARGET offscreen PROPERTY CXX_STANDARD 17)
if(MSVC)
# MSVC requires /std:c++20 for designated initializers
set_property(TARGET offscreen PROPERTY CXX_STANDARD 20)
else()
# Using C++17 as my RaspberryPi runs gcc=8.3 which doesn't implement <numbers>
set_property(TARGET offscreen PROPERTY CXX_STANDARD 17)
endif()


enable_testing()
add_test(NAME default_run COMMAND offscreen)
add_test(NAME fails_on_empty_context_arg COMMAND offscreen --context)
set_property(TEST fails_on_empty_context_arg PROPERTY WILL_FAIL true)

add_test(NAME fails_on_invalid_context COMMAND offscreen --context nonexistent_backend)
set_property(TEST fails_on_invalid_context PROPERTY WILL_FAIL true)

add_test(NAME fails_on_immediate_in_core_profile COMMAND offscreen --opengl 3.3 --profile core --mode immediate)
set_property(TEST fails_on_immediate_in_core_profile PROPERTY WILL_FAIL true)

add_test(NAME nullgl_test COMMAND offscreen --context nullgl)

add_test(NAME will_save_framebuffer COMMAND offscreen -o out.png)
add_test(NAME check_file_exists COMMAND ${CMAKE_COMMAND} -E cat out.png)
set_tests_properties(check_file_exists PROPERTIES DEPENDS will_save_framebuffer)

add_test(NAME custom_dimensions_save COMMAND offscreen --width 320 --height 240 -o out_320x240.png)
add_test(NAME check_custom_file_exists COMMAND ${CMAKE_COMMAND} -E cat out_320x240.png)
set_tests_properties(check_custom_file_exists PROPERTIES DEPENDS custom_dimensions_save)

if(APPLE)
add_test(NAME cgl_opengl2_immediate COMMAND offscreen --context cgl --opengl 2 --mode immediate)
add_test(NAME cgl_opengl2_modern COMMAND offscreen --context cgl --opengl 2 --mode modern)
add_test(NAME cgl_opengl3.2_core COMMAND offscreen --context cgl --opengl 3.2 --profile core)
add_test(NAME nsopengl_opengl2_immediate COMMAND offscreen --context nsopengl --opengl 2 --mode immediate)
add_test(NAME nsopengl_opengl2_modern COMMAND offscreen --context nsopengl --opengl 2 --mode modern)
add_test(NAME nsopengl_opengl3.2_core COMMAND offscreen --context nsopengl --opengl 3.2 --profile core)
add_test(NAME cgl_opengl2_immediate COMMAND offscreen --context cgl --opengl 2 --mode immediate)
add_test(NAME cgl_opengl2_modern COMMAND offscreen --context cgl --opengl 2 --mode modern)
add_test(NAME cgl_opengl3.2_core COMMAND offscreen --context cgl --opengl 3.2 --profile core)
add_test(NAME nsopengl_opengl2_immediate COMMAND offscreen --context nsopengl --opengl 2 --mode immediate)
add_test(NAME nsopengl_opengl2_modern COMMAND offscreen --context nsopengl --opengl 2 --mode modern)
add_test(NAME nsopengl_opengl3.2_core COMMAND offscreen --context nsopengl --opengl 3.2 --profile core)
endif(APPLE)

if(HAS_EGL)
add_test(NAME egl_opengl2_immediate COMMAND offscreen --context egl --opengl 2 --mode immediate)
add_test(NAME egl_opengl2_modern COMMAND offscreen --context egl --opengl 2 --mode modern)
add_test(NAME egl_gles2 COMMAND offscreen --context egl --gles 2)
add_test(NAME egl_opengl3_core COMMAND offscreen --context egl --opengl 3.3 --profile core)
add_test(NAME egl_opengl3_compat COMMAND offscreen --context egl --opengl 3.3 --profile compatibility)
add_test(NAME egl_save_framebuffer COMMAND offscreen --context egl -o out_egl.png)
add_test(NAME check_egl_file_exists COMMAND ${CMAKE_COMMAND} -E cat out_egl.png)
set_tests_properties(check_egl_file_exists PROPERTIES DEPENDS egl_save_framebuffer)
endif()

if(HAS_GLX)
add_test(NAME glx_opengl2_immediate COMMAND offscreen --context glx --opengl 2 --mode immediate)
add_test(NAME glx_opengl2_modern COMMAND offscreen --context glx --opengl 2 --mode modern)
add_test(NAME glx_opengl3_core COMMAND offscreen --context glx --opengl 3.3 --profile core)
add_test(NAME glx_opengl3_compat COMMAND offscreen --context glx --opengl 3.3 --profile compatibility)
add_test(NAME glx_save_framebuffer COMMAND offscreen --context glx -o out_glx.png)
add_test(NAME check_glx_file_exists COMMAND ${CMAKE_COMMAND} -E cat out_glx.png)
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)


message(STATUS " ")
message(STATUS "====================================")
message(STATUS "Build Configuration Summary")
Expand Down
4 changes: 3 additions & 1 deletion src/FBO.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ bool checkFBOStatus() {
} // namespace

std::unique_ptr<FBO> createFBO(int width, int height) {
if (hasGLExtension(ARB_framebuffer_object)) {
if (hasGLVersion3() || hasGLESVersion2() || hasGLExtension(ARB_framebuffer_object)) {
return std::make_unique<FBO>(width, height, /*useEXT*/ false);
} else if (hasGLExtension(EXT_framebuffer_object)) {
return std::make_unique<FBO>(width, height, /*useEXT*/ true);
Expand All @@ -64,6 +64,8 @@ std::unique_ptr<FBO> createFBO(int width, int height) {
}
}



FBO::FBO(int width, int height, bool useEXT) : width_(width), height_(height), use_ext_(useEXT) {
// Generate and bind FBO
GL_CHECK(glGenFramebuffers(1, &this->fbo_id_));
Expand Down
60 changes: 46 additions & 14 deletions src/OffscreenContextWGL.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ std::shared_ptr<OffscreenContext> CreateOffscreenContextWGL(size_t width, size_t
.cbSize = sizeof(WNDCLASSEX),
.style = CS_OWNDC,
.lpfnWndProc = &DefWindowProc,
.hInstance = GetModuleHandle(nullptr),
.lpszClassName = "OffscreenClass"
};
// FIXME: Check for ERROR_CLASS_ALREADY_EXISTS ?
Expand All @@ -61,7 +62,15 @@ std::shared_ptr<OffscreenContext> CreateOffscreenContextWGL(size_t width, size_t
// Style the window and remove the caption bar (WS_POPUP)
ctx->window = CreateWindowEx(0, "OffscreenClass", "offscreen", WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP,
CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, 0, 0);
if (!ctx->window) {
std::cerr << "CreateWindowEx() failed: " << GetLastError() << std::endl;
return nullptr;
}
ctx->devContext = GetDC(ctx->window);
if (!ctx->devContext) {
std::cerr << "GetDC() failed: " << GetLastError() << std::endl;
return nullptr;
}

PIXELFORMATDESCRIPTOR pixelFormatDesc = {
.nSize = sizeof(PIXELFORMATDESCRIPTOR),
Expand All @@ -73,9 +82,16 @@ std::shared_ptr<OffscreenContext> CreateOffscreenContextWGL(size_t width, size_t
.cDepthBits = 24,
.cStencilBits = 8
};

int pixelFormat = ChoosePixelFormat(ctx->devContext, &pixelFormatDesc);
SetPixelFormat(ctx->devContext, pixelFormat, &pixelFormatDesc);
// FIXME: Use wglChoosePixelFormatARB() if appropriate
if (!pixelFormat) {
std::cerr << "ChoosePixelFormat() failed: " << GetLastError() << std::endl;
return nullptr;
}
if (!SetPixelFormat(ctx->devContext, pixelFormat, &pixelFormatDesc)) {
std::cerr << "SetPixelFormat() failed: " << GetLastError() << std::endl;
return nullptr;
}

const auto tmpRenderContext = wglCreateContext(ctx->devContext);
if (tmpRenderContext == nullptr) {
Expand All @@ -87,21 +103,37 @@ std::shared_ptr<OffscreenContext> CreateOffscreenContextWGL(size_t width, size_t
wglDeleteContext(tmpRenderContext);
});

wglMakeCurrent(ctx->devContext, tmpRenderContext);
if (!wglMakeCurrent(ctx->devContext, tmpRenderContext)) {
std::cerr << "wglMakeCurrent() failed: " << GetLastError() << std::endl;
return nullptr;
}

gladLoaderLoadWGL(ctx->devContext);

int attributes[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, static_cast<int>(majorGLVersion),
WGL_CONTEXT_MINOR_VERSION_ARB, static_cast<int>(minorGLVersion),
WGL_CONTEXT_PROFILE_MASK_ARB,
compatibilityProfile ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};
ctx->renderContext = wglCreateContextAttribsARB(ctx->devContext, nullptr, attributes);
if (ctx->renderContext == nullptr) {
std::cerr << "wglCreateContextAttribsARB() failed: " << GetLastError() << std::endl;
return nullptr;
if (wglCreateContextAttribsARB) {
int attributes[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, static_cast<int>(majorGLVersion),
WGL_CONTEXT_MINOR_VERSION_ARB, static_cast<int>(minorGLVersion),
WGL_CONTEXT_PROFILE_MASK_ARB,
compatibilityProfile ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};
ctx->renderContext = wglCreateContextAttribsARB(ctx->devContext, nullptr, attributes);
if (ctx->renderContext == nullptr) {
std::cerr << "wglCreateContextAttribsARB() failed: " << GetLastError() << std::endl;
return nullptr;
}
} else {
if (majorGLVersion > 2) {
std::cerr << "wglCreateContextAttribsARB() not available, cannot create modern OpenGL context" << std::endl;
return nullptr;
}
// Fall back to the legacy context
ctx->renderContext = tmpRenderContext;
guard.dismiss();
}

return ctx;
}


Loading