class 成员使用 unique_ptr
Use of unique_ptr for class members
我正在实现一个 C++ 程序来从 TGA 文件中读取图像。我得到了 header.
的结构
struct TGA_HEADER
{
// length of id string
char id_length;
// image storage info
char colour_map_type;
char image_type;
// colour Map
short first_entry;
short num_entries;
char bits_per_entry;
// image description
short x_origin;
short y_origin;
short width;
short height;
char bits_per_pixel;
char descriptor;
};
图像 class 看起来像这样:
class image
{
private:
public:
TGA_HEADER header;
std::unique_ptr<std::vector<char>> pixel_data;
image(const std::string& image_path);
~image();
static void save_image(const std::string& file_name, image& image);
static void load_image(const std::string& path, const std::unique_ptr<std::vector<char>>& pixel_data, TGA_HEADER& header);
};
我的图像的构造函数class:
image::image(const std::string& image_path)
:
pixel_data(new std::vector<char>)
{
load_image(image_path, this->pixel_data, this->header);
}
还有我的加载class:
void image::load_image(const std::string& path, const std::unique_ptr<std::vector<char>>& pixel_data, TGA_HEADER& header)
{
std::ifstream file_stream(path, std::ios::in | std::ios::binary | std::ios::ate);
if (file_stream.is_open())
{
file_stream.seekg(0, std::ios::beg);
int tgaDesc = 0;
/* read header */
file_stream.read(&header.id_length, sizeof(header.id_length));
file_stream.read(&header.colour_map_type, sizeof(header.colour_map_type));
file_stream.read(&header.image_type, sizeof(header.image_type));
file_stream.read((char*)(&header.first_entry), sizeof(header.first_entry));
file_stream.read((char*)(&header.num_entries), sizeof(header.num_entries));
file_stream.read(&header.bits_per_entry, sizeof(header.bits_per_entry));
file_stream.read((char*)(&header.x_origin), sizeof(header.x_origin));
file_stream.read((char*)(&header.y_origin), sizeof(header.y_origin));
file_stream.read((char*)(&header.width), sizeof(header.width));
file_stream.read((char*)(&header.height), sizeof(header.height));
file_stream.read(&header.bits_per_pixel, sizeof(header.bits_per_pixel));
file_stream.read(&header.descriptor, sizeof(header.descriptor));
// Skip the ID String
char* skip = new char[256];
file_stream.read(skip, header.id_length);
// Skip the colour map if it doesn't exist
if (!(tgaDesc & 4))
{
int colourMapSize = header.colour_map_type * header.num_entries;
file_stream.read(skip, colourMapSize);
}
delete skip;
int imageDataSize = header.width * header.height * (header.bits_per_pixel / 8);
pixel_data->resize(imageDataSize);
int originalPosition = (int)file_stream.tellg();
/* read image data */
file_stream.read(pixel_data->data(), imageDataSize);
}
我的第一个问题是,如果我以正确的方式为我的 pixel_data 使用 unique_ptr
,尤其是在构造函数中,我的第二个问题是我是否初始化 TGA_HEADER
object 如果我必须手动删除它或使用智能指针,则分别正确。
我是 C++ 的新手,所以请随时评论您可能遇到的其他问题。
此致
从技术上讲,您在这里使用 std::unique_ptr
没有任何问题,但在这种情况下完全没有必要。 std::vector
本质上已经是指向底层数组的唯一指针。不要让你的 class 有一个 std::unique_ptr<std::vector<char>>
成员,它应该只有一个 std::vector<char>
成员。
我还会质疑为什么 load_image
和 save_image
是静态的,如果它们需要 references/pointers 到 class 的成员(或者对象本身,在这种情况下save_image
)。将它们作为非静态成员函数似乎更有意义。这样他们就可以隐式地访问调用它们的对象的成员。
您的 load_image
函数中也存在潜在的内存泄漏。您动态分配 skip
指向的数组。如果从那里到 delete skip
之间的任何读取操作抛出异常,您就会泄漏该数组。 256 字节也可能不足以读取整个颜色图。您可能应该使用 std::vector<char>
。更好的是,与其阅读您不想要的值,不如直接跳过它们:
// Skip the ID String
file_stream.seekg(header.id_length, std::ios::cur);
// Skip the colour map if it doesn't exist
if (!(tgaDesc & 4)) {
int colourMapSize = header.colour_map_type * header.num_entries;
file_stream.seekg(colourMapSize, std::ios::cur);
}
写这个例子让我注意到 tgaDesc
总是 0
,所以 if
块总是 运行。您是要在此处查看 header.colour_map_type
吗?当然,如果没有颜色图,那么 header.num_entries
应该为 0,所以我不确定是否需要 if
。
当我们在 load_image
时,您在打开 file_stream
时传递 std::ios::ate
标志,但随后立即 seekg
回到文件的开头。如果删除 std::ios::ate
标志,则流最初将位于文件的开头,并且可以消除额外的 seekg
。
您读取文件头的方式基本没问题。 Byte ordering (AKA endianness) 可能是个问题,但 TGA 和大多数现代 CPU 都使用小端字节顺序,所以除非你想支持一些深奥的平台,否则你可能没问题。
把它们放在一起会给你一个 image
class 看起来像这样的东西:
class image {
public:
TGA_HEADER header;
std::vector<char> pixel_data;
image(const std::string& image_path);
~image();
void save_image(const std::string& file_name);
void load_image(const std::string& path);
};
image::image(const std::string& image_path)
{
load_image(image_path);
}
void image::load_image(const std::string& path)
{
std::ifstream file_stream(path, std::ios::in | std::ios::binary);
if (file_stream.is_open()) {
int tgaDesc = 0;
/* read header */
file_stream.read(&header.id_length, sizeof(header.id_length));
file_stream.read(&header.colour_map_type, sizeof(header.colour_map_type));
file_stream.read(&header.image_type, sizeof(header.image_type));
file_stream.read((char*)(&header.first_entry), sizeof(header.first_entry));
file_stream.read((char*)(&header.num_entries), sizeof(header.num_entries));
file_stream.read(&header.bits_per_entry, sizeof(header.bits_per_entry));
file_stream.read((char*)(&header.x_origin), sizeof(header.x_origin));
file_stream.read((char*)(&header.y_origin), sizeof(header.y_origin));
file_stream.read((char*)(&header.width), sizeof(header.width));
file_stream.read((char*)(&header.height), sizeof(header.height));
file_stream.read(&header.bits_per_pixel, sizeof(header.bits_per_pixel));
file_stream.read(&header.descriptor, sizeof(header.descriptor));
// Skip the ID String
file_stream.seekg(header.id_length, std::ios::cur);
// Skip the colour map if it doesn't exist
if (!(tgaDesc & 4)) {
int colourMapSize = header.colour_map_type * header.num_entries;
file_stream.seekg(colourMapSize, std::ios::cur);
}
int imageDataSize = header.width * header.height * (header.bits_per_pixel / 8);
pixel_data.resize(imageDataSize);
/* read image data */
file_stream.read(pixel_data.data(), imageDataSize);
}
}
转发
几年前,我正在关注一组 3D 图形视频教程。该系列的创建者是 Marek A. Krzeminski,MASc,您可以在此处找到他的网站:www.marekknows.com 在他的下载选项卡下的系列中,此代码引用了 2 个系列,它们属于着色器引擎和振系列。由于多种原因,我无法显示代码的所有部分以进行复制、粘贴、编译和 运行。首先是版权原因。 Marek 之前已经允许我使用他的代码来指导其他人为他们自己的作品找到解决方案,前提是我承认他的代码。所以我要展示的代码不是我的,但是,我从他那里学到的概念和技术应该适用于帮助解决或解决特别是关于这个问题的问题。我不能显示所有代码的另一个原因是它是一个集成了许多部分的非常大的项目。我能做的就是向你展示在 TGA
文件中加载的功能,并解释他是如何设计它的,它是如何与项目的其他部分集成的,以及它是如何使用的。然后我会解释它如何适用于你的情况,你的问题。
这里是TextureFileReader::loadTga()
函数
void TextureFileReader::loadTga( Texture* pTexture ) {
if ( nullptr == pTexture ) {
throw ExceptionHandler( __FUNCTION__ + std::string( " invalid pTexture passed in" ) );
}
struct TgaHeader {
unsigned char idLength;
unsigned char colorMapType;
unsigned char imageType;
unsigned char colorMapSpecifications[5];
short xOrigin;
short yOrigin;
short imageWidth;
short imageHeight;
unsigned char pixelDepth;
unsigned char imageDescriptor;
} tgaHeader;
enum TgaFileType {
TGA_RGB = 2,
TGA_RLE_RGB = 10,
}; // TgaFileType
// Error Message Handling
std::ostringstream strStream;
strStream << __FUNCTION__ << " ";
// Open File For Reading
m_fileStream.open( m_strFilenameWithPath, std::ios_base::in | std::ios_base::binary );
if ( !m_fileStream.is_open() ) {
strStream << "can not open file for reading";
throwError( strStream );
}
// Get TGA File Header
if ( !m_fileStream.read( reinterpret_cast<char*>( &tgaHeader ), sizeof( tgaHeader ) ) ) {
strStream << "error reading header";
throwError( strStream );
}
// This TGA File Loader Can Only Load Uncompressed Or Compressed True-Color Images
if ( (tgaHeader.imageType != TGA_RGB ) && (tgaHeader.imageType != TGA_RLE_RGB ) ) {
strStream << "TGA loader only supports loading RGB{" << TGA_RGB << "} and RLE_RGB{" << TGA_RLE_RGB
<< "} encoded files. This file contains pixels encoded in an unsupported type{" << tgaHeader.imageType << "}";
throwError( strStream );
}
// Convert Bits Per Pixel To Bytes Per Pixel
unsigned uBytesPerPixel = tgaHeader.pixelDepth / 8;
if ( (uBytesPerPixel != 3) && (uBytesPerPixel != 4) ) {
strStream << "TGA loader only supports 24bpp or 32bpp images. This image uses " << tgaHeader.pixelDepth << " bits per pixel";
throwError( strStream );
}
// Make Room For All Pixel Data
if ( 0 == tgaHeader.imageWidth || 0 == tgaHeader.imageHeight ) {
strStream << "invalid image size (" << tgaHeader.imageWidth << "," << tgaHeader.imageHeight << ")";
throwError( strStream );
}
unsigned uTotalNumBytes = tgaHeader.imageWidth * tgaHeader.imageHeight * uBytesPerPixel;
pTexture->vPixelData.resize( uTotalNumBytes );
// Move Read Pointer To Beginning Of Image Data
if ( tgaHeader.idLength > 0 ) {
m_fileStream.ignore( tgaHeader.idLength );
}
// Used To Get And Flip Pixels Data
std::vector<unsigned char> vTempPixel( uBytesPerPixel, 0 );
if ( tgaHeader.imageType == TGA_RLE_RGB ) {
// TGA Data Is Compressed
// All Error Messages The Same If Error Occurs Below
strStream << "file is corrupted, missing pixel data";
unsigned char ucRepetitionCounter = 0;
unsigned uTotalNumberPixels = tgaHeader.imageWidth * tgaHeader.imageHeight;
unsigned uCurrentPixel = 0;
while( uCurrentPixel < uTotalNumberPixels ) {
// Get Repetition Count Value
if ( !m_fileStream.read( reinterpret_cast<char*>( &ucRepetitionCounter ), sizeof( unsigned char ) ) ) {
throwError( strStream );
}
if ( ucRepetitionCounter < 128 ) {
// Raw Packet. Counter Indicates How Many Different Pixels Need To Be Read
++ucRepetitionCounter;
// Get Pixel Values
if ( !m_fileStream.read( reinterpret_cast<char*>( &pTexture->vPixelData[uCurrentPixel * uBytesPerPixel] ), uBytesPerPixel * ucRepetitionCounter ) ) {
throwError( strStream );
}
} else {
// Run-Length Packet. Counter Indicates How Many Times The Text Pixel Needs To Repeat
ucRepetitionCounter -= 127;
// Get Pixel Value
if ( !m_fileStream.read( reinterpret_cast<char*>( &vTempPixel[0] ), uBytesPerPixel ) ) {
throwError( strStream );
}
// Save Pixel Multiple Times
for ( unsigned int u = uCurrentPixel; u < ( uCurrentPixel + ucRepetitionCounter ); ++u ) {
memcpy( &pTexture->vPixelData[u * uBytesPerPixel], &vTempPixel[0], uBytesPerPixel );
}
}
// Increment Counter
uCurrentPixel += ucRepetitionCounter;
}
} else {
// TGA Data Is Uncompressed
// Get Pixel Data
if ( !m_fileStream.read( reinterpret_cast<char*>( &pTexture->vPixelData[0] ), pTexture->vPixelData.size() ) ) {
strStream << "file is corrupted, missing pixel data";
throwError( strStream );
}
}
m_fileStream.close();
// Convert All Pixel Data from BGR To RGB
unsigned char ucTemp;
for ( unsigned int u = 0; u < uTotalNumBytes; u += uBytesPerPixel ) {
ucTemp = pTexture->vPixelData[u]; // Save Blue Color
pTexture->vPixelData[u] = pTexture->vPixelData[u + 2]; // Set Red Color
pTexture->vPixelData[u + 2] = ucTemp; // Set Blue Color
}
// Flip Image Horizontally
if ( tgaHeader.imageDescriptor & 0x10 ) {
short sHalfWidth = tgaHeader.imageWidth >> 1;
for ( short h = 0; h < tgaHeader.imageHeight; ++h ) {
for ( short w = 0; w < sHalfWidth; ++w ) {
unsigned uPixelLeft = uBytesPerPixel * ( h * tgaHeader.imageWidth + w );
unsigned uPixelRight = uBytesPerPixel * ( h * tgaHeader.imageWidth + tgaHeader.imageWidth - 1 - w );
memcpy( &vTempPixel[0], &pTexture->vPixelData[uPixelLeft], uBytesPerPixel ); // Store Left Pixel
memcpy( &pTexture->vPixelData[uPixelLeft], &pTexture->vPixelData[uPixelRight], uBytesPerPixel ); // Save Right Pixel @ Left
memcpy( &pTexture->vPixelData[uPixelRight], &vTempPixel[0], uBytesPerPixel ); // Save Left Pixel @ Right
}
}
}
// Flip Vertically
if ( tgaHeader.imageDescriptor & 0x20 ) {
short sHalfHeight = tgaHeader.imageHeight >> 1;
for ( short w = 0; w < tgaHeader.imageWidth; ++w ) {
for ( short h = 0; h < sHalfHeight; ++h ) {
unsigned uPixelTop = uBytesPerPixel * ( w + tgaHeader.imageWidth * h );
unsigned uPixelBottom = uBytesPerPixel * ( w + tgaHeader.imageWidth * ( tgaHeader.imageHeight - 1 - h ) );
memcpy( &vTempPixel[0], &pTexture->vPixelData[uPixelTop], uBytesPerPixel ); // Store Top Pixel
memcpy( &pTexture->vPixelData[uPixelTop], &pTexture->vPixelData[uPixelBottom], uBytesPerPixel ); // Save Bottom Pixel @ Top
memcpy( &pTexture->vPixelData[uPixelBottom], &vTempPixel[0], uBytesPerPixel ); // Save Top Pixel @ Bottom
}
}
}
// Store Other Values In Texture
pTexture->uWidth = tgaHeader.imageWidth;
pTexture->uHeight = tgaHeader.imageHeight;
pTexture->hasAlphaChannel = ( tgaHeader.pixelDepth == 32 );
}
函数被TextureFileReader::getOrCreateTextureInfo()
调用
TextureInfo TextureFileReader::getOrCreateTextureInfo( TextureInfo::FilterQuality filterQuality, bool generateMipMap, bool wrapRepeat ) {
TextureInfo textureInfo = m_pAssetStorage->getTextureInfo( m_strFilenameWithPath );
if ( INVALID_UNSIGNED == textureInfo.uTextureId ) {
// Need To Create The Texture
Texture texture( filterQuality, generateMipMap, wrapRepeat );
if ( !loadPng( &texture ) ) {
loadTga( &texture );
}
textureInfo = m_pAssetStorage->add( texture, m_strFilenameWithPath );
}
return textureInfo;
}
TextureFileReader::loadTga()
接受一个指向 Texture
object 的指针。这个纹理 object 只不过是在 AssetsStorage
class 的 header 文件中声明的结构。我不会在这里显示 AssetsStorage
class,但我会显示 Texture
声明:
struct Texture {
bool hasAlphaChannel;
bool generateMipMap;
bool wrapRepeat;
unsigned uWidth;
unsigned uHeight;
TextureInfo::FilterQuality filterQuality;
std::vector<unsigned char> vPixelData;
Texture( TextureInfo::FilterQuality filterQualityIn, bool generateMipMapIn, bool wrapRepeatIn ) :
hasAlphaChannel( false ),
generateMipMap( generateMipMapIn ),
wrapRepeat( wrapRepeatIn ),
uWidth( 0 ),
uHeight( 0 ),
filterQuality( filterQualityIn )
{}
}; // Texture
在调用TextureFileReader::loadTGA()
或TextureFileReader::loadPNG()
之前,该函数会生成一个TextureInfo
Object。这是在 "CommonStructs.h" 中声明的另一个结构,可以在这里看到:
struct TextureInfo {
enum FilterQuality {
FILTER_NONE = 1,
FILTER_GOOD,
FILTER_BETTER,
FILTER_BEST
}; // FilterQuality
unsigned uTextureId;
bool hasTransparency;
glm::uvec2 size;
TextureInfo() :
uTextureId( INVALID_UNSIGNED ),
hasTransparency( false ),
size( glm::uvec2( 0, 0 ) )
{}
};
它将尝试从 AssetStorage
中检索一个 TextureInfo
object 如果一个已经存在通过查找它的文件名作为它的 id 如果是这样它将 return它。如果在AssetStorage
中找不到textureInfo
,则需要创建一个Texture
和一个TextureInfo
object.
如果 texture
不存在并且需要创建 TextureFileReader::getOrCreateTextureInfo()
方法将创建 object 个 TextureInfo
和 Texture
object。 TextureInfo
和 Texture
object 的构造不涉及动态内存。这些 object 的内存由 AssetStorage
class 处理。如果 Texture
object 的构造成功,则此函数将尝试调用 loadPng()
或 loadTga()
,因为此引擎支持这两种图像文件格式。这是我们将地址传递给临时 Texture
object 的地方。
如果文件的打开、读取和解析成功且没有抛出任何异常并且所有图像数据都有效,控制流将离开 load
方法和 return 回到 getOrCreateTextureInfo
成员函数。然后这将尝试将 texture
object 从其 add(/*resource*/)
成员函数存储到 AssetStorage
class 的成员容器中(如果尚不存在的话)。无论哪种方式,此成员函数都会 return 被 AssetStorage
class 的 add(/*resource*/)
.[=126= 引用的 textureInfo
object ]
这就是 TextureFileReader
和 AssetStorage
用于创建在 Engine
中使用的 Texture
object 的方式...
在 game.cpp
文件或 application.cpp
文件中 Game
的构造函数中,这里是正在创建的纹理实例:
TextureFileReader titleTextureFileReader( "Assets/images/titleScreen.png" );
m_titleTextureInfo = titleTextureFileReader.getOrCreateTextureInfo( TextureInfo::FILTER_NONE, false, false );
AssetStorage
被 TextureFileReader
class 自动使用。有一个指向 AssetStorage
object 的指针,其中 AssetStorage
class 是 singleton
object。成员变量不能直接在 TextureFileReader
的 header 中看到,因为这是从 FileHandler
基础 class 派生的 class。这个引擎中有大量的继承和多态行为!
现在就你的问题提出这个完整的循环,如果你通读 loadTga()
函数并查看 TgaHeader
是如何使用的,这个结构只存在于此 loadTga()
函数的范围。一旦文件被读取,数据被存储,我们就不再需要它了。存储数据后,我们将对其进行解析并将其操作为我们想要支持的格式。至于实际的像素数据,您可以看到它们清楚地存储在 pTexture
的 vPixelData
成员中,该成员被声明为 std::vector<unsigned char>
容器。这里没有使用std::shared_ptr<T>
或std::unique_ptr<T>
的地方...
现在至于Texture
和TextureInfo
object的内存管理,这些由AssetStorage
class...
AssetStorage
class 有 typedef
Texture
支持...
typedef std::unordered_map<std::string, TextureInfo> MapTextureInfos;
从这个typedef
它有以下成员变量:
MapTextureInfos m_textureInfos;
这个class也有几种处理纹理的方法:
TextureInfo getTextureInfo( const std::string& strFilename ) const;
TextureInfo add( const Texture& texture, const std::string& strFilename );
bool removeTextureInfo( const std::string& strFilename );
bool removeTextureInfo( unsigned uTextureId );
void showLoadedTextureInfo() const;
这里我将展示属于Textures
的AssetStorage::add(/*resource*/)
方法,这个函数有添加Audio
、Fonts
、Sprites
的版本, 2D & 3D Rednerables
, 等等...
TextureInfo AssetStorage::add( const Texture& texture, const std::string& strFilename ) {
if ( INVALID_UNSIGNED != getTextureInfo( strFilename ).uTextureId ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " can not store " << strFilename << " multiple times";
throw ExceptionHandler( strStream );
}
TextureInfo textureInfo;
textureInfo.hasTransparency = texture.hasAlphaChannel;
textureInfo.size = glm::uvec2( texture.uWidth, texture.uHeight );
glGetError(); // Clear Errors
glGenTextures( 1, &textureInfo.uTextureId );
GLenum err = glGetError();
if ( err != GL_NO_ERROR ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " failed glGenTextures with error code 0x" << std::hex << err;
throw ExceptionHandler( strStream );
}
glBindTexture( GL_TEXTURE_2D, textureInfo.uTextureId );
// Wrap Textures
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, ( texture.wrapRepeat ? GL_REPEAT : GL_CLAMP_TO_EDGE ) );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, ( texture.wrapRepeat ? GL_REPEAT : GL_CLAMP_TO_EDGE ) );
const glm::uvec2& openglVersion = s_pSettings->getOpenglVersion();
if ( texture.generateMipMap ) {
switch ( texture.filterQuality ) {
case TextureInfo::FILTER_NONE : {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST );
break;
}
case TextureInfo::FILTER_GOOD: {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR );
break;
}
case TextureInfo::FILTER_BEST: {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
break;
}
default: {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
}
} // switch
if ( openglVersion.x < 3 ) {
// In OpenGL v3 GL_GENERATE_MIPMAP Is Deprecated, And In 3.1+ It Was Removed
// So For Those Versions We Use glGenerateMipmap Below
static const unsigned int GL_GENERATE_MIPMAP = 0x8191;
glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE );
}
} else {
// No MipMaps
switch ( texture.filterQuality ) {
case TextureInfo::FILTER_NONE:
case TextureInfo::FILTER_GOOD: {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
break;
}
default: {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
}
} // switch
}
// Load Texture Into Video Memory
glPixelStorei( GL_UNPACK_ALIGNMENT, texture.hasAlphaChannel ? 4 : 1 );
glTexImage2D( GL_TEXTURE_2D,
0,
( texture.hasAlphaChannel ? GL_RGBA8 : GL_RGB8 ),
texture.uWidth,
texture.uHeight,
0,
( texture.hasAlphaChannel ? GL_RGBA : GL_RGB ),
GL_UNSIGNED_BYTE,
&texture.vPixelData[0] );
if ( texture.generateMipMap && openglVersion.x >= 3 ) {
glGenerateMipmaps( GL_TEXTURE_2D );
}
// Store textureId
BlockThread blockThread( s_criticalSection );
m_textureInfos.insert( MapTextureInfos::value_type( strFilename, textureInfo ) );
if ( s_pSettings->isDebugLoggingEnabled( Settings::DEBUG_MEMORY ) ) {
Logger::log( std::string( "Created " ) + strFilename );
}
return textureInfo;
} // add
至于这个实例AssetStorage
class 是 FileHandler
class 的成员,它是 class 到 TextureFileReader
的基,声明为:
static AssetStorage* m_pAssetStorage;
并由 FileHandler
的构造函数初始化:
if ( bSaveExceptionInLog && nullptr == m_pAssetStorage ) {
m_pAssetStorage = AssetStorage::get();
}
如您所见,Engine
包含许多集成部分,没有使用 "manual" 动态内存,也没有直接在图像文件上下文中使用任何智能指针。所有这些都由所用容器的动态内存处理。现在,这个引擎还有其他部分,其中一些 object 是 shared_ptr
,其他部分是 unique_ptr
object,这取决于 object 的生命周期以及 object 的所有权。在此上下文中看不到任何 shared_ptr
或 unique_ptr
的使用是由于整个引擎的设计方式所致。这里,Game
class 在 TextureFileReader
中使用的 AssetStorage
object 是对 Engine
的引用 object的class的成员变量:
std::unique_ptr<AssetStorage> m_pAssetStorage;
Engine
class 驻留在库中,Game
class 驻留在主应用程序项目中。 Game
class继承自Engine
class。
此处 Engine
负责生命周期并拥有指向所有单例 object 的所有唯一指针,例如 ShaderManager
、AudioManager
、 BatchManager
、FontManager
和 AnimationManager
。所有这些 object 都是在 Engine
的构造函数中创建的,当 Game
class 或 Application
class 继承自它被称为。它们的生命周期都与 Engine
class 一样,或者直到它们被明确释放。现在至于 shared_ptr objects 这些将在 AudioManager
、ShaderManger
、AnimationManager
、FontManger
等中找到,因为它们将被共享可供多个object使用的资源。
这不仅仅是为了直接回答这个关于 unique_ptr
的使用或编写纹理或图像加载器的实现的问题,而且还作为对这种最小化的思想和设计过程的说明复杂而健壮,但又灵活、动态和通用集成的系统。
如果您想了解更多关于此代码的信息,它的设计结构以及在该软件的工程中使用的规划和设计方法,我强烈建议您访问 Marek 的网站。虽然他的内容不是免费的,但也不是很贵。多年来我从购买和查看他的内容中获得的知识我会说是非常值得的。我并不是说他的方法、技术或实现设计是最好的,但他对他如何以及为什么做他所做的事情的解释和演示是您 collection 中的重要资源和工具。我会说这是一笔非常好的投资。
我正在实现一个 C++ 程序来从 TGA 文件中读取图像。我得到了 header.
的结构struct TGA_HEADER
{
// length of id string
char id_length;
// image storage info
char colour_map_type;
char image_type;
// colour Map
short first_entry;
short num_entries;
char bits_per_entry;
// image description
short x_origin;
short y_origin;
short width;
short height;
char bits_per_pixel;
char descriptor;
};
图像 class 看起来像这样:
class image
{
private:
public:
TGA_HEADER header;
std::unique_ptr<std::vector<char>> pixel_data;
image(const std::string& image_path);
~image();
static void save_image(const std::string& file_name, image& image);
static void load_image(const std::string& path, const std::unique_ptr<std::vector<char>>& pixel_data, TGA_HEADER& header);
};
我的图像的构造函数class:
image::image(const std::string& image_path)
:
pixel_data(new std::vector<char>)
{
load_image(image_path, this->pixel_data, this->header);
}
还有我的加载class:
void image::load_image(const std::string& path, const std::unique_ptr<std::vector<char>>& pixel_data, TGA_HEADER& header)
{
std::ifstream file_stream(path, std::ios::in | std::ios::binary | std::ios::ate);
if (file_stream.is_open())
{
file_stream.seekg(0, std::ios::beg);
int tgaDesc = 0;
/* read header */
file_stream.read(&header.id_length, sizeof(header.id_length));
file_stream.read(&header.colour_map_type, sizeof(header.colour_map_type));
file_stream.read(&header.image_type, sizeof(header.image_type));
file_stream.read((char*)(&header.first_entry), sizeof(header.first_entry));
file_stream.read((char*)(&header.num_entries), sizeof(header.num_entries));
file_stream.read(&header.bits_per_entry, sizeof(header.bits_per_entry));
file_stream.read((char*)(&header.x_origin), sizeof(header.x_origin));
file_stream.read((char*)(&header.y_origin), sizeof(header.y_origin));
file_stream.read((char*)(&header.width), sizeof(header.width));
file_stream.read((char*)(&header.height), sizeof(header.height));
file_stream.read(&header.bits_per_pixel, sizeof(header.bits_per_pixel));
file_stream.read(&header.descriptor, sizeof(header.descriptor));
// Skip the ID String
char* skip = new char[256];
file_stream.read(skip, header.id_length);
// Skip the colour map if it doesn't exist
if (!(tgaDesc & 4))
{
int colourMapSize = header.colour_map_type * header.num_entries;
file_stream.read(skip, colourMapSize);
}
delete skip;
int imageDataSize = header.width * header.height * (header.bits_per_pixel / 8);
pixel_data->resize(imageDataSize);
int originalPosition = (int)file_stream.tellg();
/* read image data */
file_stream.read(pixel_data->data(), imageDataSize);
}
我的第一个问题是,如果我以正确的方式为我的 pixel_data 使用 unique_ptr
,尤其是在构造函数中,我的第二个问题是我是否初始化 TGA_HEADER
object 如果我必须手动删除它或使用智能指针,则分别正确。
我是 C++ 的新手,所以请随时评论您可能遇到的其他问题。
此致
从技术上讲,您在这里使用 std::unique_ptr
没有任何问题,但在这种情况下完全没有必要。 std::vector
本质上已经是指向底层数组的唯一指针。不要让你的 class 有一个 std::unique_ptr<std::vector<char>>
成员,它应该只有一个 std::vector<char>
成员。
我还会质疑为什么 load_image
和 save_image
是静态的,如果它们需要 references/pointers 到 class 的成员(或者对象本身,在这种情况下save_image
)。将它们作为非静态成员函数似乎更有意义。这样他们就可以隐式地访问调用它们的对象的成员。
您的 load_image
函数中也存在潜在的内存泄漏。您动态分配 skip
指向的数组。如果从那里到 delete skip
之间的任何读取操作抛出异常,您就会泄漏该数组。 256 字节也可能不足以读取整个颜色图。您可能应该使用 std::vector<char>
。更好的是,与其阅读您不想要的值,不如直接跳过它们:
// Skip the ID String
file_stream.seekg(header.id_length, std::ios::cur);
// Skip the colour map if it doesn't exist
if (!(tgaDesc & 4)) {
int colourMapSize = header.colour_map_type * header.num_entries;
file_stream.seekg(colourMapSize, std::ios::cur);
}
写这个例子让我注意到 tgaDesc
总是 0
,所以 if
块总是 运行。您是要在此处查看 header.colour_map_type
吗?当然,如果没有颜色图,那么 header.num_entries
应该为 0,所以我不确定是否需要 if
。
当我们在 load_image
时,您在打开 file_stream
时传递 std::ios::ate
标志,但随后立即 seekg
回到文件的开头。如果删除 std::ios::ate
标志,则流最初将位于文件的开头,并且可以消除额外的 seekg
。
您读取文件头的方式基本没问题。 Byte ordering (AKA endianness) 可能是个问题,但 TGA 和大多数现代 CPU 都使用小端字节顺序,所以除非你想支持一些深奥的平台,否则你可能没问题。
把它们放在一起会给你一个 image
class 看起来像这样的东西:
class image {
public:
TGA_HEADER header;
std::vector<char> pixel_data;
image(const std::string& image_path);
~image();
void save_image(const std::string& file_name);
void load_image(const std::string& path);
};
image::image(const std::string& image_path)
{
load_image(image_path);
}
void image::load_image(const std::string& path)
{
std::ifstream file_stream(path, std::ios::in | std::ios::binary);
if (file_stream.is_open()) {
int tgaDesc = 0;
/* read header */
file_stream.read(&header.id_length, sizeof(header.id_length));
file_stream.read(&header.colour_map_type, sizeof(header.colour_map_type));
file_stream.read(&header.image_type, sizeof(header.image_type));
file_stream.read((char*)(&header.first_entry), sizeof(header.first_entry));
file_stream.read((char*)(&header.num_entries), sizeof(header.num_entries));
file_stream.read(&header.bits_per_entry, sizeof(header.bits_per_entry));
file_stream.read((char*)(&header.x_origin), sizeof(header.x_origin));
file_stream.read((char*)(&header.y_origin), sizeof(header.y_origin));
file_stream.read((char*)(&header.width), sizeof(header.width));
file_stream.read((char*)(&header.height), sizeof(header.height));
file_stream.read(&header.bits_per_pixel, sizeof(header.bits_per_pixel));
file_stream.read(&header.descriptor, sizeof(header.descriptor));
// Skip the ID String
file_stream.seekg(header.id_length, std::ios::cur);
// Skip the colour map if it doesn't exist
if (!(tgaDesc & 4)) {
int colourMapSize = header.colour_map_type * header.num_entries;
file_stream.seekg(colourMapSize, std::ios::cur);
}
int imageDataSize = header.width * header.height * (header.bits_per_pixel / 8);
pixel_data.resize(imageDataSize);
/* read image data */
file_stream.read(pixel_data.data(), imageDataSize);
}
}
转发
几年前,我正在关注一组 3D 图形视频教程。该系列的创建者是 Marek A. Krzeminski,MASc,您可以在此处找到他的网站:www.marekknows.com 在他的下载选项卡下的系列中,此代码引用了 2 个系列,它们属于着色器引擎和振系列。由于多种原因,我无法显示代码的所有部分以进行复制、粘贴、编译和 运行。首先是版权原因。 Marek 之前已经允许我使用他的代码来指导其他人为他们自己的作品找到解决方案,前提是我承认他的代码。所以我要展示的代码不是我的,但是,我从他那里学到的概念和技术应该适用于帮助解决或解决特别是关于这个问题的问题。我不能显示所有代码的另一个原因是它是一个集成了许多部分的非常大的项目。我能做的就是向你展示在 TGA
文件中加载的功能,并解释他是如何设计它的,它是如何与项目的其他部分集成的,以及它是如何使用的。然后我会解释它如何适用于你的情况,你的问题。
这里是TextureFileReader::loadTga()
函数
void TextureFileReader::loadTga( Texture* pTexture ) {
if ( nullptr == pTexture ) {
throw ExceptionHandler( __FUNCTION__ + std::string( " invalid pTexture passed in" ) );
}
struct TgaHeader {
unsigned char idLength;
unsigned char colorMapType;
unsigned char imageType;
unsigned char colorMapSpecifications[5];
short xOrigin;
short yOrigin;
short imageWidth;
short imageHeight;
unsigned char pixelDepth;
unsigned char imageDescriptor;
} tgaHeader;
enum TgaFileType {
TGA_RGB = 2,
TGA_RLE_RGB = 10,
}; // TgaFileType
// Error Message Handling
std::ostringstream strStream;
strStream << __FUNCTION__ << " ";
// Open File For Reading
m_fileStream.open( m_strFilenameWithPath, std::ios_base::in | std::ios_base::binary );
if ( !m_fileStream.is_open() ) {
strStream << "can not open file for reading";
throwError( strStream );
}
// Get TGA File Header
if ( !m_fileStream.read( reinterpret_cast<char*>( &tgaHeader ), sizeof( tgaHeader ) ) ) {
strStream << "error reading header";
throwError( strStream );
}
// This TGA File Loader Can Only Load Uncompressed Or Compressed True-Color Images
if ( (tgaHeader.imageType != TGA_RGB ) && (tgaHeader.imageType != TGA_RLE_RGB ) ) {
strStream << "TGA loader only supports loading RGB{" << TGA_RGB << "} and RLE_RGB{" << TGA_RLE_RGB
<< "} encoded files. This file contains pixels encoded in an unsupported type{" << tgaHeader.imageType << "}";
throwError( strStream );
}
// Convert Bits Per Pixel To Bytes Per Pixel
unsigned uBytesPerPixel = tgaHeader.pixelDepth / 8;
if ( (uBytesPerPixel != 3) && (uBytesPerPixel != 4) ) {
strStream << "TGA loader only supports 24bpp or 32bpp images. This image uses " << tgaHeader.pixelDepth << " bits per pixel";
throwError( strStream );
}
// Make Room For All Pixel Data
if ( 0 == tgaHeader.imageWidth || 0 == tgaHeader.imageHeight ) {
strStream << "invalid image size (" << tgaHeader.imageWidth << "," << tgaHeader.imageHeight << ")";
throwError( strStream );
}
unsigned uTotalNumBytes = tgaHeader.imageWidth * tgaHeader.imageHeight * uBytesPerPixel;
pTexture->vPixelData.resize( uTotalNumBytes );
// Move Read Pointer To Beginning Of Image Data
if ( tgaHeader.idLength > 0 ) {
m_fileStream.ignore( tgaHeader.idLength );
}
// Used To Get And Flip Pixels Data
std::vector<unsigned char> vTempPixel( uBytesPerPixel, 0 );
if ( tgaHeader.imageType == TGA_RLE_RGB ) {
// TGA Data Is Compressed
// All Error Messages The Same If Error Occurs Below
strStream << "file is corrupted, missing pixel data";
unsigned char ucRepetitionCounter = 0;
unsigned uTotalNumberPixels = tgaHeader.imageWidth * tgaHeader.imageHeight;
unsigned uCurrentPixel = 0;
while( uCurrentPixel < uTotalNumberPixels ) {
// Get Repetition Count Value
if ( !m_fileStream.read( reinterpret_cast<char*>( &ucRepetitionCounter ), sizeof( unsigned char ) ) ) {
throwError( strStream );
}
if ( ucRepetitionCounter < 128 ) {
// Raw Packet. Counter Indicates How Many Different Pixels Need To Be Read
++ucRepetitionCounter;
// Get Pixel Values
if ( !m_fileStream.read( reinterpret_cast<char*>( &pTexture->vPixelData[uCurrentPixel * uBytesPerPixel] ), uBytesPerPixel * ucRepetitionCounter ) ) {
throwError( strStream );
}
} else {
// Run-Length Packet. Counter Indicates How Many Times The Text Pixel Needs To Repeat
ucRepetitionCounter -= 127;
// Get Pixel Value
if ( !m_fileStream.read( reinterpret_cast<char*>( &vTempPixel[0] ), uBytesPerPixel ) ) {
throwError( strStream );
}
// Save Pixel Multiple Times
for ( unsigned int u = uCurrentPixel; u < ( uCurrentPixel + ucRepetitionCounter ); ++u ) {
memcpy( &pTexture->vPixelData[u * uBytesPerPixel], &vTempPixel[0], uBytesPerPixel );
}
}
// Increment Counter
uCurrentPixel += ucRepetitionCounter;
}
} else {
// TGA Data Is Uncompressed
// Get Pixel Data
if ( !m_fileStream.read( reinterpret_cast<char*>( &pTexture->vPixelData[0] ), pTexture->vPixelData.size() ) ) {
strStream << "file is corrupted, missing pixel data";
throwError( strStream );
}
}
m_fileStream.close();
// Convert All Pixel Data from BGR To RGB
unsigned char ucTemp;
for ( unsigned int u = 0; u < uTotalNumBytes; u += uBytesPerPixel ) {
ucTemp = pTexture->vPixelData[u]; // Save Blue Color
pTexture->vPixelData[u] = pTexture->vPixelData[u + 2]; // Set Red Color
pTexture->vPixelData[u + 2] = ucTemp; // Set Blue Color
}
// Flip Image Horizontally
if ( tgaHeader.imageDescriptor & 0x10 ) {
short sHalfWidth = tgaHeader.imageWidth >> 1;
for ( short h = 0; h < tgaHeader.imageHeight; ++h ) {
for ( short w = 0; w < sHalfWidth; ++w ) {
unsigned uPixelLeft = uBytesPerPixel * ( h * tgaHeader.imageWidth + w );
unsigned uPixelRight = uBytesPerPixel * ( h * tgaHeader.imageWidth + tgaHeader.imageWidth - 1 - w );
memcpy( &vTempPixel[0], &pTexture->vPixelData[uPixelLeft], uBytesPerPixel ); // Store Left Pixel
memcpy( &pTexture->vPixelData[uPixelLeft], &pTexture->vPixelData[uPixelRight], uBytesPerPixel ); // Save Right Pixel @ Left
memcpy( &pTexture->vPixelData[uPixelRight], &vTempPixel[0], uBytesPerPixel ); // Save Left Pixel @ Right
}
}
}
// Flip Vertically
if ( tgaHeader.imageDescriptor & 0x20 ) {
short sHalfHeight = tgaHeader.imageHeight >> 1;
for ( short w = 0; w < tgaHeader.imageWidth; ++w ) {
for ( short h = 0; h < sHalfHeight; ++h ) {
unsigned uPixelTop = uBytesPerPixel * ( w + tgaHeader.imageWidth * h );
unsigned uPixelBottom = uBytesPerPixel * ( w + tgaHeader.imageWidth * ( tgaHeader.imageHeight - 1 - h ) );
memcpy( &vTempPixel[0], &pTexture->vPixelData[uPixelTop], uBytesPerPixel ); // Store Top Pixel
memcpy( &pTexture->vPixelData[uPixelTop], &pTexture->vPixelData[uPixelBottom], uBytesPerPixel ); // Save Bottom Pixel @ Top
memcpy( &pTexture->vPixelData[uPixelBottom], &vTempPixel[0], uBytesPerPixel ); // Save Top Pixel @ Bottom
}
}
}
// Store Other Values In Texture
pTexture->uWidth = tgaHeader.imageWidth;
pTexture->uHeight = tgaHeader.imageHeight;
pTexture->hasAlphaChannel = ( tgaHeader.pixelDepth == 32 );
}
函数被TextureFileReader::getOrCreateTextureInfo()
TextureInfo TextureFileReader::getOrCreateTextureInfo( TextureInfo::FilterQuality filterQuality, bool generateMipMap, bool wrapRepeat ) {
TextureInfo textureInfo = m_pAssetStorage->getTextureInfo( m_strFilenameWithPath );
if ( INVALID_UNSIGNED == textureInfo.uTextureId ) {
// Need To Create The Texture
Texture texture( filterQuality, generateMipMap, wrapRepeat );
if ( !loadPng( &texture ) ) {
loadTga( &texture );
}
textureInfo = m_pAssetStorage->add( texture, m_strFilenameWithPath );
}
return textureInfo;
}
TextureFileReader::loadTga()
接受一个指向 Texture
object 的指针。这个纹理 object 只不过是在 AssetsStorage
class 的 header 文件中声明的结构。我不会在这里显示 AssetsStorage
class,但我会显示 Texture
声明:
struct Texture {
bool hasAlphaChannel;
bool generateMipMap;
bool wrapRepeat;
unsigned uWidth;
unsigned uHeight;
TextureInfo::FilterQuality filterQuality;
std::vector<unsigned char> vPixelData;
Texture( TextureInfo::FilterQuality filterQualityIn, bool generateMipMapIn, bool wrapRepeatIn ) :
hasAlphaChannel( false ),
generateMipMap( generateMipMapIn ),
wrapRepeat( wrapRepeatIn ),
uWidth( 0 ),
uHeight( 0 ),
filterQuality( filterQualityIn )
{}
}; // Texture
在调用TextureFileReader::loadTGA()
或TextureFileReader::loadPNG()
之前,该函数会生成一个TextureInfo
Object。这是在 "CommonStructs.h" 中声明的另一个结构,可以在这里看到:
struct TextureInfo {
enum FilterQuality {
FILTER_NONE = 1,
FILTER_GOOD,
FILTER_BETTER,
FILTER_BEST
}; // FilterQuality
unsigned uTextureId;
bool hasTransparency;
glm::uvec2 size;
TextureInfo() :
uTextureId( INVALID_UNSIGNED ),
hasTransparency( false ),
size( glm::uvec2( 0, 0 ) )
{}
};
它将尝试从 AssetStorage
中检索一个 TextureInfo
object 如果一个已经存在通过查找它的文件名作为它的 id 如果是这样它将 return它。如果在AssetStorage
中找不到textureInfo
,则需要创建一个Texture
和一个TextureInfo
object.
如果 texture
不存在并且需要创建 TextureFileReader::getOrCreateTextureInfo()
方法将创建 object 个 TextureInfo
和 Texture
object。 TextureInfo
和 Texture
object 的构造不涉及动态内存。这些 object 的内存由 AssetStorage
class 处理。如果 Texture
object 的构造成功,则此函数将尝试调用 loadPng()
或 loadTga()
,因为此引擎支持这两种图像文件格式。这是我们将地址传递给临时 Texture
object 的地方。
如果文件的打开、读取和解析成功且没有抛出任何异常并且所有图像数据都有效,控制流将离开 load
方法和 return 回到 getOrCreateTextureInfo
成员函数。然后这将尝试将 texture
object 从其 add(/*resource*/)
成员函数存储到 AssetStorage
class 的成员容器中(如果尚不存在的话)。无论哪种方式,此成员函数都会 return 被 AssetStorage
class 的 add(/*resource*/)
.[=126= 引用的 textureInfo
object ]
这就是 TextureFileReader
和 AssetStorage
用于创建在 Engine
中使用的 Texture
object 的方式...
在 game.cpp
文件或 application.cpp
文件中 Game
的构造函数中,这里是正在创建的纹理实例:
TextureFileReader titleTextureFileReader( "Assets/images/titleScreen.png" );
m_titleTextureInfo = titleTextureFileReader.getOrCreateTextureInfo( TextureInfo::FILTER_NONE, false, false );
AssetStorage
被 TextureFileReader
class 自动使用。有一个指向 AssetStorage
object 的指针,其中 AssetStorage
class 是 singleton
object。成员变量不能直接在 TextureFileReader
的 header 中看到,因为这是从 FileHandler
基础 class 派生的 class。这个引擎中有大量的继承和多态行为!
现在就你的问题提出这个完整的循环,如果你通读 loadTga()
函数并查看 TgaHeader
是如何使用的,这个结构只存在于此 loadTga()
函数的范围。一旦文件被读取,数据被存储,我们就不再需要它了。存储数据后,我们将对其进行解析并将其操作为我们想要支持的格式。至于实际的像素数据,您可以看到它们清楚地存储在 pTexture
的 vPixelData
成员中,该成员被声明为 std::vector<unsigned char>
容器。这里没有使用std::shared_ptr<T>
或std::unique_ptr<T>
的地方...
现在至于Texture
和TextureInfo
object的内存管理,这些由AssetStorage
class...
AssetStorage
class 有 typedef
Texture
支持...
typedef std::unordered_map<std::string, TextureInfo> MapTextureInfos;
从这个typedef
它有以下成员变量:
MapTextureInfos m_textureInfos;
这个class也有几种处理纹理的方法:
TextureInfo getTextureInfo( const std::string& strFilename ) const;
TextureInfo add( const Texture& texture, const std::string& strFilename );
bool removeTextureInfo( const std::string& strFilename );
bool removeTextureInfo( unsigned uTextureId );
void showLoadedTextureInfo() const;
这里我将展示属于Textures
的AssetStorage::add(/*resource*/)
方法,这个函数有添加Audio
、Fonts
、Sprites
的版本, 2D & 3D Rednerables
, 等等...
TextureInfo AssetStorage::add( const Texture& texture, const std::string& strFilename ) {
if ( INVALID_UNSIGNED != getTextureInfo( strFilename ).uTextureId ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " can not store " << strFilename << " multiple times";
throw ExceptionHandler( strStream );
}
TextureInfo textureInfo;
textureInfo.hasTransparency = texture.hasAlphaChannel;
textureInfo.size = glm::uvec2( texture.uWidth, texture.uHeight );
glGetError(); // Clear Errors
glGenTextures( 1, &textureInfo.uTextureId );
GLenum err = glGetError();
if ( err != GL_NO_ERROR ) {
std::ostringstream strStream;
strStream << __FUNCTION__ << " failed glGenTextures with error code 0x" << std::hex << err;
throw ExceptionHandler( strStream );
}
glBindTexture( GL_TEXTURE_2D, textureInfo.uTextureId );
// Wrap Textures
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, ( texture.wrapRepeat ? GL_REPEAT : GL_CLAMP_TO_EDGE ) );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, ( texture.wrapRepeat ? GL_REPEAT : GL_CLAMP_TO_EDGE ) );
const glm::uvec2& openglVersion = s_pSettings->getOpenglVersion();
if ( texture.generateMipMap ) {
switch ( texture.filterQuality ) {
case TextureInfo::FILTER_NONE : {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST );
break;
}
case TextureInfo::FILTER_GOOD: {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR );
break;
}
case TextureInfo::FILTER_BEST: {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
break;
}
default: {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
}
} // switch
if ( openglVersion.x < 3 ) {
// In OpenGL v3 GL_GENERATE_MIPMAP Is Deprecated, And In 3.1+ It Was Removed
// So For Those Versions We Use glGenerateMipmap Below
static const unsigned int GL_GENERATE_MIPMAP = 0x8191;
glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE );
}
} else {
// No MipMaps
switch ( texture.filterQuality ) {
case TextureInfo::FILTER_NONE:
case TextureInfo::FILTER_GOOD: {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
break;
}
default: {
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
}
} // switch
}
// Load Texture Into Video Memory
glPixelStorei( GL_UNPACK_ALIGNMENT, texture.hasAlphaChannel ? 4 : 1 );
glTexImage2D( GL_TEXTURE_2D,
0,
( texture.hasAlphaChannel ? GL_RGBA8 : GL_RGB8 ),
texture.uWidth,
texture.uHeight,
0,
( texture.hasAlphaChannel ? GL_RGBA : GL_RGB ),
GL_UNSIGNED_BYTE,
&texture.vPixelData[0] );
if ( texture.generateMipMap && openglVersion.x >= 3 ) {
glGenerateMipmaps( GL_TEXTURE_2D );
}
// Store textureId
BlockThread blockThread( s_criticalSection );
m_textureInfos.insert( MapTextureInfos::value_type( strFilename, textureInfo ) );
if ( s_pSettings->isDebugLoggingEnabled( Settings::DEBUG_MEMORY ) ) {
Logger::log( std::string( "Created " ) + strFilename );
}
return textureInfo;
} // add
至于这个实例AssetStorage
class 是 FileHandler
class 的成员,它是 class 到 TextureFileReader
的基,声明为:
static AssetStorage* m_pAssetStorage;
并由 FileHandler
的构造函数初始化:
if ( bSaveExceptionInLog && nullptr == m_pAssetStorage ) {
m_pAssetStorage = AssetStorage::get();
}
如您所见,Engine
包含许多集成部分,没有使用 "manual" 动态内存,也没有直接在图像文件上下文中使用任何智能指针。所有这些都由所用容器的动态内存处理。现在,这个引擎还有其他部分,其中一些 object 是 shared_ptr
,其他部分是 unique_ptr
object,这取决于 object 的生命周期以及 object 的所有权。在此上下文中看不到任何 shared_ptr
或 unique_ptr
的使用是由于整个引擎的设计方式所致。这里,Game
class 在 TextureFileReader
中使用的 AssetStorage
object 是对 Engine
的引用 object的class的成员变量:
std::unique_ptr<AssetStorage> m_pAssetStorage;
Engine
class 驻留在库中,Game
class 驻留在主应用程序项目中。 Game
class继承自Engine
class。
此处 Engine
负责生命周期并拥有指向所有单例 object 的所有唯一指针,例如 ShaderManager
、AudioManager
、 BatchManager
、FontManager
和 AnimationManager
。所有这些 object 都是在 Engine
的构造函数中创建的,当 Game
class 或 Application
class 继承自它被称为。它们的生命周期都与 Engine
class 一样,或者直到它们被明确释放。现在至于 shared_ptr objects 这些将在 AudioManager
、ShaderManger
、AnimationManager
、FontManger
等中找到,因为它们将被共享可供多个object使用的资源。
这不仅仅是为了直接回答这个关于 unique_ptr
的使用或编写纹理或图像加载器的实现的问题,而且还作为对这种最小化的思想和设计过程的说明复杂而健壮,但又灵活、动态和通用集成的系统。
如果您想了解更多关于此代码的信息,它的设计结构以及在该软件的工程中使用的规划和设计方法,我强烈建议您访问 Marek 的网站。虽然他的内容不是免费的,但也不是很贵。多年来我从购买和查看他的内容中获得的知识我会说是非常值得的。我并不是说他的方法、技术或实现设计是最好的,但他对他如何以及为什么做他所做的事情的解释和演示是您 collection 中的重要资源和工具。我会说这是一笔非常好的投资。