diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 0b6ff2f90ba..cb0ef1d9a5a 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -45,10 +45,10 @@ jobs: sudo apt-get -y update sudo apt-get -y install freeglut3-dev cmake -S . -B cmake_build_dir -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCOIN_BUILD_LEGACY_GL_RENDERER=OFF -DCOIN_BUILD_TESTS=ON -DCOIN_BUILD_EXAMPLES=OFF -DCOIN_BUILD_DOCUMENTATION=OFF - - name: Build core EGL and GLSL tests - run: cmake --build cmake_build_dir --target EGLBindingTest GLSLRuntimeTest --config Release -- -j4 - - name: Run core EGL and GLSL tests - run: ctest -C Release -R '^(EGLBindingTest|GLSLRuntimeTest)$' --output-on-failure + - name: Build core EGL, GLSL, and readback tests + run: cmake --build cmake_build_dir --target EGLBindingTest GLSLRuntimeTest OffscreenReadbackTest --config Release -- -j4 + - name: Run core EGL, GLSL, and readback tests + run: ctest -C Release -R '^(EGLBindingTest|GLSLRuntimeTest|OffscreenReadbackTest)$' --output-on-failure working-directory: cmake_build_dir env: COIN_EGL: 1 diff --git a/src/glue/gl.cpp b/src/glue/gl.cpp index b4b28f3f45b..7d820cf97a5 100644 --- a/src/glue/gl.cpp +++ b/src/glue/gl.cpp @@ -5321,14 +5321,25 @@ cc_glglue_is_texture_size_legal(const cc_glglue * glw, GLenum internalformat; GLenum format; GLenum type = GL_UNSIGNED_BYTE; + const SbBool legacy = cc_glglue_context_supports_legacy_rendering(glw); switch (bytespertexel) { default: case 1: - format = internalformat = GL_LUMINANCE; + if (legacy) { + format = internalformat = GL_LUMINANCE; + } else { + internalformat = GL_R8; + format = GL_RED; + } break; case 2: - format = internalformat = GL_LUMINANCE_ALPHA; + if (legacy) { + format = internalformat = GL_LUMINANCE_ALPHA; + } else { + internalformat = GL_RG8; + format = GL_RG; + } break; case 3: format = internalformat = GL_RGB8; @@ -5403,13 +5414,14 @@ GLint coin_glglue_get_internal_texture_format(const cc_glglue * glw, SbBool compress) { GLenum format; + const SbBool legacy = cc_glglue_context_supports_legacy_rendering(glw); if (compress) { switch (numcomponents) { case 1: - format = GL_COMPRESSED_LUMINANCE_ARB; + format = legacy ? GL_COMPRESSED_LUMINANCE_ARB : GL_COMPRESSED_RED; break; case 2: - format = GL_COMPRESSED_LUMINANCE_ALPHA_ARB; + format = legacy ? GL_COMPRESSED_LUMINANCE_ALPHA_ARB : GL_COMPRESSED_RG; break; case 3: format = GL_COMPRESSED_RGB_ARB; @@ -5424,10 +5436,10 @@ GLint coin_glglue_get_internal_texture_format(const cc_glglue * glw, SbBool usenewenums = glglue_allow_newer_opengl(glw) && cc_glglue_glversion_matches_at_least(glw,1,1,0); switch (numcomponents) { case 1: - format = usenewenums ? GL_LUMINANCE8 : GL_LUMINANCE; + format = legacy ? (usenewenums ? GL_LUMINANCE8 : GL_LUMINANCE) : GL_R8; break; case 2: - format = usenewenums ? GL_LUMINANCE8_ALPHA8 : GL_LUMINANCE_ALPHA; + format = legacy ? (usenewenums ? GL_LUMINANCE8_ALPHA8 : GL_LUMINANCE_ALPHA) : GL_RG8; break; case 3: format = usenewenums ? GL_RGB8 : GL_RGB; @@ -5445,15 +5457,16 @@ GLint coin_glglue_get_internal_texture_format(const cc_glglue * glw, Convert from num components to client texture format for use in glTexImage*D's format parameter. */ -GLenum coin_glglue_get_texture_format(const cc_glglue * COIN_UNUSED_ARG(glw), int numcomponents) +GLenum coin_glglue_get_texture_format(const cc_glglue * glw, int numcomponents) { GLenum format; + const SbBool legacy = cc_glglue_context_supports_legacy_rendering(glw); switch (numcomponents) { case 1: - format = GL_LUMINANCE; + format = legacy ? GL_LUMINANCE : GL_RED; break; case 2: - format = GL_LUMINANCE_ALPHA; + format = legacy ? GL_LUMINANCE_ALPHA : GL_RG; break; case 3: format = GL_RGB; diff --git a/src/rendering/CoinOffscreenGLCanvas.cpp b/src/rendering/CoinOffscreenGLCanvas.cpp index 18f09378a65..4cd4655f23f 100644 --- a/src/rendering/CoinOffscreenGLCanvas.cpp +++ b/src/rendering/CoinOffscreenGLCanvas.cpp @@ -331,105 +331,139 @@ CoinOffscreenGLCanvas::readPixels(uint8_t * dst, unsigned int dstrowsize, unsigned int nrcomponents) const { - glPushAttrib(GL_ALL_ATTRIB_BITS); - - // First reset all settings that can influence the result of a - // glReadPixels() call, to make sure we get the actual contents of - // the buffer, unmodified. - // - // The values set up below matches the default settings of an - // OpenGL driver. - - glPixelStorei(GL_PACK_SWAP_BYTES, 0); - glPixelStorei(GL_PACK_LSB_FIRST, 0); - glPixelStorei(GL_PACK_ROW_LENGTH, (GLint)dstrowsize); - glPixelStorei(GL_PACK_SKIP_ROWS, 0); - glPixelStorei(GL_PACK_SKIP_PIXELS, 0); - - // FIXME: should use best possible alignment, for speediest - // operation. 20050617 mortene. -// glPixelStorei(GL_PACK_ALIGNMENT, 4); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - glPixelTransferi(GL_MAP_COLOR, 0); - glPixelTransferi(GL_MAP_STENCIL, 0); - glPixelTransferi(GL_INDEX_SHIFT, 0); - glPixelTransferi(GL_INDEX_OFFSET, 0); - glPixelTransferf(GL_RED_SCALE, 1); - glPixelTransferf(GL_RED_BIAS, 0); - glPixelTransferf(GL_GREEN_SCALE, 1); - glPixelTransferf(GL_GREEN_BIAS, 0); - glPixelTransferf(GL_BLUE_SCALE, 1); - glPixelTransferf(GL_BLUE_BIAS, 0); - glPixelTransferf(GL_ALPHA_SCALE, 1); - glPixelTransferf(GL_ALPHA_BIAS, 0); - glPixelTransferf(GL_DEPTH_SCALE, 1); - glPixelTransferf(GL_DEPTH_BIAS, 0); - - GLuint i = 0; - GLfloat f = 0.0f; - glPixelMapfv(GL_PIXEL_MAP_I_TO_I, 1, &f); - glPixelMapuiv(GL_PIXEL_MAP_S_TO_S, 1, &i); - glPixelMapfv(GL_PIXEL_MAP_I_TO_R, 1, &f); - glPixelMapfv(GL_PIXEL_MAP_I_TO_G, 1, &f); - glPixelMapfv(GL_PIXEL_MAP_I_TO_B, 1, &f); - glPixelMapfv(GL_PIXEL_MAP_I_TO_A, 1, &f); - glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 1, &f); - glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 1, &f); - glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 1, &f); - glPixelMapfv(GL_PIXEL_MAP_A_TO_A, 1, &f); - - // The flushing of the OpenGL pipeline before and after the - // glReadPixels() call is done as a work-around for a reported - // OpenGL driver bug: on a Win2000 system with ATI Radeon graphics - // card, the system would hang hard if the flushing was not done. - // - // This is obviously an OpenGL driver bug, but the workaround of - // doing excessive flushing has no real ill effects, so we just do - // it unconditionally for all drivers. Note that it might not be - // necessary to flush both before and after glReadPixels() to work - // around the bug (this was not established with the external - // reporter), but again it shouldn't matter if we do. - // - // For reference, the specific driver which was reported to fail has - // the following characteristics: - // - // GL_VENDOR="ATI Technologies Inc." - // GL_RENDERER="Radeon 9000 DDR x86/SSE2" - // GL_VERSION="1.3.3446 Win2000 Release" - // - // mortene. - - glFlush(); glFinish(); - - assert((nrcomponents >= 1) && (nrcomponents <= 4)); +#if defined(COIN_BUILD_LEGACY_GL_RENDERER) + const cc_glglue * glue = cc_glglue_instance((int) this->renderid); + const SbBool legacyContext = cc_glglue_context_supports_legacy_rendering(glue); + if (legacyContext) { + glPushAttrib(GL_PIXEL_MODE_BIT); + } +#endif - if (nrcomponents < 3) { - unsigned char * tmp = new unsigned char[vpdims[0]*vpdims[1]*4]; - glReadPixels(0, 0, vpdims[0], vpdims[1], - nrcomponents == 1 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, tmp); + GLint packSwapBytes; + GLint packLsbFirst; + GLint packRowLength; + GLint packSkipRows; + GLint packSkipPixels; + GLint packAlignment; + glGetIntegerv(GL_PACK_SWAP_BYTES, &packSwapBytes); + glGetIntegerv(GL_PACK_LSB_FIRST, &packLsbFirst); + glGetIntegerv(GL_PACK_ROW_LENGTH, &packRowLength); + glGetIntegerv(GL_PACK_SKIP_ROWS, &packSkipRows); + glGetIntegerv(GL_PACK_SKIP_PIXELS, &packSkipPixels); + glGetIntegerv(GL_PACK_ALIGNMENT, &packAlignment); + + // First reset all settings that can influence the result of a + // glReadPixels() call, to make sure we get the actual contents of + // the buffer, unmodified. + // + // The values set up below matches the default settings of an + // OpenGL driver. + + glPixelStorei(GL_PACK_SWAP_BYTES, 0); + glPixelStorei(GL_PACK_LSB_FIRST, 0); + glPixelStorei(GL_PACK_ROW_LENGTH, (GLint)dstrowsize); + glPixelStorei(GL_PACK_SKIP_ROWS, 0); + glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + + // FIXME: should use best possible alignment, for speediest + // operation. 20050617 mortene. + // glPixelStorei(GL_PACK_ALIGNMENT, 4); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + +#if defined(COIN_BUILD_LEGACY_GL_RENDERER) + if (legacyContext) { + glPixelTransferi(GL_MAP_COLOR, 0); + glPixelTransferi(GL_MAP_STENCIL, 0); + glPixelTransferi(GL_INDEX_SHIFT, 0); + glPixelTransferi(GL_INDEX_OFFSET, 0); + glPixelTransferf(GL_RED_SCALE, 1); + glPixelTransferf(GL_RED_BIAS, 0); + glPixelTransferf(GL_GREEN_SCALE, 1); + glPixelTransferf(GL_GREEN_BIAS, 0); + glPixelTransferf(GL_BLUE_SCALE, 1); + glPixelTransferf(GL_BLUE_BIAS, 0); + glPixelTransferf(GL_ALPHA_SCALE, 1); + glPixelTransferf(GL_ALPHA_BIAS, 0); + glPixelTransferf(GL_DEPTH_SCALE, 1); + glPixelTransferf(GL_DEPTH_BIAS, 0); + + GLuint i = 0; + GLfloat f = 0.0f; + glPixelMapfv(GL_PIXEL_MAP_I_TO_I, 1, &f); + glPixelMapuiv(GL_PIXEL_MAP_S_TO_S, 1, &i); + glPixelMapfv(GL_PIXEL_MAP_I_TO_R, 1, &f); + glPixelMapfv(GL_PIXEL_MAP_I_TO_G, 1, &f); + glPixelMapfv(GL_PIXEL_MAP_I_TO_B, 1, &f); + glPixelMapfv(GL_PIXEL_MAP_I_TO_A, 1, &f); + glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 1, &f); + glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 1, &f); + glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 1, &f); + glPixelMapfv(GL_PIXEL_MAP_A_TO_A, 1, &f); + } +#endif - const unsigned char * src = tmp; - // manually convert to grayscale - for (short y = 0; y < vpdims[1]; y++) { - for (short x = 0; x < vpdims[0]; x++) { - double v = src[0] * 0.3 + src[1] * 0.59 + src[2] * 0.11; - *dst++ = (unsigned char) v; - if (nrcomponents == 2) { - *dst++ = src[3]; + // The flushing of the OpenGL pipeline before and after the + // glReadPixels() call is done as a work-around for a reported + // OpenGL driver bug: on a Win2000 system with ATI Radeon graphics + // card, the system would hang hard if the flushing was not done. + // + // This is obviously an OpenGL driver bug, but the workaround of + // doing excessive flushing has no real ill effects, so we just do + // it unconditionally for all drivers. Note that it might not be + // necessary to flush both before and after glReadPixels() to work + // around the bug (this was not established with the external + // reporter), but again it shouldn't matter if we do. + // + // For reference, the specific driver which was reported to fail has + // the following characteristics: + // + // GL_VENDOR="ATI Technologies Inc." + // GL_RENDERER="Radeon 9000 DDR x86/SSE2" + // GL_VERSION="1.3.3446 Win2000 Release" + // + // mortene. + + glFlush(); glFinish(); + + assert((nrcomponents >= 1) && (nrcomponents <= 4)); + + if (nrcomponents < 3) { + unsigned char * tmp = new unsigned char[vpdims[0]*vpdims[1]*4]; + glReadPixels(0, 0, vpdims[0], vpdims[1], + nrcomponents == 1 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, tmp); + + const unsigned char * src = tmp; + // manually convert to grayscale + for (short y = 0; y < vpdims[1]; y++) { + for (short x = 0; x < vpdims[0]; x++) { + double v = src[0] * 0.3 + src[1] * 0.59 + src[2] * 0.11; + *dst++ = (unsigned char) v; + if (nrcomponents == 2) { + *dst++ = src[3]; + } + src += nrcomponents == 1 ? 3 : 4; } - src += nrcomponents == 1 ? 3 : 4; } + delete[] tmp; } - delete[] tmp; - } - else { - glReadPixels(0, 0, vpdims[0], vpdims[1], - nrcomponents == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, dst); - } - glFlush(); glFinish(); - - glPopAttrib(); + else { + glReadPixels(0, 0, vpdims[0], vpdims[1], + nrcomponents == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, dst); + } + glFlush(); glFinish(); + + glPixelStorei(GL_PACK_SWAP_BYTES, packSwapBytes); + glPixelStorei(GL_PACK_LSB_FIRST, packLsbFirst); + glPixelStorei(GL_PACK_ROW_LENGTH, packRowLength); + glPixelStorei(GL_PACK_SKIP_ROWS, packSkipRows); + glPixelStorei(GL_PACK_SKIP_PIXELS, packSkipPixels); + glPixelStorei(GL_PACK_ALIGNMENT, packAlignment); + +#if defined(COIN_BUILD_LEGACY_GL_RENDERER) + if (legacyContext) { + glPopAttrib(); + } +#endif } // ************************************************************************* diff --git a/testsuite/CMakeLists.txt b/testsuite/CMakeLists.txt index c229a15dde1..b642646e873 100644 --- a/testsuite/CMakeLists.txt +++ b/testsuite/CMakeLists.txt @@ -113,6 +113,18 @@ if(HAVE_EGL) ) add_test(NAME GLSLRuntimeTest COMMAND GLSLRuntimeTest) set_tests_properties(GLSLRuntimeTest PROPERTIES SKIP_RETURN_CODE 77) + + add_executable(OffscreenReadbackTest offscreen-readback-test.cpp) + target_link_libraries(OffscreenReadbackTest Coin ${COIN_TARGET_LINK_LIBRARIES}) + target_compile_definitions(OffscreenReadbackTest PRIVATE COIN_INTERNAL) + target_include_directories(OffscreenReadbackTest PRIVATE + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include + ${COIN_TARGET_INCLUDE_DIRECTORIES} + ) + add_test(NAME OffscreenReadbackTest COMMAND OffscreenReadbackTest) + set_tests_properties(OffscreenReadbackTest PROPERTIES SKIP_RETURN_CODE 77) endif() # Many warnings are generated from test macros on macOS with Xcode. diff --git a/testsuite/offscreen-readback-test.cpp b/testsuite/offscreen-readback-test.cpp new file mode 100644 index 00000000000..7c0eb595950 --- /dev/null +++ b/testsuite/offscreen-readback-test.cpp @@ -0,0 +1,83 @@ +#include "rendering/CoinOffscreenGLCanvas.h" + +#include + +#include +#include + +namespace { + +int skip(const char * reason) +{ + std::cout << "SKIP: " << reason << std::endl; + return 77; +} + +bool check(bool condition, const char * message) +{ + if (!condition) std::cerr << "FAIL: " << message << std::endl; + return condition; +} + +void set_environment(const char * name, const char * value) +{ +#ifdef _WIN32 + _putenv_s(name, value); +#else + setenv(name, value, 1); +#endif +} + +bool can_create_offscreen_context() +{ + void * context = cc_glglue_context_create_offscreen(2, 2); + if (context == NULL) return false; + + if (!cc_glglue_context_make_current(context)) { + cc_glglue_context_destruct(context); + return false; + } + + cc_glglue_context_reinstate_previous(context); + cc_glglue_context_destruct(context); + return true; +} + +} + +int main() +{ + set_environment("COIN_EGL", "1"); + set_environment("EGL_PLATFORM", "surfaceless"); + set_environment("COIN_EGL_CORE_PROFILE", "1"); + + if (!can_create_offscreen_context()) { + return skip("core EGL offscreen context could not be established"); + } + + CoinOffscreenGLCanvas canvas; + canvas.setWantedSize(SbVec2s(2, 2)); + if (canvas.activateGLContext() == 0) { + return skip("core EGL offscreen context is unavailable"); + } + + glClearColor(0.25f, 0.5f, 0.75f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + uint8_t pixels[2 * 2 * 4] = { 0 }; + canvas.readPixels(pixels, SbVec2s(2, 2), 2, 4); + canvas.deactivateGLContext(); + + for (unsigned int i = 0; i < sizeof(pixels); i += 4) { + if (!check(pixels[i + 0] >= 60 && pixels[i + 0] <= 70, + "red readback component is incorrect")) return 1; + if (!check(pixels[i + 1] >= 120 && pixels[i + 1] <= 135, + "green readback component is incorrect")) return 1; + if (!check(pixels[i + 2] >= 185 && pixels[i + 2] <= 200, + "blue readback component is incorrect")) return 1; + if (!check(pixels[i + 3] >= 245, + "alpha readback component is incorrect")) return 1; + } + + return 0; +}