Sergey Zhuravlev Posted September 22, 2016 Share Posted September 22, 2016 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: 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. Quote Link to comment Share on other sites More sharing options...
xerver Posted September 22, 2016 Share Posted September 22, 2016 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. Quote Link to comment Share on other sites More sharing options...
Sergey Zhuravlev Posted September 26, 2016 Author Share Posted September 26, 2016 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? Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted September 27, 2016 Share Posted September 27, 2016 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. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.