一、OpenGL 纹理基础
纹理基础。
cocos2dx中的Texture2D类是对GL 纹理的封装,以便对GL纹理进行管理。
二、纹理格式
Cocos 3.17支持的纹理像素格式:
class CC_DLL Texture2D : public Ref
{
enum class PixelFormat {
AUTO, // 自动检测格式,即采用引擎内部的固定转化规则,比如解析png格式图片文件时,会根据不同
// 情况,将PNG图片转化成不同的像素格式,下面会讲。
//**************普通纹理格式
// 事实上,最终的纹理格式就是RGB(A)格式,因为显示器就是采用的RGBA标准。
BGRA8888, // 32位真彩色,4个通道:red、green、blue、alpha,
RGBA8888, // 每个通道占用8bits。占用内存大,运算速度慢。
RGB888, // 24位纹理,相比于上面,缺少alpha通道,所以不支持透明度。
RGB565, // 16位纹理,红色通道5位,绿色通道6位,蓝色通道5位,没有透明通道。
// 它能尽最大努力提供一个高品质的16位纹理,前提是不需要透明度的支持。
// 因为人眼对绿色更敏感一些,所以G位多了1bit,
// 适用于游戏背景。
A8, // 用作蒙版(mask)的8位纹理,用来做遮罩图。
I8, // 8位强度/灰度纹理,用来做灰度图使用。
AI88, // 用作蒙版(mask)的16位纹理,存储透明和灰度双功能的值
RGBA4444, // 包含RGBA通道的16位纹理,适用于需要有不同的透明度精灵
RGB5A1, // 包含 RGB通道(各5bit)和alpha通道(1bit)的 6位纹理,适用于无需支持透明度的精灵
//**************压缩纹理格式
PVRTC4, // 4-bit PVRTC-compressed texture
PVRTC4A, // 4-bit PVRTC-compressed texture (has alpha channel)
PVRTC2, // 2-bit PVRTC-compressed texture
PVRTC2A, // 2-bit PVRTC-compressed texture (has alpha channel)
ETC, // ETC-compressed texture
S3TC_DXT1, // S3TC-compressed texture
S3TC_DXT3, // S3TC-compressed texture
S3TC_DXT5, // S3TC-compressed texture
ATC_RGB, // ATITC-compressed texture
ATC_EXPLICIT_ALPHA, // ATITC-compressed texture
ATC_INTERPOLATED_ALPHA, // ATITC-compressed texture
DEFAULT = AUTO, // 默认是自动检测格式。
};
......
}
对于每种格式的纹理,在用glTexImage2D创建纹理的时候都有固定的参数,因此cocos用了一个map来存储这些映射数据。
class CC_DLL Texture2D : public Ref
{
// 保存一种纹理格式对应的glTexImage2D参数。
struct PixelFormatInfo {
PixelFormatInfo(GLenum anInternalFormat,
GLenum aFormat,
GLenum aType,
int aBpp,
bool aCompressed,
bool anAlpha)
: internalFormat(anInternalFormat)
, format(aFormat)
, type(aType)
, bpp(aBpp)
, compressed(aCompressed)
, alpha(anAlpha)
{}
GLenum internalFormat; // glTexImage2D参数interalformat
GLenum format; // glTexImage2D参数format
GLenum type; // glTexImage2D参数type
int bpp; // bits per pixel
bool compressed; // 是不是压缩纹理格式,不然就要用glCompressedTexImage2D
bool alpha; // 是否支持alpha
};
// 纹理格式 -> 纹理参数
typedef std::map<Texture2D::PixelFormat, const PixelFormatInfo> PixelFormatInfoMap;
}
// ***********************************************************
// ********** CCTexture2D.cpp
// ***********************************************************
namespace {
// map的value_type
typedef Texture2D::PixelFormatInfoMap::value_type PixelFormatInfoMapValue;
// 用一个数组来保存当前设备支持的纹理格式及其对应参数。
// 通过预编译+数组来动态初始化map来减少内存占用。
static const PixelFormatInfoMapValue TexturePixelFormatInfoTablesValue[] =
{
// 普通纹理格式
PixelFormatInfoMapValue(Texture2D::PixelFormat::BGRA8888, Texture2D::PixelFormatInfo(GL_BGRA, GL_BGRA, GL_UNSIGNED_BYTE, 32, false, true)),
PixelFormatInfoMapValue(Texture2D::PixelFormat::RGBA8888, Texture2D::PixelFormatInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, 32, false, true)),
PixelFormatInfoMapValue(Texture2D::PixelFormat::RGBA4444, Texture2D::PixelFormatInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 16, false, true)),
PixelFormatInfoMapValue(Texture2D::PixelFormat::RGB5A1, Texture2D::PixelFormatInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, 16, false, true)),
PixelFormatInfoMapValue(Texture2D::PixelFormat::RGB565, Texture2D::PixelFormatInfo(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 16, false, false)),
PixelFormatInfoMapValue(Texture2D::PixelFormat::RGB888, Texture2D::PixelFormatInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, 24, false, false)),
PixelFormatInfoMapValue(Texture2D::PixelFormat::A8, Texture2D::PixelFormatInfo(GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE, 8, false, false)),
PixelFormatInfoMapValue(Texture2D::PixelFormat::I8, Texture2D::PixelFormatInfo(GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE, 8, false, false)),
PixelFormatInfoMapValue(Texture2D::PixelFormat::AI88, Texture2D::PixelFormatInfo(GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 16, false, true)),
// PVRTC压缩纹理格式
#ifdef GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG
PixelFormatInfoMapValue(Texture2D::PixelFormat::PVRTC2, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, 0xFFFFFFFF, 0xFFFFFFFF, 2, true, false)),
PixelFormatInfoMapValue(Texture2D::PixelFormat::PVRTC2A, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, 0xFFFFFFFF, 0xFFFFFFFF, 2, true, true)),
PixelFormatInfoMapValue(Texture2D::PixelFormat::PVRTC4, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, 0xFFFFFFFF, 0xFFFFFFFF, 4, true, false)),
PixelFormatInfoMapValue(Texture2D::PixelFormat::PVRTC4A, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, 0xFFFFFFFF, 0xFFFFFFFF, 4, true, true)),
#endif
#ifdef GL_ETC1_RGB8_OES
PixelFormatInfoMapValue(Texture2D::PixelFormat::ETC, Texture2D::PixelFormatInfo(GL_ETC1_RGB8_OES, 0xFFFFFFFF, 0xFFFFFFFF, 4, true, false)),
#endif
#ifdef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
PixelFormatInfoMapValue(Texture2D::PixelFormat::S3TC_DXT1, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 0xFFFFFFFF, 0xFFFFFFFF, 4, true, false)),
#endif
#ifdef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
PixelFormatInfoMapValue(Texture2D::PixelFormat::S3TC_DXT3, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 0xFFFFFFFF, 0xFFFFFFFF, 8, true, false)),
#endif
#ifdef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
PixelFormatInfoMapValue(Texture2D::PixelFormat::S3TC_DXT5, Texture2D::PixelFormatInfo(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, 0xFFFFFFFF, 0xFFFFFFFF, 8, true, false)),
#endif
#ifdef GL_ATC_RGB_AMD
PixelFormatInfoMapValue(Texture2D::PixelFormat::ATC_RGB, Texture2D::PixelFormatInfo(GL_ATC_RGB_AMD, 0xFFFFFFFF, 0xFFFFFFFF, 4, true, false)),
#endif
#ifdef GL_ATC_RGBA_EXPLICIT_ALPHA_AMD
PixelFormatInfoMapValue(Texture2D::PixelFormat::ATC_EXPLICIT_ALPHA, Texture2D::PixelFormatInfo(GL_ATC_RGBA_EXPLICIT_ALPHA_AMD, 0xFFFFFFFF, 0xFFFFFFFF, 8, true, false)),
#endif
#ifdef GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD
PixelFormatInfoMapValue(Texture2D::PixelFormat::ATC_INTERPOLATED_ALPHA, Texture2D::PixelFormatInfo(GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD, 0xFFFFFFFF, 0xFFFFFFFF, 8, true, false)),
#endif
};
}
// std::map(start, end)初始化map
const Texture2D::PixelFormatInfoMap Texture2D::_pixelFormatInfoTables(TexturePixelFormatInfoTablesValue,
TexturePixelFormatInfoTablesValue + sizeof(TexturePixelFormatInfoTablesValue) / sizeof(TexturePixelFormatInfoTablesValue[0]));
cocos检测设备对压缩纹理格式的支持:
void Configuration::gatherGPUInfo() {
......
_supportsETC1 = checkForGLExtension("GL_OES_compressed_ETC1_RGB8_texture");
_valueDict["gl.supports_ETC1"] = Value(_supportsETC1);
_supportsS3TC = checkForGLExtension("GL_EXT_texture_compression_s3tc");
_valueDict["gl.supports_S3TC"] = Value(_supportsS3TC);
_supportsATITC = checkForGLExtension("GL_AMD_compressed_ATC_texture");
_valueDict["gl.supports_ATITC"] = Value(_supportsATITC);
_supportsPVRTC = checkForGLExtension("GL_IMG_texture_compression_pvrtc");
_valueDict["gl.supports_PVRTC"] = Value(_supportsPVRTC);
......
}
bool Configuration::supportsATITC() const {
return _supportsATITC;
}
bool Configuration::supportsETC() const {
//GL_ETC1_RGB8_OES is not defined in old opengl version
#ifdef GL_ETC1_RGB8_OES
return _supportsETC1;
#else
return false;
#endif
}
bool Configuration::supportsS3TC() const {
#ifdef GL_EXT_texture_compression_s3tc
return _supportsS3TC;
#else
return false;
#endif
}
bool Configuration::supportsPVRTC() const {
return _supportsPVRTC;
}
三、操作纹理
Texture2D* pTexture = new Texture2D();
// 在显存中创建GL纹理。
pTexture->initWithData(......);
pTexture->initWidthImage(......); // 从image file初始化
pTexture->initWithMipmaps(......); // 多级纹理
pTexture->initWithString(......); // 字体纹理
......; // 使用Texture2D
CC_SAFE_RELEASE(pTexture); // 触发析构,在析构中,删除显存中的GL纹理。
1、纹理数据
Texture2D纹理中保存的数据:
class CC_DLL Texture2D : public Ref {
......
Texture2D::PixelFormat _pixelFormat; // 纹理格式
int _pixelsWide; // 像素宽度
int _pixelsHigh; // 像素高度
GLuint _name; // texture name/id,GL纹理对象的句柄
GLfloat _maxS; // 纹理坐标的s坐标的最大值(相当于X坐标)
GLfloat _maxT; // 同上理(相当于Y坐标)
Size _contentSize; // 像素宽高
bool _hasPremultipliedAlpha; // 是否开启alpha预乘
bool _hasMipmaps; // 是否是多级纹理
GLProgram* _shaderProgram; // drawAtPoint and drawInRect时使用的shader program
// 上面已讲
static const PixelFormatInfoMap _pixelFormatInfoTables;
bool _antialiasEnabled; // 是否开启抗锯齿,其实就是采用GL_LINEAR线性采样
NinePatchInfo* _ninePatchInfo; // 待完成
bool _valid; // 貌似没鸟用。
std::string _filePath; // 如果是从image file初始化,则保存该image file的路径
Texture2D* _alphaTexture; // 为了让ETC1纹理支持透明
......
}
2、初始化纹理
initWithMipmaps
// 多级纹理初始化
bool Texture2D::initWithMipmaps(MipmapInfo* mipmaps, // MipmapInfo其实就是一个数组,保存纹理数据
int mipmapsNum, // 纹理级数,=1表示普通纹理,只有一张纹理。
PixelFormat pixelFormat, // 纹理像素格式
int pixelsWide, // 纹理像素宽高
int pixelsHigh)
{
//the pixelFormat must be a certain value
CCASSERT(pixelFormat != PixelFormat::NONE && pixelFormat != PixelFormat::AUTO, "the \"pixelFormat\" param must be a certain value!");
CCASSERT(pixelsWide>0 && pixelsHigh>0, "Invalid size");
if (mipmapsNum <= 0)
{
CCLOG("cocos2d: WARNING: mipmap number is less than 1");
return false;
}
// 纹理格式pixelFormat对应的glTexImage2D函数参数
auto formatItr = _pixelFormatInfoTables.find(pixelFormat);
if(formatItr == _pixelFormatInfoTables.end())
{
CCLOG("cocos2d: WARNING: unsupported pixelformat: %lx", (unsigned long)pixelFormat );
return false;
}
const PixelFormatInfo& info = formatItr->second;
// 纹理为压缩纹理,且设备不支持cocos的支持的所有压缩纹理
if (info.compressed && !Configuration::getInstance()->supportsPVRTC()
&& !Configuration::getInstance()->supportsETC()
&& !Configuration::getInstance()->supportsS3TC()
&& !Configuration::getInstance()->supportsATITC())
{
CCLOG("cocos2d: WARNING: PVRTC/ETC images are not supported");
return false;
}
if (mipmapsNum == 1 && !info.compressed) // 单张普通纹理的纹理内存对齐(在显存中内存对齐)
{
unsigned int bytesPerRow = pixelsWide * info.bpp / 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);
}
}
else { // 其他情况,都不考虑内存对齐
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}
if(_name != 0) // 删除先前的GL纹理
{
GL::deleteTexture(_name);
_name = 0;
}
glGenTextures(1, &_name);
GL::bindTexture2D(_name);
// 单张纹理的MIN_FILTER:GL_LINEAR、GL_NEAREST
// 多级纹理的MIN_FILETER参数:GL_LINEAR_MIPMAP_NEAREST、GL_NEAREST_MIPMAP_NEAREST
if (mipmapsNum == 1)
{
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _antialiasEnabled ? GL_LINEAR : GL_NEAREST);
}else
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _antialiasEnabled ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST);
}
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _antialiasEnabled ? GL_LINEAR : GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
#if CC_ENABLE_CACHE_TEXTURE_DATA
if (_antialiasEnabled)
{
TexParams texParams = {(GLuint)(_hasMipmaps?GL_LINEAR_MIPMAP_NEAREST:GL_LINEAR),GL_LINEAR,GL_NONE,GL_NONE};
VolatileTextureMgr::setTexParameters(this, texParams);
}
else
{
TexParams texParams = {(GLuint)(_hasMipmaps?GL_NEAREST_MIPMAP_NEAREST:GL_NEAREST),GL_NEAREST,GL_NONE,GL_NONE};
VolatileTextureMgr::setTexParameters(this, texParams);
}
#endif
// 检查前面GL调用的错误日志
GLenum err = glGetError();
if (err != GL_NO_ERROR)
{
cocos2d::log("OpenGL error 0x%04X in %s %s %d\n", err, __FILE__, __FUNCTION__, __LINE__);
}
int width = pixelsWide;
int height = pixelsHigh;
// Specify OpenGL texture image
// 如果是多级纹理,无非就是多调用几次glTexImage2D,mipmapsNum=1就是普通单张纹理,也即调用一次glTexImage2D
// 压缩纹理调用glCompressedTexImage2D
// 非压缩纹理调用glTexImage2D
for (int i = 0; i < mipmapsNum; ++i)
{
unsigned char *data = mipmaps[i].address;
GLsizei datalen = mipmaps[i].len;
if (info.compressed)
{
glCompressedTexImage2D(GL_TEXTURE_2D, i, info.internalFormat, (GLsizei)width, (GLsizei)height, 0, datalen, data);
}
else
{
glTexImage2D(GL_TEXTURE_2D, i, info.internalFormat, (GLsizei)width, (GLsizei)height, 0, info.format, info.type, data);
}
// 多级纹理必须是正方的,且必须是POT(2次幂尺寸的)
if (i > 0 && (width != height || ccNextPOT(width) != width ))
{
CCLOG("cocos2d: Texture2D. WARNING. Mipmap level %u is not squared. Texture won't render correctly. width=%d != height=%d", i, width, height);
}
// 检查前面GL调用的错误日志
err = glGetError();
if (err != GL_NO_ERROR)
{
CCLOG("cocos2d: Texture2D: Error uploading compressed texture level: %u . glError: 0x%04X", i, err);
return false;
}
// 多级纹理,n+1级的尺寸=n级尺寸的1/2
width = MAX(width >> 1, 1);
height = MAX(height >> 1, 1);
}
_contentSize = Size((float)pixelsWide, (float)pixelsHigh);
_pixelsWide = pixelsWide;
_pixelsHigh = pixelsHigh;
_pixelFormat = pixelFormat;
_maxS = 1;
_maxT = 1;
_hasPremultipliedAlpha = false;
_hasMipmaps = mipmapsNum > 1;
// 指定使用的着色器,在调用draw***方法时。
setGLProgram(GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE));
return true;
}
initWithData
// 通过void *data初始化纹理,其实本质就是mipmapsNum=1的mipmap
bool Texture2D::initWithData(const void *data,
ssize_t dataLen,
Texture2D::PixelFormat pixelFormat,
int pixelsWide,
int pixelsHigh,
const Size& /*contentSize*/) // 没鸟用
{
CCASSERT(dataLen>0 && pixelsWide>0 && pixelsHigh>0, "Invalid size");
//if data has no mipmaps, we will consider it has only one mipmap
MipmapInfo mipmap;
mipmap.address = (unsigned char*)data;
mipmap.len = static_cast<int>(dataLen);
return initWithMipmaps(&mipmap, 1, pixelFormat, pixelsWide, pixelsHigh);
}
initWithImage
// 从一个image file 初始化纹理。 并将image format 转换成指定的texture format,比如png -> RGBA4444
bool Texture2D::initWithImage(Image *image, PixelFormat format)
{
if (image == nullptr)
{
CCLOG("cocos2d: Texture2D. Can't create Texture. UIImage is nil");
return false;
}
int imageWidth = image->getWidth();
int imageHeight = image->getHeight();
this->_filePath = image->getFilePath(); // image file的full path
// 单张纹理大小有尺寸限制,glGetIntegerv(GL_MAX_TEXTURE_SIZE)获得
Configuration *conf = Configuration::getInstance();
int maxTextureSize = conf->getMaxTextureSize();
if (imageWidth > maxTextureSize || imageHeight > maxTextureSize)
{
CCLOG("cocos2d: WARNING: Image (%u x %u) is bigger than the supported %u x %u", imageWidth, imageHeight, maxTextureSize, maxTextureSize);
return false;
}
unsigned char* tempData = image->getData(); // image data
size_t tempDataLen = image->getDataLen();
Size imageSize = Size((float)imageWidth, (float)imageHeight);
// pixelFormat表示我们需要纹理是什么格式的
PixelFormat pixelFormat = ((PixelFormat::NONE == format) || (PixelFormat::AUTO == format)) ? image->getRenderFormat() : format;
// renderFormat表示加载image file引擎默认转换的纹理格式
PixelFormat renderFormat = image->getRenderFormat();
if (image->getNumberOfMipmaps() > 1) // 多级纹理
{
// 如果是多级纹理,且不会转换成指定格式的纹理,在加载image file时是什么格式就是什么格式。
if (pixelFormat != image->getRenderFormat())
{
CCLOG("cocos2d: WARNING: This image has more than 1 mipmaps and we will not convert the data format");
}
initWithMipmaps(image->getMipmaps(), image->getNumberOfMipmaps(), image->getRenderFormat(), imageWidth, imageHeight);
// set the premultiplied tag
_hasPremultipliedAlpha = image->hasPremultipliedAlpha();
return true;
}
else if (image->isCompressed()) //压缩纹理
{
// 暂不支持压缩纹理的格式转换
if (pixelFormat != image->getRenderFormat())
{
CCLOG("cocos2d: WARNING: This image is compressed and we can't convert it for now");
}
initWithData(tempData, tempDataLen, image->getRenderFormat(), imageWidth, imageHeight, imageSize);
// set the premultiplied tag
_hasPremultipliedAlpha = image->hasPremultipliedAlpha();
return true;
}
else // 单张普通纹理,只有这种情况才支持转换成指定纹理格式
{
unsigned char* outTempData = nullptr;
ssize_t outTempDataLen = 0;
// 首先要从renderFormat纹理格式转换成我们指定的pixelFormat纹理格式
pixelFormat = convertDataToFormat(tempData, tempDataLen, renderFormat, pixelFormat, &outTempData, &outTempDataLen);
// 执行初始化
initWithData(outTempData, outTempDataLen, pixelFormat, imageWidth, imageHeight, imageSize);
if (outTempData != nullptr && outTempData != tempData) {
free(outTempData);
}
// set the premultiplied tag
_hasPremultipliedAlpha = image->hasPremultipliedAlpha();
return true;
}
}
initWithString
创建字体纹理。
bool Texture2D::initWithString(
const char *text, // A null terminated string.
const std::string &fontName, // The font name.
float fontSize, // The font size.
const Size& dimensions = Size(0, 0), // The font dimension.
TextHAlignment hAlignment = TextHAlignment::CENTER, // The font horizontal text alignment type.
TextVAlignment vAlignment = TextVAlignment::TOP, // The font vertical text alignment type.
bool enableWrap = true, // Whether enable text wrap or not.
int overflow = 0); // Whether shrink font size when content larger than the dimensions.
{
FontDefinition tempDef;
tempDef._shadow._shadowEnabled = false;
tempDef._stroke._strokeEnabled = false;
tempDef._fontName = fontName;
tempDef._fontSize = fontSize;
tempDef._dimensions = dimensions;
tempDef._alignment = hAlignment;
tempDef._vertAlignment = vAlignment;
tempDef._fontFillColor = Color3B::WHITE;
tempDef._enableWrap = enableWrap;
tempDef._overflow = overflow;
return initWithString(text, tempDef);
}
bool Texture2D::initWithString(const char *text, const FontDefinition& textDefinition)
{
if(!text || 0 == strlen(text))
{
return false;
}
#if CC_ENABLE_CACHE_TEXTURE_DATA
// cache the texture data
VolatileTextureMgr::addStringTexture(this, text, textDefinition);
#endif
bool ret = false;
Device::TextAlign align;
if (TextVAlignment::TOP == textDefinition._vertAlignment)
{
align = (TextHAlignment::CENTER == textDefinition._alignment) ? Device::TextAlign::TOP
: (TextHAlignment::LEFT == textDefinition._alignment) ? Device::TextAlign::TOP_LEFT : Device::TextAlign::TOP_RIGHT;
}
else if (TextVAlignment::CENTER == textDefinition._vertAlignment)
{
align = (TextHAlignment::CENTER == textDefinition._alignment) ? Device::TextAlign::CENTER
: (TextHAlignment::LEFT == textDefinition._alignment) ? Device::TextAlign::LEFT : Device::TextAlign::RIGHT;
}
else if (TextVAlignment::BOTTOM == textDefinition._vertAlignment)
{
align = (TextHAlignment::CENTER == textDefinition._alignment) ? Device::TextAlign::BOTTOM
: (TextHAlignment::LEFT == textDefinition._alignment) ? Device::TextAlign::BOTTOM_LEFT : Device::TextAlign::BOTTOM_RIGHT;
}
else
{
CCASSERT(false, "Not supported alignment format!");
return false;
}
#if (CC_TARGET_PLATFORM != CC_PLATFORM_ANDROID) && (CC_TARGET_PLATFORM != CC_PLATFORM_IOS)
CCASSERT(textDefinition._stroke._strokeEnabled == false, "Currently stroke only supported on iOS and Android!");
#endif
PixelFormat pixelFormat = g_defaultAlphaPixelFormat; // 字体纹理采用默认纹理格式
unsigned char* outTempData = nullptr;
ssize_t outTempDataLen = 0;
int imageWidth;
int imageHeight;
auto textDef = textDefinition;
auto contentScaleFactor = CC_CONTENT_SCALE_FACTOR();
textDef._fontSize *= contentScaleFactor;
textDef._lineSpacing *= contentScaleFactor;
textDef._dimensions.width *= contentScaleFactor;
textDef._dimensions.height *= contentScaleFactor;
textDef._stroke._strokeSize *= contentScaleFactor;
textDef._shadow._shadowEnabled = false;
bool hasPremultipliedAlpha;
// 获取纹理数据,分平台实现
Data outData = Device::getTextureDataForText(text, textDef, align, imageWidth, imageHeight, hasPremultipliedAlpha);
if(outData.isNull())
{
return false;
}
Size imageSize = Size((float)imageWidth, (float)imageHeight);
// 转换纹理格式,cocos 3.17版本pixelFormat=AUTO,表示无需转换格式,使用默认格式
// 从这里我们也知道,字体纹理的格式默认看成是RGBA8888
pixelFormat = convertDataToFormat(outData.getBytes(), imageWidth*imageHeight*4, PixelFormat::RGBA8888, pixelFormat, &outTempData, &outTempDataLen);
ret = initWithData(outTempData, outTempDataLen, pixelFormat, imageWidth, imageHeight, imageSize);
if (outTempData != nullptr && outTempData != outData.getBytes())
{
free(outTempData);
}
_hasPremultipliedAlpha = hasPremultipliedAlpha;
return ret;
}
3、更新纹理
// 用data的数据更新纹理的这块子区域:
// x: [offsetX, offsetX + width - 1]
// y: [offsetY, offsetY + height - 1]
bool Texture2D::updateWithData(const void *data,int offsetX,int offsetY,int width,int height)
{
if (_name)
{
GL::bindTexture2D(_name);
const PixelFormatInfo& info = _pixelFormatInfoTables.at(_pixelFormat);
glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, info.format, info.type, data);
return true;
}
return false;
}
4、删除纹理
// 方法一
Texture2D::~Texture2D() // 析构时,清除显存纹理
{
......
if(_name)
{
GL::deleteTexture(_name); // glDeleteTexture清除显存纹理。
}
}
// 方法二
void Texture2D::releaseGLTexture()
{
if(_name)
{
GL::deleteTexture(_name);
}
_name = 0;
}
四、抗锯齿
注意与多重采样扛锯齿区分,这里的抗锯齿是通过设置Filtering为GL_LINEAR来得到模糊抗锯齿效果。
1、setAntiAliasTexParameters
// 开启抗锯齿,其实就是;
// - GL_TEXTURE_MIN_FILTER = GL_LINEAR
// - GL_TEXTURE_MAG_FILTER = GL_LINEAR
void Texture2D::setAntiAliasTexParameters()
{
if ( _antialiasEnabled )
{
return;
}
_antialiasEnabled = true;
if (_name == 0)
{
return;
}
GL::bindTexture2D( _name );
if( ! _hasMipmaps )
{
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
}
else
{
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
}
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
#if CC_ENABLE_CACHE_TEXTURE_DATA
TexParams texParams = {(GLuint)(_hasMipmaps?GL_LINEAR_MIPMAP_NEAREST:GL_LINEAR),GL_LINEAR,GL_NONE,GL_NONE};
VolatileTextureMgr::setTexParameters(this, texParams);
#endif
}
2、setAliasTexParameters
// 关闭扛锯齿,其实就是;
// - GL_TEXTURE_MIN_FILTER = GL_NEAREST
// - GL_TEXTURE_MAG_FILTER = GL_NEAREST
void Texture2D::setAliasTexParameters()
{
if (! _antialiasEnabled)
{
return;
}
_antialiasEnabled = false;
if (_name == 0)
{
return;
}
GL::bindTexture2D( _name );
if( ! _hasMipmaps )
{
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
}
else
{
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST );
}
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
#if CC_ENABLE_CACHE_TEXTURE_DATA
TexParams texParams = {(GLuint)(_hasMipmaps?GL_NEAREST_MIPMAP_NEAREST:GL_NEAREST),GL_NEAREST,GL_NONE,GL_NONE};
VolatileTextureMgr::setTexParameters(this, texParams);
#endif
}
默认是开启抗锯齿:
Texture2D::Texture2D()
......
, _antialiasEnabled(true)
......
{
}
3、多重采样抗锯齿
OpenGL多重采样抗锯齿知识。
cocos中默认不开启多重采样。
cocos如何开启多重采样:
//*****************AppDelegate.h
// if you want a different context, modify the value of glContextAttrs
// it will affect all platforms
void AppDelegate::initGLContextAttrs()
{
// set OpenGL context attributes: red,green,blue,alpha,depth,stencil
GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8, 0};
GLView::setGLContextAttrs(glContextAttrs);
}
//*****************CCGLView.h
struct GLContextAttrs
{
int redBits;
int greenBits;
int blueBits;
int alphaBits;
int depthBits;
int stencilBits;
int multisamplingCount; // 采样数量
// 默认为0,即不开启多重采样
// 大于0,表示开启多重采样
};
//*****************glview.h
#define GL_MAX_SAMPLES 0x8D57 //最大采样数
五、动态多级纹理
通过前面initWithMipmaps源码的学习,我们知道,引擎默认是采用静态多级纹理模式,也即是在程序外部借助工具预生成多级纹理,这样可以提高性能。
另外我们也可以通过glGenerateMipmap在运行时动态生成多级纹理,这很方便但也损耗性能。
void Texture2D::generateMipmap()
{
// 只支持2次幂纹理
CCASSERT(_pixelsWide == ccNextPOT(_pixelsWide) && _pixelsHigh == ccNextPOT(_pixelsHigh), "Mipmap texture only works in POT textures");
GL::bindTexture2D( _name );
glGenerateMipmap(GL_TEXTURE_2D);
_hasMipmaps = true;
#if CC_ENABLE_CACHE_TEXTURE_DATA
VolatileTextureMgr::setHasMipmaps(this, _hasMipmaps);
#endif
}
六、设置纹理参数
// 从下面我们可以看到,支持设置以下纹理参数
// 过滤:GL_TEXTURE_MIN_FILTER、GL_TEXTURE_MAG_FILTER
// 封装:GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T
typedef struct _TexParams {
GLuint minFilter;
GLuint magFilter;
GLuint wrapS;
GLuint wrapT;
} TexParams;
void Texture2D::setTexParameters(const TexParams &texParams)
{
CCASSERT((_pixelsWide == ccNextPOT(_pixelsWide) || texParams.wrapS == GL_CLAMP_TO_EDGE) &&
(_pixelsHigh == ccNextPOT(_pixelsHigh) || texParams.wrapT == GL_CLAMP_TO_EDGE),
"GL_CLAMP_TO_EDGE should be used in NPOT dimensions");
GL::bindTexture2D( _name );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, texParams.minFilter );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, texParams.magFilter );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, texParams.wrapS );
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, texParams.wrapT );
#if CC_ENABLE_CACHE_TEXTURE_DATA
VolatileTextureMgr::setTexParameters(this, texParams);
#endif
}
七、手动OpenGL绘制
drawAtPoint
// 以point为左下角点,绘制一张矩形纹理,宽高为纹理的宽高。
void Texture2D::drawAtPoint(const Vec2& point)
{
GLfloat width = (GLfloat)_pixelsWide * _maxS,
GLfloat height = (GLfloat)_pixelsHigh * _maxT;
// 我们可以发现,是将纹理的Y方向倒置然后映射到顶点坐标构成的矩形上 。
GLfloat coordinates[] = {
0.0f, _maxT, // 纹理左上角 -> 顶点矩形区域左下角
_maxS, _maxT, // 纹理右上角 -> 顶点矩形区域右下角
0.0f, 0.0f, // 纹理左下角 -> 顶点矩形区域左上角
_maxS, 0.0f // 纹理右下角 -> 顶点矩形区域右上角
};
GLfloat vertices[] = {
point.x, point.y,
width + point.x, point.y,
point.x, height + point.y,
width + point.x, height + point.y };
// 启用用于绘制纹理的shader
_shaderProgram->use();
_shaderProgram->setUniformsForBuiltins();
// 绑定纹理
GL::bindTexture2D( _name );
// 设置顶点属性
GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_TEX_COORD );
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, coordinates);
// 绘制三角形带,顶点绘制顺序如下:
// 2 ----- 3
// | \ |
// | \ |
// 0 ----- 1
// 构成两个三角形012和123。
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
drawInRect
// 将纹理绘制到rect指定的矩形区域
// 和drawAtPoint的区别是,前者只指定了绘制位置,而drawInRect同时还指定了绘制的宽高
void Texture2D::drawInRect(const Rect& rect)
{
GLfloat coordinates[] = {
0.0f, _maxT,
_maxS, _maxT,
0.0f, 0.0f,
_maxS, 0.0f
};
// 和drawAtPoint的区别就在这里,矩形区域的宽高不同。
GLfloat vertices[] = {
rect.origin.x, rect.origin.y, /*0.0f,*/
rect.origin.x + rect.size.width, rect.origin.y, /*0.0f,*/
rect.origin.x, rect.origin.y + rect.size.height, /*0.0f,*/
rect.origin.x + rect.size.width, rect.origin.y + rect.size.height, /*0.0f*/ };
// 启用用于绘制纹理的shader
_shaderProgram->use();
_shaderProgram->setUniformsForBuiltins();
// 绑定纹理
GL::bindTexture2D( _name );
// 设置顶点属性
GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_TEX_COORD );
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, coordinates);
// 绘制同上
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
八、alpha预乘
场景:纹理支持alpha。
一般做法如下:
// ****************************************
// ****** C++ 代 码
// ****************************************
......;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
createTexture(......); // glTexImage2D加载纹理到显存
......; // 各种使用纹理
// ****************************************
// ****** 片段着色器代码
// ****************************************
uniform Sampler2D texture; // 纹理
varying vec2 v_texCoord; // 纹理坐标
void main() {
vec3 texColor = texture2D(texture, v_texCoord);
// 纹理的alpha值就在texColor.a中
// 在后续的Blending阶段,将按如下公式计算片段的最终颜色值
// (Rs, Gs, Bs) * As + (Rd, Gd, Bd) * (1 - As)
// Rs: source片段的R值
// As: source片段的alpha值
// Rd: 目标片段的R值
// 前后两部分都要进行计算。
gl_FragColor = texColor;
}
优化做法(alpha预乘):
// ****************************************
// ****** C++ 代 码
// ****************************************
......;
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // GL_ONE代替GL_SRC_ALPHA
createTextureEx(......); // 在这一步中,纹理的RGB值已经乘以了alpha值。
......; // 各种使用纹理
// ****************************************
// ****** 片段着色器代码
// ****************************************
uniform Sampler2D texture; // 纹理
varying vec2 v_texCoord; // 纹理坐标
void main() {
vec3 texColor = texture2D(texture, v_texCoord);
// 在后续的Blending阶段,将按如下公式计算片段的最终颜色值
// (Rs, Gs, Bs) * 1 + (Rd, Gd, Bd) * (1 - As)
// 省去了(Rs, Gs, Bs) * As的计算工作,提升了性能。
gl_FragColor = texColor;
}
相关源码:
Texture2D::Texture2D()
......
, _hasPremultipliedAlpha(false) // 纹理默认不开启alpha预乘
......
{
}
// 从下面几个函数的相关源码,我们可以看出,只有在加载字体纹理和通过Image初始化纹理时,才可能支持alpha预乘。
bool Texture2D::initWithMipmaps(......)
{
......
_hasPremultipliedAlpha = false;
......
}
bool Texture2D::initWithImage(Image *image, PixelFormat format)
{
......
if (image->getNumberOfMipmaps() > 1) {
......
initWithMipmaps(......);
......
_hasPremultipliedAlpha = image->hasPremultipliedAlpha();
......
}
else if (image->isCompressed()) {
......
initWithData(......);
......
_hasPremultipliedAlpha = image->hasPremultipliedAlpha();
......
}
else {
......
initWithData(outTempData, outTempDataLen, pixelFormat, imageWidth, imageHeight, imageSize);
......
_hasPremultipliedAlpha = image->hasPremultipliedAlpha();
......
}
}
bool Texture2D::initWithString(const char *text, const FontDefinition& textDefinition)
{
......
bool hasPremultipliedAlpha;
Data outData = Device::getTextureDataForText(......, hasPremultipliedAlpha);
ret = initWithData(......);
_hasPremultipliedAlpha = hasPremultipliedAlpha;
return ret;
}
/// halx99 spec, ANDROID ETC1 ALPHA supports.
// 这个函数主要是用于增加ETC1压缩纹理的alpha支持,ETC1本身是不支持alpha的。
// 实现方法就是额外添加一张同大小的alpha纹理,与原纹理混合,从而达到alpha效果。
void Texture2D::setAlphaTexture(Texture2D* alphaTexture)
{
if (alphaTexture != nullptr) { // 这张纹理其实就保存在了上面着色器代码中的texture_alpha
alphaTexture->retain();
CC_SAFE_RELEASE(_alphaTexture);
_alphaTexture = alphaTexture;
_hasPremultipliedAlpha = true; // 采用alpha预乘,提升性能
}
}
ETC1添加alpha支持
ETC1压缩纹理并不支持alpha,通过额外添加一张相同大小的alpha纹理,并与原纹理混合,从而达到支持alpha的效果,具体实现原理如下:
// ****************************************
// ****** C++ 代 码
// ****************************************
......;
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // 使用alpha预乘
......;
// ****************************************
// ****** 片段着色器代码
// ****************************************
uniform Sampler2D texture; // 纹理
uniform Sampler2D texture_alpha; // alpha纹理,GL_RED格式,即用R分量保存alpha值
varying vec2 v_texCoord; // 纹理坐标
void main() {
float alpha = texture2D(texture_alpha, v_texCoord).r;
vec3 texColor = vec4(texture2D(texture, v_texCoord).rgb, alpha);
texColor.rgb *= alpha; // 在这里提前将源片段颜色值与alpha相乘,在后面的混合阶段,就无需在乘以alpha了。
gl_FragColor = texColor;
}
void Texture2D::setAlphaTexture(Texture2D* alphaTexture)
{
if (alphaTexture != nullptr) // 就对应上面的texture_alpha 采样器
{
alphaTexture->retain();
CC_SAFE_RELEASE(_alphaTexture);
_alphaTexture = alphaTexture;
_hasPremultipliedAlpha = true; // PremultipliedAlpha should be true.
}
}
九、纹理内存计算
工具检测
XCode的OpenGL ES Analytics工具可以检测纹理占用大小。
程序计算
工具检测不够灵活、不够精细,无能实时检测程序各个时间点的内存占用情况,并关联到具体的纹理上。我们可以自己进行计算:
// textureBytes: 纹理内存占用大小(字节数)
// size: 纹理像素宽高
// bpp: 每像素的bit数(bits per pixel)
textureBytes = size.width * size.height * bpp >> 3;
cocos中各纹理格式的BPP如下:
格式 | bpp | 是否支持alpha |
---|---|---|
BGRA8888 | 32 | 是 |
RGBA8888 | 32 | 是 |
RGB888 | 24 | 否 |
RGB565 | 16 | 否 |
A8 | 8 | 否 |
I8 | 8 | 否 |
AI88 | 16 | 否 |
RGBA4444 | 16 | 是 |
RGB5A1 | 16 | 1 bit alpha |
PVRTC4 | 4 | 是 |
PVRTC4A | 4 | 是 |
PVRTC2 | 2 | 是 |
PVRTC2A | 2 | 是 |
ETC | 4 | 否 |
S3TC_DXT1 | 4 | 1 bit alpha |
S3TC_DXT3 | 8 | 是 |
S3TC_DXT5 | 8 | 是 |
ATC_RGB | 4 | 否 |
ATC_EXPLICIT_ALPHA | 8 | 是 |
ATC_INTERPOLATED_ALPHA | 8 | 是 |
注意多级纹理的计算,外部工具预生成的多级纹理,按上面的公式正常计算,OpenGL ES命令(glGenerateMipmap)生成的多级纹理,则还需要额外计算每级纹理的内存。