From e5490924a7845cae24fce8bfbe8a21eec443c934 Mon Sep 17 00:00:00 2001 From: kartoffels123 Date: Tue, 6 Jan 2026 22:01:51 -0500 Subject: [PATCH 1/8] Add BC7 DDS texture support --- src/osg/Image.cpp | 39 ++++++++++++++++++++++++-- src/osg/Texture.cpp | 27 +++++++++++++++--- src/osg/dxtctool.cpp | 2 ++ src/osg/dxtctool.h | 23 ++++++++++++++- src/osgPlugins/dds/ReaderWriterDDS.cpp | 28 ++++++++++++++++++ 5 files changed, 111 insertions(+), 8 deletions(-) diff --git a/src/osg/Image.cpp b/src/osg/Image.cpp index bf55cbf74fe..287289e30db 100644 --- a/src/osg/Image.cpp +++ b/src/osg/Image.cpp @@ -707,6 +707,11 @@ unsigned int Image::computeNumComponents(GLenum pixelFormat) case (GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR) : return 4; case (GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR) : return 4; case (GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR) : return 4; + // BPTC (BC6H/BC7) + case (0x8E8C) : return 4; // GL_COMPRESSED_RGBA_BPTC_UNORM + case (0x8E8D) : return 4; // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM + case (0x8E8E) : return 3; // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT + case (0x8E8F) : return 3; // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT default: { OSG_WARN<<"error pixelFormat = "< 0) { + osg::Vec3i footprint = computeBlockFootprint(pixelFormat); + int blocksWide = (width + footprint.x() - 1) / footprint.x(); + unsigned int size = blockSize * blocksWide; + return roudUpToMultiple(size, packing); + } + + // Non-compressed formats unsigned int pixelSize = computePixelSizeInBits(pixelFormat,type); int widthInBits = width*pixelSize; int packingInBits = packing!=0 ? packing*8 : 8; @@ -1827,6 +1859,7 @@ void Image::flipVertical() const bool dxtc(dxtc_tool::isDXTC(_pixelFormat)); const bool rgtc(dxtc_tool::isRGTC(_pixelFormat)); + const bool bptc(dxtc_tool::isBPTC(_pixelFormat)); if (_mipmapData.empty()) { @@ -1834,7 +1867,7 @@ void Image::flipVertical() // so we can safely handle 3d textures for(int r=0;r<_r;++r) { - if (dxtc || rgtc) + if (dxtc || rgtc || bptc) { if (!dxtc_tool::VerticalFlip(_s,_t,_pixelFormat,data(0,0,r))) { @@ -1854,7 +1887,7 @@ void Image::flipVertical() } else if (_r==1) { - if (dxtc || rgtc) + if (dxtc || rgtc || bptc) { if (!dxtc_tool::VerticalFlip(_s,_t,_pixelFormat,_data)) { @@ -1881,7 +1914,7 @@ void Image::flipVertical() t >>= 1; if (s==0) s=1; if (t==0) t=1; - if (dxtc || rgtc) + if (dxtc || rgtc || bptc) { if (!dxtc_tool::VerticalFlip(s,t,_pixelFormat,_data+_mipmapData[i])) { diff --git a/src/osg/Texture.cpp b/src/osg/Texture.cpp index 189d941ef81..eaeac82b589 100644 --- a/src/osg/Texture.cpp +++ b/src/osg/Texture.cpp @@ -188,10 +188,10 @@ InternalPixelRelations compressedInternalFormats[] = { , { GL_COMPRESSED_SIGNED_RED_RGTC1_EXT , GL_RED , GL_COMPRESSED_SIGNED_RED_RGTC1_EXT } // , { GL_COMPRESSED_RG_RGTC2 , GL_RG , GL_COMPRESSED_RG_RGTC2 } // , { GL_COMPRESSED_SIGNED_RG_RGTC2 , GL_RG , GL_COMPRESSED_SIGNED_RG_RGTC2 } - // , { GL_COMPRESSED_RGBA_BPTC_UNORM , GL_RGBA , GL_COMPRESSED_RGBA_BPTC_UNORM } - // , { GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM , GL_RGBA , GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM } - // , { GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT , GL_RGB , GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT } - // , { GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT , GL_RGB , GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT } + , { 0x8E8C , GL_RGBA , 0x8E8C } // GL_COMPRESSED_RGBA_BPTC_UNORM + , { 0x8E8D , GL_RGBA , 0x8E8D } // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM + , { 0x8E8E , GL_RGB , 0x8E8E } // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT + , { 0x8E8F , GL_RGB , 0x8E8F } // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT , { GL_COMPRESSED_RGB_S3TC_DXT1_EXT , GL_RGB , GL_COMPRESSED_RGB_S3TC_DXT1_EXT } , { GL_COMPRESSED_RGBA_S3TC_DXT1_EXT , GL_RGBA , GL_COMPRESSED_RGBA_S3TC_DXT1_EXT } @@ -366,6 +366,12 @@ void Texture::TextureProfile::computeSize() case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: numBitsPerTexel = 8; break; case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: numBitsPerTexel = 8; break; + // BPTC (BC6H/BC7) + case 0x8E8C: numBitsPerTexel = 8; break; // GL_COMPRESSED_RGBA_BPTC_UNORM + case 0x8E8D: numBitsPerTexel = 8; break; // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM + case 0x8E8E: numBitsPerTexel = 8; break; // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT + case 0x8E8F: numBitsPerTexel = 8; break; // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT + case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG: numBitsPerTexel = 2; break; case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG: numBitsPerTexel = 2; break; case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG: numBitsPerTexel = 4; break; @@ -1888,6 +1894,11 @@ bool Texture::isCompressedInternalFormat(GLint internalFormat) case (GL_COMPRESSED_RED_RGTC1_EXT): case (GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT): case (GL_COMPRESSED_RED_GREEN_RGTC2_EXT): + // BPTC (BC6H/BC7) + case (0x8E8C): // GL_COMPRESSED_RGBA_BPTC_UNORM + case (0x8E8D): // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM + case (0x8E8E): // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT + case (0x8E8F): // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT case (GL_ETC1_RGB8_OES): case (GL_COMPRESSED_RGB8_ETC2): case (GL_COMPRESSED_SRGB8_ETC2): @@ -1959,6 +1970,9 @@ void Texture::getCompressedSize(GLenum internalFormat, GLint width, GLint height blockSize = 8; else if (internalFormat == GL_COMPRESSED_RED_GREEN_RGTC2_EXT || internalFormat == GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT) blockSize = 16; + // BPTC (BC6H/BC7) - 128 bits per block + else if (internalFormat == 0x8E8C || internalFormat == 0x8E8D || internalFormat == 0x8E8E || internalFormat == 0x8E8F) + blockSize = 16; else if (internalFormat == GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG || internalFormat == GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG) { blockSize = 8 * 4; // Pixel by pixel block size for 2bpp @@ -2387,6 +2401,11 @@ void Texture::applyTexImage2D_load(State& state, GLenum target, const Image* ima case(GL_COMPRESSED_SIGNED_RG11_EAC): case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: _internalFormat = GL_RG; break; + // BPTC (BC6H/BC7) + case 0x8E8C: // GL_COMPRESSED_RGBA_BPTC_UNORM + case 0x8E8D: _internalFormat = GL_RGBA; break; // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM + case 0x8E8E: // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT + case 0x8E8F: _internalFormat = GL_RGB; break; // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT } } diff --git a/src/osg/dxtctool.cpp b/src/osg/dxtctool.cpp index ea114604f05..dfbb6f06a77 100644 --- a/src/osg/dxtctool.cpp +++ b/src/osg/dxtctool.cpp @@ -74,6 +74,8 @@ bool dxtc_pixels::VFlip() const VFlip_RGTC1(); else if (RGTC2()) VFlip_RGTC2(); + else if (BPTC()) + return true; // BPTC cannot be flipped in-place; images should be pre-flipped else return false; // We should never get there diff --git a/src/osg/dxtctool.h b/src/osg/dxtctool.h index 943654b308b..0e0cc3b0b19 100644 --- a/src/osg/dxtctool.h +++ b/src/osg/dxtctool.h @@ -79,6 +79,8 @@ bool isDXTC(GLenum pixelFormat); bool isRGTC(GLenum pixelFormat); +bool isBPTC(GLenum pixelFormat); + bool VerticalFlip(size_t Width, size_t Height, GLenum Format, void * pPixels); bool isCompressedImageTranslucent(size_t Width, size_t Height, GLenum Format, void * pPixels); @@ -112,6 +114,7 @@ class dxtc_pixels inline bool DXT5() const; inline bool RGTC1() const; inline bool RGTC2() const; + inline bool BPTC() const; inline bool OpenGLSize() const; inline bool SupportedFormat() const; @@ -191,6 +194,20 @@ inline bool isRGTC(GLenum pixelFormat) } } +inline bool isBPTC(GLenum pixelFormat) +{ + switch(pixelFormat) + { + case(0x8E8C): // GL_COMPRESSED_RGBA_BPTC_UNORM + case(0x8E8D): // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM + case(0x8E8E): // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT + case(0x8E8F): // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT + return true; + default: + return false; + } +} + inline bool VerticalFlip(size_t Width, size_t Height, GLenum Format, void * pPixels) { return (dxtc_pixels(Width, Height, Format, pPixels)).VFlip(); } @@ -228,9 +245,13 @@ inline bool dxtc_pixels::RGTC2() const { return ((m_Format == GL_COMPRESSED_RED_GREEN_RGTC2_EXT) || (m_Format == GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT)); } +inline bool dxtc_pixels::BPTC() const { + return isBPTC(m_Format); +} + inline bool dxtc_pixels::SupportedFormat() const { - return (DXT1() || DXT3() || DXT5() || RGTC1() || RGTC2()); + return (DXT1() || DXT3() || DXT5() || RGTC1() || RGTC2() || BPTC()); } diff --git a/src/osgPlugins/dds/ReaderWriterDDS.cpp b/src/osgPlugins/dds/ReaderWriterDDS.cpp index 6007ff3e5b7..eac575e656d 100644 --- a/src/osgPlugins/dds/ReaderWriterDDS.cpp +++ b/src/osgPlugins/dds/ReaderWriterDDS.cpp @@ -814,6 +814,34 @@ osg::Image* ReadDDSFile(std::istream& _istream, bool flipDDSRead) pixelFormat = GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; break; + case OSG_DXGI_FORMAT_BC6H_UF16: + internalFormat = 0x8E8F; // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT + pixelFormat = 0x8E8F; + packing = 4; // 8 bits/pixel. 4 px = 4 bytes + isDXTC = true; + break; + + case OSG_DXGI_FORMAT_BC6H_SF16: + internalFormat = 0x8E8E; // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT + pixelFormat = 0x8E8E; + packing = 4; // 8 bits/pixel. 4 px = 4 bytes + isDXTC = true; + break; + + case OSG_DXGI_FORMAT_BC7_UNORM: + internalFormat = 0x8E8C; // GL_COMPRESSED_RGBA_BPTC_UNORM + pixelFormat = 0x8E8C; + packing = 4; // 8 bits/pixel. 4 px = 4 bytes + isDXTC = true; + break; + + case OSG_DXGI_FORMAT_BC7_UNORM_SRGB: + internalFormat = 0x8E8D; // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM + pixelFormat = 0x8E8D; + packing = 4; // 8 bits/pixel. 4 px = 4 bytes + isDXTC = true; + break; + default: OSG_WARN << "ReadDDSFile warning: unhandled DX10 pixel format 0x" << std::hex << std::setw(8) << std::setfill('0') From 8cac6377cb408c867205ef1abca4e0a1da70ebd5 Mon Sep 17 00:00:00 2001 From: kartoffels123 Date: Sun, 11 Jan 2026 23:01:02 -0500 Subject: [PATCH 2/8] Handle BC6H/BC7 textures by marking TOP_LEFT origin instead of flipping --- src/osgPlugins/dds/ReaderWriterDDS.cpp | 31 +++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/osgPlugins/dds/ReaderWriterDDS.cpp b/src/osgPlugins/dds/ReaderWriterDDS.cpp index eac575e656d..ea72d3f584a 100644 --- a/src/osgPlugins/dds/ReaderWriterDDS.cpp +++ b/src/osgPlugins/dds/ReaderWriterDDS.cpp @@ -1129,15 +1129,30 @@ osg::Image* ReadDDSFile(std::istream& _istream, bool flipDDSRead) if (mipmap_offsets.size()>0) osgImage->setMipmapLevels(mipmap_offsets); if (flipDDSRead) { - osgImage->setOrigin(osg::Image::BOTTOM_LEFT); - if (!isDXTC || ((s>4 && s%4==0 && t>4 && t%4==0) || s<=4)) // Flip may crash (access violation) or fail for non %4 dimensions (except for s<4). Tested with revision trunk 2013-02-22. - { - OSG_INFO<<"Flipping dds on load"<flipVertical(); + // BC6H and BC7 formats cannot be flipped in compressed form due to their complex + // block encoding (partitions, anchor indices). Mark them as TOP_LEFT origin so + // the engine can apply UV flip at render time instead. + bool isBC6H_BC7 = (internalFormat == 0x8E8C || // GL_COMPRESSED_RGBA_BPTC_UNORM (BC7) + internalFormat == 0x8E8D || // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM (BC7 sRGB) + internalFormat == 0x8E8E || // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT (BC6H SF16) + internalFormat == 0x8E8F); // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT (BC6H UF16) + + if (isBC6H_BC7) { + // Don't flip BC6H/BC7 - mark as TOP_LEFT for runtime UV compensation + osgImage->setOrigin(osg::Image::TOP_LEFT); + OSG_INFO << "ReadDDSFile info: BC6H/BC7 texture kept in TOP_LEFT orientation (no flip)" << std::endl; } - else - { - OSG_WARN << "ReadDDSFile warning: Vertical flip was skipped. Image dimensions have to be multiple of 4." << std::endl; + else { + osgImage->setOrigin(osg::Image::BOTTOM_LEFT); + if (!isDXTC || ((s>4 && s%4==0 && t>4 && t%4==0) || s<=4)) // Flip may crash (access violation) or fail for non %4 dimensions (except for s<4). Tested with revision trunk 2013-02-22. + { + OSG_INFO<<"Flipping dds on load"<flipVertical(); + } + else + { + OSG_WARN << "ReadDDSFile warning: Vertical flip was skipped. Image dimensions have to be multiple of 4." << std::endl; + } } } From 323e3b81519238466a067b3ee87cb115817d7961 Mon Sep 17 00:00:00 2001 From: kartoffels123 Date: Tue, 13 Jan 2026 23:27:15 -0500 Subject: [PATCH 3/8] Clean up. Removes magic numbers, adds some comments. --- src/osg/Image.cpp | 35 +++++++++++++------------- src/osg/Texture.cpp | 34 ++++++++++++------------- src/osg/dxtctool.h | 8 +++--- src/osgPlugins/dds/ReaderWriterDDS.cpp | 35 ++++++++++++-------------- 4 files changed, 55 insertions(+), 57 deletions(-) diff --git a/src/osg/Image.cpp b/src/osg/Image.cpp index 287289e30db..dda06b9e1ae 100644 --- a/src/osg/Image.cpp +++ b/src/osg/Image.cpp @@ -708,10 +708,10 @@ unsigned int Image::computeNumComponents(GLenum pixelFormat) case (GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR) : return 4; case (GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR) : return 4; // BPTC (BC6H/BC7) - case (0x8E8C) : return 4; // GL_COMPRESSED_RGBA_BPTC_UNORM - case (0x8E8D) : return 4; // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM - case (0x8E8E) : return 3; // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT - case (0x8E8F) : return 3; // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT + case (GL_COMPRESSED_RGBA_BPTC_UNORM) : return 4; + case (GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM) : return 4; + case (GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT) : return 3; + case (GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) : return 3; default: { OSG_WARN<<"error pixelFormat = "< 0) { osg::Vec3i footprint = computeBlockFootprint(pixelFormat); @@ -1859,7 +1860,7 @@ void Image::flipVertical() const bool dxtc(dxtc_tool::isDXTC(_pixelFormat)); const bool rgtc(dxtc_tool::isRGTC(_pixelFormat)); - const bool bptc(dxtc_tool::isBPTC(_pixelFormat)); + const bool bptc(dxtc_tool::isBPTC(_pixelFormat)); // safety: If someone calls flipVertical on a BPTC compressed image, this defensively prevents it. if (_mipmapData.empty()) { diff --git a/src/osg/Texture.cpp b/src/osg/Texture.cpp index eaeac82b589..d6b0d4b4a09 100644 --- a/src/osg/Texture.cpp +++ b/src/osg/Texture.cpp @@ -188,10 +188,10 @@ InternalPixelRelations compressedInternalFormats[] = { , { GL_COMPRESSED_SIGNED_RED_RGTC1_EXT , GL_RED , GL_COMPRESSED_SIGNED_RED_RGTC1_EXT } // , { GL_COMPRESSED_RG_RGTC2 , GL_RG , GL_COMPRESSED_RG_RGTC2 } // , { GL_COMPRESSED_SIGNED_RG_RGTC2 , GL_RG , GL_COMPRESSED_SIGNED_RG_RGTC2 } - , { 0x8E8C , GL_RGBA , 0x8E8C } // GL_COMPRESSED_RGBA_BPTC_UNORM - , { 0x8E8D , GL_RGBA , 0x8E8D } // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM - , { 0x8E8E , GL_RGB , 0x8E8E } // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT - , { 0x8E8F , GL_RGB , 0x8E8F } // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT + , { GL_COMPRESSED_RGBA_BPTC_UNORM , GL_RGBA , GL_COMPRESSED_RGBA_BPTC_UNORM } + , { GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM , GL_RGBA , GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM } + , { GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT , GL_RGB , GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT } + , { GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT , GL_RGB , GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT } , { GL_COMPRESSED_RGB_S3TC_DXT1_EXT , GL_RGB , GL_COMPRESSED_RGB_S3TC_DXT1_EXT } , { GL_COMPRESSED_RGBA_S3TC_DXT1_EXT , GL_RGBA , GL_COMPRESSED_RGBA_S3TC_DXT1_EXT } @@ -367,10 +367,10 @@ void Texture::TextureProfile::computeSize() case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: numBitsPerTexel = 8; break; // BPTC (BC6H/BC7) - case 0x8E8C: numBitsPerTexel = 8; break; // GL_COMPRESSED_RGBA_BPTC_UNORM - case 0x8E8D: numBitsPerTexel = 8; break; // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM - case 0x8E8E: numBitsPerTexel = 8; break; // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT - case 0x8E8F: numBitsPerTexel = 8; break; // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT + case GL_COMPRESSED_RGBA_BPTC_UNORM: numBitsPerTexel = 8; break; + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: numBitsPerTexel = 8; break; + case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT: numBitsPerTexel = 8; break; + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: numBitsPerTexel = 8; break; case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG: numBitsPerTexel = 2; break; case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG: numBitsPerTexel = 2; break; @@ -1895,10 +1895,10 @@ bool Texture::isCompressedInternalFormat(GLint internalFormat) case (GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT): case (GL_COMPRESSED_RED_GREEN_RGTC2_EXT): // BPTC (BC6H/BC7) - case (0x8E8C): // GL_COMPRESSED_RGBA_BPTC_UNORM - case (0x8E8D): // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM - case (0x8E8E): // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT - case (0x8E8F): // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT + case (GL_COMPRESSED_RGBA_BPTC_UNORM): + case (GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM): + case (GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT): + case (GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT): case (GL_ETC1_RGB8_OES): case (GL_COMPRESSED_RGB8_ETC2): case (GL_COMPRESSED_SRGB8_ETC2): @@ -1971,7 +1971,7 @@ void Texture::getCompressedSize(GLenum internalFormat, GLint width, GLint height else if (internalFormat == GL_COMPRESSED_RED_GREEN_RGTC2_EXT || internalFormat == GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT) blockSize = 16; // BPTC (BC6H/BC7) - 128 bits per block - else if (internalFormat == 0x8E8C || internalFormat == 0x8E8D || internalFormat == 0x8E8E || internalFormat == 0x8E8F) + else if (internalFormat == GL_COMPRESSED_RGBA_BPTC_UNORM || internalFormat == GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM || internalFormat == GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT || internalFormat == GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) blockSize = 16; else if (internalFormat == GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG || internalFormat == GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG) { @@ -2402,10 +2402,10 @@ void Texture::applyTexImage2D_load(State& state, GLenum target, const Image* ima case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: _internalFormat = GL_RG; break; // BPTC (BC6H/BC7) - case 0x8E8C: // GL_COMPRESSED_RGBA_BPTC_UNORM - case 0x8E8D: _internalFormat = GL_RGBA; break; // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM - case 0x8E8E: // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT - case 0x8E8F: _internalFormat = GL_RGB; break; // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT + case GL_COMPRESSED_RGBA_BPTC_UNORM: + case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: _internalFormat = GL_RGBA; break; + case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: _internalFormat = GL_RGB; break; } } diff --git a/src/osg/dxtctool.h b/src/osg/dxtctool.h index 0e0cc3b0b19..85dfe1b83cc 100644 --- a/src/osg/dxtctool.h +++ b/src/osg/dxtctool.h @@ -198,10 +198,10 @@ inline bool isBPTC(GLenum pixelFormat) { switch(pixelFormat) { - case(0x8E8C): // GL_COMPRESSED_RGBA_BPTC_UNORM - case(0x8E8D): // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM - case(0x8E8E): // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT - case(0x8E8F): // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT + case(GL_COMPRESSED_RGBA_BPTC_UNORM): + case(GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM): + case(GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT): + case(GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT): return true; default: return false; diff --git a/src/osgPlugins/dds/ReaderWriterDDS.cpp b/src/osgPlugins/dds/ReaderWriterDDS.cpp index ea72d3f584a..84c093bc0de 100644 --- a/src/osgPlugins/dds/ReaderWriterDDS.cpp +++ b/src/osgPlugins/dds/ReaderWriterDDS.cpp @@ -815,29 +815,29 @@ osg::Image* ReadDDSFile(std::istream& _istream, bool flipDDSRead) break; case OSG_DXGI_FORMAT_BC6H_UF16: - internalFormat = 0x8E8F; // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT - pixelFormat = 0x8E8F; + internalFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + pixelFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; packing = 4; // 8 bits/pixel. 4 px = 4 bytes isDXTC = true; break; case OSG_DXGI_FORMAT_BC6H_SF16: - internalFormat = 0x8E8E; // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT - pixelFormat = 0x8E8E; + internalFormat = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT; + pixelFormat = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT; packing = 4; // 8 bits/pixel. 4 px = 4 bytes isDXTC = true; break; case OSG_DXGI_FORMAT_BC7_UNORM: - internalFormat = 0x8E8C; // GL_COMPRESSED_RGBA_BPTC_UNORM - pixelFormat = 0x8E8C; + internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM; + pixelFormat = GL_COMPRESSED_RGBA_BPTC_UNORM; packing = 4; // 8 bits/pixel. 4 px = 4 bytes isDXTC = true; break; case OSG_DXGI_FORMAT_BC7_UNORM_SRGB: - internalFormat = 0x8E8D; // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM - pixelFormat = 0x8E8D; + internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; + pixelFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; packing = 4; // 8 bits/pixel. 4 px = 4 bytes isDXTC = true; break; @@ -1129,18 +1129,15 @@ osg::Image* ReadDDSFile(std::istream& _istream, bool flipDDSRead) if (mipmap_offsets.size()>0) osgImage->setMipmapLevels(mipmap_offsets); if (flipDDSRead) { - // BC6H and BC7 formats cannot be flipped in compressed form due to their complex - // block encoding (partitions, anchor indices). Mark them as TOP_LEFT origin so - // the engine can apply UV flip at render time instead. - bool isBC6H_BC7 = (internalFormat == 0x8E8C || // GL_COMPRESSED_RGBA_BPTC_UNORM (BC7) - internalFormat == 0x8E8D || // GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM (BC7 sRGB) - internalFormat == 0x8E8E || // GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT (BC6H SF16) - internalFormat == 0x8E8F); // GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT (BC6H UF16) - - if (isBC6H_BC7) { - // Don't flip BC6H/BC7 - mark as TOP_LEFT for runtime UV compensation + // BC6H/BC7 (BPTC) cannot be flipped in compressed form - mark as TOP_LEFT for runtime UV flip + bool isBPTC = (internalFormat == GL_COMPRESSED_RGBA_BPTC_UNORM || + internalFormat == GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM || + internalFormat == GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT || + internalFormat == GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT); + + if (isBPTC) { osgImage->setOrigin(osg::Image::TOP_LEFT); - OSG_INFO << "ReadDDSFile info: BC6H/BC7 texture kept in TOP_LEFT orientation (no flip)" << std::endl; + // OSG_INFO << "ReadDDSFile info: BC6H/BC7 texture kept in TOP_LEFT orientation (no flip)" << std::endl; } else { osgImage->setOrigin(osg::Image::BOTTOM_LEFT); From 5869cd43cb174ed5031fd4f8fd4be86135038073 Mon Sep 17 00:00:00 2001 From: kartoffels123 Date: Mon, 19 Jan 2026 23:25:44 -0500 Subject: [PATCH 4/8] needed for build to have BPTC --- include/osg/Texture | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/osg/Texture b/include/osg/Texture index f6614633acd..f2fe92cfcd5 100644 --- a/include/osg/Texture +++ b/include/osg/Texture @@ -75,6 +75,13 @@ #define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE #endif +#ifndef GL_ARB_texture_compression_bptc + #define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C + #define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM 0x8E8D + #define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E + #define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F +#endif + #ifndef GL_IMG_texture_compression_pvrtc #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 #define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 From b194384293bb1bd0f8654897a3b7134d9420efb8 Mon Sep 17 00:00:00 2001 From: kartoffels123 Date: Tue, 20 Jan 2026 14:29:14 -0500 Subject: [PATCH 5/8] Addresses Review: dxtctool reverted to upstream most logic moved to osg::Image (isBPTC, warnings, early out) ReaderWriterDDS is cleaned up --- src/osg/Image.cpp | 30 ++++++++++++++++++++++---- src/osg/dxtctool.cpp | 2 -- src/osg/dxtctool.h | 23 +------------------- src/osgPlugins/dds/ReaderWriterDDS.cpp | 14 ++---------- 4 files changed, 29 insertions(+), 40 deletions(-) diff --git a/src/osg/Image.cpp b/src/osg/Image.cpp index dda06b9e1ae..fce6939ab24 100644 --- a/src/osg/Image.cpp +++ b/src/osg/Image.cpp @@ -335,6 +335,20 @@ bool Image::isPackedType(GLenum type) } } +bool Image::isBPTC(GLenum pixelFormat) +{ + switch(pixelFormat) + { + case(GL_COMPRESSED_RGBA_BPTC_UNORM): + case(GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM): + case(GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT): + case(GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT): + return true; + default: + return false; + } +} + GLenum Image::computePixelFormat(GLenum format) { @@ -1855,12 +1869,20 @@ void Image::flipVertical() return; } + // BPTC (BC6H/BC7) textures cannot be flipped in compressed form due to complex per-block encoding. + // Callers should check isBPTC() and set TOP_LEFT origin instead of calling flipVertical(). + if (Image::isBPTC(_pixelFormat)) + { + OSG_WARN << "Image::flipVertical(): BPTC (BC6H/BC7) textures cannot be flipped in compressed form. " + << "Use TOP_LEFT origin with runtime UV flip instead." << std::endl; + return; + } + unsigned int rowSize = getRowSizeInBytes(); unsigned int rowStep = getRowStepInBytes(); const bool dxtc(dxtc_tool::isDXTC(_pixelFormat)); const bool rgtc(dxtc_tool::isRGTC(_pixelFormat)); - const bool bptc(dxtc_tool::isBPTC(_pixelFormat)); // safety: If someone calls flipVertical on a BPTC compressed image, this defensively prevents it. if (_mipmapData.empty()) { @@ -1868,7 +1890,7 @@ void Image::flipVertical() // so we can safely handle 3d textures for(int r=0;r<_r;++r) { - if (dxtc || rgtc || bptc) + if (dxtc || rgtc) { if (!dxtc_tool::VerticalFlip(_s,_t,_pixelFormat,data(0,0,r))) { @@ -1888,7 +1910,7 @@ void Image::flipVertical() } else if (_r==1) { - if (dxtc || rgtc || bptc) + if (dxtc || rgtc) { if (!dxtc_tool::VerticalFlip(_s,_t,_pixelFormat,_data)) { @@ -1915,7 +1937,7 @@ void Image::flipVertical() t >>= 1; if (s==0) s=1; if (t==0) t=1; - if (dxtc || rgtc || bptc) + if (dxtc || rgtc) { if (!dxtc_tool::VerticalFlip(s,t,_pixelFormat,_data+_mipmapData[i])) { diff --git a/src/osg/dxtctool.cpp b/src/osg/dxtctool.cpp index dfbb6f06a77..ea114604f05 100644 --- a/src/osg/dxtctool.cpp +++ b/src/osg/dxtctool.cpp @@ -74,8 +74,6 @@ bool dxtc_pixels::VFlip() const VFlip_RGTC1(); else if (RGTC2()) VFlip_RGTC2(); - else if (BPTC()) - return true; // BPTC cannot be flipped in-place; images should be pre-flipped else return false; // We should never get there diff --git a/src/osg/dxtctool.h b/src/osg/dxtctool.h index 85dfe1b83cc..943654b308b 100644 --- a/src/osg/dxtctool.h +++ b/src/osg/dxtctool.h @@ -79,8 +79,6 @@ bool isDXTC(GLenum pixelFormat); bool isRGTC(GLenum pixelFormat); -bool isBPTC(GLenum pixelFormat); - bool VerticalFlip(size_t Width, size_t Height, GLenum Format, void * pPixels); bool isCompressedImageTranslucent(size_t Width, size_t Height, GLenum Format, void * pPixels); @@ -114,7 +112,6 @@ class dxtc_pixels inline bool DXT5() const; inline bool RGTC1() const; inline bool RGTC2() const; - inline bool BPTC() const; inline bool OpenGLSize() const; inline bool SupportedFormat() const; @@ -194,20 +191,6 @@ inline bool isRGTC(GLenum pixelFormat) } } -inline bool isBPTC(GLenum pixelFormat) -{ - switch(pixelFormat) - { - case(GL_COMPRESSED_RGBA_BPTC_UNORM): - case(GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM): - case(GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT): - case(GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT): - return true; - default: - return false; - } -} - inline bool VerticalFlip(size_t Width, size_t Height, GLenum Format, void * pPixels) { return (dxtc_pixels(Width, Height, Format, pPixels)).VFlip(); } @@ -245,13 +228,9 @@ inline bool dxtc_pixels::RGTC2() const { return ((m_Format == GL_COMPRESSED_RED_GREEN_RGTC2_EXT) || (m_Format == GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT)); } -inline bool dxtc_pixels::BPTC() const { - return isBPTC(m_Format); -} - inline bool dxtc_pixels::SupportedFormat() const { - return (DXT1() || DXT3() || DXT5() || RGTC1() || RGTC2() || BPTC()); + return (DXT1() || DXT3() || DXT5() || RGTC1() || RGTC2()); } diff --git a/src/osgPlugins/dds/ReaderWriterDDS.cpp b/src/osgPlugins/dds/ReaderWriterDDS.cpp index 84c093bc0de..8ad9ecb2bc3 100644 --- a/src/osgPlugins/dds/ReaderWriterDDS.cpp +++ b/src/osgPlugins/dds/ReaderWriterDDS.cpp @@ -818,28 +818,24 @@ osg::Image* ReadDDSFile(std::istream& _istream, bool flipDDSRead) internalFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; pixelFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; packing = 4; // 8 bits/pixel. 4 px = 4 bytes - isDXTC = true; break; case OSG_DXGI_FORMAT_BC6H_SF16: internalFormat = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT; pixelFormat = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT; packing = 4; // 8 bits/pixel. 4 px = 4 bytes - isDXTC = true; break; case OSG_DXGI_FORMAT_BC7_UNORM: internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM; pixelFormat = GL_COMPRESSED_RGBA_BPTC_UNORM; packing = 4; // 8 bits/pixel. 4 px = 4 bytes - isDXTC = true; break; case OSG_DXGI_FORMAT_BC7_UNORM_SRGB: internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; pixelFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; packing = 4; // 8 bits/pixel. 4 px = 4 bytes - isDXTC = true; break; default: @@ -1129,15 +1125,9 @@ osg::Image* ReadDDSFile(std::istream& _istream, bool flipDDSRead) if (mipmap_offsets.size()>0) osgImage->setMipmapLevels(mipmap_offsets); if (flipDDSRead) { - // BC6H/BC7 (BPTC) cannot be flipped in compressed form - mark as TOP_LEFT for runtime UV flip - bool isBPTC = (internalFormat == GL_COMPRESSED_RGBA_BPTC_UNORM || - internalFormat == GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM || - internalFormat == GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT || - internalFormat == GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT); - - if (isBPTC) { + // BPTC (BC6H/BC7) cannot be flipped in compressed form - set TOP_LEFT for runtime UV flip + if (osg::Image::isBPTC(internalFormat)) { osgImage->setOrigin(osg::Image::TOP_LEFT); - // OSG_INFO << "ReadDDSFile info: BC6H/BC7 texture kept in TOP_LEFT orientation (no flip)" << std::endl; } else { osgImage->setOrigin(osg::Image::BOTTOM_LEFT); From 8b723fa17d14d37f0d12694963d4bca5e7467868 Mon Sep 17 00:00:00 2001 From: kartoffels123 Date: Tue, 20 Jan 2026 17:07:51 -0500 Subject: [PATCH 6/8] Fix DDS file origin handling in ReaderWriterDDS --- src/osgPlugins/dds/ReaderWriterDDS.cpp | 28 ++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/osgPlugins/dds/ReaderWriterDDS.cpp b/src/osgPlugins/dds/ReaderWriterDDS.cpp index 8ad9ecb2bc3..e4d000a18a8 100644 --- a/src/osgPlugins/dds/ReaderWriterDDS.cpp +++ b/src/osgPlugins/dds/ReaderWriterDDS.cpp @@ -1124,22 +1124,20 @@ osg::Image* ReadDDSFile(std::istream& _istream, bool flipDDSRead) if (mipmap_offsets.size()>0) osgImage->setMipmapLevels(mipmap_offsets); - if (flipDDSRead) { - // BPTC (BC6H/BC7) cannot be flipped in compressed form - set TOP_LEFT for runtime UV flip - if (osg::Image::isBPTC(internalFormat)) { - osgImage->setOrigin(osg::Image::TOP_LEFT); + osgImage->setOrigin(osg::Image::TOP_LEFT); + + // BPTC (BC6H/BC7) textures cannot be flipped in compressed form + if (flipDDSRead && !osg::Image::isBPTC(internalFormat)) + { + osgImage->setOrigin(osg::Image::BOTTOM_LEFT); + if (!isDXTC || ((s>4 && s%4==0 && t>4 && t%4==0) || s<=4)) // Flip may crash (access violation) or fail for non %4 dimensions (except for s<4). Tested with revision trunk 2013-02-22. + { + OSG_INFO<<"Flipping dds on load"<flipVertical(); } - else { - osgImage->setOrigin(osg::Image::BOTTOM_LEFT); - if (!isDXTC || ((s>4 && s%4==0 && t>4 && t%4==0) || s<=4)) // Flip may crash (access violation) or fail for non %4 dimensions (except for s<4). Tested with revision trunk 2013-02-22. - { - OSG_INFO<<"Flipping dds on load"<flipVertical(); - } - else - { - OSG_WARN << "ReadDDSFile warning: Vertical flip was skipped. Image dimensions have to be multiple of 4." << std::endl; - } + else + { + OSG_WARN << "ReadDDSFile warning: Vertical flip was skipped. Image dimensions have to be multiple of 4." << std::endl; } } From 21449e2aa0df73512e6161071a336de31057ac48 Mon Sep 17 00:00:00 2001 From: kartoffels123 Date: Tue, 20 Jan 2026 19:39:50 -0500 Subject: [PATCH 7/8] small warning message update. --- src/osg/Image.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/osg/Image.cpp b/src/osg/Image.cpp index fce6939ab24..7e2d0af25ce 100644 --- a/src/osg/Image.cpp +++ b/src/osg/Image.cpp @@ -1873,8 +1873,7 @@ void Image::flipVertical() // Callers should check isBPTC() and set TOP_LEFT origin instead of calling flipVertical(). if (Image::isBPTC(_pixelFormat)) { - OSG_WARN << "Image::flipVertical(): BPTC (BC6H/BC7) textures cannot be flipped in compressed form. " - << "Use TOP_LEFT origin with runtime UV flip instead." << std::endl; + OSG_WARN << "Image::flipVertical(): BPTC (BC6H/BC7) textures cannot be flipped in compressed form." << std::endl; return; } From 8b255c14170f11da48deb8b87ae02f0e60c08c67 Mon Sep 17 00:00:00 2001 From: kartoffels123 Date: Thu, 22 Jan 2026 21:56:29 -0500 Subject: [PATCH 8/8] added definition --- include/osg/Image | 1 + 1 file changed, 1 insertion(+) diff --git a/include/osg/Image b/include/osg/Image index 28c28429d7d..4b135c1e4bf 100644 --- a/include/osg/Image +++ b/include/osg/Image @@ -439,6 +439,7 @@ class OSG_EXPORT Image : public BufferData void ensureValidSizeForTexturing(GLint maxTextureSize); static bool isPackedType(GLenum type); + static bool isBPTC(GLenum pixelFormat); static GLenum computePixelFormat(GLenum pixelFormat); static GLenum computeFormatDataType(GLenum pixelFormat);