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

Add BitmapText support? #393

Open
reececomo opened this issue Jan 9, 2024 · 13 comments
Open

Add BitmapText support? #393

reececomo opened this issue Jan 9, 2024 · 13 comments

Comments

@reececomo
Copy link

Any chance of support for using PIXI.BitmapText(...) under-the-hood instead of PIXI.Text(...)?

This library is awesome, but for performance reasons (especially on mobile devices), it would be phenomenal to leverage PIXI.BitmapText instead of PIXI.Text.

Example:

This is an almost drop-in replacement, that shouldn't be too much of a performance hit in 99% of cases:

Obviously there would be other downstream implications. Like if style is changed dynamically at runtime, the text fields and fonts would need to be regenerated at runtime.

// main/src/TaggedText.ts

export default class TaggedText extends PIXI.Sprite {


  // ----- NEW PROPERTIES: -----

  /**
   * Global default BitmapFont options. 
   * @see {PIXI.IBitmapFontOptions}
   */
  public static BITMAP_FONT_OPTIONS?: PIXI.IBitmapFontOptions;

  /**
   * Cache to make sure BitmapFonts are re-used wherever possible.
   */
  private static _cachedBitmapFonts = Map<string, PIXI.BitmapFont>();


  // ----- MODIFIED METHODS: -----

  private createTextFieldForToken(token: TextSegmentToken): PIXI.BitmapText | PIXI.Text {
    /** ... */

    // Creates textfield as PIXI.BitmapText instead of PIXI.Text
    const textField = createBitmapText(text, sanitizedStyle)

    /** ... */
  }


  // ----- NEW METHODS: -----

  private createBitmapText(text: string, style: Partial<PIXI.TextStyle>): PIXI.BitmapText {
    const fontName = this.loadBitmapFontNameFor(sanitizedStyle);
    const bitmapTextStyle = { ...style, fontName } as Partial<PIXI.IBitmapTextStyle>;

    return new PIXI.BitmapText(text, bitmapTextStyle);
  }

  /**
   * Generates a PIXI.BitmapFont for this style configuration (or re-use cached).
   * @returns Unique identifier for this configuration. Use as `fontName` in `PIXI.IBitmapTextStyle`.
   */
  private loadBitmapFontNameFor(style: Partial<TextStyle>, options:): string {
    const fontName: string = md5(JSON.stringify(style));
    const existing = TaggedText._cachedBitmapFonts.get(fontName);

    if (!existing) {
      const font = PIXI.BitmapFont.from(fontName, style, TaggedText.BITMAP_FONT_OPTIONS);
      TaggedText._cachedBitmapFonts.set(fontName, font);
    }

    return fontName;
  }
}
@rfvtgbzxc
Copy link

Can't agree more, pixi-tagged-text has awsome flexibility but with pretty much cost.

@reececomo
Copy link
Author

Even being able to use the "minimum" number of PIXI.Text components - instead of 1-per-word, or 1-per-word-segment - would be a huge performance boost. It's a very big performance overhead to show a paragraph (like an in-game chat box) on certain mobile devices, especially iOS where each texture is uploaded sequentially to the GPU through a webgl-to-metal layer, blocking the main thread.

Will have a bit of a hack and see if I can quickly create a fork that leverages BitmapText instead ❤️

@reececomo
Copy link
Author

reececomo commented Jan 13, 2024

Alright I hacked together a basic fork that is sufficient for our use case (draft PR here).

Setup

Update package.json to the fork:

    "pixi-tagged-text": "reececomo/pixi-tagged-text#stable-v1.0.0",

Implement createTextField(...)

Implement a subclass, handle font loading however you like.

import TaggedText from 'pixi-tagged-text';

export class MyText extends TaggedText {

  protected createTextField(token: TextSegmentToken, text: string, style: Partial<ITextStyle>): Text | BitmapText {
    // You can replace this logic with whatever you need in order to
    // load and style your BitmapFonts (e.g. resize or tint).
    return new BitmapText(text, { fontName: 'MyCustomFont' });
  }
}

I can't gaurantee there won't be issues or edge-cases, but I re-used our existing bitmap font cache, and everything worked on the first second try ❤️.

Have not updated lint or unit tests, but if the maintainers or someone more passionate about getting this into the main repo wants to have a crack, go ahead!

@mimshwright
Copy link
Owner

mimshwright commented Jan 14, 2024

Hello. Thanks @reececomo for the idea. It has been a while since I started this project so I can't remember exactly why I didn't do this from the start. Most likely it was unfamiliarity with the details of the library on my part.
If I can incorporate it without doing a major rewrite, I'll include it in a new version... probably a major version and probably with the option of overriding the change through options.

@mimshwright
Copy link
Owner

mimshwright commented Jan 14, 2024

I refreshed my knowledge of BitmapText and I think this change would actually be fairly major and would be difficult / impossible to get the full feature set. Here's what I am seeing...

  • IBitmapTextStyle is not the same as ITextStyle. Most importantly, bitmaps require a bitmapFont property.
  • IBitmapTextStyle doesn't support most style properties like fontWeight, stroke, wordWrap so a lot of the benefits of tagged-text would be lost.
  • Layout is partially done by creating and measuring a sample of the text for sizing purposes. That would also need to be updated to use bitmaptext
  • Obviously, BitmapFonts require some additional overhead for loading so for testing and demo purposes it would require some extra work.

