diff --git a/lib/src/gpu/uhdr_gl_utils.cpp b/lib/src/gpu/uhdr_gl_utils.cpp index af86447f..50a248af 100644 --- a/lib/src/gpu/uhdr_gl_utils.cpp +++ b/lib/src/gpu/uhdr_gl_utils.cpp @@ -67,6 +67,11 @@ void uhdr_opengl_ctxt::init_opengl_ctxt() { "eglMakeCurrent() failed") #undef RET_IF_TRUE + // Image buffers handed to and read from this context are tightly packed (rows are w * bpp + // bytes), so texture uploads and read backs must not assume the default 4-byte row alignment. + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + setup_quad(); } @@ -184,9 +189,7 @@ GLuint uhdr_opengl_ctxt::create_texture(uhdr_img_fmt_t fmt, int w, int h, void* glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h * 3 / 2, 0, GL_RED, GL_UNSIGNED_BYTE, data); break; case UHDR_IMG_FMT_8bppYCbCr400: - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); break; case UHDR_IMG_FMT_32bppRGBA8888: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); @@ -343,9 +346,7 @@ void uhdr_opengl_ctxt::read_texture(GLuint* texture, uhdr_img_fmt_t fmt, int w, } else if (fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) { glReadPixels(0, 0, w, h, GL_RGBA, GL_HALF_FLOAT, data); } else if (fmt == UHDR_IMG_FMT_8bppYCbCr400) { - glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(0, 0, w, h, GL_RED, GL_UNSIGNED_BYTE, data); - glPixelStorei(GL_PACK_ALIGNMENT, 4); } glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &frm_buffer); diff --git a/tests/editorhelper_test.cpp b/tests/editorhelper_test.cpp index 975b543a..b20748c5 100644 --- a/tests/editorhelper_test.cpp +++ b/tests/editorhelper_test.cpp @@ -498,4 +498,48 @@ INSTANTIATE_TEST_SUITE_P( UHDR_IMG_FMT_64bppRGBAHalfFloat, UHDR_IMG_FMT_32bppRGBA8888))); +#ifdef UHDR_ENABLE_GLES +// Planes are tightly packed, so textures must hold exactly the bytes of the buffer for widths +// whose rows are not a multiple of 4 bytes. +TEST(OpenGLContextTest, TextureUploadOfTightlyPackedRows) { + uhdr_opengl_ctxt opengl_ctxt; + opengl_ctxt.init_opengl_ctxt(); + if (opengl_ctxt.mErrorStatus.error_code != UHDR_CODEC_OK) { + GTEST_SKIP() << "opengl context unavailable"; + } + struct { + uhdr_img_fmt_t fmt; + int rows_per_line; // texture rows per image row, in halves + int bpp; + } formats[] = { + {UHDR_IMG_FMT_8bppYCbCr400, 2, 1}, {UHDR_IMG_FMT_12bppYCbCr420, 3, 1}, + {UHDR_IMG_FMT_16bppYCbCr422, 4, 1}, {UHDR_IMG_FMT_24bppYCbCr444, 6, 1}, + {UHDR_IMG_FMT_24bppRGB888, 2, 3}, + }; + const int ht = 8; + for (const auto& f : formats) { + for (int wd = 1; wd <= 10; wd++) { + std::string msg = "format: " + std::to_string(f.fmt) + " width: " + std::to_string(wd); + const int tex_ht = ht * f.rows_per_line / 2; + std::vector src(static_cast(wd) * tex_ht * f.bpp); + for (size_t i = 0; i < src.size(); i++) src[i] = static_cast(i * 7 + 1); + GLuint texture = opengl_ctxt.create_texture(f.fmt, wd, ht, src.data()); + ASSERT_NE(texture, 0u) << msg; + std::vector dst; + if (f.bpp == 1) { + dst.resize(src.size()); + opengl_ctxt.read_texture(&texture, UHDR_IMG_FMT_8bppYCbCr400, wd, tex_ht, dst.data()); + } else { + std::vector rgba(static_cast(wd) * tex_ht * 4); + opengl_ctxt.read_texture(&texture, UHDR_IMG_FMT_32bppRGBA8888, wd, tex_ht, rgba.data()); + for (size_t i = 0; i < rgba.size(); i += 4) dst.insert(dst.end(), &rgba[i], &rgba[i + 3]); + } + glDeleteTextures(1, &texture); + ASSERT_EQ(opengl_ctxt.mErrorStatus.error_code, UHDR_CODEC_OK) << msg; + ASSERT_EQ(src, dst) << msg; + } + } +} +#endif + } // namespace ultrahdr