Skip to content

Commit

Permalink
convert: Speed up converter texture packing
Browse files Browse the repository at this point in the history
cythonized more the texture packing, also changed the
list passed to factor(), before was a list of frame objects, now
a list of only width, height and index of the frames.
  • Loading branch information
fabiobarkoski committed Dec 4, 2024
1 parent ed26812 commit ea3ccac
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 57 deletions.
6 changes: 6 additions & 0 deletions openage/convert/processor/export/texture_merge.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright 2024-2024 the openage authors. See copying.md for legal info.

cdef struct block:
unsigned int index
unsigned int width
unsigned int height
18 changes: 12 additions & 6 deletions openage/convert/processor/export/texture_merge.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2014-2023 the openage authors. See copying.md for legal info.
# Copyright 2014-2024 the openage authors. See copying.md for legal info.
#
# cython: infer_types=True
# pylint: disable=too-many-locals
Expand Down Expand Up @@ -57,14 +57,15 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc
:type cache: list
"""
cdef list frames = texture.frames
cdef list blocks = [block(idx, frame.width, frame.height) for idx, frame in enumerate(frames)]

if len(frames) == 0:
raise ValueError("cannot create texture with empty input frame list")

cdef BestPacker packer

if cache:
packer = BestPacker([DeterministicPacker(margin=MARGIN,hints=cache)])
packer = BestPacker([DeterministicPacker(margin=MARGIN, hints=cache)])

else:
if packer_type == PackerType.ROW:
Expand All @@ -81,7 +82,7 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc
RowPacker(margin=MARGIN),
ColumnPacker(margin=MARGIN)])

packer.pack(frames)
packer.pack(blocks)

cdef int width = packer.width()
cdef int height = packer.height()
Expand All @@ -106,11 +107,11 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc
cdef int sub_h

cdef list drawn_frames_meta = []
for sub_frame in frames:
for index, sub_frame in enumerate(frames):
sub_w = sub_frame.width
sub_h = sub_frame.height

pos_x, pos_y = packer.pos(sub_frame)
pos_x, pos_y = packer.pos(index)

spam("drawing frame %03d on atlas at %d x %d...",
len(drawn_frames_meta), pos_x, pos_y)
Expand Down Expand Up @@ -143,4 +144,9 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc
if isinstance(packer, BestPacker):
# Only generate these values if no custom packer was used
# TODO: It might make sense to do it anyway for debugging purposes
texture.best_packer_hints = packer.get_mapping_hints(frames)
texture.best_packer_hints = packer.get_mapping_hints(blocks)

cdef struct block:
unsigned int index
unsigned int width
unsigned int height
27 changes: 18 additions & 9 deletions openage/convert/service/export/png/binpack.pxd
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
# Copyright 2021-2021 the openage authors. See copying.md for legal info.
# Copyright 2021-2024 the openage authors. See copying.md for legal info.

from libcpp.memory cimport shared_ptr

from openage.convert.processor.export.texture_merge cimport block

ctypedef (unsigned int, unsigned int, unsigned int) mapping_value

cdef class Packer:
cdef unsigned int margin
cdef dict mapping

cdef void pack(self, list blocks)
cdef (unsigned int, unsigned int) pos(self, block)
cdef (unsigned int, unsigned int) pos(self, int index)
cdef unsigned int width(self)
cdef unsigned int height(self)
cdef list get_mapping_hints(self, list blocks)
cdef (unsigned int) get_packer_settings(self)

cdef class DeterministicPacker(Packer):
pass
Expand All @@ -20,9 +26,11 @@ cdef class BestPacker:

cdef void pack(self, list blocks)
cdef Packer best_packer(self)
cdef (unsigned int, unsigned int) pos(self, block)
cdef (unsigned int, unsigned int) pos(self, int index)
cdef unsigned int width(self)
cdef unsigned int height(self)
cdef list get_mapping_hints(self, list blocks)
cdef (unsigned int) get_packer_settings(self)

cdef class RowPacker(Packer):
pass
Expand All @@ -34,12 +42,13 @@ cdef class BinaryTreePacker(Packer):
cdef unsigned int aspect_ratio
cdef packer_node *root

cdef void fit(self, block)
cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height)
cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height)
cdef packer_node *grow_node(self, unsigned int width, unsigned int height)
cdef packer_node *grow_right(self, unsigned int width, unsigned int height)
cdef packer_node *grow_down(self, unsigned int width, unsigned int height)
cdef void fit(self, block block)
cdef (unsigned int) get_packer_settings(self)
cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height) noexcept
cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height) noexcept
cdef packer_node *grow_node(self, unsigned int width, unsigned int height) noexcept
cdef packer_node *grow_right(self, unsigned int width, unsigned int height) noexcept
cdef packer_node *grow_down(self, unsigned int width, unsigned int height) noexcept

cdef struct packer_node:
unsigned int x
Expand Down
83 changes: 41 additions & 42 deletions openage/convert/service/export/png/binpack.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2016-2023 the openage authors. See copying.md for legal info.
# Copyright 2016-2024 the openage authors. See copying.md for legal info.
#
# cython: infer_types=True,profile=False
# TODO pylint: disable=C,R
Expand All @@ -7,14 +7,12 @@
Routines for 2D binpacking
"""

