Why are pyglet.image and texture so heavy?

Question:

Envrionment:

  • python: 3.6.6
  • pyglet: 1.3.2

Here is my code and results:

import pyglet

images = []
textures = []

with_textures = True
count = 10
for x in range(count):
    image = pyglet.image.load("big.png")  # 2.1 Mb source 2400*2400px
    images.append(image)
    if with_textures:
        texture_grid = pyglet.image.ImageGrid(image, 10, 10).get_texture_sequence()
        textures.append(texture_grid)

# RES in htop result without textures
# count = 10 - 300Mb
# count = 20 - 553Mb
# count = 30 - 753Mb
# count = 40 - 973Mb
# ~23Mb just for each Image

# RES in htop result with textures
# count = 10 - 996Mb
# count = 20 - 1878Mb
# count = 30 - 2716Mb
# count = 40 - 3597Mb
# ~86Mb for Image and prepared grid


input("Press enter to exit")

Questions:

  1. Why each 2.1Mb file leads to 23Mb of memory usage with pyglet.image.AbstractImage?
    • If ImageGrid is used for creating sprite sheet -> it leads to additional ~60Mb
  2. How to deal with it? Because if game contains 50+ big sprites it would be not real to dedicate such many memory only for textures.
  3. Maybe there is some other approach in creating games which is used sprites? Or I should change my stack technology(pyglet as main library, also was trying with pygame) for client side?

PS: First time I’ve rewritten my application from pygame to pyglet, because I didn’t consider some aspects of event loop in pygame, and now I hadn’t test resource usage of pyglet library for my use-cases.

Update/clarification:

I’m using ImageGrid as for 3d part in vertices as for 2d part in pyglet.sprite.Sprite

Example of using in 3D part:

# texture_group is created once for each sprite sheet, same as texture_grid
texture_group = pyglet.graphics.TextureGroup(texture_grid, order_group)
...

tex_map = texture_grid[self.texture_grid_index].texture.tex_coords
tex_coords = ('t3f', tex_map)
self.entity = self.batch.add(
    4, pyglet.gl.GL_QUADS,
    texture_group,
    ('v3f', (x, y, z,
             x_, y, z,
             x_, y_, z,
             x, y_, z)
     ),
    tex_coords)

Example of using in 2D part:

pyglet.sprite.Sprite(img=texture_grid[self.texture_grid_index], x=0, y=0,
                     batch=self.batch, group=some_order_group)

Update #2:

As I figure out, allowed sizes for using pyglet.image.CompressedImageData is:

1 True
2 True
4 True
8 True
16 True
32 True
64 True
128 True
256 True
512 True
1024 True
2048 True
4096 True

But can’t get texture from CompressedImageData:

big = pyglet.image.load("big.png")  # 2048*2048
compressed_format = pyglet.graphics.GL_COMPRESSED_ALPHA
compressed_image = pyglet.image.CompressedImageData(
    big.width, big.height, compressed_format, big.data)
compressed_image.texture  # exception GLException: b'invalid enumerant'

Tried with all possible GL_COMPRESS in pyglet:

allowed_formats = [x for x in dir(pyglet.graphics) if "GL_COMPRESSED_" in x]
big = pyglet.image.load("big.png")  # 2048*2048
for form in allowed_formats:
    compressed_image = pyglet.image.CompressedImageData(
        big.width, big.height, form, big.data)
    try:
        compressed_image.texture
        print("positive:", form)  # 0 positive prints
    except Exception:
        pass

Update #3:

Exceptions are:

Traceback (most recent call last):
  File "<ipython-input-72-1b802ff07742>", line 7, in <module>
    compressed_image.texture
  File "/<venv>/lib/python3.6/site-packages/pyglet/image/__init__.py", line 410, in texture
    return self.get_texture()
  File "/<venv>/lib/python3.6/site-packages/pyglet/image/__init__.py", line 1351, in get_texture
    len(self.data), self.data)
ctypes.ArgumentError: argument 3: <class 'TypeError'>: wrong type

What is going in pyglet:

if self._have_extension():
    glCompressedTexImage2DARB(texture.target, texture.level,
                              self.gl_format,
                              self.width, self.height, 0,
                              len(self.data), self.data)

ipdb:

> /<venv>/lib/python3.6/site-packages/pyglet/image/__init__.py(1349)get_texture()
   1348             import ipdb;ipdb.set_trace()
-> 1349             glCompressedTexImage2DARB(texture.target, texture.level,
   1350                                       self.gl_format,

ipdb> glCompressedTexImage2DARB
<_FuncPtr object at 0x7fca957ee1d8>
ipdb> texture.target
3553
ipdb> texture.level
0
ipdb> self.gl_format
'GL_COMPRESSED_TEXTURE_FORMATS_ARB'    
ipdb> self.width
2048
ipdb> self.height
2048
ipdb> len(self.data)
16777216
ipdb> type(self.data)
<class 'bytes'>
Asked By: Yuriy Leonov

||

Answers:

The answer to #1 is plain and simple: PNG is a compressed format. The 2400×2400 image just has to occupy 2400×2400×32bit = 23040000 bytes in RAM after unpacking, even though its disk size is 2 MB.

Possible methods of dealing with this issue are many, but all of them boil down to finding a suitable tradeoff between memory requirements, image quality, and access speed.

For example, in Pyglet there is a class named CompressedImageData that allows you to use GPU built-in texture compression in case you are using OpenGL for rendering. If not, you are probably stuck with implementing one of those methods in software, for PNG compression is mostly unsuitable for fast pixel access. Here you can find more info on GPU texture compression.

As a quick and dirty workaround, you can try to reduce the number of colors in your image to ≤256 and use a palette. That`ll give a 4x memory benefit right away.

Answered By: hidefromkgb
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.