为什么 pyglet.image 和质地这么重?
Why are pyglet.image and texture so heavy?
环境:
- python: 3.6.6
- pyglet: 1.3.2
这是我的代码和结果:
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")
问题:
- 为什么 pyglet.image.AbstractImage 每个 2.1Mb 的文件会导致 23Mb 的内存使用?
- 如果 ImageGrid 用于创建 sprite sheet -> 它会导致额外的 ~60Mb
- 如何处理?因为如果游戏包含 50 多个大精灵,那么只为纹理分配这么多内存是不真实的。
- 也许还有其他方法可以创建使用精灵的游戏?或者我应该为客户端更改我的堆栈技术(pyglet 作为主库,也尝试使用 pygame)?
PS:我第一次将我的应用程序从pygame
重写为pyglet
,因为我在pygame
中没有考虑事件循环的某些方面,现在我还没有为我的用例测试 pyglet
库的资源使用情况。
Update/clarification:
我在顶点中使用 ImageGrid
作为 3d 部分,在 pyglet.sprite.Sprite
中使用 2d 部分
3D部分使用示例:
# 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)
二维部分使用示例:
pyglet.sprite.Sprite(img=texture_grid[self.texture_grid_index], x=0, y=0,
batch=self.batch, group=some_order_group)
更新#2:
据我所知,使用 pyglet.image.CompressedImageData 的允许大小是:
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
但无法从 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'
在 pyglet 中尝试了所有可能的 GL_COMPRESS:
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
更新 #3:
例外情况是:
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
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'>
#1 的答案简单明了:PNG 是一种压缩格式。 2400×2400的图片解包后只需要占用2400×2400×32bit = 23040000字节的RAM,即使它的磁盘大小是2MB
处理这个问题的可能方法有很多,但所有这些方法都归结为在内存要求、图像质量和访问速度之间找到合适的权衡。
例如,在 Pyglet 中有一个名为 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 的 class 您可以找到有关 GPU 纹理压缩的更多信息。
作为一种快速而肮脏的解决方法,您可以尝试将图像中的颜色数量减少到≤256 并使用调色板。这将立即带来 4 倍的内存优势。
环境:
- python: 3.6.6
- pyglet: 1.3.2
这是我的代码和结果:
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")
问题:
- 为什么 pyglet.image.AbstractImage 每个 2.1Mb 的文件会导致 23Mb 的内存使用?
- 如果 ImageGrid 用于创建 sprite sheet -> 它会导致额外的 ~60Mb
- 如何处理?因为如果游戏包含 50 多个大精灵,那么只为纹理分配这么多内存是不真实的。
- 也许还有其他方法可以创建使用精灵的游戏?或者我应该为客户端更改我的堆栈技术(pyglet 作为主库,也尝试使用 pygame)?
PS:我第一次将我的应用程序从pygame
重写为pyglet
,因为我在pygame
中没有考虑事件循环的某些方面,现在我还没有为我的用例测试 pyglet
库的资源使用情况。
Update/clarification:
我在顶点中使用 ImageGrid
作为 3d 部分,在 pyglet.sprite.Sprite
3D部分使用示例:
# 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)
二维部分使用示例:
pyglet.sprite.Sprite(img=texture_grid[self.texture_grid_index], x=0, y=0,
batch=self.batch, group=some_order_group)
更新#2:
据我所知,使用 pyglet.image.CompressedImageData 的允许大小是:
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
但无法从 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'
在 pyglet 中尝试了所有可能的 GL_COMPRESS:
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
更新 #3:
例外情况是:
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
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'>
#1 的答案简单明了:PNG 是一种压缩格式。 2400×2400的图片解包后只需要占用2400×2400×32bit = 23040000字节的RAM,即使它的磁盘大小是2MB
处理这个问题的可能方法有很多,但所有这些方法都归结为在内存要求、图像质量和访问速度之间找到合适的权衡。
例如,在 Pyglet 中有一个名为 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 的 class 您可以找到有关 GPU 纹理压缩的更多信息。
作为一种快速而肮脏的解决方法,您可以尝试将图像中的颜色数量减少到≤256 并使用调色板。这将立即带来 4 倍的内存优势。