一、问题描述
今天在使用OpenGL ES 加载一个 TGA 图片文件的时候,出现了加载失败的问题。
关于什么是TGA文件以及如何打开TGA文件?
可以参考我的博客:【我的OpenGL学习进阶之旅】什么是TGA文件以及如何打开TGA文件?
如下图所示,没有texture加载进来,黑黢黢的页面。
查看日志打印,发现加载tga图片失败,如下所示:
2021-11-25 15:42:31.690 6385-6548/com.oyp.openglesdemo I/NDK_JNI_LOG_TAG:
[GLUtils.cpp][loadTgaTexture][240]: Error loading (texture/heightmap.tga) image.
关于日志打印的内容带有文件文件名、方法名、行号 等信息的实现方法
可以参考我的博客 【我的Android进阶之旅】NDK开发之在C++代码中使用Android Log打印日志,打印内容带有文件文件名、方法名、行号 等信息,方便定位日志输出的地方
二、分析问题
2.1 断点调试
- loadTgaTexture 函数
loadTgaTexture 函数代码如下:
//
// Load texture from disk
//
GLuint GLUtils::loadTgaTexture (const char *fileName )
{
int width, height;
char *buffer = esLoadTGA (fileName, &width, &height );
GLuint texId;
if ( buffer == nullptr )
{
LOGI ( "Error loading (%s) image.\n", fileName )
return 0;
}
glGenTextures ( 1, &texId );
glBindTexture ( GL_TEXTURE_2D, texId );
glTexImage2D ( GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, buffer );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
free ( buffer );
return texId;
}
可以发现,因为loadTgaTexture
函数调用esLoadTGA
函数返回的buffer
为nullptr
所以报错了。
- esLoadTGA 函数
esLoadTGA 函数代码如下:
//
// esLoadTGA()
//
// Loads a 8-bit, 24-bit or 32-bit TGA image from a file
//
char * esLoadTGA (const char *fileName, int *width, int *height )
{
char *buffer;
esFile *fp;
TGA_HEADER Header;
int bytesRead;
// Open the file for reading
fp = esFileOpen (fileName );
if ( fp == nullptr )
{
// Log error as 'error in opening the input file from apk'
LOGE ( "esLoadTGA FAILED to load : { %s }\n", fileName )
return nullptr;
}
LOGD ( "sizeof ( TGA_HEADER ) : { %d }\n", sizeof ( TGA_HEADER ) )
bytesRead = esFileRead ( fp, sizeof ( TGA_HEADER ), &Header );
*width = Header.Width;
*height = Header.Height;
if ( Header.ColorDepth == 8 ||
Header.ColorDepth == 24 || Header.ColorDepth == 32 )
{
int bytesToRead = sizeof ( char ) * ( *width ) * ( *height ) * Header.ColorDepth / 8;
// Allocate the image data buffer
buffer = ( char * ) malloc ( bytesToRead );
if ( buffer )
{
bytesRead = esFileRead ( fp, bytesToRead, buffer );
esFileClose ( fp );
return ( buffer );
}
}
return ( nullptr );
}
我们在这个esLoadTGA
函数打断点,调试一下看看:
定位到问题,因为Header.ColorDepth
为87 'W'
,既不等于8,也不等于24,还不等于32,所以最终返回了nullptr
。
2.2 为啥呢?
原来产生这个问题的原因和C/C++内存对齐
有关。这里,读者可以参考下面的文章了解。
我在esLoadTGA
函数里面有两句代码,如下所示,在读取TGA
文件之前,打印了TGA_HEADER
的sizeof
LOGD ( "sizeof ( TGA_HEADER ) : { %d }\n", sizeof ( TGA_HEADER ) )
bytesRead = esFileRead ( fp, sizeof ( TGA_HEADER ), &Header );
打印出来的sizeof 大小为20,所以导致了无法加载TGA。
2021-11-25 15:42:31.688 6385-6548/com.oyp.openglesdemo D/NDK_JNI_LOG_TAG:
[GLUtils.cpp][esLoadTGA][203]: sizeof ( TGA_HEADER ) : { 20 }
2021-11-25 15:42:31.690 6385-6548/com.oyp.openglesdemo I/NDK_JNI_LOG_TAG:
[GLUtils.cpp][loadTgaTexture][240]: Error loading (texture/heightmap.tga) image.
- 结构体TGA_HEADER
结构体 TGA_HEADER 的定义如下,因为C/C++内存对齐,导致TGA_HEADER 的size为20,所以不对劲,如下所示:
typedef struct
{
unsigned char IdSize,
MapType,
ImageType;
unsigned short PaletteStart,
PaletteSize;
unsigned char PaletteEntryDepth;
unsigned short X,
Y,
Width,
Height;
unsigned char ColorDepth,
Descriptor;
} TGA_HEADER;
三、解决问题
3.1 使用 __attribute__ ( ( packed ) )
参考下面博客:
3.1.1 使用 __attribute__ ( ( packed ) )
修改结构体
如下所示:
typedef struct
// C语言__attribute__的使用 https://blog.csdn.net/qlexcel/article/details/92656797
// 使用该属性对struct 或者union 类型进行定义,设定其类型的每一个变量的内存约束。
// 就是告诉编译器取消结构在编译过程中的优化对齐(使用1字节对齐),按照实际占用字节数进行对齐,是GCC特有的语法。
// 这个功能是跟操作系统没关系,跟编译器有关,gcc编译器不是紧凑模式的
__attribute__ ( ( packed ) )
{
unsigned char IdSize,
MapType,
ImageType;
unsigned short PaletteStart,
PaletteSize;
unsigned char PaletteEntryDepth;
unsigned short X,
Y,
Width,
Height;
unsigned char ColorDepth,
Descriptor;
} TGA_HEADER;
3.1.2 修改后的效果
改完之后,重新执行,正常运行。
断点调试,发现 ColorDepth
为 8
,所以正常加载了 TGA 图片
实现的效果如下所示:
3.1.3 分析结构体
打印出来的 sizeof ( TGA_HEADER ) 为 18,所以加载TGA文件成功。
2021-11-25 16:03:30.881 7080-7188/com.oyp.openglesdemo D/NDK_JNI_LOG_TAG:
[GLUtils.cpp][esLoadTGA][203]: sizeof ( TGA_HEADER ) : { 18 }
3.2 使用 #pragma pack(1)
参考下面两篇博客:
3.2.1 使用 #pragma pack(1)
修改代码
// 注意点:保证内存是连续的,不然读取错误 使用 #pragma pack(1) 或者 __attribute__ ( ( packed ) ) 都可以
// C/C++内存对齐详解 https://zhuanlan.zhihu.com/p/30007037
// #pragma的常用方法讲解 https://blog.csdn.net/weixin_39640298/article/details/84503428
#pragma pack(push,x1) // Byte alignment (8-bit)
#pragma pack(1) // 如果前面加上#pragma pack(1),那么此时有效对齐值为1字节
typedef struct
{
unsigned char IdSize,
MapType,
ImageType;
unsigned short PaletteStart,
PaletteSize;
unsigned char PaletteEntryDepth;
unsigned short X,
Y,
Width,
Height;
unsigned char ColorDepth,
Descriptor;
} TGA_HEADER;
#pragma pack(pop,x1)
3.2.2 修改后的效果
效果和 使用 __attribute__ ( ( packed ) )
修改结构体 的一样,这里不再重复描述
3.2.3 分析结构体
打印出来的 sizeof ( TGA_HEADER ) 为 18,所以加载TGA文件成功。
2021-11-25 16:11:12.936 7408-7542/com.oyp.openglesdemo D/NDK_JNI_LOG_TAG:
[GLUtils.cpp][esLoadTGA][198]: sizeof ( TGA_HEADER ) : { 18 }
四、参考链接
参考下面博客: