Open matsukaz opened 9 years ago
I have also seen a similar issue when updating ui::Text on applicationWillEnterForeground. As described above, letters would be missing. I think this must be an issue with font atlas as the same letters will be missing across all ui::Text. If another scene is launched, the issue is resolved.
See the example below. On Moto G (v4.4.4, cocos2d-x-3.2), the ui::Text instances initially read 'Init', then immediately update to 'Change' - all is ok. However, when the app returns from background, they are supposed to update to 'Resume' but instead read ' e e'. In the code, I have used 'Comfortaa-Bold.ttf', but the issue occurs with any TTF font.
As a workaround, delaying the update seems to have resolved the issue but it's not an ideal solution.
HelloWorld::~HelloWorld()
{
ccArrayFree(array);
array = nullptr;
}
bool HelloWorld::init()
{
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
int widthNum = visibleSize.width / 150;
int heightNum = visibleSize.height / 100;
widthNum++;
heightNum++;
array = ccArrayNew(heightNum * widthNum);
for(int h = 0; h < heightNum; h++)
{
for(int w = 0; w < widthNum; w++)
{
ui::Text* txt = ui::Text::create("Init", "Comfortaa-Bold.ttf", 34);
txt->setPosition(Vec2(origin.x + (w * 150) + 75,
origin.y + (h * 100) + 50));
this->addChild(txt);
int idx = (h * widthNum) + w;
array->arr[idx] = txt;
}
}
for(int i = 0; i < array->max; i++)
{
ui::Text* txt = dynamic_cast<ui::Text*>(array->arr[i]);
txt->setString("Change");
}
return true;
}
void HelloWorld::applicationWillEnterForeground()
{
for(int i = 0; i < array->max; i++)
{
ui::Text* txt = dynamic_cast<ui::Text*>(array->arr[i]);
/* Letters are missing */
txt->setString("Resume");
}
}
I've found that when updating the _atlasTextures
map, in some resolutions (larger ones) the height of the data is just too large. Inside bool FontAtlas::prepareLetterDefinitions(const std::u16string&)
there is (near the end) a line like this:
_atlasTextures[_currentPage]->updateWithData(data, 0, startY,
CacheTextureWidth, _currentPageOrigY - startY + _commonLineHeight);
In that line, the last parameter (the height) is (in my case) larger than 900 and the maximum value it seems to be 512 so OpenGL throws me the error GL_INVALID_VALUE and the game crashes.
@unkiwii What's the engine version? You can modify FontAtlas::CacheTextureWidth and FontAtlas::CacheTextureHeight in cocos\2d\CCFontAtlas.cpp
@Dhilan007 I fixed here by making FontAtlas::CacheTextureHeight and FontAtlas::CacheTextureWidth non-const and so I can modify them from my game. In the build we have (cocos2d-x-3.2) the values are 512 for both, in some devices (sd) that is a fine value but in other devices (hd) that size is too short and we use 1024, but as we don't want textures so big in small devices we still use 512 as the default value and in the larger devices we change it to 1024, maybe this can be done from cocos instead of the game or you can change it to be able to set it at least in the initialization of the game (before any font texture is created)
I have encountered exactly the same problem as @matsukaz reported.
It can be easily reproduced by a piece of simple code.
#ifndef __test__HomeScene2__
#define __test__HomeScene2__
#include "cocos2d.h"
USING_NS_CC;
#include <string>
#include <vector>
using namespace std;
class HomeScene2 : public Scene {
public:
CREATE_FUNC(HomeScene2);
bool init();
void onEnter();
void onKeyReleased(EventKeyboard::KeyCode keycode, Event* event);
private:
void go1();
void go2();
void go(const vector<string>& text);
int offsetY;
};
#endif /* defined(__test__HomeScene2__) */
#include "HomeScene2.h"
bool HomeScene2::init(){
Scene::init();
offsetY = CCDirector::getInstance()->getWinSize().height - 120;
LayerColor* bg = LayerColor::create(Color4B(128, 128, 128, 255));
addChild(bg);
return true;
}
void HomeScene2::onEnter(){
Scene::onEnter();
//keyback
auto listener = EventListenerKeyboard::create();
listener->onKeyReleased = CC_CALLBACK_2(HomeScene2::onKeyReleased, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
go1();
}
void HomeScene2::onKeyReleased(EventKeyboard::KeyCode keycode, cocos2d::Event *event) {
if (keycode == EventKeyboard::KeyCode::KEY_ESCAPE) {
Director::getInstance()->end();
}
}
const vector<string>& getText1() {
static vector<string> v;
if (v.empty()) {
v.push_back("cocos2d-x 3.3 字体测试");
}
return v;
}
const vector<string>& getText2() {
static vector<string> v;
if (v.empty()) {
v.push_back("[1] ");
v.push_back("我是");
v.push_back("你的 ");
v.push_back("小呀小苹果");
v.push_back(" 怎么爱你都不嫌多");
}
return v;
}
void HomeScene2::go1() {
go(getText1());
runAction(CCSequence::createWithTwoActions(CCDelayTime::create(1.0f), CCCallFunc::create(this, callfunc_selector(HomeScene2::go2))));
}
void HomeScene2::go2() {
go(getText2());
runAction(CCSequence::createWithTwoActions(CCDelayTime::create(1.0f), CCCallFunc::create(this, callfunc_selector(HomeScene2::go1))));
}
void HomeScene2::go(const vector<string> &text) {
int offsetX = 30;
for (int i = 0; i < text.size(); i++) {
string s = text[i];
Label* label = Label::createWithTTF(s, "font/STHeiti-Medium.ttc", 22);
label->setAnchorPoint(Vec2::ZERO);
addChild(label, 1);
label->setPosition(offsetX, offsetY);
offsetX += label->getContentSize().width;
}
offsetY -= 50;
}
If it works properly, it should turn out to be like this:
But sometimes it fails like this: or this:
The problematic devices include: Galaxy S3, Xperia Z, and Xiaomi MI2. My cocos2d-x version is 3.2 and 3.3.
My workaround: After some study, I also find the problem is because of glTexSubImage2D. When the outcome label is corrupted, the underlying texture (which is _atlasTextures in FontAtlas) also lacks of the missed characters. My solution is rather rough: I make _rendererRecreate always true, that is, totally avoid calling glTexSubImage2D. It surely decreases the execution performance. But due to the font atlas cache, it will not rewrite the texture too often. And the whole application fps keeps acceptable.
Further study: In the test code above, I also find an interesting phenomenon. If I change the code
offsetY = CCDirector::getInstance()->getWinSize().height - 120;
to:
offsetY = CCDirector::getInstance()->getWinSize().height + 120;
that is, make the first several lines outside the screen. After that, the problem disappears. Note that the lines outside the screen must contain all the characters used in future.
And here is a clue about this problem: http://dheartf.blog.163.com/blog/static/3850546520127231034460/ I'm sorry that the original blog is in Chinese... Let me try to give a summary. The author dealt with a glTexSubImage2D failure problem on Xiaomi MI2, which is also a problematic device in this issue. The conclusion is, on this device, glTexSubImage2D will fail if the target texture is 'locked' by some other action (may be a drawing, for example). So he added glUseProgram(0) after each drawing action, and the problem was solved.
The way is worth a try because it explains the outside-screen-correct phenomenon above. For outside screen labels, only glTexSubImage2D is called, and the drawing is skipped. So all the characters are processed correctly before the texture is actually used. However when I try this method directly in cocos2d-x 3.3, the problem still occurs. Maybe it's because the rendering is more complicated in cocos2d-x. Actually I'm not very familiar with the new cocos2d-x 3.x rendering. If anyone interested in this issue finally resoled the problem, it would be greatly appreciated.
When I create multiple Labels at once, sometimes fonts disappeared like "H o Wor d" instead of "Hello World". This occurs on some android devices like Galaxy S3 or Xperia Z.
I found out that this problem occurs when Texture2D object, defined as "FontAtlas::_atlasTextures", fails to update correctly. Texture2D::updateWithData() is calling
to update Texture2D object, but it seems like this call is ignored without any errors. My cocos2d-x version is 3.2 and 3.3 RC0.
Founded workaround:
Calling glFlush() after glTexSubImage2D() reduced the occurrence of this problem (still occurs, but rare).
Questions:
Is there any good workaround or solution for this problem? I know that calling glFlush() each time may impact on performance.