From 1f15133dd985442af20d42a96fbcd0007f03bd2b Mon Sep 17 00:00:00 2001 From: yum Date: Tue, 21 Mar 2023 17:13:32 -0700 Subject: Reduce texture memory usage for English speakers We used to populate 7 4k textures + 1 2k texture for all users. Now if the user has configured `bytes_per_char=1` in the Unity panel, we just populate a single 512x512 texture containing the first 128 ASCII characters. This reduces texture memory usage by 99.74%, from 134.67 MB to 340 KB. --- Scripts/generate_fonts.py | 79 +++++++++++++++++++++++++++++++++------------- Scripts/generate_shader.py | 36 +++++++++++++-------- 2 files changed, 80 insertions(+), 35 deletions(-) (limited to 'Scripts') diff --git a/Scripts/generate_fonts.py b/Scripts/generate_fonts.py index 931abfe..cf73e6a 100644 --- a/Scripts/generate_fonts.py +++ b/Scripts/generate_fonts.py @@ -114,36 +114,71 @@ def in_range(x, range_pair) -> bool: max_char = max(allowlist) print("max char: {}".format(max_char)) print("num chars: {}".format(len(allowlist))) -total_rows = math.ceil(max_char / n_cols) -print("total rows {}".format(total_rows)) -total_textures = math.ceil(total_rows / n_rows) -print("total textures {}".format(total_textures)) - -for nth_texture in range(0, total_textures): - # Create an 8K grayscale ("L") or black and white ("1") image - image = Image.new(mode="1", size=(8192,8192), color=0) + +def genUnicode(): + total_rows = math.ceil(max_char / n_cols) + print("total rows {}".format(total_rows)) + total_textures = math.ceil(total_rows / n_rows) + print("total textures {}".format(total_textures)) + + for nth_texture in range(0, total_textures): + # Create an 8K grayscale ("L") or black and white ("1") image + # Unity will re-encode b&w to grayscale, so using b&w just helps keep + # the package size low (we vendor these, we don't generate them + # client-side). + image = Image.new(mode="1", size=(8192,8192), color=0) + draw = ImageDraw.Draw(image) + + row_begin = nth_texture * n_rows + + for row in range(row_begin, row_begin + n_rows): + line = "" + for col in range(0, n_cols): + # Generate the unicode character for this spot. + n = row * n_cols + col + char = None + font_info = None + if n in allowlist.keys(): + char = chr(n) + font_info = allowlist[n] + else: + char = " " + font_info = FontInfo(unifont, 0) + # Hack: Chinese, Japanese, and Korean characters are all double + # width and are all on textures [1,6]. To fit them in the same + # grid, we use a half-size font. + draw.text((col * font_pixels / 2, (row - row_begin) * font_pixels + + font_info.dy), char, font=font_info.font, fill=255) + + image.save("Fonts/Bitmaps/font-%01d.png" % nth_texture) + +def genASCII(): + # Create an 8k grayscale image. 16 glyphs wide, 8 glyphs tall. + # Only characters on the range [0, 128). + image = Image.new(mode="L", size=(8192,8192), color=0) draw = ImageDraw.Draw(image) + n_rows = 8 + n_cols = 16 - row_begin = nth_texture * n_rows + font = ImageFont.truetype( + "Fonts/Noto_Sans_Mono/static/NotoSansMono/NotoSansMono-Bold.ttf", + int((8192 / 8) * 0.75), index=0, layout_engine=layout_engine) - for row in range(row_begin, row_begin + n_rows): - line = "" + for row in range(0, n_rows): for col in range(0, n_cols): - # Generate the unicode character for this spot. n = row * n_cols + col char = None font_info = None if n in allowlist.keys(): char = chr(n) - font_info = allowlist[n] else: char = " " - font_info = FontInfo(unifont, 0) - # Hack: Chinese, Japanese, and Korean characters are all double - # width and are all on textures [1,6]. To fit them in the same - # grid, we use a half-size font. - draw.text((col * font_pixels / 2, (row - row_begin) * font_pixels + - font_info.dy), char, font=font_info.font, fill=255) - - image.save("Fonts/Bitmaps/font-%01d.png" % nth_texture) - + draw.text((col * font_pixels * 8 / 2, row * font_pixels * 8 - 20), + char, font=font, fill=255) + image.save("Fonts/Bitmaps/font-ascii.png") + +if __name__ == "__main__": + print("Generating unicode fonts") + genUnicode() + print("Generating ASCII fonts") + genASCII() diff --git a/Scripts/generate_shader.py b/Scripts/generate_shader.py index cf46533..2348141 100644 --- a/Scripts/generate_shader.py +++ b/Scripts/generate_shader.py @@ -37,12 +37,15 @@ def generateCgParams(nbytes: int, nrows: int, ncols: int, prefix: str = "") -> s # uniform int BYTES_PER_CHAR = $nbytes; # uniform int NROWS = $nrows; # uniform int NCOLS = $ncols; -def generateCgConstants(nbytes: int, nrows: int, ncols: int, prefix: str = "") -> str: +def generateCgConstants(nbytes: int, board_nrows: int, board_ncols: int, + texture_nrows: int, texture_ncols: int, prefix: str = "") -> str: lines = [] lines.append(prefix + "// BEGIN GENERATED CODE BLOCK") lines.append(prefix + "#define BYTES_PER_CHAR {}".format(nbytes)) - lines.append(prefix + "#define NROWS {}".format(nrows)) - lines.append(prefix + "#define NCOLS {}".format(ncols)) + lines.append(prefix + "#define BOARD_NROWS {}".format(board_nrows)) + lines.append(prefix + "#define BOARD_NCOLS {}".format(board_ncols)) + lines.append(prefix + "#define TEXTURE_NROWS {}".format(texture_nrows)) + lines.append(prefix + "#define TEXTURE_NCOLS {}".format(texture_ncols)) lines.append(prefix + "// END GENERATED CODE BLOCK") return '\n'.join(lines) @@ -117,37 +120,44 @@ if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--bytes_per_char", type=str, help="The number of bytes to use to represent each character") - parser.add_argument("--rows", type=str, help="The number of rows on the board") - parser.add_argument("--cols", type=str, help="The number of columns on the board") + parser.add_argument("--board_rows", type=str, help="The number of rows on the board") + parser.add_argument("--board_cols", type=str, help="The number of columns on the board") + parser.add_argument("--texture_rows", type=str, help="The number of rows on the font textures") + parser.add_argument("--texture_cols", type=str, help="The number of columns on the font textures") parser.add_argument("--shader_template", type=str, help="The path to the shader template") parser.add_argument("--shader_path", type=str, help="The path where the generated shader will be written") args = parser.parse_args() - if not args.bytes_per_char or not args.rows or not args.cols \ + if not args.bytes_per_char or not args.board_rows or not args.board_cols \ + or not args.texture_rows or not args.texture_cols \ or not args.shader_template or not args.shader_path: - print("--bytes_per_char, --rows, --cols, --shader_template, --shader_path required", file=sys.stderr) + print(("--bytes_per_char, --board_rows, --board_cols, --texture_rows, " + "--texture_cols, --shader_template, --shader_path required"), file=sys.stderr) sys.exit(1) nbytes = int(args.bytes_per_char) - nrows = int(args.rows) - ncols = int(args.cols) + board_nrows = int(args.board_rows) + board_ncols = int(args.board_cols) + texture_nrows = int(args.texture_rows) + texture_ncols = int(args.texture_cols) - replacement = generateUnityParams(nbytes, nrows, ncols, prefix = "") + replacement = generateUnityParams(nbytes, board_nrows, board_ncols, prefix = "") #print(replacement) macro = "// %TEMPLATE__UNITY_ROW_COL_PARAMS%" applyLineMacro(args.shader_template, args.shader_path, macro, replacement) - replacement = generateCgParams(nbytes, nrows, ncols, prefix = " ") + replacement = generateCgParams(nbytes, board_nrows, board_ncols, prefix = " ") #print(replacement) macro = "// %TEMPLATE__CG_ROW_COL_PARAMS%" applyLineMacro(args.shader_path, args.shader_path, macro, replacement) - replacement = generateCgConstants(nbytes, nrows, ncols, prefix = " ") + replacement = generateCgConstants(nbytes, board_nrows, board_ncols, + texture_nrows, texture_ncols, prefix = " ") #print(replacement) macro = "// %TEMPLATE__CG_ROW_COL_CONSTANTS%" applyLineMacro(args.shader_path, args.shader_path, macro, replacement) - replacement = generateLetterAccessor(nbytes, nrows, ncols, prefix = " ") + replacement = generateLetterAccessor(nbytes, board_nrows, board_ncols, prefix = " ") #print(replacement) macro = "// %TEMPLATE__CG_LETTER_ACCESSOR%" applyLineMacro(args.shader_path, args.shader_path, macro, replacement) -- cgit v1.2.3