Minimog

How to optimise large textures for mobile devices

Recommended Posts

I am working on a game that has few background graphics, these are stored in app bundle at big resolutions like 2000x2000 pixels to support bigger devices like iPad pro.
Obviously something this big is an overkill for mobile and I wanted to ask if following optimisation assumption is correct:

1. I preload these assets with pixi loader
2. I create sprite for each texture
3. I then resize this sprite to fit device dimensions better i.e. 1000x1000 px for iphone

When I inspect my sprites textures I still see them at 2000x2000 pixels even though sprite itself is 1000x1000. I am concerned that I am not optimising this correctly,
especially because I use prepare plugin https://pixijs.download/dev/docs/PIXI.Prepare_.html for some of these assets, I upload my sprites not textures, but I still feel
like I might be uploading those big 2000x2000 assets alongside which is a problem, as these occupy a lot of GPU memory.

Hence this thread to clarify if my approach to this problem is correct. I don't want to create separate asset resolutions i.e. 2x / 3x etc if possible to avoid increasing final
app bundle size.

Share this post


Link to post
Share on other sites

Best way is to make the textures in multiple sizes beforehand and then load only the ones that are your wanted size.

In theory you could do resizing and dynamic texture generation in client also, but you would suffer qualitywise when compared to doing the resolution changes with software with better scaling algorithms.

Anyways if you want to do without uploading large textures to gpu and while still using original sized textures then here's a short way how you could do that:

- Create a temp canvas that is you target resolution size.
- Get 2d context from it.
- Draw your image to that canvas with scaling to correct size.
- Create a basetexture that has that canvas as element.
- Create your sprite using that basetexture.
- Destroy your original image, basetexture and remove it from loader.

I do not recommend using this method, but rather look into how you can have separate asset resolutions and load only the correct one.

Share this post


Link to post
Share on other sites

I upload my sprites not textures

prepare recursively looks everywhere and searches for textures.

Sprite resize wont affect anything, you have to create a renderTexture of appropriate size, render your sprite there, and then use this new texture, and destroy old one.

Alternatively: do it manually on canvas2d, and give this canvas to pixi. You can also set width=height=1 for canvas after its uploaded to save the regular memory, but turn off pixi gc so it wont release the texture from videomemory. Both images are in this case are RGBA, 4 bytes per pixel. 

You can use compressed textures to get 1 byte per pixel but that's special kind of compression, it has downsides that you have to be aware of.

Edited by ivan.popelyshev

Share this post


Link to post
Share on other sites

@ivan.popelyshev Gotcha, so just to confirm that I am cleaning everything up correctly here is a snippet that works for me and resizes everything correctly, am I correct assuming that the only cleanup I need to do here is destroy a loader with original textures?
 

      loader.load((_, resources) => {
        const sprites = {};

        Object.keys(resources).forEach(key => {
          const sprite = new Sprite(resources[key]?.texture);
          const { width, height } = sprite;
           
          // maxWidth is pre-determined beforehand
          if (width > maxWidth) {
            const ratio = height / width;
            const maxHeight = maxWidth * ratio;
            const renderTexture = RenderTexture.create({
              width: maxWidth,
              height: maxHeight
            });
            sprite.width = maxWidth;
            sprite.height = maxHeight;
            renderer.render(sprite, renderTexture);
            sprites[key] = new Sprite(renderTexture);
          } else {
            sprites[key] = sprite;
          }
        });

        // sprites object is then sent to prepare plugin

        loader.destroy();
      });

 

Edited by Minimog
Add js syntax highlighting

Share this post


Link to post
Share on other sites

@ivan.popelyshev Alright, I tried to do that after it's been uploaded to GPU i.e following my example above
 

    renderer.plugins.prepare.upload(sprite, () => {
      resources[key]?.texture.destroy(true);
    });

But this throws various errors related to sprite scale / width etc being undefined. It feels like old texture is still being referenced somewhere. Can this be the case with my previous example above?

Share this post


Link to post
Share on other sites

Just to add to the above, calling reset and destroy methods on loader seems to also work. I looked up sprites in chrome's memory profiler and ones stored there are correct renderTextures

Share this post


Link to post
Share on other sites

@ivan.popelyshev You are tight, I didn't even know renderTexture achieved what I wanted to do with prepare. I cleaned up the code and added appropriate destroy methods, could you please give this a final glance to confirm that I understood everything correctly.
Only caveat here that I can't figure out when comparing this to prepare plugin, is how can I know when renderTexture is loaded to gpu? Prepare provided a callback for this. Or is renderTexture gpu upload synchronous? 

 

       loader.load((_, resources) => {
        const sprites = {};

        Object.keys(resources).forEach(key => {
          const texture = resources[key]?.texture;
          const sprite = new Sprite(texture);
          const { width, height } = sprite;
          const renderTexture = RenderTexture.create();

          // Resize image if it is too big for the device
          if (width > maxWidth) {
            const ratio = height / width;
            const maxHeight = maxWidth * ratio;
            renderTexture.resize(maxWidth, maxHeight);
            sprite.width = maxWidth;
            sprite.height = maxHeight;
          } else {
            renderTexture.resize(width, height);
          }

          renderer.render(sprite, renderTexture);
          sprites[key] = new Sprite(renderTexture);

          sprite.destroy();
          texture?.destroy();
        });

        loader.reset();
        loader.destroy();
      });

 

Edited by Minimog

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.