Open wtholliday opened 9 years ago
It is only used to cache the points per frame.
Sorry, what do you mean by "cache the points per frame"?
That is, it is the buffer that holds the tessellated points for rendering.
Ok, cool. Do you have a sense of whether it would be a good idea for me to add something that caches tessellations between frames? I was thinking of hashing the path commands (I'd prefer that to having the client need to keep track of a path object).
Probably not a bad idea. I postponed the geometry caching in order to see how the GL3 backend evolved. Also, I did not want to add any more memory consumption.
The smallest amount of data to hash is the commands ctx->ncommands
, and I would recommend to cache the results of nvg__expandFill
and nvg__expandStroke
. It means to also include the stroke width and line-join in the hash too. A really smart cache would allow affine transformation of the path data too, but that is a bit mot involved.
Hi,
I'm currently working on this too. So you might want to check out my fork. I did not want to create a pull request yet as I still got some problems with antialiasing when scaling baked paths correctly - the most important features should work though. See https://github.com/ytsedan/nanovg.
My fork adds the following features:
Here is some more detail.
Display lists are implemented in the front end. All calls to the back end render functions (renderFill, etc.) are cached if a display list is bound. In my use case, I still want to be able to change the transform and alpha of the baked paths when finally rendering the display list. Imagine animating the position or alpha of a UI button without re-tesselating the button geometry or the button's label text layout. That's why I did a major change to nanovg, which might break some scenarios. Instead of transforming the vertices in the front end on the CPU before passing them to the back end, all vertex transformations are now performed in the vertex shader. That means I had to change the interface for the back end (which will break other back end, e.g. DX11). Also, it is no longer possible to change the transform while constructing a path, e.g. changing the transform between two calls to nvgLineTo - which is ok for me and you can still transform your points before passing them to nanovg. I added a define that marks all the code changes releated to this feature (see NVG_TRANSFORM_IN_VERTEX_SHADER). I hope I got all antialiasing handling correctly (as far as possible without retesselating) I had to change the tesselation of butt/square caps for this.
When drawing the display list the baked paths are still copied to the back end's vertex buffer - I thought a while if it possible to avoid this additional copy, but could not come up with a simple solution that does not require a lot more front end back end interaction (like different VBO for less dynamic stuff). I also added a little unit test to the demo app (see CACHE define in gl2 demo) which renders the demo scene every 60th frame to the cache and in all other frames just the display list with an animated transform. Note, when rendering the same animation directly without draw lists the text layouting begins du 'shake', probably due to pixel alignment/rounding when scaling the scene; so this is another advantage of using the lists.
A font fallback can be used to basically mix multiple fonts: e.g. use the system font for rendering text and use a custom icon font for glyphs in a certain unicode range. The advantage of doing this directly in fontstash is, that text layouting just works. This really comes in handy when writing tool tips that include icons etc. You probably can use this feature for chinese symbols too.
The text layouting did not break lines at hyphens, which is a great feature to have especially for languages that can have really long words.
I needed this to call nvgTextGlyphPositions with a fixed size array of NVGglyphPosition. When returning the next iterator you can call nvgTextGlyphPositions in chunks of e.g. 32 glyphs (similar the next row pointer in nvgTextBreakLines).
Thanks for this great library .. I'll create a pull request once I have tested the new features a little better; but feel free to review my changes.
@ytsedan, cool stuff! I see you pass a scale factor into nvg__tesselateBezier
to get the tolerance right.
Tessellation in local space would definitely be better for my path hashing idea (I'd like to avoid having to keep track of display lists in my client code). What sort of performance improvements are you seeing in the demo?
Tesselating the whole demo scene vs. just copying the baked paths to the backend is more than 10x faster, just talking about the CPU side. But I haven't measured alot yet; FPS increase is not equally high.
For hashing you will still have to fill the command buffers in the front end to calculate the hash etc. so that will probably be slower. Other problem with hashing is, you need some simple kind of garbage collection to free no longer used paths, but depends on how you want to use that. For my UI use case the display lists work pretty well.
@ytsedan 10x! Great!
My thought for collecting the paths was just to delete any that aren't used by the time nvgEndFrame
is called. That's rather eager, but should work fine for most cases.
After noticing my Ui renderer sometimes spend ~50 to ~90% of the profiler time in nvg__tesselateBezier
, I'm really interested by your changes ytsedan. Caching stuff simply makes more sense in the context of an Ui library anyway. Can we imagine your changes ever being merged into nanovg vanilla ? I guess that depends on Mikko ?
I'm gonna give a try at your fork and see how it's working out
EDIT : just switched my Ui elements to one list per element and I've got about 5X performance improvement. Congrats for the work !
Hi!
I'm working on a cocos-like game engine based on nanovg.
I've tried @ytsedan 's fork to improve performance, but there are some issues when rendering using flip transforms even without using display lists:
if(mFlipX)
{
if(mFlipY)
{
const float otherTransform[6] = { -1, 0, 0, -1, 0, 0 }; // WORKS
nvgTransformPremultiply(transform, otherTransform);
}
else
{
const float otherTransform[6] = { -1, 0, 0, 1, 0, 0 }; // NOTHING SHOWS
nvgTransformPremultiply(transform, otherTransform);
}
}
else if(mFlipY)
{
const float otherTransform[6] = { 1, 0, 0, -1, 0, 0 }; // NOTHING SHOWS
nvgTransformPremultiply(transform, otherTransform);
}
The problem dissapears disabling NVG_TRANSFORM_IN_VERTEX_SHADER.
Any ideas?
My guess: might be the backface culling. Typically nanovg transforms the vertices prior to generating triangles, which ensures that there are no backfacing polygons. If you use NVG_TRANSFORM_IN_VERTEX_SHADER
however, the vertices are transformed later - so triangles may be backfacing. You could try to remove all glEnable(GL_CULL_FACE);
in the nanovg source to see if this makes any difference.
Removing all glEnable(GL_CULL_FACE); calls fixes the problem with direct rendering, but it appears again when display lists are used.
Sorry for not replying earlier to this thread, building a house has taken it's toll to my free time :) A couple of quick answers:
No problem. Feel free to review my fork, if you find some time. I changed a lot of stuff that I needed for my project, that might not be of interest for everybody though (like a fast and simple way to render rectangles).
There a still some problems with the caching: e.g. when a display cache contains text and therefore references the glyph atlas, it will become invalid if the atlas needs to grow and is recreated in the endFrame method. I just added a utility function to poll if the atlas will change, so that I can also recreate the cached paths .. but that's only a workaround. Also combining a cached scissor with the current scissor is only correct if both are axis aligned (otherwise clip shape would not be rectangle, I guess).
HTML? Seriously ;) I actually like the simplicity of defining a unicode range. Though I already thought about supporting soft hyphen in LaTeX style \-
@GValiente I cannot reproduce the problem. I tried to applied different transforms to the cached demo scene and it worked.
I think fast way to render a quad / blit image and 9-sprite should be part of the API.
html, why not? ;) More seriously, one option would be to add a bit more state and allow text spans, something like this:
nvgBeginText(vg, x,y);
nvgTextColor(vg, ...);
nvgTextSpan(vg, "Red ", NULL);
nvgTextColor(vg, ...);
nvgTextSpan(vg, "Blue", NULL);
nvgEndText(vg);
Ditto for text box. Multiline text will become even more complicated, though. Maybe it would be better to just allow easy co-operation with pango :)
The engine uses display lists for rendering everything except when a node (a bunch of NanoVG primitives):
Without these exceptions, the output of the two branches seems to be the same.
@ytsedan I'll try to reproduce the problem when I have time.
Just some quick notes:
Another known issue you should be aware of is, that text will be rendered incorrectly if the glyph atlas grows (as pointed out above). A workaround is to call nvgFindOutdatedDisplayListResources before nvgEndFrame and invalidate all display lists that use text.
@ytsedan Thanks for the quick notes :)
Steps to reproduce the flipped transforms problem:
1) Remove all glEnable(GL_CULL_FACE); calls in nanovg_gl.h
2) Create a nvgFlipX function in nanovg:
void nvgFlipX(NVGcontext* ctx) { NVGstate* state = nvg__getState(ctx); float t[6] = { -1, 0, 0, 1, 0, 0 }; nvgTransformPremultiply(state->xform, t);
nvgTransformInverse(state->invxform, state->xform);
}
3) Add to example_gl2.c:
if (cache) //draw display list to screen with custom transform { nvgFlipX(vg);
float scale = 0.8f;
// ...
}
Just curious is this is already doing some caching of path tessellations? (A lot of my runtime is spent in
nvg__tesselateBezier
, tessellating the same paths).Thanks!