Search code examples
androidlibgdx

LibGDX - Issue with FrameBuffer, Scene2D Table and clipping


I have a custom ImageTextButton in which I render the button to a FrameBuffer first and then draw with frameBuffer.getColorBufferTexture(). I don't really want to do this but I use a custom shader with this button that creates some visual effects and the only way I have been able to achieve it is with a FrameBuffer. I was surprised to find this actually works very smooth and fast though, the whole process takes 1-2ms on slow devices and having several instances doesn't cause any kind of framerate drop, so I am happy with this bit.

The issue I am having though is when I enable clipping on the ImageTextButton (with setClip(true)). The reason for this is the button can change in width, and I would like it to clip the text within the bounds of the button. If I disable the FrameBuffer and render normally, this part also works very well. If I combine the 2, it seems the clipping process gets confused and the result is either no text or very small parts of the text.

So here is the relevant code. I assumed it was because I set the FrameBuffer and SpriteBatch size/projection matrix just to deal with the active area (for efficiency) however if I don't modify any of this and use the same batch/projection matrix, so the FrameBuffer manages the whole screen, it is still the same result.

public void initFrameBuffer(){
    xCache = (int) super.getX(); yCache = (int) super.getY();
    widthCache = (int) super.getWidth(); heightCache = (int) super.getHeight();

    frameBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, widthCache, heightCache, false);

    fboProjectionMatrix.setToOrtho2D(xCache, yCache+heightCache, widthCache, -heightCache);

    this.fbBatch = new SpriteBatch();
    this.fbBatch.setProjectionMatrix(fboProjectionMatrix);

    this.frameBufferReady = true;
}

public void doFrameBuffer(Batch batch, float parentAlpha){
    batch.end();

    frameBuffer.begin();
    fbBatch.begin();
    Gdx.gl20.glClearColor(0f, 0.0f, 0.0f, 0.0f);
    Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
    super.draw(fbBatch, parentAlpha);
    fbBatch.end();
    frameBuffer.end();

    batch.begin();
}

public void drawFrameBufferObject(Batch batch, float parentAlpha){
    batchColorCache = batch.getColor();
    batch.setColor(1.0f, 1.0f, 1.0f, parentAlpha);
    batch.draw(frameBuffer.getColorBufferTexture(), getX(), getY());
    batch.setColor(batchColorCache);
}

@Override
public void draw(Batch batch, float parentAlpha) {
    if (!this.frameBufferReady) initFrameBuffer();
    doFrameBuffer(batch, parentAlpha);
    drawFrameBufferObject(batch, parentAlpha);   
}

Sorry for the long code, it's actually heavily trimmed down for the necessary parts.. Help hugely appreciated as always!


Solution

  • After much playing, the solution I have found is one that could probably be useful in other situations, and that is true clipping of the BitmapFontCache by vertex modification, no scissors involved! So if anyone would find this useful, the code is;

            float xStart = ...start position of clip
            float xEnd = ...end position of clip
    
            //vertex offset numbers
            int x_1 = 0, x_2 = 5, x2_1 = 10, x2_2 = 15;
            int u_1 = 3, u_2 = 8, u2_1 = 13, u2_2 = 18;
    
            for (int j = 0, n = pageCount; j < n; j++) {
                int c = cache.getVertexCount(j);
                int newIdx = 0;
                if (c > 0) { // ignore if this texture has no glyphs
                    float[] vertices = cache.getVertices(j);
                    for(int i = 0; i < vertices.length; i+=20){
    
                        //if any of the vertices are outside the label, don't put them in the new cache
                        if(vertices[i+x2_1] > xStart && vertices[i+x_1] < xEnd){
                            for(int k = 0; k < 20; k++){
                                clippedVerts[j][newIdx+k] = vertices[i+k];
                            }
    
                            //case on major left glyph
                            if(vertices[i+x_1] < xStart){
                                float xDiff = vertices[i+x2_1]-xStart; //difference between right of glyph and clip
                                float xRatio = xDiff / (vertices[i+x2_1]-vertices[i+x_1]);
                                float uDiff = vertices[i+u2_1] - vertices[i+u_1];
                                float newU = vertices[i+u2_1] - uDiff*xRatio;
    
                                clippedVerts[j][newIdx+x_1] = xStart;
                                clippedVerts[j][newIdx+x_2] = xStart;
                                clippedVerts[j][newIdx+u_1] = newU;
                                clippedVerts[j][newIdx+u_2] = newU;
                            }
    
                            //case on major right glyph
                            if(vertices[i+x2_1] > xEnd){
                                float xDiff = xEnd-vertices[i+x_1]; //difference between left of glyph and clip
                                float xRatio = xDiff / (vertices[i+x2_1]-vertices[i+x_1]);
                                float uDiff = vertices[i+u2_1] - vertices[i+u_1];
                                float newU_2 = vertices[i+u_1] + uDiff*xRatio;
    
                                clippedVerts[j][newIdx+x2_1] = xEnd;
                                clippedVerts[j][newIdx+x2_2] = xEnd;
                                clippedVerts[j][newIdx+u2_1] = newU_2;
                                clippedVerts[j][newIdx+u2_2] = newU_2;
                            }
                            newIdx += 20;
                        }
                    }
                }
                clippedIdx[j] = newIdx;
            }
    
            for (int j = 0, n = pageCount; j < n; j++) {
                int idx = clippedIdx[j];
                if (idx > 0) { // ignore if this texture has no glyphs
                    float[] vertices = clippedVerts[j];
                    batch.draw(regions.get(j).getTexture(), vertices, 0, idx);
                }
            }