From ea3ccaca72fd6409048b7ba67262374254e574d9 Mon Sep 17 00:00:00 2001 From: fabiobarkoski Date: Wed, 7 Aug 2024 00:22:52 -0300 Subject: [PATCH] convert: Speed up converter texture packing 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. --- .../processor/export/texture_merge.pxd | 6 ++ .../processor/export/texture_merge.pyx | 18 ++-- .../convert/service/export/png/binpack.pxd | 27 ++++-- .../convert/service/export/png/binpack.pyx | 83 +++++++++---------- 4 files changed, 77 insertions(+), 57 deletions(-) create mode 100644 openage/convert/processor/export/texture_merge.pxd diff --git a/openage/convert/processor/export/texture_merge.pxd b/openage/convert/processor/export/texture_merge.pxd new file mode 100644 index 0000000000..dc2806bdcd --- /dev/null +++ b/openage/convert/processor/export/texture_merge.pxd @@ -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 diff --git a/openage/convert/processor/export/texture_merge.pyx b/openage/convert/processor/export/texture_merge.pyx index f726d0af02..743900b741 100644 --- a/openage/convert/processor/export/texture_merge.pyx +++ b/openage/convert/processor/export/texture_merge.pyx @@ -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 @@ -57,6 +57,7 @@ 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") @@ -64,7 +65,7 @@ cdef void cmerge_frames(texture, packer_type=PackerType.BINPACK, cache=None) exc 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: @@ -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() @@ -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) @@ -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 diff --git a/openage/convert/service/export/png/binpack.pxd b/openage/convert/service/export/png/binpack.pxd index e13725a367..05f3a27c79 100644 --- a/openage/convert/service/export/png/binpack.pxd +++ b/openage/convert/service/export/png/binpack.pxd @@ -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 @@ -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 @@ -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 diff --git a/openage/convert/service/export/png/binpack.pyx b/openage/convert/service/export/png/binpack.pyx index 30e39cd95f..b28cebe742 100644 --- a/openage/convert/service/export/png/binpack.pyx +++ b/openage/convert/service/export/png/binpack.pyx @@ -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 @@ -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) @@ -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 = sqrt(n) + cdef int num for num in range(a, 0, -1): if n % num == 0: return num, n // num @@ -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): """ @@ -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 @@ -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: @@ -97,18 +97,17 @@ 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() @@ -116,10 +115,10 @@ cdef class BestPacker: 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) @@ -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 @@ -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 @@ -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)] @@ -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. """ @@ -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 = malloc(sizeof(packer_node)) @@ -246,9 +245,9 @@ 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)) @@ -256,7 +255,7 @@ cdef class BinaryTreePacker(Packer): 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 = malloc(sizeof(packer_node)) @@ -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" @@ -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 = malloc(sizeof(packer_node)) @@ -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 = malloc(sizeof(packer_node))