from enum import Enum

cimport cython
from libc.stdint cimport uintptr_t
from libc.stdlib cimport malloc

from libc.math cimport sqrt
from libc.stdlib cimport malloc
from libcpp.unordered_map cimport unordered_map

from openage.convert.processor.export.texture_merge cimport block

@cython.boundscheck(False)
@cython.wraparound(False)
Expand All @@ -24,6 +22,7 @@ cdef inline (unsigned int, unsigned int) factor(unsigned int n):
Return two (preferable close) factors of n.
"""
cdef unsigned int a = <unsigned int>sqrt(n)
cdef int num
for num in range(a, 0, -1):
if n % num == 0:
return num, n // num
Expand All @@ -33,9 +32,9 @@ cdef class Packer:
"""
Packs blocks.
"""
def __init__(self, margin):
def __init__(self, int margin):
self.margin = margin
self.mapping = {}
self.mapping = unordered_map[int, mapping_value]()

cdef void pack(self, list blocks):
"""
Expand All @@ -45,31 +44,32 @@ cdef class Packer:
"""
raise NotImplementedError

cdef (unsigned int, unsigned int) pos(self, block):
return self.mapping[block]
cdef (unsigned int, unsigned int) pos(self, int index):
return self.mapping[index]

cdef unsigned int width(self):
"""
Gets the total width of the packing.
"""
return max(self.pos(block)[0] + block.width for block in self.mapping)
return max(self.pos(idx)[0] + block[2][0] for idx, block in self.mapping.items())

cdef unsigned int height(self):
"""
Gets the total height of the packing.
"""
return max(self.pos(block)[1] + block.height for block in self.mapping)
return max(self.pos(idx)[1] + block[2][1] for idx, block in self.mapping.items())

def get_packer_settings(self):
cdef (unsigned int) get_packer_settings(self):
"""
Get the init parameters set for the packer.
"""
return (self.margin,)

def get_mapping_hints(self, blocks):
hints = []
cdef list get_mapping_hints(self, list blocks):
cdef list hints = []
cdef block block
for block in blocks:
hints.append(self.pos(block))
hints.append(self.pos(block.index))

return hints

Expand All @@ -79,13 +79,13 @@ cdef class DeterministicPacker(Packer):
Packs blocks based on predetermined settings.
"""

def __init__(self, margin, hints):
def __init__(self, int margin, list hints):
super().__init__(margin)
self.hints = hints

cdef void pack(self, list blocks):
for idx, block in enumerate(blocks):
self.mapping[block] = self.hints[idx]
self.mapping[block.index] = self.hints[idx]


cdef class BestPacker:
Expand All @@ -97,29 +97,28 @@ cdef class BestPacker:
self.current_best = None

cdef void pack(self, list blocks):
cdef Packer p
cdef Packer packer
for packer in self.packers:
p = packer
p.pack(blocks)
packer.pack(blocks)

self.current_best = self.best_packer()

cdef Packer best_packer(self):
return min(self.packers, key=lambda Packer p: p.width() * p.height())

cdef (unsigned int, unsigned int) pos(self, block):
return self.current_best.pos(block)
cdef (unsigned int, unsigned int) pos(self, int index):
return self.current_best.pos(index)

cdef unsigned int width(self):
return self.current_best.width()

cdef unsigned int height(self):
return self.current_best.height()

def get_packer_settings(self):
cdef (unsigned int) get_packer_settings(self):
return self.current_best.get_packer_settings()

def get_mapping_hints(self, blocks):
cdef list get_mapping_hints(self, list blocks):
return self.current_best.get_mapping_hints(blocks)


