Sprite
其实内部必然包含一个纹理图片,但我们写代码时候用到的是传入一个图片路径,从下面看:
//直接 传入Texture2D 对象
Sprite* Sprite::createWithTexture(Texture2D *texture)
{
Sprite *sprite = new (std::nothrow) Sprite();
if (sprite && sprite->initWithTexture(texture))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}
//直接 传入Texture2D 对象
Sprite* Sprite::createWithTexture(Texture2D *texture, const Rect& rect, bool rotated)
{
Sprite *sprite = new (std::nothrow) Sprite();
if (sprite && sprite->initWithTexture(texture, rect, rotated))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}
//传入路径
Sprite* Sprite::create(const std::string& filename)
{
Sprite *sprite = new (std::nothrow) Sprite();
if (sprite && sprite->initWithFile(filename))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}
Sprite* Sprite::create(const std::string& filename, const Rect& rect)
{
Sprite *sprite = new (std::nothrow) Sprite();
if (sprite && sprite->initWithFile(filename, rect))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}
//直接 传入SpriteFrame 对象
Sprite* Sprite::createWithSpriteFrame(SpriteFrame *spriteFrame)
{
Sprite *sprite = new (std::nothrow) Sprite();
if (sprite && spriteFrame && sprite->initWithSpriteFrame(spriteFrame))
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}
Sprite* Sprite::createWithSpriteFrameName(const std::string& spriteFrameName)
{
SpriteFrame *frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(spriteFrameName);
#if COCOS2D_DEBUG > 0
char msg[256] = {0};
sprintf(msg, "Invalid spriteFrameName: %s", spriteFrameName.c_str());
CCASSERT(frame != nullptr, msg);
#endif
return createWithSpriteFrame(frame);
}
Sprite* Sprite::create()
{
Sprite *sprite = new (std::nothrow) Sprite();
if (sprite && sprite->init())
{
sprite->autorelease();
return sprite;
}
CC_SAFE_DELETE(sprite);
return nullptr;
}
bool Sprite::init(void)
{
return initWithTexture(nullptr, Rect::ZERO );
}
bool Sprite::initWithTexture(Texture2D *texture)
{
CCASSERT(texture != nullptr, "Invalid texture for sprite");
Rect rect = Rect::ZERO;
rect.size = texture->getContentSize();
return initWithTexture(texture, rect);
}
bool Sprite::initWithTexture(Texture2D *texture, const Rect& rect)
{
return initWithTexture(texture, rect, false);
}
//传入路径
bool Sprite::initWithFile(const std::string& filename)
{
CCASSERT(filename.size()>0, "Invalid filename for sprite");
//获取对应的 Texture2D
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
if (texture)
{
Rect rect = Rect::ZERO;
rect.size = texture->getContentSize();
return initWithTexture(texture, rect);
}
// don't release here.
// when load texture failed, it's better to get a "transparent" sprite then a crashed program
// this->release();
return false;
}
bool Sprite::initWithFile(const std::string &filename, const Rect& rect)
{
CCASSERT(filename.size()>0, "Invalid filename");
//同理 获取Texture2D
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
if (texture)
{
return initWithTexture(texture, rect);
}
// don't release here.
// when load texture failed, it's better to get a "transparent" sprite then a crashed program
// this->release();
return false;
}
bool Sprite::initWithSpriteFrameName(const std::string& spriteFrameName)
{
CCASSERT(spriteFrameName.size() > 0, "Invalid spriteFrameName");
SpriteFrame *frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(spriteFrameName);
return initWithSpriteFrame(frame);
}
bool Sprite::initWithSpriteFrame(SpriteFrame *spriteFrame)
{
CCASSERT(spriteFrame != nullptr, "");
//最终从 spriteFrame获取Texture2D
bool bRet = initWithTexture(spriteFrame->getTexture(), spriteFrame->getRect());
setSpriteFrame(spriteFrame);
return bRet;
}
//最终的初始化位置
bool Sprite::initWithTexture(Texture2D *texture, const Rect& rect, bool rotated)
class CC_DLL TextureCache : public Ref
{
public:
CC_DEPRECATED_ATTRIBUTE static TextureCache * getInstance();
public:
//根据文件 路径 添加图片 返回Texture2D
Texture2D* addImage(const std::string &filepath);
//在 单独线程中 加载图片
virtual void addImageAsync(const std::string &filepath, const std::function<void(Texture2D*)>& callback);
//添加的时候 传入Image key
Texture2D* addImage(Image *image, const std::string &key);
private:
void addImageAsyncCallBack(float dt);
void loadImage();
public:
//异步 结构体 传入 文件名 以及 回调
struct AsyncStruct
{
public:
AsyncStruct(const std::string& fn, std::function<void(Texture2D*)> f) : filename(fn), callback(f) {}
std::string filename;
std::function<void(Texture2D*)> callback;
};
protected:
// 图片信息 文件名 回调 以及 image对象
typedef struct _ImageInfo
{
AsyncStruct *asyncStruct;
//后面细讲
Image *image;
} ImageInfo;
//用于 加载的线程
std::thread* _loadingThread;
std::queue<AsyncStruct*>* _asyncStructQueue;
std::deque<ImageInfo*>* _imageInfoQueue;
//两个 互斥量
std::mutex _asyncStructQueueMutex;
std::mutex _imageInfoMutex;
std::mutex _sleepMutex;
//条件变量
std::condition_variable _sleepCondition;
bool _needQuit;
int _asyncRefCount;
//最重要的 用于存储 Texture2D的map
std::unordered_map<std::string, Texture2D*> _textures;
};
其中 addImage
的实现
Texture2D * TextureCache::addImage(const std::string &path)
{
//这两个 对象后面说
Texture2D * texture = nullptr;
Image* image = nullptr;
// Split up directory and filename
// MUTEX:
// Needed since addImageAsync calls this method from a different thread
//获取 全路径
std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);
if (fullpath.size() == 0)
{
return nullptr;
}
//先以全路径为 key 查找一下
auto it = _textures.find(fullpath);
//找到了 直接返回 这就是cache的作用
if( it != _textures.end() )
texture = it->second;
//没找到
if (! texture)
{
// all images are handled by UIImage except PVR extension that is handled by our own handler
do
{
image = new (std::nothrow) Image();
CC_BREAK_IF(nullptr == image);
//加载一下
bool bRet = image->initWithImageFile(fullpath);
CC_BREAK_IF(!bRet);
//以Image初始化 texture
texture = new (std::nothrow) Texture2D();
if( texture && texture->initWithImage(image) )
{
#if CC_ENABLE_CACHE_TEXTURE_DATA
// cache the texture file name
//支持缓存 就存起来
VolatileTextureMgr::addImageTexture(texture, fullpath);
#endif
// texture already retained, no need to re-retain it
//加入 map中
_textures.insert( std::make_pair(fullpath, texture) );
}
else
{
CCLOG("cocos2d: Couldn't create texture for file:%s in TextureCache", path.c_str());
}
} while (0);
}
//没用了
CC_SAFE_RELEASE(image);
//返回 texture
return texture;
}
Texture2D* TextureCache::addImage(Image *image, const std::string &key)
{
CCASSERT(image != nullptr, "TextureCache: image MUST not be nil");
Texture2D * texture = nullptr;
do
{
//不同点在于 用的是key
auto it = _textures.find(key);
if( it != _textures.end() ) {
texture = it->second;
break;
}
// prevents overloading the autorelease pool
//此时 已经有了Image
texture = new (std::nothrow) Texture2D();
texture->initWithImage(image);
if(texture)
{
_textures.insert( std::make_pair(key, texture) );
texture->retain();
texture->autorelease();
}
else
{
CCLOG("cocos2d: Couldn't add UIImage in TextureCache");
}
} while (0);
#if CC_ENABLE_CACHE_TEXTURE_DATA
VolatileTextureMgr::addImage(texture, image);
#endif
return texture;
}
现在看看 addImageAsync
void TextureCache::addImageAsync(const std::string &path, const std::function<void(Texture2D*)>& callback)
{
Texture2D *texture = nullptr;
//获取 全路径
std::string fullpath = FileUtils::getInstance()->fullPathForFilename(path);
auto it = _textures.find(fullpath);
if( it != _textures.end() )
texture = it->second;
if (texture != nullptr)
{
//如果 已经存在了 就通知 回调
callback(texture);
return;
}
// 如果 队列为空 就创建
if (_asyncStructQueue == nullptr)
{
_asyncStructQueue = new queue<AsyncStruct*>();
_imageInfoQueue = new deque<ImageInfo*>();
// create a new thread to load images
//新线程
_loadingThread = new std::thread(&TextureCache::loadImage, this);
_needQuit = false;
}
//第一次 就注册调度 回调
if (0 == _asyncRefCount)
{
Director::getInstance()->getScheduler()->schedule(CC_SCHEDULE_SELECTOR(TextureCache::addImageAsyncCallBack), this, 0, false);
}
++_asyncRefCount;
// generate async struct
AsyncStruct *data = new (std::nothrow) AsyncStruct(fullpath, callback);
//获取 互斥量 后 将 异步加载的结构体信息 传过去
//进程通信
// add async struct into queue
_asyncStructQueueMutex.lock();
_asyncStructQueue->push(data);
_asyncStructQueueMutex.unlock();
//通知 休眠的线程 你有事做了!
_sleepCondition.notify_one();
}
//下面 看看 loadImage 异步的主要函数
void TextureCache::loadImage()
{
AsyncStruct *asyncStruct = nullptr;
while (true)
{
std::queue<AsyncStruct*> *pQueue = _asyncStructQueue;
//获得锁
_asyncStructQueueMutex.lock();
if (pQueue->empty())
{
//没数据 解锁
_asyncStructQueueMutex.unlock();
//继续遍历 尝试获取所
if (_needQuit) {
break;
}
else {
std::unique_lock<std::mutex> lk(_sleepMutex);
//休眠
_sleepCondition.wait(lk);
continue;
}
}
else
{
// 有数据 取一个数据
asyncStruct = pQueue->front();
pQueue->pop();
//释放锁
_asyncStructQueueMutex.unlock();
}
Image *image = nullptr;
bool generateImage = false;
auto it = _textures.find(asyncStruct->filename);
//没找到
if( it == _textures.end() )
{
//将这个 锁起来
_imageInfoMutex.lock();
ImageInfo *imageInfo;
size_t pos = 0;
size_t infoSize = _imageInfoQueue->size();
for (; pos < infoSize; pos++)
{
imageInfo = (*_imageInfoQueue)[pos];
//如果找到了 路径相同 就不加载了
if(imageInfo->asyncStruct->filename.compare(asyncStruct->filename) == 0)
break;
}
_imageInfoMutex.unlock();
if(infoSize == 0 || pos == infoSize)
generateImage = true;
}
//否则 产生image对象
if (generateImage)
{
const std::string& filename = asyncStruct->filename;
// generate image
image = new (std::nothrow) Image();
//线程安全的加载
if (image && !image->initWithImageFileThreadSafe(filename))
{
CC_SAFE_RELEASE(image);
CCLOG("can not load %s", filename.c_str());
continue;
}
}
//创建 ImageInfo 加载完成的
// generate image info
ImageInfo *imageInfo = new (std::nothrow) ImageInfo();
imageInfo->asyncStruct = asyncStruct;
imageInfo->image = image;
// put the image info into the queue
_imageInfoMutex.lock();
_imageInfoQueue->push_back(imageInfo);
_imageInfoMutex.unlock();
}
if(_asyncStructQueue != nullptr)
{
//如果 退出了 就不再加载了
delete _asyncStructQueue;
_asyncStructQueue = nullptr;
delete _imageInfoQueue;
_imageInfoQueue = nullptr;
}
}
//在每帧调用 处理回调
void TextureCache::addImageAsyncCallBack(float dt)
{
// the image is generated in loading thread
//获取 加载好的 队列
std::deque<ImageInfo*> *imagesQueue = _imageInfoQueue;
//先锁住
_imageInfoMutex.lock();
if (imagesQueue->empty())
{
//没数据 还说什么呢
_imageInfoMutex.unlock();
}
else
{
//取出一个数据
ImageInfo *imageInfo = imagesQueue->front();
imagesQueue->pop_front();
_imageInfoMutex.unlock();
AsyncStruct *asyncStruct = imageInfo->asyncStruct;
Image *image = imageInfo->image;
const std::string& filename = asyncStruct->filename;
Texture2D *texture = nullptr;
if (image)
{
// generate texture in render thread
texture = new (std::nothrow) Texture2D();
//从 Image中获取 texture
texture->initWithImage(image);
#if CC_ENABLE_CACHE_TEXTURE_DATA
// cache the texture file name
VolatileTextureMgr::addImageTexture(texture, filename);
#endif
// cache the texture. retain it, since it is added in the map
//这个时候 才放回到 map中
_textures.insert( std::make_pair(filename, texture) );
texture->retain();
texture->autorelease();
}
else
{
//为空 没有返回 image 则说明前面已经返回了 再找找
auto it = _textures.find(asyncStruct->filename);
if(it != _textures.end())
texture = it->second;
}
//然后调用他的回调
if (asyncStruct->callback)
{
asyncStruct->callback(texture);
}
//过河拆桥了
if(image)
{
image->release();
}
delete asyncStruct;
delete imageInfo;
//异步任务 减一 没任务就直接取消注册
--_asyncRefCount;
if (0 == _asyncRefCount)
{
Director::getInstance()->getScheduler()->unschedule(CC_SCHEDULE_SELECTOR(TextureCache::addImageAsyncCallBack), this);
}
}
}
//多级渐进纹理
typedef struct _MipmapInfo
{
unsigned char* address;
//级别
int len;
_MipmapInfo():address(NULL),len(0){}
}MipmapInfo;
class CC_DLL Image : public Ref
{
public:
friend class TextureCache;
/**
* @js ctor
*/
Image();
/**
* @js NA
* @lua NA
*/
virtual ~Image();
/** Supported formats for Image */
//我们熟悉的 图片格式
enum class Format
{
//! JPEG
JPG,
//! PNG
PNG,
//! TIFF
TIFF,
//! WebP
WEBP,
//! PVR
PVR,
//! ETC
ETC,
//! S3TC
S3TC,
//! ATITC
ATITC,
//! TGA
TGA,
//! Raw Data
RAW_DATA,
//! Unknown format
UNKOWN
};
/**
@brief Load the image from the specified path.
@param path the absolute file path.
@return true if loaded correctly.
*/
//从 路径 加载 图片
bool initWithImageFile(const std::string& path);
/**
@brief Load image from stream buffer.
@param data stream buffer which holds the image data.
@param dataLen data length expressed in (number of) bytes.
@return true if loaded correctly.
* @js NA
* @lua NA
*/
//直接从 字节数组
bool initWithImageData(const unsigned char * data, ssize_t dataLen);
// @warning kFmtRawData only support RGBA8888
bool initWithRawData(const unsigned char * data, ssize_t dataLen, int width, int height, int bitsPerComponent, bool preMulti = false);
// Getters
inline unsigned char * getData() { return _data; }
inline ssize_t getDataLen() { return _dataLen; }
inline Format getFileType() {return _fileType; }
inline Texture2D::PixelFormat getRenderFormat() { return _renderFormat; }
inline int getWidth() { return _width; }
inline int getHeight() { return _height; }
inline int getNumberOfMipmaps() { return _numberOfMipmaps; }
inline MipmapInfo* getMipmaps() { return _mipmaps; }
inline bool hasPremultipliedAlpha() { return _hasPremultipliedAlpha; }
CC_DEPRECATED_ATTRIBUTE inline bool isPremultipliedAlpha() { return _hasPremultipliedAlpha; }
int getBitPerPixel();
bool hasAlpha();
bool isCompressed();
/**
@brief Save Image data to the specified file, with specified format.
@param filePath the file's absolute path, including file suffix.
@param isToRGB whether the image is saved as RGB format.
*/
//保存到 文件
bool saveToFile(const std::string &filename, bool isToRGB = true);
/** treats (or not) PVR files as if they have alpha premultiplied.
Since it is impossible to know at runtime if the PVR images have the alpha channel premultiplied, it is
possible load them as if they have (or not) the alpha channel premultiplied.
By default it is disabled.
*/
//Alpha预乘
static void setPVRImagesHavePremultipliedAlpha(bool haveAlphaPremultiplied);
protected:
//从各种 不同格式 图片
bool initWithJpgData(const unsigned char * data, ssize_t dataLen);
bool initWithPngData(const unsigned char * data, ssize_t dataLen);
bool initWithTiffData(const unsigned char * data, ssize_t dataLen);
bool initWithWebpData(const unsigned char * data, ssize_t dataLen);
bool initWithPVRData(const unsigned char * data, ssize_t dataLen);
bool initWithPVRv2Data(const unsigned char * data, ssize_t dataLen);
bool initWithPVRv3Data(const unsigned char * data, ssize_t dataLen);
bool initWithETCData(const unsigned char * data, ssize_t dataLen);
bool initWithS3TCData(const unsigned char * data, ssize_t dataLen);
bool initWithATITCData(const unsigned char *data, ssize_t dataLen);
typedef struct sImageTGA tImageTGA;
bool initWithTGAData(tImageTGA* tgaData);
//保存成 那种图片
bool saveImageToPNG(const std::string& filePath, bool isToRGB = true);
bool saveImageToJPG(const std::string& filePath);
void premultipliedAlpha();
protected:
/**
@brief Determine how many mipmaps can we have.
Its same as define but it respects namespaces
*/
//渐进纹理 的级别
static const int MIPMAP_MAX = 16;
//实际数据
unsigned char *_data;
//数据长度
ssize_t _dataLen;
//图片长宽
int _width;
int _height;
//是否
bool _unpack;
//文件类型
Format _fileType;
//像素格式
Texture2D::PixelFormat _renderFormat;
//多级都存起来
MipmapInfo _mipmaps[MIPMAP_MAX]; // pointer to mipmap images
//级别数目
int _numberOfMipmaps;
// false if we cann't auto detect the image is premultiplied or not.
//是否预乘
bool _hasPremultipliedAlpha;
//文件路径
std::string _filePath;
protected:
// noncopyable
Image(const Image& rImg);
Image & operator=(const Image&);
/*
@brief The same result as with initWithImageFile, but thread safe. It is caused by
loadImage() in TextureCache.cpp.
@param fullpath full path of the file.
@param imageType the type of image, currently only supporting two types.
@return true if loaded correctly.
*/
//线程安全的加载
bool initWithImageFileThreadSafe(const std::string& fullpath);
//判断是哪种格式
Format detectFormat(const unsigned char * data, ssize_t dataLen);
bool isPng(const unsigned char * data, ssize_t dataLen);
bool isJpg(const unsigned char * data, ssize_t dataLen);
bool isTiff(const unsigned char * data, ssize_t dataLen);
bool isWebp(const unsigned char * data, ssize_t dataLen);
bool isPvr(const unsigned char * data, ssize_t dataLen);
bool isEtc(const unsigned char * data, ssize_t dataLen);
bool isS3TC(const unsigned char * data,ssize_t dataLen);
bool isATITC(const unsigned char *data, ssize_t dataLen);
};
//实现 主要是对系统 以及各种 常用图片格式的封装
bool Image::initWithImageFile(const std::string& path)
{
bool ret = false;
//获取文件路径
_filePath = FileUtils::getInstance()->fullPathForFilename(path);
#ifdef EMSCRIPTEN
// Emscripten includes a re-implementation of SDL that uses HTML5 canvas
// operations underneath. Consequently, loading images via IMG_Load (an SDL
// API) will be a lot faster than running libpng et al as compiled with
// Emscripten.
SDL_Surface *iSurf = IMG_Load(fullPath.c_str());
int size = 4 * (iSurf->w * iSurf->h);
ret = initWithRawData((const unsigned char*)iSurf->pixels, size, iSurf->w, iSurf->h, 8, true);
unsigned int *tmp = (unsigned int *)_data;
int nrPixels = iSurf->w * iSurf->h;
for(int i = 0; i < nrPixels; i++)
{
unsigned char *p = _data + i * 4;
tmp[i] = CC_RGB_PREMULTIPLY_ALPHA( p[0], p[1], p[2], p[3] );
}
SDL_FreeSurface(iSurf);
#else
//没什么 高端的 就是 读文件 把文件的字节读出来
Data data = FileUtils::getInstance()->getDataFromFile(_filePath);
if (!data.isNull())
{
ret = initWithImageData(data.getBytes(), data.getSize());
}
#endif // EMSCRIPTEN
return ret;
}
//为什么 说线程安全 没看出来 事实我看了github 作者也在讨论
bool Image::initWithImageFileThreadSafe(const std::string& fullpath)
{
bool ret = false;
_filePath = fullpath;
Data data = FileUtils::getInstance()->getDataFromFile(fullpath);
if (!data.isNull())
{
ret = initWithImageData(data.getBytes(), data.getSize());
}
return ret;
}
//就是 初始化数据的地方
bool Image::initWithImageData(const unsigned char * data, ssize_t dataLen)
{
bool ret = false;
do
{
CC_BREAK_IF(! data || dataLen <= 0);
unsigned char* unpackedData = nullptr;
ssize_t unpackedLen = 0;
//看看 哪一种压缩方式
//detecgt and unzip the compress file
if (ZipUtils::isCCZBuffer(data, dataLen))
{
unpackedLen = ZipUtils::inflateCCZBuffer(data, dataLen, &unpackedData);
}
else if (ZipUtils::isGZipBuffer(data, dataLen))
{
unpackedLen = ZipUtils::inflateMemory(const_cast<unsigned char*>(data), dataLen, &unpackedData);
}
else
{
unpackedData = const_cast<unsigned char*>(data);
unpackedLen = dataLen;
}
//获取 图片的格式 不管是具体哪一种 都是 解析像素的格式 然后赋值本类的相关数据
_fileType = detectFormat(unpackedData, unpackedLen);
switch (_fileType)
{
case Format::PNG:
ret = initWithPngData(unpackedData, unpackedLen);
break;
case Format::JPG:
ret = initWithJpgData(unpackedData, unpackedLen);
break;
case Format::TIFF:
ret = initWithTiffData(unpackedData, unpackedLen);
break;
case Format::WEBP:
ret = initWithWebpData(unpackedData, unpackedLen);
break;
case Format::PVR:
ret = initWithPVRData(unpackedData, unpackedLen);
break;
case Format::ETC:
ret = initWithETCData(unpackedData, unpackedLen);
break;
case Format::S3TC:
ret = initWithS3TCData(unpackedData, unpackedLen);
break;
case Format::ATITC:
ret = initWithATITCData(unpackedData, unpackedLen);
break;
default:
{
// load and detect image format
tImageTGA* tgaData = tgaLoadBuffer(unpackedData, unpackedLen);
if (tgaData != nullptr && tgaData->status == TGA_OK)
{
ret = initWithTGAData(tgaData);
}
else
{
CCAssert(false, "unsupport image format!");
}
free(tgaData);
break;
}
}
if(unpackedData != data)
{
free(unpackedData);
}
} while (0);
return ret;
}
下面再看看 Texture2D
class CC_DLL Texture2D : public Ref
#ifdef EMSCRIPTEN
, public GLBufferedNode
#endif // EMSCRIPTEN
{
public:
/** @typedef Texture2D::PixelFormat
Possible texture pixel formats
*/
enum class PixelFormat
{
//! auto detect the type
AUTO,
//! 32-bit texture: BGRA8888
BGRA8888,
//! 32-bit texture: RGBA8888
RGBA8888,
//! 24-bit texture: RGBA888
RGB888,
};
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;
GLenum format;
GLenum type;
int bpp;
bool compressed;
bool alpha;
};
//格式是对应的 信息
typedef std::map<Texture2D::PixelFormat, const PixelFormatInfo> PixelFormatInfoMap;
/**
Extension to set the Min / Mag filter
*/
//处理 纹理过滤方式
typedef struct _TexParams {
GLuint minFilter;
GLuint magFilter;
GLuint wrapS;
GLuint wrapT;
}TexParams;
public:
//用data初始化
bool initWithData(const void *data, ssize_t dataLen, Texture2D::PixelFormat pixelFormat, int pixelsWide, int pixelsHigh, const Size& contentSize);
/** Initializes with mipmaps */
bool initWithMipmaps(MipmapInfo* mipmaps, int mipmapsNum, Texture2D::PixelFormat pixelFormat, int pixelsWide, int pixelsHigh);
/** draws a texture at a given point */
void drawAtPoint(const Vec2& point);
/** draws a texture inside a rect */
void drawInRect(const Rect& rect);
//用 Image 初始化
bool initWithImage(Image * image);
bool initWithImage(Image * image, PixelFormat format);
/** Initializes a texture from a string with dimensions, alignment, font name and font size */
bool initWithString(const char *text, const std::string &fontName, float fontSize, const Size& dimensions = Size(0, 0), TextHAlignment hAlignment = TextHAlignment::CENTER, TextVAlignment vAlignment = TextVAlignment::TOP);
/** Initializes a texture from a string using a text definition*/
bool initWithString(const char *text, const FontDefinition& textDefinition);
void generateMipmap();
//各种格式转换
//I8 to XXX
static void convertI8ToRGB888(const unsigned char* data, ssize_t dataLen, unsigned char* outData);
static void convertI8ToRGBA8888(const unsigned char* data, ssize_t dataLen, unsigned char* outData);
//具体数据
protected:
/** pixel format of the texture */
Texture2D::PixelFormat _pixelFormat;
/** width in pixels */
int _pixelsWide;
/** height in pixels */
int _pixelsHigh;
/** texture name */
GLuint _name;
/** texture max S */
GLfloat _maxS;
/** texture max T */
GLfloat _maxT;
/** content size */
Size _contentSize;
/** whether or not the texture has their Alpha premultiplied */
bool _hasPremultipliedAlpha;
bool _hasMipmaps;
/** shader program used by drawAtPoint and drawInRect */
GLProgram* _shaderProgram;
static const PixelFormatInfoMap _pixelFormatInfoTables;
bool _antialiasEnabled;
};
//看看 具体实现
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();
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;
}
//获取Image的数据 以及各种属性
unsigned char* tempData = image->getData();
Size imageSize = Size((float)imageWidth, (float)imageHeight);
PixelFormat pixelFormat = ((PixelFormat::NONE == format) || (PixelFormat::AUTO == format)) ? image->getRenderFormat() : format;
PixelFormat renderFormat = image->getRenderFormat();
size_t tempDataLen = image->getDataLen();
//如果存在多级纹理
if (image->getNumberOfMipmaps() > 1)
{
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);
return true;
}
else if (image->isCompressed())
{
if (pixelFormat != image->getRenderFormat())
{
CCLOG("cocos2d: WARNING: This image is compressed and we cann't convert it for now");
}
initWithData(tempData, tempDataLen, image->getRenderFormat(), imageWidth, imageHeight, imageSize);
return true;
}
else
{
unsigned char* outTempData = nullptr;
ssize_t outTempDataLen = 0;
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;
}
}
//多级纹理的 初始化
bool Texture2D::initWithMipmaps(MipmapInfo* mipmaps, int mipmapsNum, 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;
}
if(_pixelFormatInfoTables.find(pixelFormat) == _pixelFormatInfoTables.end())
{
CCLOG("cocos2d: WARNING: unsupported pixelformat: %lx", (unsigned long)pixelFormat );
return false;
}
const PixelFormatInfo& info = _pixelFormatInfoTables.at(pixelFormat);
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;
}
//Set the row align only when mipmapsNum == 1 and the data is uncompressed
//只有一个
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::deleteTexture(_name);
_name = 0;
}
//生成 Texture
glGenTextures(1, &_name);
GL::bindTexture2D(_name);
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
CHECK_GL_ERROR_DEBUG(); // clean possible GL error
// Specify OpenGL texture image
int width = pixelsWide;
int height = pixelsHigh;
//根据级别创建 传输数据到OpenGL
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);
}
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);
}
GLenum err = glGetError();
if (err != GL_NO_ERROR)
{
CCLOG("cocos2d: Texture2D: Error uploading compressed texture level: %u . glError: 0x%04X", i, err);
return false;
}
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;
// shader
setGLProgram(GLProgramCache::getInstance()->getGLProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE));
return true;
}
//总之就是 根据Image里面的数据 生成texture 传输数据到opengl
//并 进行相关配置
下面看怎么渲染的
//在定点绘制
void Texture2D::drawAtPoint(const Vec2& point)
{
GLfloat coordinates[] = {
0.0f, _maxT,
_maxS,_maxT,
0.0f, 0.0f,
_maxS,0.0f };
GLfloat width = (GLfloat)_pixelsWide * _maxS,
height = (GLfloat)_pixelsHigh * _maxT;
GLfloat vertices[] = {
point.x, point.y,
width + point.x, point.y,
point.x, height + point.y,
width + point.x, height + point.y };
GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_TEX_COORD );
_shaderProgram->use();
_shaderProgram->setUniformsForBuiltins();
GL::bindTexture2D( _name );
#ifdef EMSCRIPTEN
setGLBufferData(vertices, 8 * sizeof(GLfloat), 0);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, 0);
setGLBufferData(coordinates, 8 * sizeof(GLfloat), 1);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, 0);
#else
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);
#endif // EMSCRIPTEN
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
//指定矩形的位置绘制
void Texture2D::drawInRect(const Rect& rect)
{
GLfloat coordinates[] = {
0.0f, _maxT,
_maxS,_maxT,
0.0f, 0.0f,
_maxS,0.0f };
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*/ };
GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_TEX_COORD );
_shaderProgram->use();
_shaderProgram->setUniformsForBuiltins();
GL::bindTexture2D( _name );
#ifdef EMSCRIPTEN
setGLBufferData(vertices, 8 * sizeof(GLfloat), 0);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, 0);
setGLBufferData(coordinates, 8 * sizeof(GLfloat), 1);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, 0, 0);
#else
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);
#endif // EMSCRIPTEN
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
//相关属性的配置 简单的的opengl操作
void Texture2D::generateMipmap()
{
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
}
//配置 过滤方式
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
}
主要思想就是,使用同一个纹理的多个精灵,就可以只保存它们的顶点以及纹理信息,共用同一个纹理,从而合并到一个批次。
class CC_DLL TextureAtlas : public Ref
{
public:
/** creates a TextureAtlas with an filename and with an initial capacity for Quads.
* The TextureAtlas capacity can be increased in runtime.
*/
//从文件 创建 无非最后得到其中的纹理 复制给本地
static TextureAtlas* create(const std::string& file , ssize_t capacity);
/** creates a TextureAtlas with a previously initialized Texture2D object, and
* with an initial capacity for n Quads.
* The TextureAtlas capacity can be increased in runtime.
*/
static TextureAtlas* createWithTexture(Texture2D *texture, ssize_t capacity);
bool initWithTexture(Texture2D *texture, ssize_t capacity);
/** updates a Quad (texture, vertex and color) at a certain index
* index must be between 0 and the atlas capacity - 1
@since v0.8
*/
void updateQuad(V3F_C4B_T2F_Quad* quad, ssize_t index);
/** Inserts a Quad (texture, vertex and color) at a certain index
index must be between 0 and the atlas capacity - 1
@since v0.8
*/
void insertQuad(V3F_C4B_T2F_Quad* quad, ssize_t index);
void drawNumberOfQuads(ssize_t n);
/** draws n quads from an index (offset).
n + start can't be greater than the capacity of the atlas
@since v1.0
*/
void drawNumberOfQuads(ssize_t numberOfQuads, ssize_t start);
/** draws all the Atlas's Quads
*/
void drawQuads();
/** listen the event that renderer was recreated on Android
*/
private:
void renderCommand();
void setupIndices();
void mapBuffers();
void setupVBOandVAO();
void setupVBO();
protected:
//存储 索引
GLushort* _indices;
GLuint _VAOname;
GLuint _buffersVBO[2]; //0: vertex 1: indices
bool _dirty; //indicates whether or not the array buffer of the VBO needs to be updated
/** quantity of quads that are going to be drawn */
ssize_t _totalQuads;
/** quantity of quads that can be stored with the current texture atlas size */
ssize_t _capacity;
/** Texture of the texture atlas */
Texture2D* _texture;
/** Quads that are going to be rendered */
//所有 精灵的 的矩形 信息
V3F_C4B_T2F_Quad* _quads;
};
//实现
//最终都到了 bool TextureAtlas::initWithTexture(Texture2D *texture, ssize_t capacity)
TextureAtlas * TextureAtlas::create(const std::string& file, ssize_t capacity)
{
TextureAtlas * textureAtlas = new (std::nothrow) TextureAtlas();
if(textureAtlas && textureAtlas->initWithFile(file, capacity))
{
textureAtlas->autorelease();
return textureAtlas;
}
CC_SAFE_DELETE(textureAtlas);
return nullptr;
}
TextureAtlas * TextureAtlas::createWithTexture(Texture2D *texture, ssize_t capacity)
{
TextureAtlas * textureAtlas = new (std::nothrow) TextureAtlas();
if (textureAtlas && textureAtlas->initWithTexture(texture, capacity))
{
textureAtlas->autorelease();
return textureAtlas;
}
CC_SAFE_DELETE(textureAtlas);
return nullptr;
}
bool TextureAtlas::initWithFile(const std::string& file, ssize_t capacity)
{
// retained in property
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(file);
if (texture)
{
return initWithTexture(texture, capacity);
}
else
{
CCLOG("cocos2d: Could not open file: %s", file.c_str());
return false;
}
}
bool TextureAtlas::initWithTexture(Texture2D *texture, ssize_t capacity)
{
CCASSERT(capacity>=0, "Capacity must be >= 0");
// CCASSERT(texture != nullptr, "texture should not be null");
_capacity = capacity;
_totalQuads = 0;
// retained in property
this->_texture = texture;
CC_SAFE_RETAIN(_texture);
// Re-initialization is not allowed
CCASSERT(_quads == nullptr && _indices == nullptr, "");
_quads = (V3F_C4B_T2F_Quad*)malloc( _capacity * sizeof(V3F_C4B_T2F_Quad) );
_indices = (GLushort *)malloc( _capacity * 6 * sizeof(GLushort) );
if( ! ( _quads && _indices) && _capacity > 0)
{
//CCLOG("cocos2d: TextureAtlas: not enough memory");
CC_SAFE_FREE(_quads);
CC_SAFE_FREE(_indices);
// release texture, should set it to null, because the destruction will
// release it too. see cocos2d-x issue #484
CC_SAFE_RELEASE_NULL(_texture);
return false;
}
//先 置0
memset( _quads, 0, _capacity * sizeof(V3F_C4B_T2F_Quad) );
memset( _indices, 0, _capacity * 6 * sizeof(GLushort) );
#if CC_ENABLE_CACHE_TEXTURE_DATA
/** listen the event that renderer was recreated on Android/WP8 */
_rendererRecreatedListener = EventListenerCustom::create(EVENT_RENDERER_RECREATED, CC_CALLBACK_1(TextureAtlas::listenRendererRecreated, this));
Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(_rendererRecreatedListener, -1);
#endif
//索引顺序都是一样的 不需要数据 所以先填充
this->setupIndices();
if (Configuration::getInstance()->supportsShareableVAO())
{
//设置 VAO VBO
setupVBOandVAO();
}
else
{
setupVBO();
}
_dirty = true;
return true;
}
void TextureAtlas::updateQuad(V3F_C4B_T2F_Quad *quad, ssize_t index)
{
CCASSERT( index >= 0 && index < _capacity, "updateQuadWithTexture: Invalid index");
_totalQuads = MAX( index+1, _totalQuads);
_quads[index] = *quad;
_dirty = true;
}
//插入 或者 更新 V3F_C4B_T2F_Quad
void TextureAtlas::insertQuad(V3F_C4B_T2F_Quad *quad, ssize_t index)
{
CCASSERT( index>=0 && index<_capacity, "insertQuadWithTexture: Invalid index");
_totalQuads++;
CCASSERT( _totalQuads <= _capacity, "invalid totalQuads");
// issue #575. index can be > totalQuads
auto remaining = (_totalQuads-1) - index;
// last object doesn't need to be moved
if( remaining > 0)
{
// texture coordinates
memmove( &_quads[index+1],&_quads[index], sizeof(_quads[0]) * remaining );
}
_quads[index] = *quad;
_dirty = true;
}
真正的核心 ,就是batchCommand的渲染逻辑。
// TextureAtlas - Drawing
void TextureAtlas::drawQuads()
{
this->drawNumberOfQuads(_totalQuads, 0);
}
void TextureAtlas::drawNumberOfQuads(ssize_t numberOfQuads)
{
CCASSERT(numberOfQuads>=0, "numberOfQuads must be >= 0");
this->drawNumberOfQuads(numberOfQuads, 0);
}
//最后都到这里了
void TextureAtlas::drawNumberOfQuads(ssize_t numberOfQuads, ssize_t start)
{
CCASSERT(numberOfQuads>=0 && start>=0, "numberOfQuads and start must be >= 0");
if(!numberOfQuads)
return;
GL::bindTexture2D(_texture->getName());
//绑定VAO
if (Configuration::getInstance()->supportsShareableVAO())
{
//
// Using VBO and VAO
//
// FIXME:: update is done in draw... perhaps it should be done in a timer
//更新数据
if (_dirty)
{
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
// option 1: subdata
// glBufferSubData(GL_ARRAY_BUFFER, sizeof(_quads[0])*start, sizeof(_quads[0]) * n , &_quads[start] );
// option 2: data
// glBufferData(GL_ARRAY_BUFFER, sizeof(quads_[0]) * (n-start), &quads_[start], GL_DYNAMIC_DRAW);
// option 3: orphaning + glMapBuffer
glBufferData(GL_ARRAY_BUFFER, sizeof(_quads[0]) * _capacity, nullptr, GL_DYNAMIC_DRAW);
void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
memcpy(buf, _quads, sizeof(_quads[0])* _totalQuads);
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, 0);
_dirty = false;
}
GL::bindVAO(_VAOname);
//这里 是把这些精灵 统一绘制 合并成一次DrawCall
glDrawElements(GL_TRIANGLES, (GLsizei) numberOfQuads*6, GL_UNSIGNED_SHORT, (GLvoid*) (start*6*sizeof(_indices[0])) );
}
//这里不是 绘制 只是一个向 Render报告 我使用批次绘制了一次 绘制了 多少个顶点
//Render用这些 信息 只是为了调试 统计
CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,numberOfQuads*6);
}
下一篇 分析 UI系统 的实现。