-
-
Notifications
You must be signed in to change notification settings - Fork 10.7k
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
Request for feedback: custom rect / custom glyphs / custom loaders with dynamic_fonts
branch.
#8466
Comments
I am updating my Rust bindings and backend (easy-imgui-rs) and it goes pretty well. However I have some issues with CustomRects, that I think are reproducible in C++ too.
The crash is in
because
|
Thank you Rodrigo for your feedback, this is very useful! (1) I have fixed this now. (2) The function is not public, however there are other factors which could lead to
(3) I tried to build a repro and couldn't reproduce this behavior. if (ImGui::Button("Add Image"))
{
data->CustomRectImage = atlas->AddCustomRectRegular(200, 200);
const ImTextureRect* r = atlas->GetCustomRect(data->CustomRectImage);
unsigned char* pixels = atlas->TexData->GetPixelsAt(r->x, r->y);
for (int y = 0; y < r->h; y++)
{
for (int x = 0; x < r->w; x++)
{
float d = ImSqrt(ImLengthSqr({ ImFabs(r->w * 0.5f - x), ImFabs(r->h * 0.5f - y) }));
int alpha = (int)ImLinearRemapClamp(80, 95, 255, 0, d);
((ImU32*)pixels)[x] = IM_COL32(120, 255, 120, alpha);
}
pixels += atlas->TexData->GetPitch();
}
}
if (data->CustomRectImage != -1)
{
const ImTextureRect* r = atlas->GetCustomRect(data->CustomRectImage);
ImVec2 uv0, uv1;
atlas->GetCustomRectUV(r, &uv0, &uv1);
ImGui::Image(atlas->TexID, ImVec2(r->w, r->h), uv0, uv1);
} |
Hi again! I have additional feedback. (1). This works now, thanks! (2). A function to remove a rect would be nice although I don't really need it yet. But as long as I will be able to avoid crashes I'll be happy. (3). After some intense debugging I've seen that indeed I'm setting the texture_id to 0, but not on the first frame... The problem came from this comment in
So when writing the Rust binding to When drawing an
That in my Rust binding becomes something equivalent to:
This usually works just fine, but sometimes when the atlas texture is about to be rebuilt, the value of I've just disobeyed the comment and wrote the BTW, |
I'll add one.
Right. The suggestion in comment makes sense if only using images that you loaded yourself, for which you'd immediately have a valid ImTextureUserID. My assumption is that drawing using the font atlas texture only made sense for debugging.... but that reasoning is entirely flawed when we consider the use of custom rect, which we aim to facilitate and promote going onward. We need a different strategy and should tell non-C bindings to allow constructing a full-fledged
I will add comments and work on improving it. If it is still fresh in your mind, can you share more about about your confusion/assumption on those types, so I can tailor comments to reduce that confusion for future users? |
Sure... Well, I think I actually understand them, but I keep on re-reading my notes... After some thought, I think the confusion comes down to these two points. First, the name. The old Second, the invariants of
But that is clearly impossible to maintain if Then I thought that if
I think they should be instead:
If that is the case I would expect And finally there is the fact that
The |
Thanks! I'm moving this discussion to the other thread #8465 (comment) |
Just to clarify on that one point: the TexID is required to users to use e.g. |
Hello, Omar! Hello everyone! We are now one step closer to produce these effects using MSDF. I have created playground application to generate and display glyphs with distance-field data. The application is located in that repository. It uses msdfgen under the hood. You will need Vcpkg to compile it. First few words about how it works: Next some notes and thoughts |
Hello @ocornut! When using a I have traced the issue back to this line in
That is, the last Nobody noticed because all images in the Demo are square. And all of mine too, except one. |
Ooh, an excellent find. Should this code use vector operators more? If it were written as |
@rodrigorc Oops! Fixed by ee36d5f. @db48x They are user facing structures storing unsigned short values, there's no value in adding dedicated type, nor there is in general to make long-term user facing structures use high-level types. It's mostly showing that embarrassingly I haven't started writing tests for this. |
I understand about the types, but can’t you get the readability and correctness benefits of vector operators even without changing them? Try this: void ImFontAtlas::GetCustomRectUV(const ImTextureRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const
{
IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates
auto position = ImVec2((float)rect->x, (float)rect->y);
auto size = ImVec2((float)rect->w, (float)rect->h);
*out_uv_min = position * TexUvScale;
*out_uv_max = (position + size) * TexUvScale;
} Note that I haven’t actually compiled it; that would take extra steps. |
Heh, I just noticed that you have almost exactly the same code in |
The typo was that we used rect->w instead of rect->h, it could have happened just as well with the alternative you suggested. |
Yes, but if that typo had been in the line |
Amended that change now. |
Hello @ocornut. I've been pondering for a while about that The thing is that now the font atlas grows dynamically, but if during a frame a quad is emitted, then that UVs and texture must be valid during the rendering of that frame... A new bigger texture may be created, but the old one can't be destroyed until the next frame. I assume that the atlas can be extended any time during the frame building when adding a glyph or a custom-rect, although maybe that's not currently the case (but the API is as it were). To put it in code, this would be correct:
But this would be wrong:
Because when getting the I think that what confused me was that An API that I would find easier to grasp, would be like this (I guess would run into problems of backwards compatibility and renderers without
With this API my wrong code above is now correct:
This example may look a bit contrieved, but I actually had this issue in one of my programs. I'm rendering a lot of text in an OpenGl FrameBufferObject, and for that I'm (ab)using the imgui atlas. Since the text changes infrequently I'm baking the quads into a VertexBufferObject, and then I just render it when I need it. When the text changes I just re-bake the text geometry. With the current DearImGui version I can rebuild the geometry and/or do the FBO rendering whenever I want, it just works, because the atlas texture is always the same, and the glyph coordinates are invariant. But with these dynamic fonts things get trickier. Now the textures may not be valid until after And the last one is what makes me think that PS: Sorry for the long message, maybe I've thought too much about this... |
Correct.
Correct.
a = io.Fonts->GetCustomRect(rectA);
b = io.Fonts->GetCustomRect(rectB);
io.Fonts->GetCustomRectUV(a, &uva0, &uva1);
io.Fonts->GetCustomRectUV(b, &uvb0, &uvb1);
ImGui::Image(io.Fonts->TexRef, size, uva0, uva1);
ImGui::Image(io.Fonts->TexRef, size, uvb0, uvb1);
This is actually correct ;) but only because you used I believe the issue you wanted to showcase was: a_idx = io.Fonts->AddCustomRectRegular(....);
a = GetCustomRect(a_idx);
io.Fonts->GetCustomRectUV(a, &uva0, &uva1); // Get UV
b_idx = io.Fonts->AddCustomRectRegular(....); // may invalidate UV above
b = io.Fonts->GetCustomRect(rectB);
io.Fonts->GetCustomRectUV(b, &uvb0, &uvb1); // Get UV
ImGui::Image(io.Fonts->TexRef, size, uva0, uva1);
ImGui::Image(io.Fonts->TexRef, size, uvb0, uvb1); or even simpler: a_idx = io.Fonts->AddCustomRectRegular(....);
a = GetCustomRect(a_idx);
io.Fonts->GetCustomRectUV(a, &uva0, &uva1); // Get UV
ImGui::Text("SOME TEST"); // may invalidate UV above!!
ImGui::Image(io.Fonts->TexRef, size, uva0, uva1); I am adding more aggressive comments to outline that fact: c414104.
ImFontGlyph X0/Y0/X1/Y1 are not rectangle coordinates they are offsets from current layout/text position, and they need to be floats. It's however true that X1-X0 is going to be equal to the corresponding rectangle width, and likewise for height. But they are not pixel aligned. I would also want to reserve the possibility of storing those as half-precision fp16 or fixed-point eventually. The U0/V0/U1/V1 are indeed cached versions of if you were to call GetCustomRectUV() with ImFontGlyph::PackId. They need to be cached for performances reason because text rendering is a rather hot path.
Breaking backward compatibility with old renderers would be a big no but then I am not sure I understand how this proposed change would affect backends? That said, your proposed change doesn't make sense once you understand they are different values and any extra indirection or touching of more memory in hot-loop is potentially a performance issues. (I have a FIXME next to PackID because ideally it could be moved in a separate array.)
I understand this create an extra challenge for cases were you were caching UV coordinates, but I don't see how your proposed potential change would change anything to it. Your baked VertexBufferObject would still refer to invalid coordiates. You may use
I don't understand what this means or imply, can you clarify?
Feedback is always greatly useful, thanks. |
Further musing on those specific API: (1) I think I should rename (2) Since underlying logic for (3) I have considered, and still considering offering a single function that generate a struct that also carry UV coordinates. I don't like the two functions. But note that if I make the change highlighted in (2) then many codepaths who only need the UV coordinates would call the second function only, not both. |
I potential way to solve (2) and (3) would be: struct ImFontAtlasRect
{
unsigned short x, y;
unsigned short w, h;
ImVec2 uv0, uv1; // corresponding UV coordinates
}; bool ImFontAtlas::GetCustomRect(int id, ImFontAtlasRect* out_rect); Typical call site: const ImTextureRect* r = atlas->GetCustomRect(data->CustomRectImage);
ImVec2 uv0, uv1;
atlas->GetCustomRectUV(r, &uv0, &uv1);
ImGui::Image(atlas->TexRef, ImVec2(r->w, r->h), uv0, uv1); Would become ImFontAtlasRect r;
atlas->GetCustomRect(data->CustomRectImage, &r);
ImGui::Image(atlas->TexRef, ImVec2(r.w, r.h), r.uv0, r.uv1); What do you think? |
Ah, I assumed
What I mean is that every time I get UV coordinates from the atlas, they refer to a specific texture. I think that currently that texture is the value of I like the new
And now to use it:
You could even have a And the same could be said about
I may want to create a button with a big $ sign doing this:
My proposed change doesn't actually allows me to do anything new. I could just carefully read I don't actually care too much if I have to write I think that a comment about About my baked VBOs, my current intention is to create a separate VBO per |
Yes, I'm changing it now.
Adding those.
That would have undesirable performances side-effects as hot loops would be touching +35% more cache lines. Here's the full reworked API // An identifier to a rectangle in the atlas. -1 when invalid.
// The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it.
typedef int ImFontAtlasRectId;
#define ImFontAtlasRectId_Invalid -1
// Output of ImFontAtlas::GetCustomRect() when using custom rectangles.
// Those values may not be cached/stored as they are only valid for the current value of atlas->TexRef
// (this is in theory derived from ImTextureRect but we use separate structures for reasons)
struct ImFontAtlasRect
{
unsigned short x, y; // Position (in current texture)
unsigned short w, h; // Size
ImVec2 uv0, uv1; // UV coordinates (in current texture)
ImFontAtlasRect() { memset(this, 0, sizeof(*this)); }
}; ImFontAtlasRectId AddCustomRect(int width, int height); // Register a rectangle. Return -1 (ImFontAtlasRectId_Invalid) on error.
bool GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const; // Get rectangle coordinates for current texture. Valid immediately, never store this (read above)! Legacy inline ImFontAtlasRectId AddCustomRectRegular(int w, int h) { return AddCustomRect(w, h); } // RENAMED in 1.92.X
inline const ImFontAtlasRect* GetCustomRectByIndex(ImFontAtlasRectId id) { return GetCustomRect(id, &TempRect) ? &TempRect : NULL; } // OBSOLETED in 1.92.X
inline void CalcCustomRectUV(const ImFontAtlasRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const { *out_uv_min = r->uv0; *out_uv_max = r->uv1; } // OBSOLETED in 1.92.X
IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.X: Use custom ImFontLoader in ImFontConfig
IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // ADDED AND OBSOLETED in 1.92.X Added recap of changes on top post. |
This new API with the comments are great, thanks. I assume the same caveat applies to But this line doesn't say all the truth:
because I intend to consider them valid for as long as But I don't think it's a use case frequent enough to deserve any change in your new comments. I'm an advanced user now! 🤓. |
I have added an optional output to I have also added a I have an (unpushed patch) for this, encoding clear/build regeneration in Custom rectangles are cleared in
Now, the first 3 I don't care/mind so much: they are rare or explicit. And we can remove the "Clear Output" button from user-facing spots if it is problematic, or move it to a lower-level debug feature. But the fact that changing font loader needs to clear custom rects really bothers me. Because I want to make it easy and natural to experiment with font settings and that include dynamically changing between e.g. stb_truetype and Freetype. And simultaneously the end goal is that custom rect should be first-class citizens used by custom widgets, and it would be unacceptable if their use constrained our ability to toy with font settings live. So what I am going to do if to try working out a way for |
March 31: reworked API.
AddCustomRect()
,GetCustomRect()
,ImFontAtlasRect
now.Before this branch:
Before March 31:
From March 31:
March 5:
This is an extension to #8465 solely dedicated to feedback and questions related to:
AddCustomRectRegular()
AddCustomRectFontGlyph()
(now made obsolete)Because the atlas may be compacted/shuffled, and because fonts have dynamic size, those API cannot be necessarily used in the same way.
In particular, while
AddCustomRectRegular()
may be of use,AddCustomRectFontGlyph()
doesn't make sense very much if the font can be rescaled. The legacy function assume a "default" font size which is the one passed to AddFontXXX function, but that concept is likely to be lifted.Instead, using a custom
ImFontLoader
source is likely to be a preferred solution, but that solution is still in fix, andImFontLoader
is still only in imgui_internal.h.Linking to #8107, #7962, #1282.
Here's an example of how to create a "fully procedural" font using imgui_internal.h API:
In particular, the question e.g. #2127 which is essentially using a custom bitmap loader could use this technique, possibly coupled with "locking" the font to a fixed size:
If you try using this at another size e.g. 20.0f, it will use the closest size (16.0f) and rely on bilinear filtering.
The text was updated successfully, but these errors were encountered: