Browse Source

Allow the image cache to store many images for one texture

Cube maps in Plasma are six separate images; however, Blender expects to
be supplied with one image that encompasses all of the cube faces.
Therefore, our texture cache needs to be able to support many images for
one request.
pull/125/head
Adam Johnson 6 years ago
parent
commit
3ea2f973e2
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 63
      korman/exporter/image.py
  2. 9
      korman/exporter/material.py

63
korman/exporter/image.py

@ -46,6 +46,8 @@ class _EntryBits(enum.IntEnum):
source_size = 4 source_size = 4
export_size = 5 export_size = 5
last_export = 6 last_export = 6
image_count = 7
tag_string = 8
class _CachedImage: class _CachedImage:
@ -59,6 +61,8 @@ class _CachedImage:
self.compression = None self.compression = None
self.export_time = None self.export_time = None
self.modify_time = None self.modify_time = None
self.image_count = 1
self.tag = None
def __str__(self): def __str__(self):
return self.name return self.name
@ -71,10 +75,10 @@ class ImageCache:
self._read_stream = hsFileStream() self._read_stream = hsFileStream()
self._stream_handles = 0 self._stream_handles = 0
def add_texture(self, texture, num_levels, export_size, compression, data): def add_texture(self, texture, num_levels, export_size, compression, images):
image = texture.image image, tag = texture.image, texture.tag
image_name = str(texture) image_name = str(texture)
key = (image_name, compression) key = (image_name, tag, compression)
ex_method, im_method = self._exporter().texcache_method, image.plasma_image.texcache_method ex_method, im_method = self._exporter().texcache_method, image.plasma_image.texcache_method
method = set((ex_method, im_method)) method = set((ex_method, im_method))
if texture.ephemeral or "skip" in method: if texture.ephemeral or "skip" in method:
@ -89,7 +93,9 @@ class ImageCache:
image.compression = compression image.compression = compression
image.source_size = texture.image.size image.source_size = texture.image.size
image.export_size = export_size image.export_size = export_size
image.image_data = data image.image_data = images
image.image_count = len(images)
image.tag = tag
self._images[key] = image self._images[key] = image
def _compact(self): def _compact(self):
@ -111,7 +117,7 @@ class ImageCache:
self._read_stream.close() self._read_stream.close()
def get_from_texture(self, texture, compression): def get_from_texture(self, texture, compression):
bl_image = texture.image bl_image, tag = texture.image, texture.tag
# If the texture is ephemeral (eg a lightmap) or has been marked "rebuild" or "skip" # If the texture is ephemeral (eg a lightmap) or has been marked "rebuild" or "skip"
# in the UI, we don't want anything from the cache. In the first two cases, we never # in the UI, we don't want anything from the cache. In the first two cases, we never
@ -121,7 +127,7 @@ class ImageCache:
if method != {"use"} or texture.ephemeral: if method != {"use"} or texture.ephemeral:
return None return None
key = (str(texture), compression) key = (str(texture), tag, compression)
cached_image = self._images.get(key) cached_image = self._images.get(key)
if cached_image is None: if cached_image is None:
return None return None
@ -199,17 +205,25 @@ class ImageCache:
# between iterations, so we'd best bookkeep the position # between iterations, so we'd best bookkeep the position
pos = stream.pos pos = stream.pos
for i in range(image.mip_levels): def _read_image_mips():
for _ in range(image.mip_levels):
nonlocal pos
if stream.pos != pos:
stream.seek(pos)
assert stream.read(4) == _MIP_MAGICK
# this should only ever be image data...
# store your flags somewhere else!
size = stream.readInt()
data = stream.read(size)
pos = stream.pos
yield data
for _ in range(image.image_count):
if stream.pos != pos: if stream.pos != pos:
stream.seek(pos) stream.seek(pos)
assert stream.read(4) == _MIP_MAGICK yield tuple(_read_image_mips())
# this should only ever be image data...
# store your flags somewhere else!
size = stream.readInt()
data = stream.read(size)
pos = stream.pos
yield data
def _read_index(self, index_pos, stream): def _read_index(self, index_pos, stream):
stream.seek(index_pos) stream.seek(index_pos)
@ -250,9 +264,14 @@ class ImageCache:
image.export_size = (stream.readInt(), stream.readInt()) image.export_size = (stream.readInt(), stream.readInt())
if flags[_EntryBits.last_export]: if flags[_EntryBits.last_export]:
image.export_time = stream.readDouble() image.export_time = stream.readDouble()
if flags[_EntryBits.image_count]:
image.image_count = stream.readInt()
if flags[_EntryBits.tag_string]:
# tags should not contain user data, so we will use a latin_1 backed string
image.tag = stream.readSafeStr()
# do we need to check for duplicate images? # do we need to check for duplicate images?
self._images[(image.name, image.compression)] = image self._images[(image.name, image.tag, image.compression)] = image
@property @property
def _report(self): def _report(self):
@ -300,9 +319,10 @@ class ImageCache:
flags.write(stream) flags.write(stream)
for i in image.image_data: for i in image.image_data:
stream.write(_MIP_MAGICK) for j in i:
stream.writeInt(len(i)) stream.write(_MIP_MAGICK)
stream.write(i) stream.writeInt(len(j))
stream.write(j)
def _write_index(self, stream): def _write_index(self, stream):
flags = hsBitVector() flags = hsBitVector()
@ -327,6 +347,8 @@ class ImageCache:
flags[_EntryBits.source_size] = True flags[_EntryBits.source_size] = True
flags[_EntryBits.export_size] = True flags[_EntryBits.export_size] = True
flags[_EntryBits.last_export] = True flags[_EntryBits.last_export] = True
flags[_EntryBits.image_count] = True
flags[_EntryBits.tag_string] = image.tag is not None
stream.write(_ENTRY_MAGICK) stream.write(_ENTRY_MAGICK)
flags.write(stream) flags.write(stream)
@ -339,3 +361,6 @@ class ImageCache:
stream.writeInt(image.export_size[0]) stream.writeInt(image.export_size[0])
stream.writeInt(image.export_size[1]) stream.writeInt(image.export_size[1])
stream.writeDouble(time.time()) stream.writeDouble(time.time())
stream.writeInt(image.image_count)
if image.tag is not None:
stream.writeSafeStr(image.tag)

