Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: nanovg with sokol_gfx? #633

Open
ib00 opened this issue Feb 20, 2022 · 30 comments
Open

Question: nanovg with sokol_gfx? #633

ib00 opened this issue Feb 20, 2022 · 30 comments

Comments

@ib00
Copy link

ib00 commented Feb 20, 2022

Has anybody attempted to write a nanovg renderer with sokol_gfx?

@RobLoach
Copy link

A Google Search didn't return any meaningful results, other than #29 . You could ask pplux.

@Sakari369
Copy link

Sakari369 commented Jul 13, 2022

Would be interested in the same thing, did you find out anything @ib00 ? How about @pplux, did you get nanovg working with sokol_gfx, perhaps you could share some sample code ? Thanks for any information :)

@ib00
Copy link
Author

ib00 commented Jul 13, 2022

Unfortunately not. The closest thing (for 2D drawing) is sokol_gp:
https://github.com/edubart/sokol_gp

@pplux
Copy link
Contributor

pplux commented Jul 13, 2022

Hi! at the time I was mixing sokol rendering with another gl-drawing library, in the end it worked. And maybe you can try that, nanovg supports render directly with gl so you just need to be careful to recover and save the proper gl state between the two libraries.

But depends very much on what you need, if you just need some basic 2D rendering... well, consider imgui to which sokol offers a proper full implementation on top of sokol_gfx, and you ca pretty much draw whatever you want with it:

ocornut/imgui#3606

@Sakari369
Copy link

Thanks @pplux and @ib00 for the tips.
Basically was looking into a quick way to render some mock lines and paths, but managed actually just with the Sokol_GL sgl_begin_lines() for now until doing a proper implementation.

@darkuranium
Copy link
Contributor

Hi. I made this a while ago, but never published it because it was never properly tested (plus I wanted to write a custom library). But I guess it'll be useful to others, too.

I still need to figure out the shader compiler to be able to distribute it for everything, but in principle, it works.

I don't have access to it today (not at home), but if someone reminds me in a few days, I can try to find the code.

@DagAgren
Copy link

DagAgren commented Nov 2, 2022

@darkuranium Very interested here.

@darkuranium
Copy link
Contributor

@DagAgren @ib00 Okay, so I wanted to clean this up and make a "proper" fork, but didn't get around to it due to a lack of time.

So here's just the nanovg_sokol.h file: https://gist.github.com/darkuranium/746a1cdbb73004d6567783650a4b557f

Caveats:

  • Currently only contains code for GLES2/WebGL, GLES3/WebGL2, and GLCORE33 (i.e. no D3D or similar). I've been wanting to use the Sokol shader compiler, but could never quite get it working.
  • It's based off of the official/unmaintained repository (because I made this a while ago).

If anyone would be interested in making a "proper" fork of NanoVG (as in: a hard fork, to clean some things up), let me know. There are a few things that bother me about it (e.g. the fact that it defines STB_IMAGE_IMPLEMENTATION itself, which conflicts with other uses of that library).


Note: I've been wanting to make an alternative to NanoVG (a completely new API [as in: no compatibility with NanoVG], to work around some NanoVG limitations), but there's a few obstacles I've yet to figure out. The main features I'd like to add on top of NanoVG are (in descending order of priority):

  • the ability to store computed path geometry into a buffer for reuse (this is the main thing that I need)
  • better anti-aliasing, especially of strokes (NanoVG struggles with thin strokes; somewhat high-priority)
  • the ability to properly render/anti-alias in perspective mode; in other words, proper support for perspective transform matrices instead of just 2x3 (rationale: to support the full set of CSS transforms, and for "pretty" rendering in 3D scenes)
  • better state management + batching, to reduce the number of draw calls (somewhat low-priority)
  • pixel-perfect bezier rasterization (fragment shader-based, instead of subdivision; low-priority)

The main obstacle at the moment is in figuring out how to do anti-aliasing for paths (there is a very promising paper on wavelet rasterization, but it might need compute shaders to implement on GPUs). Ideally, I would also like to avoid relying on the stencil buffer, because it's problematic for anti-aliasing and because for 2D uses, it means unnecessary extra memory for the depth buffer (and for 3D software, it means the depth buffer cannot be 32-bit, but must be 24+8 depth/stencil).
If I have to use a stencil buffer, I'm thinking some sort of SMAA or FXAA perhaps? Or simply the NanoVG approach of drawing lines around the path edges for AA (it's a shame that conservative rasterization isn't widely available, because it would be of great help here).

