上一节讲了CCImage,这节讲CCTexture。毕竟我们就是从CCTexture讲岔到CCImage的。CCTexture负责纹理的加载和维护。他的函数分为三部分:
- 初始化函数
- 加载函数
- 管理函数
我们先来看加载函数,这是CCTexture的根本所在:
bool CCTexture2D::initWithData(const void *data, CCTexture2DPixelFormat pixelFormat,
unsigned int pixelsWide, unsigned int pixelsHigh, const CCSize& contentSize)
{
unsigned int bitsPerPixel;
//Hack: bitsPerPixelForFormat returns wrong number for RGB_888 textures. See function.
if(pixelFormat == kCCTexture2DPixelFormat_RGB888)
{
bitsPerPixel = 24;
}
else
{
bitsPerPixel = bitsPerPixelForFormat(pixelFormat);
}
unsigned int bytesPerRow = pixelsWide * bitsPerPixel / 8;
if(bytesPerRow % 8 == 0)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 8);
}
else if(bytesPerRow % 4 == 0)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
}
else if(bytesPerRow % 2 == 0)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
}
else
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}
glGenTextures(1, &m_uName);
ccGLBindTexture2D(m_uName);
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 );
// Specify OpenGL texture image
switch(pixelFormat)
{
case kCCTexture2DPixelFormat_RGBA8888:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)pixelsWide, (GLsizei)pixelsHigh,
0, GL_RGBA, GL_UNSIGNED_BYTE, data);
break;
case kCCTexture2DPixelFormat_RGB888:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, (GLsizei)pixelsWide, (GLsizei)pixelsHigh,
0, GL_RGB, GL_UNSIGNED_BYTE, data);
break;
case kCCTexture2DPixelFormat_RGBA4444:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)pixelsWide, (GLsizei)pixelsHigh,
0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, data);
break;
case kCCTexture2DPixelFormat_RGB5A1:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)pixelsWide, (GLsizei)pixelsHigh,
0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, data);
break;
case kCCTexture2DPixelFormat_RGB565:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, (GLsizei)pixelsWide, (GLsizei)pixelsHigh,
0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, data);
break;
case kCCTexture2DPixelFormat_AI88:
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, (GLsizei)pixelsWide, (GLsizei)pixelsHigh,
0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, data);
break;
case kCCTexture2DPixelFormat_A8:
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, (GLsizei)pixelsWide, (GLsizei)pixelsHigh,
0, GL_ALPHA, GL_UNSIGNED_BYTE, data);
break;
case kCCTexture2DPixelFormat_I8:
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, (GLsizei)pixelsWide, (GLsizei)pixelsHigh,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
break;
default:
CCAssert(0, "NSInternalInconsistencyException");
}
m_tContentSize = contentSize;
m_uPixelsWide = pixelsWide;
m_uPixelsHigh = pixelsHigh;
m_ePixelFormat = pixelFormat;
m_fMaxS = contentSize.width / (float)(pixelsWide);
m_fMaxT = contentSize.height / (float)(pixelsHigh);
m_bHasPremultipliedAlpha = false;
m_bHasMipmaps = false;
setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTexture));
return true;
}
对于那些OpenGL函数是做什么用的,我就不多做介绍了。不明真相的同学可以参考相关资料。这里要注意的是那个m_uName,他是由OpenGL分配的,也是Texture的索引。我们可以注意到,OpenGL是支持相当多的格式的,根据自身的情况选择合适的格式才是王道。如果你要开发一款像素风格的游戏,还要采用RGBA8888的话,我只能说你很有想法了。
下面来看初始化函数:
bool initWithImage(CCImage* uiImage);
/** Initializes a texture from a string with dimensions, alignment, font name and font size */
bool initWithString(const char*text, const char*fontName,float fontSize,const CCSize &dimensions,CCTextAlignmenth Alignment,CCVerticalTextAlignmentv Alignment);
/** Initializes a texture from a string with font name and font size */
bool initWithString(const char*text,cons tchar*fontName,float fontSize);
/** Initializes a texture from a string using a text definition*/
bool initWithString(const char*text,ccFontDefinition* textDefinition);
/** Initializes a texture from a PVR file */
bool initWithPVRFile(const char*file);
/** Initializes a texture from a ETC file */
bool initWithETCFile(const char*file);
先来讲讲从CCImage来加载纹理。我们通过不同的图片来解压,得到的其实是一个RGBA8888的位图(Bitmap)格式。这个格式是很占用内存的,在initWithImage的内部调用了initPremultipliedATextureWithImage(uiImage, imageWidth, imageHeight);这个函数来将ARGB8888格式转化为我们预设的格式,当然默认情况下是RGBA8888的。这个函数是一个很好的位图格式转化示例,有需求的朋友可以参考下。格式可以由下面的函数来设置:
void CCTexture2D::setDefaultAlphaPixelFormat(CCTexture2DPixelFormat format)
{
g_defaultAlphaPixelFormat=format;
}
CCTexture2DPixelFormat CCTexture2D::defaultAlphaPixelFormat()
{
return g_defaultAlphaPixelFormat;
}
这是一个静态变量,使用的时候需要注意:
// If the image has alpha, you can create RGBA8 (32-bit) or RGBA4 (16-bit) or RGB5A1 (16-bit)
// Default is: RGBA8888 (32-bit textures)
static CCTexture2DPixelFormat g_defaultAlphaPixelFormat = kCCTexture2DPixelFormat_Default;
我们还可以从一个String初始化一个纹理。还记得我们能从一个String初始化一个CCImage吗?没错,CCTexutre就是先将用String初始化一个CCImage,再调用上面的方法由CCImage初始化的。
最后我们来看看从纹理格式初始化,ETC和PVR个过程极其类似,我们利用PVR进行讲解看其函数:
bool CCTexture2D::initWithPVRFile(const char* file)
{
bool bRet=false;
// nothing to do with CCObject::init
CCTexturePVR* pvr = new CCTexturePVR;
bRet = pvr->initWithContentsOfFile(file);
if(bRet)
{
pvr->setRetainName(true);// don't dealloc texture on release
m_uName=pvr->getName();
m_fMaxS=1.0f;
m_fMaxT=1.0f;
m_uPixelsWide=pvr->getWidth();
m_uPixelsHigh=pvr->getHeight();
m_tContentSize=CCSizeMake((float)m_uPixelsWide,(float)m_uPixelsHigh);
m_bHasPremultipliedAlpha=PVRHaveAlphaPremultiplied_;
m_ePixelFormat=pvr->getFormat();
m_bHasMipmaps=pvr->getNumberOfMipmaps()>1;
pvr->release();
}
else
{
CCLOG("cocos2d: Couldn't load PVR image %s",file);
}
return bRet;
}
我们看到,它将一些工作交给CCTexturePVR来做,我们只要注意的是那个m_uName就可以了,有了这个索引,我们就能从OpenGL中绑定指定的纹理来进行绘制。它的载入工作也是类似的,不过有一段代码比较有意思:
if(!((unpackPVRv2Data(pvrdata,pvrlen) ||unpackPVRv3Data(pvrdata,pvrlen))&&createGLTexture()))
{
CC_SAFE_DELETE_ARRAY(pvrdata);
this->release();
return false;
}
注意那个if语句中的函数调用,最后那个函数便是将数据导入到OpenGL中去。具体代码我就不贴了。从CCTexture的导入中我们可以看到,如果使用PVR或者ECT格式的话,加载过程和速度都要比使用图片方便快捷。唯一的不便在于这两种格式的体积略大。
看到这里,我想大家对于cocos2d-x如何加载纹理有了清晰的了解。我最好还有强调一点,就是那个m_uName,它是OpenGL中的纹理索引,而且是一个GLunit类型的。之后我们将会看到是如何管理和使用这个索引的。
CC_PROPERTY_READONLY(GLuint,m_uName,Name)
最后我们来看看纹理的删除:
CCTexture2D::~CCTexture2D()
{
#if CC_ENABLE_CACHE_TEXTURE_DATA
VolatileTexture::removeTexture(this);
#endif
CCLOGINFO("cocos2d: deallocing CCTexture2D %u.",m_uName);
CC_SAFE_RELEASE(m_pShaderProgram);
if(m_uName)
{
ccGLDeleteTexture(m_uName);
}
}
void ccGLDeleteTexture(GLuint textureId)
{
ccGLDeleteTextureN(0,textureId);
}
void ccGLDeleteTextureN(GLuint textureUnit,GLuint textureId)
{
#if CC_ENABLE_GL_STATE_CACHE
if(s_uCurrentBoundTexture[textureUnit]==textureId)
{
s_uCurrentBoundTexture[textureUnit]=-1;
}
#endif // CC_ENABLE_GL_STATE_CACHE
glDeleteTextures(1,&textureId);
}
与加载过程相比,删除过程真是简单粗暴啊。CCTexture是OpenGL中纹理的呈现,里面还有一些管理函数,都比较简单。有兴趣的可以查看。还有一个有意思的东西就是Premultiplied alpha,如果你使用纹理格式,一定要注意这个属性。对其原理,请Google之。