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

PROTO: Create text_objects and use them for rendering to improve text rendering performance #773

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

Vagabond
Copy link
Member

@Vagabond Vagabond commented Nov 19, 2024

This is a cherry-pick/rework of the commit from the n64 branch. It builds and works but it needs more work/cleanups.

EDIT(Nopey): Closes #677

src/engine.c Outdated Show resolved Hide resolved
@@ -411,6 +416,11 @@ void menu_set_padding(component *c, int padding) {
m->padding = padding;
}

void menu_invalidate_help_text_cache(component *c) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anything use this?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

game_menu_return should be calling it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I goofed the rebase?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also be I missed it in the original PROTO submit.

@@ -212,6 +213,7 @@ static void textinput_free(component *c) {
textinput *tb = widget_get_obj(c);
surface_free(&tb->sur);
str_free(&tb->text);
text_objects_free(tb->text_cache, (sizeof(tb->text_cache) / sizeof(tb->text_cache[0])));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't the second parameter an integer?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size_t is an unsigned int, but will be 64bit or 32bit based on your architecture. And it's not a signed int because I never expect to have a negative amount of objects to destroy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean why not just pass the number of text objects

Copy link
Contributor

@Nopey Nopey Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size_t is fine, but the arraysize magic should probably be replaced with a named constant (same as the [0]'s, [1]'s, [2]'s and [3]'s, as pointed out in a different conversation)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should become a macro called ARRAY_SIZE(tb->text_cache).
That makes it forward compatible, when someone expands or reduces the array, they won't need to change this instance.

src/game/gui/textinput.c Outdated Show resolved Hide resolved
@@ -27,6 +27,7 @@ typedef struct {

textinput_done_cb done_cb;
void *userdata;
text_object text_cache[3];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be 2 not 3 here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These text_caches should seriously be named variables.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't disagree, or at least used named indices

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

src/game/scenes/scoreboard.c Show resolved Hide resolved
@@ -312,73 +319,83 @@ void vs_render(scene *scene) {
char text[256];
char money[16];
fight_stats *fight_stats = &scene->gs->fight_stats;
static int force_redraw = 1;
if(force_redraw) {
// TODO: Find out why this caching doesn't work. The cache is built before the stats are correct.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: investigate this

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This problem can be seen when bringing up the in-game menu the second time through, it will show an empty spot where the "explanation" is on the N64 branch indicating that the text object wasn't correctly dirtied.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a game menu on the 'vs' screen?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhhh, that's interesting. I didn't realize I left this comment in the VS, it probably needs to be in arena somewhere.

src/game/scenes/vs.c Outdated Show resolved Hide resolved
@@ -38,6 +39,8 @@ vec2i interpolate(vec2i start, vec2i end, float fraction) {
}

void chr_score_create(chr_score *score) {
score->text_cache[0].dynamic = true;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear why this needs to be dynamic. Only the x/y position changes, maybe those should not be cached?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dynamic means something special, it means that the frame-over-frame rendering of the text object cannot be guaranteed to be the same. As the object may get occluded and require a re-render. Unfortunately I couldn't figure out a way to track this, so I resorted to hand picking objects that were NOT-SAFE to copy from the previous frame.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only really matters on the N64, where we can copy from the previous frame buffer, instead of allocating an intermediate texture.
The fallback would be to allocate the intermediate texture on the N64.
And can be completely ignored on the PC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, ok, maybe we can have a better name than dynamic, or have a comment that explains it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other names I've considered: not_copy_safe, needs_intermediate_texture, not_static_composition,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is uncachable or changes_frequently appropriate?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more of a hint to the renderer telling it not to 2nd level optimize.
0th lvl - render each letter.
1st lvl - allocate a new texture, render each letter to texture on dirty bit, use the allocated texture on all invocations without the dirty bit.
2nd lvl - render each letter to FB on dirty bit, copy from FB without dirty bit.

@@ -246,3 +248,10 @@ void video_draw_remap(const surface *src_surface, int x, int y, int remap_offset
dst.y = y;
draw_args(src_surface, &dst, remap_offset, remap_rounds, 0, 255, 255, 0, options);
}

void video_render_text_block(text_object *text) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this belongs in video.c

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here was that the video plugin can decide what to do with a text object.
For instance the N64 side doesn't traverse the letters if the object is not marked dirty. The code in here for video.c (PC) is unchanged from the original call pattern. If there is a better place that would allow the renderer to decide how to handle the text objects, I'm open to suggestions. This really seemed like the correct place. For instance the NULL renderer will just do nothing here too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why we discussed the possible pluggable text renderer backend earlier. The plugin backend would decide how to call the video renderer instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but maybe we can do something intermediate for now, before we go to the "solves all the problems" pluggable text renderer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I WANT IT NOW! We should throw in kubernetes too somehow ;)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@katajakasa Would this be something you could rework later, after this change goes in?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is. Let's get this part merged first.

@@ -55,7 +57,7 @@ component *label_create_with_width(const text_settings *tconf, const char *text,
widget_set_obj(c, local);
widget_set_render_cb(c, label_render);
widget_set_free_cb(c, label_free);

// local->text_cache[0].dynamic = true;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

@@ -110,7 +110,11 @@ static void menu_render(component *c) {
if(m->help_bg2) {
video_draw(m->help_bg2, m->help_x - 8, m->help_y - 8);
}
text_render(&m->help_text_conf, TEXT_DEFAULT, m->help_x, m->help_y, m->help_w, m->help_h, (*tmp)->help);
// TODO: The text_cache should move into inner component. In this case the one of the s->objs.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this comment is wrong and it's not invalidated every frame

Copy link

@nopjne nopjne Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what I ran into was that the menu didn't animate, so I marked all these dynamic.

Comment on lines 84 to 85
int offset;
int limit;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int offset;
int limit;
int palette_offset;
int palette_limit;

Might also be possible to make these a smaller (8-bit) integer type, but that sounds like work for another PR.

int y;
int offset;
int limit;
surface *sur;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
surface *sur;
surface const *sur;

Since this is not an owned surface, and we do not plan to modify the surface via this pointer.

if(cached_text->max_letters < (text_len * 4)) {
size_t size = (text_len * sizeof(letter) * 4);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is magic number 4?

Copy link
Contributor

@Nopey Nopey Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like 4 is the number of draw calls per letter.
At the very least, it should be a named constant.

Perhaps all four draw calls should be stored in one struct letter? This would increase the number of fields in struct letter/struct text_object, but should decrease overall memory usage.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the shadows, we support up to 3 shadows per letter, including the letter itself. Actually per the code we support 4 shadows per letter (+letter). But I've never seen more than 3 being used.

render_char_surface(settings, state, sur, x, y);
return sur->w;
void letter_set_parameters(text_object *cached_text, surface *sur, int x, int y, int offset, int limit) {
cached_text->cur_letter->x = x;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an assert that checks that cached_text->cur_letter < cached_text->letter + cached_text->max_letters

@Vagabond
Copy link
Member Author

Vagabond commented Nov 25, 2024

Ok, I think I've gained some familiarity with this PR now, and I'd like to suggest some changes:

  • I'd like the invalidation of a text cache to be explicit, not automatic. We can know when we invalidate text strings and update them appropriately.
  • I'd like to separate flowing text into a text object and rendering a text object in the API.
  • I'd like not to hold a pointer to the 'text' inside the text cache, since it should no longer be needed as per point 1
  • It might be useful, in some cases, to render multiple strings to the same text_cache. A good example is the fight stats page in tournament mode, or the HAR/Player stats in the mechlab. If we aren't tracking strings anymore, this also seems doable. This would massively reduce the number of text cache objects we have to maintain.

cc @nopjne @katajakasa

It might be useful, in some cases, to render multiple strings to the same text_cache. A good example is the fight stats page in tournament mode, or the HAR/Player stats in the mechlab. If we aren't tracking strings anymore, this also seems doable. This would massively reduce the number of text cache objects we have to maintain.

As long as that doesn't unnecessarily extend the bounds of the text object.

@Vagabond
Copy link
Member Author

It might be useful, in some cases, to render multiple strings to the same text_cache. A good example is the fight stats page in tournament mode, or the HAR/Player stats in the mechlab. If we aren't tracking strings anymore, this also seems doable. This would massively reduce the number of text cache objects we have to maintain.

As long as that doesn't unnecessarily extend the bounds of the text object.

My idea would be that if we were flowing several chunks of text into the same text_object, then we could 'finish' the flow that would allow any underlying texture allocation stuff to happen, with the final set of bounds?

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

Successfully merging this pull request may close these issues.

4 participants