The reason I'm mentioning this is to query to see if anyone would be interested in contributing to such a project. The main issue is, as mentioned, anti-aliasing; and to some extent rasterization if I want to avoid stencil buffer approaches.
It would definitely use sokol_gfx for it, at least if at all possible (and if not, then I'd create an extension header for sokol, to add the needed functionality).

@ib00
Copy link
Author

ib00 commented Nov 20, 2022

Thanks for taking the time and making your implementation available.

There is a cool small library for 2D drawing different than nanovg:
https://github.com/edubart/sokol_gp

But, ultimately, something like this would be fantastic:
https://acegikmo.com/shapes/

@zeromake
Copy link

zeromake commented Oct 11, 2023

@darkuranium

nanovg_sokol.h file: https://gist.github.com/darkuranium/746a1cdbb73004d6567783650a4b557f

#define NANOVG_SG_PIPELINE_CACHE_SIZE 32

output:

sokol_gfx.h:15715:0: 
        PIPELINE_POOL_EXHAUSTED: pipeline pool exhausted

change NANOVG_SG_PIPELINE_CACHE_SIZE to 4 not error
or change sg_setup pipeline_pool_size

I try sokol shader compiler but draw is invalid, I'll try to see if it works fine in opengl

my nanovg_sokol.h
my example_sokol.c

on windows opengl dx11 all rendered normally
on macosx metal draw is invalid: on xcode gpu debug find vs_buffer always 0, nanovg metal not use vs_buffer but use MTLRenderCommandEncoder->setVertexBuffer(0), may vs buffer and uniform exist at the same time
use black apple osx is not work, but white apple osx work is ok……

@ib00
Copy link
Author

ib00 commented Mar 18, 2024

@zeromake Did you get this to work (in OpenGL)?

@zeromake
Copy link

zeromake commented Mar 19, 2024

@ib00
merge to master branch
my nanovg_sokol.h
my example_sokol.c

example

> git clone https://github.com/zeromake/nanovg.git && cd nanovg
> xmake f -c -vD -m debug --example=y --pkg='sokol' -y
> xmake b example.sokol
> xmake r example.sokol

@ib00
Copy link
Author

ib00 commented Mar 21, 2024

@zeromake Very cool! I have some trouble compiling on linux. Shaders don't get compiled (and output into .h files)
properly.

@zeromake
Copy link

@zeromake Very cool! I have some trouble compiling on linux. Shaders don't get compiled (and output into .h files) properly.

I missed sokol_shader target, now is fix

@void256
Copy link

void256 commented Oct 21, 2024

I'm looking for a nanovg_sokol.h and I think right here is still the best place to look for one 😉

I've got the @zeromake nanovg_sokol.h to work but I'm failing to get text rendering to work, unfortunately.

I think the problem is, that sgnvg__renderUpdateTexture is called with a partial texture update by nanovg which sg_update_image does not support since it wants to update the whole texture (see this answer by master floooh sokol_gfx.h: Partial buffer/image update #323 ).

I think we need to keep the image in cpu memory, do the partial update there and then upload the whole texture to the gpu...

@floooh
Copy link
Owner

floooh commented Oct 22, 2024

In sokol_fontstash.h (AFAIK FontStash is by the same author as NanoVG) I solved a similar problem by maintaining a complete system memory copy of the font atlas texture, let fontstash apply partial updates on that system memory copy, and then call sg_update_image() to update the entire sokol-gfx texture (behind a dirty flag so that this only happens at most once per frame).

This texture update is in a separate sfons_flush() function which must be called once per frame:

See:

sokol/util/sokol_fontstash.h

Lines 1772 to 1783 in 4bda146

SOKOL_API_IMPL void sfons_flush(FONScontext* ctx) {
SOKOL_ASSERT(ctx && ctx->params.userPtr);
_sfons_t* sfons = (_sfons_t*) ctx->params.userPtr;
if (sfons->img_dirty) {
sfons->img_dirty = false;
sg_image_data data;
_sfons_clear(&data, sizeof(data));
data.subimage[0][0].ptr = ctx->texData;
data.subimage[0][0].size = (size_t) (sfons->cur_width * sfons->cur_height);
sg_update_image(sfons->img, &data);
}
}

@void256
Copy link

void256 commented Oct 23, 2024

Thanks master @floooh for the comment and the code example!

Very helpful and I added it to nanovg_sokol.h in a similar way (keep the whole texture in cpu memory and flush all textures right before the draw call) and guess what, it actually seems to work and I now have working text as well 😎

Here is the modified nanovg_sokol.h (Revision 1) where I added the following:

  • inline the sokol-shdc compiled shaders to get a truly single file header without external dependencies
    • which increased the line count from about 1500 to 8500 lines .. whoops 😳
    • we should probably still keep the original shader source code around somewhere ... 🤔
  • added a cpu memory copy of the texture that is flushed right at the start of sgnvg__renderFlush to fix text rendering
  • added some optional debug trace log to log all sgnvg__* calls if necessary (to see what's actually going on)

This probably still needs more testing and optimizing but it's a start or let's say a steady progress of getting to a proper nanovg_sokol.h some day (most of the actual work was done by @darkuranium and @zeromake of course!).

Note: This still needs a pipeline_pool_size increase, something like the following or it won't work:

    sg_desc ctx = (sg_desc) {
        .logger.func = slog_func,
        .environment = sglue_environment(),
        .pipeline_pool_size = 1024, // <-------- add this line or you'll get errors
    };

🤓

@floooh
Copy link
Owner

floooh commented Oct 24, 2024

Embedding the shader is a good thing IMHO.

we should probably still keep the original shader source code around somewhere

...in the sokol headers with embedded shaders I do that in a comment, together with the sokol-shdc cmdline (kinda important because after big changes to sokol-gfx or sokol-shdc I need to go through those headers and update the shaders, and having the original source and sokol-shdc cmdline right in the header is the best way to do that - still a bit of a hassle, but manageable).

/*
Embedded source code compiled with:
sokol-shdc -i sfons.glsl -o sfons.h -l glsl410:glsl300es:hlsl4:metal_macos:metal_ios:metal_sim:wgsl -b
(not that for Metal and D3D11 byte code, sokol-shdc must be run
on macOS and Windows)
@vs vs
uniform vs_params {
uniform mat4 mvp;
uniform mat4 tm;
};
in vec4 position;
in vec2 texcoord0;
in vec4 color0;
in float psize;
out vec4 uv;
out vec4 color;
void main() {
gl_Position = mvp * position;
#ifndef SOKOL_WGSL
gl_PointSize = psize;
#endif
uv = tm * vec4(texcoord0, 0.0, 1.0);
color = color0;
}
@end
@fs fs
uniform texture2D tex;
uniform sampler smp;
in vec4 uv;
in vec4 color;
out vec4 frag_color;
void main() {
frag_color = vec4(1.0, 1.0, 1.0, texture(sampler2D(tex, smp), uv.xy).r) * color;
}
@end
@program sfontstash vs fs
*/

Having to bump the pipeline_pool_size to 1024 is a bit strange, that's a lot of pipeline objects. Do we know why this is needed?

I don't see any sg_make_pipeline in the linked source code. Can you point me to the place where the pipeline objects are created? I'd at least want to have a quick look if I see anything weird there :)

@void256
Copy link

void256 commented Oct 24, 2024

Yes, I was thinking of embedding the shader source as well. Thanks for the encourangement and the example. I'll look into it 👀

The 1024 I've got from one of the examples linked earlier by @zeromake (I think) and I was happy to get something working without any errors and didn't question it further 😉

The pipelines are allocated with sg_alloc_pipeline in the sgnvg__renderCreate function. The interesting part starts here.

I haven't figured it out completely but there seem to be 7 different pipelines for the different stroke and fill types (see SGNVGpipelineType and a LRU pipeline cache with 32 entries per pipeline type (see NANOVG_SG_PIPELINE_CACHE_SIZE)

So, I think we eventually keep 32 pipelines for each of the 7 different kind of stroke and fill types cached around? Which would be a max of 224 pipelines?

I'm not yet sure why we need that cache though probably to save the most recently used settings or something?

I've only started to take a closer look at all the inner workings. Maybe @darkuranium is still around and can explain some more details of the inner workings a bit?

@void256
Copy link

void256 commented Oct 24, 2024

nanovg_sokol.h (Revision 2) - now with embedded source shader files and a awk command line call to automatically extract the source shaders back as files 😎 see the direct Link to the embedded shader source for details.

Use the following awk:

awk '/^8< / {out_file=$2; print "// " out_file > out_file; next} /^>8/ {out_file=""; next} {if (out_file) print $0 >> out_file}' nanovg_sokol.h

to extract the shader .glsl files back (only tested with the macOS awk but should work with gawk as well).

The files are included as a source code comment similar to the example from master floooh above and are marked with ascii scissors 8< like so:

8< filename.glsl
precision highp float;
...
>8

The awk copies everything between 8< and >8 to a file with a name specified after the opening 8<.

@floooh
Copy link
Owner

floooh commented Oct 25, 2024

Ah ok, I guess the pipeline cache allows direct lookup via some index built from 'state bits', that would explain the high number of 'slots', and maybe only few of those slots are actually occupied for a typical NanoVG scene. That's fine I think.

@void256
Copy link

void256 commented Oct 26, 2024

There were a couple of little issues left that I had to fix:

  • sg_update_image was sometimes immediately called when the texture was created and
  • partial texture updates with x position other than 0 were not handled properly.

Both issues have now been fixed and the original memononen/nanovg demo now looks like this on my MacBookPro, rendering happy using Sokol with SOKOL_METAL 😎:

nanovg_sokol_demo.mov

🥳

I've updated nanovg_sokol.h and put it together with the demo from memononen/nanovg into the following repository: void256/nanovg_sokol.h.

@darkuranium
Copy link
Contributor

darkuranium commented Oct 28, 2024

I've only started to take a closer look at all the inner workings. Maybe @darkuranium is still around and can explain some more details of the inner workings a bit?

Hi, I am generally around, sorry about the lack of reply — I was busy Thu & Fri, and then ill over the weekend.

I want to make it clear that there are definitely better ways of doing vector graphics — I believe the [alas abandoned, as is typical of their projects] Intel library manages to do it with just a handful of pipeline states, for one. This one was more or less a 1:1 port of NanoVG, enough to get it working.

I'm actually very interested in making a new library in this vein (though I wouldn't make it Sokol-specific, but agnostic, like NanoVG is), especially since I need a few features NanoVG doesn't offer --- most notably, caching of computed primitives and (optionally, but it would be a big nice-to-have) 3D transforms.

NanoVG could then be offered as a compatibility layer if there's enough demand, I suppose.

Edit: And to answer your question, it's been a while, so I don't remember much anymore, but I could have another look and try to remember — if this is still needed.

@void256
Copy link

void256 commented Oct 28, 2024

Thanks for responding @darkuranium and of course for creating nanovg_sokol.h in the first place 😉

I've not been able to test it a lot yet but so far it works quite excellent! So, many kudos for creating it 🥇 even when you only ported it from nanovg_gl (I suppose?) it was still quite a bit of work to bring it to Sokol, I'm sure.

If, one day, you're able to take another look and maybe remember some things, it would probably help future maintainers to not mess things up too much 😉 And it doesn't have to be much, maybe some background ideas or notes about what's going on might already help.

The intel link looks very interesting and your idea of a new NanoVG as well, especially since the original one is not really maintained anymore.

Caching of computed primitives sounds very interesting too and something I was thinking about as well 👀 I mean, it's not necessary to redraw stuff all the time if it didn't change, so caching and reusing parts sounds very promising.

Maybe instead of creating a complete new NanoVG it would be possible to make it into, I don't know, some kind of extension or something? Like stick with the original nanovg and add nanovg_extension or so to it which provides some of the missing functionality?

@darkuranium
Copy link
Contributor

darkuranium commented Oct 29, 2024

There are a few other reasons as to why I'd go for a new implementation. The main one being that I'd like to choose a completely different approach, one based on modern research into this, to get better performance and fewer pipeline changes (the latter is the big issue).

Another one is that I'm not a huge fan of NanoVG depending on fontstash, and how fontstash then uses STB_TRUETYPE_IMPLEMENTATION unconditionally (which causes linker errors if I ever try to use that in tandem with some other TrueType needs). That's a much smaller problem in that it's much more easily fixed (as opposed to the performance issues, which are primarily core architectural issues as to how NanoVG even operates/draws primitives).


It's also a bit offtopic here — I'd be more than happy to discuss this further, but it's perhaps best moved somewhere else ^^

@floooh
Copy link
Owner

floooh commented Oct 29, 2024

It's also a bit offtopic here — I'd be more than happy to discuss this further, but it's perhaps best moved somewhere else ^^

FWIW I enjoy lurking and for lack of alternatives the sokol Github issues also doubles as discussion forum ;)