For me, I don't think this is something I want to pursue, especially if the second point about styles being lost holds true.

If your fork is working for you and you're happy, I think that's awesome. If you have been using it and you're happy with it, and if you can think of a way to incorporate it as an optional feature, that could be cool. Maybe instead of asking people to create a subclass, we provide a new class called TaggedBitmapText. I don't have the bandwidth to write tests and documentation right now so I'd appreciate if you add that to any PRs.

BTW, I think there are some other areas, like caching / memoizing, that could yield some performance benefits. Maybe not on the magnitude of switching to bitmap text, but also wouldn't sacrifice any user experience or features.

Thanks!

(Keeping this issue open for now in case more discussion is needed)

@reececomo
Copy link
Author

I don't think you fully refreshed your knowledge of BitmapText 😂

IBitmapTextStyle is not a drop-in replacement because that style goes into the BitmapFont like in my examples above.

I appreciate you wanting to eagerly reject the PR, but you should at least change the "private" to "protected" for createTextField(...) to make this library extensible.

Honestly the library is super handy, but the performance is very poor, and it only needs some minor tweaks to be ready for a production environment.

Best of luck!

@reececomo reececomo closed this as not planned Won't fix, can't repro, duplicate, stale Jan 14, 2024
@reececomo
Copy link
Author

@rfvtgbzxc quick update: We've published a stable-v2.0.0 version that uses BitmapText as the default text element - and automatically handles font generation, caching, style overrides, etc.

Setup

Update package.json to the fork:

    "pixi-tagged-text": "reececomo/pixi-tagged-text#stable-v2.0.0",

Basic

See README is filled with practical examples (basic & advanced).

import TaggedText from 'pixi-tagged-text';

const myText = new TaggedText('<red>Hello</red> <green>there</green>', {
  default: {
    fontFamily: 'Arial',
    fontSize: 24,
  },
  red: { tint: 0xFF0000 },
  green: { tint: 0x00FF00 }
});

Example

Here it is in action in a mobile game:

Example running game with bitmap text

Everything except the FPS counter. All bitmap fonts are generated at runtime, except the "Game over" text, which is a file-based font.

Note: We also pre-hydrate the font cache to avoid hitching/stuttering during gameplay. You may want to consider this if you have a lot of styles.

Caveats?

There are risks here if you've never used BitmapText - but for experienced users this should mostly be a drop-in replacement.

  1. You will need to consider the impact of dynamically generating fonts at runtime.
  • We pre-generate most of our bitmap fonts from standard styles to avoid hitching/stuttering at runtime. You may want to consider that.
  1. Modifying style attributes after generating text will be a little broken, have not extensively tested that side of it.
  2. There's probably other tidbits like font baseline, but only the standard kind of issues you run into with BitmapFont. There's plenty of resources.

Future

Moving on for now, just threw this together after work so it was there for others. May strip it down and move it to a hard-forked npm package called pixi-bitmap-tagged-text or something when we have time.

@mimshwright mimshwright reopened this Jan 29, 2024
@mimshwright
Copy link
Owner

Thanks for adding the extra details. It helps a lot to see a more complete solution.
I'm going to set the permission to protected and make it a generic class to accommodate a different class for the text.

mimshwright added a commit that referenced this issue Jan 29, 2024
TaggedText is now a generic class and some methods are extendable so that it could be made to work
with bitmapText objects. It shouldn't affect existing code.

re #393
@mimshwright
Copy link
Owner

@reececomo I updated the code in 3.15.0. Let me know if this is helpful or if it's missing something. From here, you should be able to create a TaggedBitmapText subclass that reimplements text field creation methods (or maybe give the users options within TaggedText like you did in your v2).

  • TaggedText is now generic so you can set the internal Pixi type. It defaults to PIXI.Text
  • TextStyleExtended now also extends IBitmapTextStyle
  • There is a separate method for creating the text field, like in your first commit.
  • Several methods are protected now instead of private

@reececomo
Copy link
Author

Awesome! Thanks man, that's a huge help 🙌

@mimshwright
Copy link
Owner

@reececomo I'd encourage you to submit a new pull request with a TaggedBitmapText class that's fully implemented. It would be especially great if you include tests, docs, etc.

@reececomo
Copy link
Author

Would love to, might have to be a future weekend project though, between work and this game 😆

@reececomo
Copy link
Author

These two have already made it massively configurable:

  • There is a separate method for creating the text field, like in your first commit.
  • Several methods are protected now instead of private

It's the whole Pareto thing - making these small changes has solved 90% of it. Like you flagged @mimshwright (and rightly so), there are heaps of BitmapText-specific considerations that make it not 1:1 with Text. In particular things like preloading/generating BitmapFonts.

e.g. I noticed problems with emoji, even with generated font characters. We don't actually use emoji anyway so I left it alone.

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

3 participants