Set node's position on integer screen coordinates

When a cocos2d::Label has its bottom left corner on a non-integer coordinate, say (10.6, 9,2), then it appears blurry. I have tried to fix this by enabling 2D projection in the director and by disabling antialiasing on the label’s texture, but it is still ugly.

As you can see on the attached image, the only way to have a nice text is to place it on integer coordinates. Otherwise it appears blurry if antialiasing is enabled or messed up if it is disabled. Note that these tests have been done with the 2D projection.

Is there a way to force a node to be displayed on integer coordinates?

1 Like

There is a macro you could check and disable if not already. Not sure it will fix your issues though.

#define CC_NODE_RENDER_SUBPIXEL 0
#define CC_SPRITEBATCHNODE_RENDER_SUBPIXEL 0

Following your suggestion I have disabled these two options. The problem persists though.

If you were able to get it to align on integer coordinates for the left example images then you can just always truncate/round the values whenever you set the position of the label.

There’s probably also a similar label or fontatlas subpixel macro. I believe these subpixel macros force the vertices onto integer coordinates, but if not you can always go into the internals of CCNode.cpp and round or truncate the x,y values of the polyinfo/quad.

One last thought: The label may need to have no parent (except for the scene itself) in order to guarantee the integer coordinate doesn’t get messed up when the parent transform matrix is applied.

If a label is a child of another node(s) then the transform matrix will be multiplied and likely make the resulting coordinates non-integer. If you want to test this out you should make the label a child of only the scene itself and then update the label’s position whenever the original parent node moves to the parent’s world position.

You are right on the parent/child positions. Actually I made the left part of the image by having a single label in the scene at integer coordinates. After further investigation I have found that cocos2d::Sprite follows the subpixel config option only when rendered using a batch node. Unfortunately I use a cocos2d::Label with a system font and thus without batch.

If I could apply the subpixel config on non batch sprites I think I would get the expected result.

Yeah it looks like you’re correct. You could try updating the following to force sprite’s as well as those that are children of a batch node. However, I thought that LabelTTF rendered to sprites in a batch node, so if this works that’s interesting that they’re individual sprites now. If that’s the case it’s probably due to the team assuming or finding that auto-batch for labels works well/fast enough.

CCSprite.cpp:444 (approx line num)

// self rendering
// ...
// Don't update Z.
// SRISUBPIXEL == SPRITE_RENDER_IN_SUBPIXEL for brevity
_quad.bl.vertices.set(SRISUBPIXEL(x1), SRISUBPIXEL(y1), _positionZ);
_quad.br.vertices.set(SRISUBPIXEL(x2), SRISUBPIXEL(y1), _positionZ);
_quad.tl.vertices.set(SRISUBPIXEL(x1), SRISUBPIXEL(y2), _positionZ);
_quad.tr.vertices.set(SRISUBPIXEL(x2), SRISUBPIXEL(y2), _positionZ);

Maybe you’re using “System Font” instead of “TTF Font”? I think the former creates regular sprites and the latter creates sprites that are children of (and associated with) a batch node (texture atlas).

I use System Fonts indeed, it is the most efficient and allows emojis.

The modification in CCSprite.cpp would not work because it does not modify the transformed coordinates (i.e. when the sprite has a parent).

Still, I managed to obtained integer coordinates by adding an option to TriangleCommands to force integer coordinates. I pass set this option when initializing the command in Sprite::draw(). Then Renderer checks this value in fillVerticesAndIndices() at line 738 in CCRenderer.cpp:

    for(ssize_t i=0; i< count; ++i)
    {
        V3F_C4B_T2F *q = &_verts[i + _filledVertex];
        Vec3 *vec1 = (Vec3*)&q->vertices;
        modelView.transformPoint(vec1);
    }

    // add this test and loop.
    if ( !cmd->isSubpixelRenderingEnabled() )
        for( ssize_t i=0; i != count; ++i )
        {
            V3F_C4B_T2F* const q( &_verts[ i + _filledVertex ] );
            Vec3* const vec1( (Vec3*)&q->vertices );
            vec1->x = ceil( vec1->x );
            vec1->y = ceil( vec1->y );
        }
    
    const unsigned short* indices = cmd->getIndices();

With this code my labels are perfect :slight_smile:

2 Likes

Out of curiosity does CC_NODE_RENDER_SUBPIXEL even do anything??

Looking at the code CC_NODE_RENDER_SUBPIXEL is only used in CCNode.cpp to define RENDER_IN_SUBPIXEL, which is never used…

Perhaps we should replace it with CC_RENDER_SUBPIXEL and #if @j_jorge’s proposal?

–EDIT–
Furthermore CC_SPRITEBATCHNODE_RENDER_SUBPIXEL is actually used in CCSprite.h to define SPRITE_RENDER_IN_SUBPIXEL, which is used by Label as well - not specifically for SpriteBatchNode. This is all very misleading.

I think it was put in as a hack. So in short, yes I think it’s misleading because it wasn’t given much thought. CC_NODE_RENDER_SUBPIXEL just defines the RENDER_IN_SUBPIXEL() macro to perform ceil() on coordinates if it’s not defined. The english of that seems oppositely defined since it’s rounding to integer values if XXXX_IN_SUBPIXEL is applied.

I think the whole [anti]aliased multi resolution support could use a detailed once-over, among many other things.

However, if you want to throw up a PR to fix the system fonts to match this discussion, please feel free.

I don’t think there’s an easy fix to be honest. And really depends on use case.

Using nearest texture filtering and integer vertex data (I modified the Label vert shader to floor a_position) solves the issue for static text but causes artifacts when scaled, rotated or jitter during a translation animation.

Distance field fonts can minimise the blurriness without nearest filtering, but I have experienced artifacts at low font sizes (size 12 shown):

Nearest texture filter + modified vert shader

Distance field enabled

However my girlfriend is currently playing through the Witcher 3 right now :smiley: and I noticed that it suffers from the same translation artifacts, particularly on in-world subtitles.

So this is likely unavoidable for pixel perfect font rendering, but should absolutely be configurable per renderable, so developers can decide based on how the text is expected to be displayed. And not as a global macro.

1 Like