@darkuranium
Copy link
Contributor

FWIW I enjoy lurking and for lack of alternatives the sokol Github issues also doubles as discussion forum ;)

Fair, but there's probably a bunch of people subscribed to this issue to keep track of the nanovg situation, and they have no interest in wider discussions.

On that topic, GitHub did add a "Discussions" feature which is apparently free for public repos --- might be something to consider? Not saying it's the best solution, just food for thought.

@void256 If you'd be interested in further discussion on major NanoVG changes / alternatives, I can be reached via IRC, Discord, Matrix, and a few other things --- plus I have a Mattermost server. The invitation is open to others too, of course (including @floooh).

@floooh
Copy link
Owner

floooh commented Oct 29, 2024

might be something to consider? Not saying it's the best solution, just food for thought.

Yeah it's rolling around the back of my head for a while now, but so far the issue threads are 'good enough'.

@void256
Copy link

void256 commented Oct 29, 2024

I pretty much agree with everything you said @darkuranium, including that this thread is getting slightly off topic now 😉
I'll contact you on Discord and we continue there.

I'm sure that a new and improved NanoVG would be appreciated by many of it's current users. There are more than 200 issues open over there.

@framkant
Copy link

framkant commented Nov 1, 2024

What a treat to find this thread! Just started to use sokol for a small project and the last time I needed 2D shapes I used nanovg extensively (quite som years ago!). Just wanted to show some appreciation! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants