From 3543fd0eaaf3ed0f068ed0012e9da8a6d500f298 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Mon, 2 Nov 2020 08:17:45 +0200 Subject: Embed: Build resource files faster As part of the CMake configuration, build bincat (23 lines of C) to concatenate resource files together. This is much faster because CMake doesn't have to get involved in the contents of the binary files. --- CMakeLists.txt | 3 +- Embed.cmake | 159 ---------------------------------------------------- res/CMakeLists.txt | 4 ++ res/Embed.cmake | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++++ res/bincat.c | 48 ++++++++++++++++ 5 files changed, 215 insertions(+), 161 deletions(-) delete mode 100644 Embed.cmake create mode 100644 res/CMakeLists.txt create mode 100644 res/Embed.cmake create mode 100644 res/bincat.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f4b0376c..cb97185c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,6 @@ # https://libsdl.org/). To make configuration easier, consider writing # for your personal use a pkg-config sdl2.pc file that uses the Windows # version of the library. -# - `cat` is relied upon for merging all the resource files together. cmake_minimum_required (VERSION 3.9) @@ -33,7 +32,7 @@ option (ENABLE_RESOURCE_EMBED "Embed resources inside the executable" OFF) option (ENABLE_WINDOWPOS_FIX "Set position after showing window (workaround for SDL bug)" OFF) include (BuildType.cmake) -include (Embed.cmake) +include (res/Embed.cmake) if (NOT EXISTS ${CMAKE_SOURCE_DIR}/lib/the_Foundation/CMakeLists.txt) set (INSTALL_THE_FOUNDATION YES) find_package (the_Foundation REQUIRED) diff --git a/Embed.cmake b/Embed.cmake deleted file mode 100644 index 75f3e06f..00000000 --- a/Embed.cmake +++ /dev/null @@ -1,159 +0,0 @@ -# CMake Helper for Binary Resources -# Copyright: 2020 Jaakko Keränen -# License: BSD 2-Clause - -option (ENABLE_RESOURCE_EMBED "Embed resources inside the executable" OFF) -# Note: If disabled, the Unix "cat" tool is required for concatenating -# the resources into a single "resources.binary" file. - -function (embed_getname output fn) - get_filename_component (name ${fn} NAME_WE) - string (REPLACE "-" "" name ${name}) - string (REPLACE "=" "" name ${name}) - string (SUBSTRING ${name} 0 1 first) - string (TOUPPER ${first} first) - string (SUBSTRING ${name} 1 -1 remainder) - set (name "${first}${remainder}") - get_filename_component (ext ${fn} EXT) - if (ext STREQUAL .ttf) - set (resName "font") - elseif (ext STREQUAL .png) - set (resName "image") - else () - set (resName "blob") - endif () - set (resName "${resName}${name}_Embedded" PARENT_SCOPE) -endfunction (embed_getname) - -function (embed_write path name fnSource fnHeader) - message (STATUS "${path}") - file (READ ${path} fileData HEX) - string (REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," cList ${fileData}) - string (REGEX REPLACE - "(0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],)" - "\\1\n " cList ${cList}) - string (LENGTH ${fileData} len) - math (EXPR alen "${len} / 2") - set (src "static const uint8_t bytes_${name}_[] = {\n") - string (APPEND src " ${cList}\n") - string (APPEND src "}; -static iBlockData data_${name}_ = { - .refCount = 2, .data = iConstCast(char *, bytes_${name}_), .size = ${alen}, .allocSize = ${alen} -}; -const iBlock ${name} = { &data_${name}_ }; -") - set (header "extern const iBlock ${name};\n") - # Output the results. - file (APPEND ${fnSource} "${src}") - file (APPEND ${fnHeader} "${header}") -endfunction (embed_write) - -function (embed_filesize path sizeVar) - file (READ ${path} data HEX) - string (LENGTH ${data} fsize) - math (EXPR fsize "${fsize}/2") - set (${sizeVar} ${fsize} PARENT_SCOPE) -endfunction (embed_filesize) - -function (embed_make) - set (EMB_H ${CMAKE_CURRENT_BINARY_DIR}/embedded.h) - set (EMB_C ${CMAKE_CURRENT_BINARY_DIR}/embedded.c) - if (ENABLE_RESOURCE_EMBED) - set (needGen NO) - if (NOT EXISTS ${EMB_H} OR NOT EXISTS ${EMB_C}) - set (needGen YES) - else () - file (TIMESTAMP ${EMB_H} genTime %s) - foreach (resPath ${ARGV}) - set (fn "${CMAKE_CURRENT_LIST_DIR}/${resPath}") - file (TIMESTAMP ${fn} resTime %s) - if (${resTime} GREATER ${genTime}) - set (needGen YES) - endif () - endforeach (resPath) - endif () - else () - set (needGen YES) - endif () - if (needGen) - if (ENABLE_RESOURCE_EMBED) - # Compose a source file with the resource data in an array. - file (WRITE ${EMB_H} "#include \n") - file (WRITE ${EMB_C} "#include \"embedded.h\"\n") - foreach (fn ${ARGV}) - embed_getname (resName ${fn}) - embed_write (${fn} ${resName} ${EMB_C} ${EMB_H}) - endforeach (fn) - else () - # Collect resources in a single binary file. - set (EMB_BIN ${CMAKE_CURRENT_BINARY_DIR}/resources.binary) - file (REMOVE ${EMB_BIN}) - execute_process (COMMAND cat ${ARGV} OUTPUT_FILE ${EMB_BIN} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) - set (offsets) - set (fpos 0) - foreach (fn ${ARGV}) - embed_filesize (${CMAKE_SOURCE_DIR}/${fn} fileSize) - list (APPEND offsets "${fpos}") - math (EXPR fpos "${fpos} + ${fileSize}") - endforeach (fn) - file (WRITE ${EMB_H} "#include \n -#define iHaveLoadEmbed 1 -iBool load_Embed(const char *path);\n\n") - file (WRITE ${EMB_C} [[ -#include "embedded.h" -#include -#include - -iDeclareType(EmbedChunk) - -struct Impl_EmbedChunk { - size_t pos; - size_t size; -}; - -static const iEmbedChunk chunks_Embed_[] = { -]]) - set (index 0) - foreach (fn ${ARGV}) - embed_filesize (${CMAKE_SOURCE_DIR}/${fn} fileSize) - list (GET offsets ${index} fpos) - file (APPEND ${EMB_C} " { ${fpos}, ${fileSize} },\n") - math (EXPR index "${index} + 1") - endforeach (fn) - file (APPEND ${EMB_C} "};\n\n") - foreach (fn ${ARGV}) - embed_getname (resName ${fn}) - file (APPEND ${EMB_H} "extern iBlock ${resName};\n") - file (APPEND ${EMB_C} "iBlock ${resName};\n") - endforeach (fn) - file (APPEND ${EMB_C} "\nstatic iBlock *blocks_Embed_[] = {\n") - foreach (fn ${ARGV}) - embed_getname (resName ${fn}) - file (APPEND ${EMB_C} " &${resName},\n") - endforeach (fn) - file (APPEND ${EMB_C} [[ -}; - -iBool load_Embed(const char *path) { - const size_t fileSize = (size_t) fileSizeCStr_FileInfo(path); - iFile *f = iClob(newCStr_File(path)); - if (open_File(f, readOnly_FileMode)) { - iForIndices(i, blocks_Embed_) { - const iEmbedChunk *chunk = &chunks_Embed_[i]; - iBlock *data = blocks_Embed_[i]; - if (chunk->pos + chunk->size > fileSize) { - return iFalse; - } - init_Block(data, chunk->size); - seek_File(f, chunk->pos); - readData_File(f, chunk->size, data_Block(data)); - } - return iTrue; - } - return iFalse; -} -]]) - endif () - endif () -endfunction (embed_make) diff --git a/res/CMakeLists.txt b/res/CMakeLists.txt new file mode 100644 index 00000000..0a8c6260 --- /dev/null +++ b/res/CMakeLists.txt @@ -0,0 +1,4 @@ +# CMakeLists for bincat +cmake_minimum_required (VERSION 3.0) +project (BINCAT VERSION 1.0 LANGUAGES C) +add_executable (bincat bincat.c) diff --git a/res/Embed.cmake b/res/Embed.cmake new file mode 100644 index 00000000..8da9e1d6 --- /dev/null +++ b/res/Embed.cmake @@ -0,0 +1,162 @@ +# CMake Helper for Binary Resources +# Copyright: 2020 Jaakko Keränen +# License: BSD 2-Clause + +option (ENABLE_RESOURCE_EMBED "Embed resources inside the executable" OFF) + +# Build "bincat" for concatenating files. +if (NOT ENABLE_RESOURCE_EMBED) + message (STATUS "Compiling bincat for merging resource files...") + set (_catDir ${CMAKE_BINARY_DIR}/res) + execute_process (COMMAND ${CMAKE_COMMAND} -E make_directory ${_catDir}) + execute_process (COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR}/res WORKING_DIRECTORY ${_catDir}) + execute_process (COMMAND ${CMAKE_COMMAND} --build . WORKING_DIRECTORY ${_catDir}) + set (BINCAT_COMMAND ${_catDir}/bincat) +endif () + +function (embed_getname output fn) + get_filename_component (name ${fn} NAME_WE) + string (REPLACE "-" "" name ${name}) + string (REPLACE "=" "" name ${name}) + string (SUBSTRING ${name} 0 1 first) + string (TOUPPER ${first} first) + string (SUBSTRING ${name} 1 -1 remainder) + set (name "${first}${remainder}") + get_filename_component (ext ${fn} EXT) + if (ext STREQUAL .ttf) + set (resName "font") + elseif (ext STREQUAL .png) + set (resName "image") + else () + set (resName "blob") + endif () + set (resName "${resName}${name}_Embedded" PARENT_SCOPE) +endfunction (embed_getname) + +function (embed_write path name fnSource fnHeader) + message (STATUS "${path}") + file (READ ${path} fileData HEX) + string (REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," cList ${fileData}) + string (REGEX REPLACE + "(0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],0x[0-9a-f][0-9a-f],)" + "\\1\n " cList ${cList}) + string (LENGTH ${fileData} len) + math (EXPR alen "${len} / 2") + set (src "static const uint8_t bytes_${name}_[] = {\n") + string (APPEND src " ${cList}\n") + string (APPEND src "}; +static iBlockData data_${name}_ = { + .refCount = 2, .data = iConstCast(char *, bytes_${name}_), .size = ${alen}, .allocSize = ${alen} +}; +const iBlock ${name} = { &data_${name}_ }; +") + set (header "extern const iBlock ${name};\n") + # Output the results. + file (APPEND ${fnSource} "${src}") + file (APPEND ${fnHeader} "${header}") +endfunction (embed_write) + +function (embed_make) + set (EMB_H ${CMAKE_CURRENT_BINARY_DIR}/embedded.h) + set (EMB_C ${CMAKE_CURRENT_BINARY_DIR}/embedded.c) + if (ENABLE_RESOURCE_EMBED) + set (needGen NO) + if (NOT EXISTS ${EMB_H} OR NOT EXISTS ${EMB_C}) + set (needGen YES) + else () + file (TIMESTAMP ${EMB_H} genTime %s) + foreach (resPath ${ARGV}) + set (fn "${CMAKE_CURRENT_LIST_DIR}/${resPath}") + file (TIMESTAMP ${fn} resTime %s) + if (${resTime} GREATER ${genTime}) + set (needGen YES) + endif () + endforeach (resPath) + endif () + else () + set (needGen YES) + endif () + if (needGen) + if (ENABLE_RESOURCE_EMBED) + # Compose a source file with the resource data in an array. + file (WRITE ${EMB_H} "#include \n") + file (WRITE ${EMB_C} "#include \"embedded.h\"\n") + foreach (fn ${ARGV}) + embed_getname (resName ${fn}) + embed_write (${fn} ${resName} ${EMB_C} ${EMB_H}) + endforeach (fn) + else () + # Collect resources in a single binary file. + set (EMB_BIN ${CMAKE_CURRENT_BINARY_DIR}/resources.binary) + file (REMOVE ${EMB_BIN}) + list (LENGTH ARGV fileCount) + execute_process (COMMAND ${BINCAT_COMMAND} ${EMB_BIN} ${ARGV} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE fileSizes + ) + set (offsets) + set (fpos 0) + foreach (fileSize ${fileSizes}) + list (APPEND offsets "${fpos}") + math (EXPR fpos "${fpos} + ${fileSize}") + endforeach (fileSize) + file (WRITE ${EMB_H} "#include \n +#define iHaveLoadEmbed 1 +iBool load_Embed(const char *path);\n\n") + file (WRITE ${EMB_C} [[ +#include "embedded.h" +#include +#include + +iDeclareType(EmbedChunk) + +struct Impl_EmbedChunk { + size_t pos; + size_t size; +}; + +static const iEmbedChunk chunks_Embed_[] = { +]]) + set (index 0) + foreach (fn ${ARGV}) + list (GET fileSizes ${index} fileSize) + list (GET offsets ${index} fpos) + file (APPEND ${EMB_C} " { ${fpos}, ${fileSize} },\n") + math (EXPR index "${index} + 1") + endforeach (fn) + file (APPEND ${EMB_C} "};\n\n") + foreach (fn ${ARGV}) + embed_getname (resName ${fn}) + file (APPEND ${EMB_H} "extern iBlock ${resName};\n") + file (APPEND ${EMB_C} "iBlock ${resName};\n") + endforeach (fn) + file (APPEND ${EMB_C} "\nstatic iBlock *blocks_Embed_[] = {\n") + foreach (fn ${ARGV}) + embed_getname (resName ${fn}) + file (APPEND ${EMB_C} " &${resName},\n") + endforeach (fn) + file (APPEND ${EMB_C} [[ +}; + +iBool load_Embed(const char *path) { + const size_t fileSize = (size_t) fileSizeCStr_FileInfo(path); + iFile *f = iClob(newCStr_File(path)); + if (open_File(f, readOnly_FileMode)) { + iForIndices(i, blocks_Embed_) { + const iEmbedChunk *chunk = &chunks_Embed_[i]; + iBlock *data = blocks_Embed_[i]; + if (chunk->pos + chunk->size > fileSize) { + return iFalse; + } + init_Block(data, chunk->size); + seek_File(f, chunk->pos); + readData_File(f, chunk->size, data_Block(data)); + } + return iTrue; + } + return iFalse; +} +]]) + endif () + endif () +endfunction (embed_make) diff --git a/res/bincat.c b/res/bincat.c new file mode 100644 index 00000000..8fc9d831 --- /dev/null +++ b/res/bincat.c @@ -0,0 +1,48 @@ +/* Copyright 2020 Jaakko Keränen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +/* bincat.c: Tiny tool for concatenating binary files */ + +#include +#include + +int main(int argc, char *argv[]) { + const size_t bufSize = 1024 * 256; + char *buf = malloc(bufSize); + FILE *out = fopen(argv[1], "wb"); + int i; + for (i = 2; i < argc; ++i) { + FILE *f = fopen(argv[i], "rb"); + size_t fileSize = 0; + for (;;) { + size_t num = fread(buf, 1, bufSize, f); + if (num <= 0) break; + fileSize += num; + fwrite(buf, 1, num, out); + } + fclose(f); + printf("%zu;", fileSize); + } + fclose(out); + free(buf); + return 0; +} -- cgit v1.2.3