Jump to content

pixi.Graphics objects batching without sprite


Sergey Zhuravlev
 Share

Recommended Posts

Hi, I've got some problem here.

I'm making prototype of document rendering solution and now i'm stuck because of rendering performance issue.

Shortly:

I've got opentype.js, that loads font and parses it. Then, when i want to render some word, i'm creating TextContainer, that extends pixi.Container and filling it with Glyph instances (one for each symbol).

Every Glyph is extended pixi.Graphics and looks like this

class GlyphRenderModel extends pixi.Graphics {
    constructor(font, char) {
        super();

        this.meta = {
            width: 0
        };

        this._fillGlyph(font, char);
    }

    getInstance() {
        return new GlyphRenderModelInstance(this);
    }

    _fillGlyph(font, char) {
        let charCode = char.charCodeAt(0);
        let glyphIndex = font.tables.cmap.glyphIndexMap[charCode];

        if (!charCode || !glyphIndex) {
            return;
        }

        let glyphModel = font.glyphs.glyphs[glyphIndex];
        let renderModel = font.getPath(char, 0, 0, DEFAULT_FONT_SIZE);

        this._convertPathToGraphics(renderModel);
        this.meta.width = glyphModel.advanceWidth / glyphModel.path.unitsPerEm * DEFAULT_FONT_SIZE;
    }

    _convertPathToGraphics(path) {
        // some convert from opentype format
        // creating GraphicsData here (lineTo, bezierCurveTo, quadraticCurveTo)
    }
}

GlyphRenderModelInstance here is simplest thing on earth: 

class GlyphRenderModelInstance extends pixi.Graphics {
    constructor(glyphRenderModel) {
        super();
        this.renderModel = glyphRenderModel;
    }

    renderWebGL(renderer) {
        // plugin, that renders this.renderModel with transformations of current object
        let renderPlugin = renderer.plugins[RENDER_PLUGIN]; 

        renderer.setObjectRenderer(renderPlugin);

        renderPlugin.render(this);
    }

    getWidth() {
        return this.renderModel.meta.width * this.scale.x;
    }
}

It gets link to GlyphRenderModel and renders in directly to parent with transformations:

let textModel = new TextRenderModel(); //extended pixi.Container

textModel.setFill(fill);
textModel.setFontSize(fontSize);
textModel.position.set(x, y);

let letterX = 0;

for (let index = 0; index < charsCount; index++) {
    let char = chars[index];
    let glyph = this.getGlyph(font, char).getInstance();

    if (index) {
        letterX += glyphs[index - 1].getWidth(); //position without kerning
    }

    glyph.x = letterX;
    glyphs[index] = glyph;

    textModel.addChild(glyph);
}

This solution is good for memory, but has poor performance. Here is example for 1 page, on 20 pages it freeze:

pixi perf.jpg

Most of time takes calling webGL's binding and drawElements. (VectorArrayObject.active and VectorArrayObject.draw)

In my case I can't create sprites, because I need to have smooth scaling and vector paths is only solution. 

Also i can't create full copy of original Glyph, because it will eat a lot of memory, only link.

My question is - can i merge few Graphics elements into one or draw some Graphics in one draw call?

It looks weird, that i can't batch draw requests into one, because it's only case, when GPU can be very fast.

Link to comment
Share on other sites

On 22.09.2016 at 11:39 PM, xerver said:

A graphics object per glyph is going to be terrible. If you want to rendering text efficiently using the geometry from a font file, then look into SDF text.

Thanks! I've tried to use sprites, and i've got 1-2 ms per page. But it's still slowly, so i've got some more questions:

  • Why updateTransform and rendering are separated methods? Each of them iterate over all children and calls their methods. When I extended pixi.Container and overrided renderWebGL to something line this
    renderWebGL(renderer) {
        let children = this.children;
    
        // over ~500 elements here
        for (let index = 0, count = children.length; index < count; index++) {
            children[index].updateTransform();
            children[index].renderWebGL(renderer);
        }
    }

    timing for full re-rendering decreased from 4ms to 1.5ms.

  • How can i re-render only part of scene? when I'm changing content on one page, I don't want to touch other pages. Sprite caching doesn't work: some times pages are too large and each of them hits canvas size limit (actual for smartphones and tablets).

  • I've made AtlasManager, that creates and controls glyph atlases. What is the best size of one atlas? I heard, 1024px is GPU-friendly value, is it true?

  • I've got over 200 pages on stage, each has ~1400 glyphs. If I use container.getBounds(true) for off-canvas checking and disabling rendering, it takes too much time. Can I make it faster somehow?

Link to comment
Share on other sites

  • Because they are just different. updateTransform() is also called whenever you want to get current bounds after some changes.
  • There's no way to re-render only part of stage in PIXI.
  • up to 2048 is supported everywhere, some mobile devices cant work with 4096.
  • create "MyBitmapText" class, extend Container, override calculateBounds() method that way it doesnt look into children, override _calculateBounds() that way it computes bounds faster.

Also look into BitmapText implementation, may be you can take something from it.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...