Expand All @@ -129,7 +128,7 @@ cdef class RowPacker(Packer):
"""

cdef void pack(self, list blocks):
self.mapping = {}
self.mapping = unordered_map[int, mapping_value]()

cdef unsigned int num_rows
cdef list rows
Expand All @@ -148,7 +147,7 @@ cdef class RowPacker(Packer):
x = 0

for block in row:
self.mapping[block] = (x, y)
self.mapping[block.index] = (x, y, (block.width, block.height))
x += block.width + self.margin

y += max(block.height for block in row) + self.margin
Expand All @@ -160,7 +159,7 @@ cdef class ColumnPacker(Packer):
"""

cdef void pack(self, list blocks):
self.mapping = {}
self.mapping = unordered_map[int, mapping_value]()

num_columns, _ = factor(len(blocks))
columns = [[] for _ in range(num_columns)]
Expand All @@ -176,13 +175,13 @@ cdef class ColumnPacker(Packer):
y = 0

for block in column:
self.mapping[block] = (x, y)
self.mapping[block.index] = (x, y, (block.width, block.height))
y += block.height + self.margin

x += max(block.width for block in column) + self.margin


cdef inline (unsigned int, unsigned int, unsigned int, unsigned int) maxside_heuristic(block):
cdef inline (unsigned int, unsigned int, unsigned int, unsigned int) maxside_heuristic(block block):
"""
Heuristic: Order blocks by maximum side.
"""
Expand All @@ -202,27 +201,27 @@ cdef class BinaryTreePacker(Packer):
textures.
"""

def __init__(self, margin, aspect_ratio=1):
def __init__(self, int margin, int aspect_ratio=1):
# ASF: what about heuristic=max_heuristic?
super().__init__(margin)
self.aspect_ratio = aspect_ratio
self.root = NULL

cdef void pack(self, list blocks):
self.mapping = {}
self.mapping = unordered_map[int, mapping_value]()
self.root = NULL

for block in sorted(blocks, key=maxside_heuristic, reverse=True):
self.fit(block)

cdef (unsigned int, unsigned int) pos(self, block):
node = self.mapping[block]
cdef (unsigned int, unsigned int) pos(self, int index):
node = self.mapping[index]
return node[0], node[1]

def get_packer_settings(self):
cdef (unsigned int) get_packer_settings(self):
return (self.margin,)

cdef void fit(self, block):
cdef void fit(self, block block):
cdef packer_node *node
if self.root == NULL:
self.root = <packer_node *>malloc(sizeof(packer_node))
Expand All @@ -246,17 +245,17 @@ cdef class BinaryTreePacker(Packer):
node = self.grow_node(block.width + self.margin,
block.height + self.margin)

self.mapping[block] = (node.x, node.y)
self.mapping[block.index] = (node.x, node.y, (block.width, block.height))

cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height):
cdef packer_node *find_node(self, packer_node *root, unsigned int width, unsigned int height) noexcept:
if root.used:
return (self.find_node(root.right, width, height) or
self.find_node(root.down, width, height))

elif width <= root.width and height <= root.height:
return root

cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height):
cdef packer_node *split_node(self, packer_node *node, unsigned int width, unsigned int height) noexcept:
node.used = True

node.down = <packer_node *>malloc(sizeof(packer_node))
Expand All @@ -280,7 +279,7 @@ cdef class BinaryTreePacker(Packer):
return node

@cython.cdivision(True)
cdef packer_node *grow_node(self, unsigned int width, unsigned int height):
cdef packer_node *grow_node(self, unsigned int width, unsigned int height) noexcept:
cdef bint can_grow_down = width <= self.root.width
cdef bint can_grow_right = height <= self.root.height
# assert can_grow_down or can_grow_right, "Bad block ordering heuristic"
Expand All @@ -302,7 +301,7 @@ cdef class BinaryTreePacker(Packer):
else:
return self.grow_down(width, height)

cdef packer_node *grow_right(self, unsigned int width, unsigned int height):
cdef packer_node *grow_right(self, unsigned int width, unsigned int height) noexcept:
old_root = self.root

self.root = <packer_node *>malloc(sizeof(packer_node))
Expand All @@ -327,7 +326,7 @@ cdef class BinaryTreePacker(Packer):
if node != NULL:
return self.split_node(node, width, height)

cdef packer_node *grow_down(self, unsigned int width, unsigned int height):
cdef packer_node *grow_down(self, unsigned int width, unsigned int height) noexcept:
old_root = self.root

self.root = <packer_node *>malloc(sizeof(packer_node))
Expand Down

0 comments on commit ea3ccac

Please sign in to comment.