Skip to content

Commit 987984b

Browse files
committed
Look at the ktxOrientation metadata field for ktx textures in order to ensure compatible textures.
1 parent e47ab9d commit 987984b

2 files changed

Lines changed: 106 additions & 9 deletions

File tree

src/osg/Image.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,7 +1827,6 @@ void Image::flipVertical()
18271827

18281828
const bool dxtc(dxtc_tool::isDXTC(_pixelFormat));
18291829
const bool rgtc(dxtc_tool::isRGTC(_pixelFormat));
1830-
const bool astc(isCompressed() && (_pixelFormat >= GL_COMPRESSED_RGBA_ASTC_4x4_KHR && _pixelFormat <= GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR));
18311830

18321831
if (_mipmapData.empty())
18331832
{
@@ -1842,8 +1841,7 @@ void Image::flipVertical()
18421841
OSG_NOTICE << "Notice Image::flipVertical(): Vertical flip do not succeed" << std::endl;
18431842
}
18441843
}
1845-
// ASTC textures are stored in KTX format with native OpenGL orientation - skip flipping
1846-
else if (!astc)
1844+
else
18471845
{
18481846
if (isCompressed()) OSG_NOTICE << "Notice Image::flipVertical(): file=" << _fileName << " image is compressed but normal v-flip is used" << std::endl;
18491847
// its not a compressed image, so implement flip oursleves.
@@ -1890,8 +1888,7 @@ void Image::flipVertical()
18901888
OSG_NOTICE << "Notice Image::flipVertical(): Vertical flip did not succeed" << std::endl;
18911889
}
18921890
}
1893-
// ASTC textures are stored in KTX format with native OpenGL orientation - skip flipping
1894-
else if (!astc)
1891+
else
18951892
{
18961893
// it's not a compressed image, so implement flip ourselves.
18971894
unsigned int mipRowSize = computeRowWidthInBytes(s, _pixelFormat, _dataType, _packing);

src/osgPlugins/ktx/ReaderWriterKTX.cpp

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include <osgDB/FileNameUtils>
1717
#include <osgDB/FileUtils>
1818
#include <istream>
19+
#include <vector>
20+
#include <cstring>
1921

2022
// Macro similar to what's in FLT/TRP plugins (except it uses wide char under Windows if OSG_USE_UTF8_FILENAME)
2123
#if defined(_WIN32)
@@ -115,8 +117,96 @@ osgDB::ReaderWriter::ReadResult ReaderWriterKTX::readKTXStream(std::istream& fin
115117
if (header.numberOfMipmapLevels == 0)
116118
header.numberOfMipmapLevels = 1;
117119

118-
//read keyvalue data. Will be ignoring for now
119-
fin.ignore(header.bytesOfKeyValueData);
120+
// Parse key-value metadata to check for KTXorientation
121+
bool hasValidOrientation = true; // Default to true for non-ASTC or when no metadata
122+
std::string ktxOrientation;
123+
124+
if (header.bytesOfKeyValueData > 0)
125+
{
126+
std::vector<char> kvData(header.bytesOfKeyValueData);
127+
fin.read(kvData.data(), header.bytesOfKeyValueData);
128+
if (!fin.good())
129+
{
130+
OSG_WARN << "Failed to read KTX key-value data." << std::endl;
131+
return ReadResult(ReadResult::ERROR_IN_READING_FILE);
132+
}
133+
134+
// Parse key-value pairs
135+
size_t offset = 0;
136+
while (offset < header.bytesOfKeyValueData)
137+
{
138+
if (offset + sizeof(uint32_t) > header.bytesOfKeyValueData)
139+
break;
140+
141+
uint32_t keyAndValueByteSize;
142+
memcpy(&keyAndValueByteSize, kvData.data() + offset, sizeof(uint32_t));
143+
if (header.endianness != MyEndian)
144+
osg::swapBytes4(reinterpret_cast<char*>(&keyAndValueByteSize));
145+
offset += sizeof(uint32_t);
146+
147+
if (offset + keyAndValueByteSize > header.bytesOfKeyValueData)
148+
break;
149+
150+
// Find the null terminator to separate key from value
151+
std::string key;
152+
size_t keyStart = offset;
153+
size_t keyEnd = keyStart;
154+
while (keyEnd < offset + keyAndValueByteSize && kvData[keyEnd] != '\0')
155+
keyEnd++;
156+
157+
if (keyEnd < offset + keyAndValueByteSize)
158+
{
159+
key = std::string(kvData.data() + keyStart, keyEnd - keyStart);
160+
161+
// Check for KTXorientation (note: some files incorrectly use KTXOrientation)
162+
if (key == "KTXorientation" || key == "KTXOrientation")
163+
{
164+
size_t valueStart = keyEnd + 1;
165+
size_t valueSize = keyAndValueByteSize - (valueStart - keyStart);
166+
ktxOrientation = std::string(kvData.data() + valueStart, valueSize);
167+
168+
// Remove any trailing null bytes
169+
size_t nullPos = ktxOrientation.find('\0');
170+
if (nullPos != std::string::npos)
171+
ktxOrientation = ktxOrientation.substr(0, nullPos);
172+
173+
OSG_INFO << "Found KTXorientation: " << ktxOrientation << std::endl;
174+
}
175+
}
176+
177+
// Align to 4 bytes
178+
offset += keyAndValueByteSize;
179+
uint32_t padding = (4 - (keyAndValueByteSize % 4)) % 4;
180+
offset += padding;
181+
}
182+
}
183+
else
184+
{
185+
// No key-value data, skip
186+
fin.ignore(0);
187+
}
188+
189+
// Check if this is an ASTC texture
190+
bool isASTCTexture = (header.glInternalFormat >= 0x93B0 && header.glInternalFormat <= 0x93DD);
191+
192+
// For ASTC textures, validate orientation
193+
if (isASTCTexture)
194+
{
195+
// ASTC textures must have S=r,T=u orientation (OpenGL native, bottom-left origin)
196+
// If no KTXorientation is specified, we assume the default which is incorrect for ASTC
197+
if (ktxOrientation.empty())
198+
{
199+
OSG_WARN << "ASTC texture in KTX file lacks KTXorientation metadata. "
200+
<< "ASTC textures require explicit KTXorientation=S=r,T=u" << std::endl;
201+
return ReadResult(ReadResult::FILE_NOT_HANDLED);
202+
}
203+
else if (ktxOrientation != "S=r,T=u" && ktxOrientation != "S=r,T=u,R=o")
204+
{
205+
OSG_WARN << "ASTC texture in KTX file has incompatible orientation: " << ktxOrientation
206+
<< ". ASTC textures require KTXorientation=S=r,T=u" << std::endl;
207+
return ReadResult(ReadResult::FILE_NOT_HANDLED);
208+
}
209+
}
120210

121211
uint32_t imageSize;
122212
uint32_t totalImageSize = fileLength -
@@ -210,8 +300,18 @@ osgDB::ReaderWriter::ReadResult ReaderWriterKTX::readKTXStream(std::istream& fin
210300
header.glInternalFormat, header.glFormat,
211301
header.glType, totalImageData, osg::Image::USE_NEW_DELETE);
212302

213-
// KTX textures are stored in their original coordinate system (TOP_LEFT)
214-
image->setOrigin(osg::Image::TOP_LEFT);
303+
// Set origin based on KTXorientation metadata
304+
// S=r,T=u means OpenGL orientation (bottom-left origin)
305+
// S=r,T=d means standard image orientation (top-left origin)
306+
if (ktxOrientation == "S=r,T=u" || ktxOrientation == "S=r,T=u,R=o")
307+
{
308+
image->setOrigin(osg::Image::BOTTOM_LEFT);
309+
}
310+
else
311+
{
312+
// Default to TOP_LEFT for S=r,T=d or when no orientation is specified
313+
image->setOrigin(osg::Image::TOP_LEFT);
314+
}
215315

216316
if (header.numberOfMipmapLevels > 1)
217317
image->setMipmapLevels(mipmapData);

0 commit comments

Comments
 (0)