9
korman/exporter/material.py

@ -84,13 +84,14 @@ class _Texture:
self.extension = kwargs.get("extension", self.auto_ext) self.extension = kwargs.get("extension", self.auto_ext)
self.ephemeral = kwargs.get("ephemeral", False) self.ephemeral = kwargs.get("ephemeral", False)
self.image = image self.image = image
self.tag = kwargs.get("tag", None)
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, _Texture): if not isinstance(other, _Texture):
return False return False
# Yeah, the string name is a unique identifier. So shoot me. # Yeah, the string name is a unique identifier. So shoot me.
if str(self) == str(other): if str(self) == str(other) and self.tag == other.tag:
self._update(other) self._update(other)
return True return True
return False return False
@ -692,6 +693,8 @@ class MaterialConverter:
- indent: (optional) indentation level for log messages - indent: (optional) indentation level for log messages
default: 2 default: 2
- ephemeral: (optional) never cache this image - ephemeral: (optional) never cache this image
- tag: (optional) an optional identifier hint that allows multiple images with the
same name to coexist in the cache
""" """
owner = kwargs.pop("owner", None) owner = kwargs.pop("owner", None)
indent = kwargs.pop("indent", 2) indent = kwargs.pop("indent", 2)
@ -785,10 +788,10 @@ class MaterialConverter:
for i in range(numLevels): for i in range(numLevels):
mipmap.CompressImage(i, data[i]) mipmap.CompressImage(i, data[i])
data[i] = mipmap.getLevel(i) data[i] = mipmap.getLevel(i)
texcache.add_texture(key, numLevels, (eWidth, eHeight), compression, data) texcache.add_texture(key, numLevels, (eWidth, eHeight), compression, [data,])
else: else:
eWidth, eHeight = cached_image.export_size eWidth, eHeight = cached_image.export_size
data = cached_image.image_data data = cached_image.image_data[0]
numLevels = cached_image.mip_levels numLevels = cached_image.mip_levels
# Now we poke our new bitmap into the pending layers. Note that we have to do some funny # Now we poke our new bitmap into the pending layers. Note that we have to do some funny

Loading…
Cancel
Save