From a6b6062be4d5c0b0b6a58c37f409070c9fc1e5a4 Mon Sep 17 00:00:00 2001 From: Sebastian Krzyszkowiak Date: Sun, 13 Jan 2019 18:44:44 +0100 Subject: [PATCH] recompress flac to opus and images to lossy webp for emscripten and android builds, with asset cache --- cmake/AssetCache.cmake | 13 ++++++++ cmake/FlacToOgg.cmake | 20 ++++++++--- cmake/FlacToOpus.cmake | 28 ++++++++++++++++ cmake/ImgToWebp.cmake | 37 +++++++++++++++++++++ cmake/libsuperderpy.cmake | 34 ++++++++++++++----- src/internal.c | 3 ++ src/utils.c | 70 +++++++++++++++++++++++++++++++++++++-- 7 files changed, 189 insertions(+), 16 deletions(-) create mode 100644 cmake/AssetCache.cmake create mode 100644 cmake/FlacToOpus.cmake create mode 100644 cmake/ImgToWebp.cmake diff --git a/cmake/AssetCache.cmake b/cmake/AssetCache.cmake new file mode 100644 index 0000000..4c9aca6 --- /dev/null +++ b/cmake/AssetCache.cmake @@ -0,0 +1,13 @@ +function(GetFromAssetCache CACHE FILENAME SALT OUTPUT_PATH_VAR OUTPUT_HASH_VAR) + file(SHA256 ${FILENAME} HASH) + string(SHA1 HASHED_SALT ${SALT}) + if (EXISTS ${CACHE}/${HASH}-${HASHED_SALT}) + set(${OUTPUT_PATH_VAR} ${CACHE}/${HASH}-${HASHED_SALT} PARENT_SCOPE) + endif() + set(${OUTPUT_HASH_VAR} ${HASH} PARENT_SCOPE) +endfunction() + +function(AddToAssetCache CACHE HASH SALT FILENAME) + string(SHA1 HASHED_SALT ${SALT}) + configure_file(${FILENAME} ${CACHE}/${HASH}-${HASHED_SALT} COPYONLY) +endfunction() diff --git a/cmake/FlacToOgg.cmake b/cmake/FlacToOgg.cmake index 941f9c3..593d476 100644 --- a/cmake/FlacToOgg.cmake +++ b/cmake/FlacToOgg.cmake @@ -1,14 +1,26 @@ +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") +include(AssetCache) + find_program(OGGENC NAMES oggenc NO_CMAKE_FIND_ROOT_PATH) if(OGGENC) file(GLOB_RECURSE FLAC_FILES RELATIVE ${DATADIR} ${DATADIR}/*.flac) message(STATUS "FlacToOgg engaging... (using ${OGGENC})") foreach(file IN LISTS FLAC_FILES) message(STATUS ${file}) - execute_process(COMMAND ${OGGENC} -Q -b 192 --resample 44100 ${file} WORKING_DIRECTORY ${DATADIR} RESULT_VARIABLE OGGENC_RESULT) - if(OGGENC_RESULT) - message(WARNING "ERROR: ${OGGENC_RESULT}") - else() + string(REGEX REPLACE "^(.+)(\\.[^.]+)$" "\\1" filename ${file}) + GetFromAssetCache(${CACHE} ${DATADIR}/${file} "ogg-${BITRATE}-${SAMPLERATE}" CACHED HASH) + if (CACHED) file(REMOVE ${DATADIR}/${file}) + configure_file(${CACHED} ${DATADIR}/${filename}.ogg COPYONLY) + unset(CACHED) + else() + execute_process(COMMAND ${OGGENC} -Q -b ${BITRATE} --resample ${SAMPLERATE} ${file} -o ${filename}.ogg WORKING_DIRECTORY ${DATADIR} RESULT_VARIABLE OGGENC_RESULT) + if(OGGENC_RESULT) + message(WARNING "ERROR: ${OGGENC_RESULT}") + else() + file(REMOVE ${DATADIR}/${file}) + AddToAssetCache(${CACHE} ${HASH} "ogg-${BITRATE}-${SAMPLERATE}" "${DATADIR}/${filename}.ogg") + endif() endif() endforeach(file) else(OGGENC) diff --git a/cmake/FlacToOpus.cmake b/cmake/FlacToOpus.cmake new file mode 100644 index 0000000..426be98 --- /dev/null +++ b/cmake/FlacToOpus.cmake @@ -0,0 +1,28 @@ +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") +include(AssetCache) + +find_program(OPUSENC NAMES opusenc NO_CMAKE_FIND_ROOT_PATH) +if(OPUSENC) + file(GLOB_RECURSE FLAC_FILES RELATIVE ${DATADIR} ${DATADIR}/*.flac) + message(STATUS "FlacToOpus engaging... (using ${OPUSENC})") + foreach(file IN LISTS FLAC_FILES) + message(STATUS ${file}) + string(REGEX REPLACE "^(.+)(\\.[^.]+)$" "\\1" filename ${file}) + GetFromAssetCache(${CACHE} ${DATADIR}/${file} "opus-${BITRATE}" CACHED HASH) + if (CACHED) + file(REMOVE ${DATADIR}/${file}) + configure_file(${CACHED} ${DATADIR}/${filename}.opus COPYONLY) + unset(CACHED) + else() + execute_process(COMMAND ${OPUSENC} --quiet --bitrate ${BITRATE} ${file} ${filename}.opus WORKING_DIRECTORY ${DATADIR} RESULT_VARIABLE OPUSENC_RESULT) + if(OPUSENC_RESULT) + message(WARNING "ERROR: ${OPUSENC_RESULT}") + else() + file(REMOVE ${DATADIR}/${file}) + AddToAssetCache(${CACHE} ${HASH} "opus-${BITRATE}" "${DATADIR}/${filename}.opus") + endif() + endif() + endforeach(file) +else(OPUSENC) + message(WARNING "FlacToOpus: can't find opusenc!") +endif(OPUSENC) diff --git a/cmake/ImgToWebp.cmake b/cmake/ImgToWebp.cmake new file mode 100644 index 0000000..052746c --- /dev/null +++ b/cmake/ImgToWebp.cmake @@ -0,0 +1,37 @@ +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") +include(AssetCache) + +find_program(CONVERT NAMES convert NO_CMAKE_FIND_ROOT_PATH) +if(CONVERT) + file(GLOB_RECURSE IMAGE_FILES RELATIVE ${DATADIR} ${DATADIR}/*.png ${DATADIR}/*.jpg ${DATADIR}/*.JPG ${DATADIR}/*.webp) + message(STATUS "ImgToWebp engaging... (using ${CONVERT})") + foreach(file IN LISTS IMAGE_FILES) + if (NOT file MATCHES "^icons/") + message(STATUS ${file}) + string(REGEX REPLACE "^(.+)(\\.[^.]+)$" "\\1" filepath ${file}) + if (RESIZE) + set(resizecmd -resize ${RESIZE}) + else() + set(resizecmd "") + endif() + GetFromAssetCache(${CACHE} ${DATADIR}/${file} "${QUALITY}-${RESIZE}-${PARAMS}" CACHED HASH) + if (CACHED) + file(REMOVE ${DATADIR}/${file}) + configure_file(${CACHED} ${DATADIR}/${filepath}.wbp COPYONLY) + unset(CACHED) + else() + separate_arguments(PARAMS) + set(CONVERT_CMD ${CONVERT} ${file} -quality ${QUALITY} ${PARAMS} ${resizecmd} webp:${filepath}.wbp) + execute_process(COMMAND ${CONVERT_CMD} WORKING_DIRECTORY ${DATADIR} RESULT_VARIABLE CONVERT_RESULT) + if(CONVERT_RESULT) + message(WARNING "ERROR: ${CONVERT_RESULT}") + else() + file(REMOVE ${DATADIR}/${file}) + AddToAssetCache(${CACHE} ${HASH} "${QUALITY}-${RESIZE}-${PARAMS}" "${DATADIR}/${filepath}.wbp") + endif() + endif() + endif() + endforeach(file) +else(CONVERT) + message(WARNING "ImgToWebp: can't find convert!") +endif(CONVERT) diff --git a/cmake/libsuperderpy.cmake b/cmake/libsuperderpy.cmake index ce79879..d239d16 100644 --- a/cmake/libsuperderpy.cmake +++ b/cmake/libsuperderpy.cmake @@ -314,16 +314,28 @@ if (NOT LIBSUPERDERPY_CONFIG_INCLUDED) if (ANDROID OR EMSCRIPTEN) if (ANDROID) - set(FLACTOOGG_DATADIR "${CMAKE_BINARY_DIR}/android/assets/data") - set(FLACTOOGG_DEPEND "") + set(ASSET_PIPELINE_DATADIR "${CMAKE_BINARY_DIR}/android/assets/data") + set(ASSET_PIPELINE_DEPEND "") else (EMSCRIPTEN) - set(FLACTOOGG_DATADIR "${CMAKE_INSTALL_PREFIX}/share/${LIBSUPERDERPY_GAMENAME}/data") - set(FLACTOOGG_DEPEND ${LIBSUPERDERPY_GAMENAME}_install) + set(ASSET_PIPELINE_DATADIR "${CMAKE_INSTALL_PREFIX}/share/${LIBSUPERDERPY_GAMENAME}/data") + set(ASSET_PIPELINE_DEPEND ${LIBSUPERDERPY_GAMENAME}_install) endif() - add_custom_target(${LIBSUPERDERPY_GAMENAME}_flac_to_ogg - DEPENDS ${FLACTOOGG_DEPEND} - COMMAND ${CMAKE_COMMAND} -DDATADIR=${FLACTOOGG_DATADIR} -P ${LIBSUPERDERPY_DIR}/cmake/FlacToOgg.cmake) + set(FLACTOOPUS_BITRATE "192" CACHE STRING "Bitrate of resulting Opus files (kbps)") + + add_custom_target(${LIBSUPERDERPY_GAMENAME}_flac_to_opus + DEPENDS ${ASSET_PIPELINE_DEPEND} + COMMAND ${CMAKE_COMMAND} -DDATADIR=${ASSET_PIPELINE_DATADIR} -DCACHE="${CMAKE_SOURCE_DIR}/.assetcache" -DBITRATE=${FLACTOOPUS_BITRATE} -P ${LIBSUPERDERPY_DIR}/cmake/FlacToOpus.cmake + USES_TERMINAL) + + set(IMGTOWEBP_QUALITY "75" CACHE STRING "Quality of resulting WebP files") + set(IMGTOWEBP_RESIZE "" CACHE STRING "Value passed as -resize to ImageMagick (no resizing when empty)") + set(IMGTOWEBP_PARAMS "" CACHE STRING "Additional ImageMagick parameters") + + add_custom_target(${LIBSUPERDERPY_GAMENAME}_img_to_webp + DEPENDS ${ASSET_PIPELINE_DEPEND} + COMMAND ${CMAKE_COMMAND} -DQUALITY="${IMGTOWEBP_QUALITY}" -DRESIZE="${IMGTOWEBP_RESIZE}" -DPARAMS="${IMGTOWEBP_PARAMS}" -DCACHE="${CMAKE_SOURCE_DIR}/.assetcache" -DDATADIR=${ASSET_PIPELINE_DATADIR} -P ${LIBSUPERDERPY_DIR}/cmake/ImgToWebp.cmake + USES_TERMINAL) endif(ANDROID OR EMSCRIPTEN) @@ -335,22 +347,25 @@ if (NOT LIBSUPERDERPY_CONFIG_INCLUDED) set(APK_PATH ${CMAKE_BINARY_DIR}/android/bin/${LIBSUPERDERPY_GAMENAME}-debug.apk) add_custom_target(${LIBSUPERDERPY_GAMENAME}_apk ALL - DEPENDS ${EXECUTABLE} ${LIBSUPERDERPY_GAMENAME}_flac_to_ogg + DEPENDS ${EXECUTABLE} ${LIBSUPERDERPY_GAMENAME}_flac_to_opus ${LIBSUPERDERPY_GAMENAME}_img_to_webp BYPRODUCTS ${APK_PATH} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/android" COMMAND ${ANT_COMMAND} debug + USES_TERMINAL ) add_custom_target(${LIBSUPERDERPY_GAMENAME}_install_apk DEPENDS ${LIBSUPERDERPY_GAMENAME}_apk WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/android" COMMAND adb -d install -r ${APK_PATH} + USES_TERMINAL ) add_custom_target(${LIBSUPERDERPY_GAMENAME}_run_apk DEPENDS ${LIBSUPERDERPY_GAMENAME}_install_apk COMMAND adb -d shell 'am start -a android.intent.action.MAIN -n ${LIBSUPERDERPY_APPID}/net.dosowisko.libsuperderpy.Activity' + USES_TERMINAL ) else(ANDROID) @@ -371,9 +386,10 @@ if (NOT LIBSUPERDERPY_CONFIG_INCLUDED) add_custom_target(${LIBSUPERDERPY_GAMENAME}_js - DEPENDS ${LIBSUPERDERPY_GAMENAME}_install ${LIBSUPERDERPY_GAMENAME}_flac_to_ogg + DEPENDS ${LIBSUPERDERPY_GAMENAME}_install ${LIBSUPERDERPY_GAMENAME}_flac_to_opus ${LIBSUPERDERPY_GAMENAME}_img_to_webp WORKING_DIRECTORY "${CMAKE_INSTALL_PREFIX}/${LIBSUPERDERPY_GAMENAME}" COMMAND "${CMAKE_C_COMPILER}" ${CFLAGS_LIST} ../bin/${LIBSUPERDERPY_GAMENAME}${CMAKE_EXECUTABLE_SUFFIX} ../lib/libsuperderpy${CMAKE_SHARED_LIBRARY_SUFFIX} ../lib/libsuperderpy-${LIBSUPERDERPY_GAMENAME}${CMAKE_SHARED_LIBRARY_SUFFIX} ${ALLEGRO5_LIBS} ${EMSCRIPTEN_FLAGS} -s MAIN_MODULE=1 -s TOTAL_MEMORY=${EMSCRIPTEN_TOTAL_MEMORY_BYTES} -o ${LIBSUPERDERPY_GAMENAME}.html --preload-file ../share/${LIBSUPERDERPY_GAMENAME}/data --preload-file gamestates@/ + USES_TERMINAL VERBATIM ) endif(EMSCRIPTEN) diff --git a/src/internal.c b/src/internal.c index 2866096..bed8809 100644 --- a/src/internal.c +++ b/src/internal.c @@ -649,6 +649,9 @@ SYMBOL_INTERNAL ALLEGRO_BITMAP* AddBitmap(struct Game* game, char* filename) { rc->counter = 1; rc->id = strdup(filename); rc->data = al_load_bitmap(GetDataFilePath(game, filename)); + if (!rc->data) { + FatalError(game, false, "Bitmap %s (%s) failed to load.", filename, GetDataFilePath(game, filename)); + } game->_priv.bitmaps[bucket] = AddToList(game->_priv.bitmaps[bucket], rc); } return rc->data; diff --git a/src/utils.c b/src/utils.c index 9e0d5ca..73538ea 100644 --- a/src/utils.c +++ b/src/utils.c @@ -384,7 +384,17 @@ SYMBOL_EXPORT char* GetDataFilePath(struct Game* game, const char* filename) { #ifdef MAEMO5 char origfn[255] = "maemo5/"; - strncat(origfn, filename, 246); + strncat(origfn, filename, 247); + + result = TestDataFilePath(game, origfn); + if (result) { + return AddGarbage(game, result); + } +#endif + +#ifdef __EMSCRIPTEN__ + char origfn[255] = "emscripten/"; + strncat(origfn, filename, 243); result = TestDataFilePath(game, origfn); if (result) { @@ -398,14 +408,68 @@ SYMBOL_EXPORT char* GetDataFilePath(struct Game* game, const char* filename) { if (sub) { sub[0] = '.'; sub[1] = 'o'; - sub[2] = 'g'; - sub[3] = 'g'; + sub[2] = 'p'; + sub[3] = 'u'; + sub[4] = 's'; + sub[5] = 0; + } + result = TestDataFilePath(game, file); + if (result) { + return AddGarbage(game, result); + } + + sub = strstr(file, ".png"); + if (sub) { + sub[0] = '.'; + sub[1] = 'w'; + sub[2] = 'b'; + sub[3] = 'p'; sub[4] = 0; } result = TestDataFilePath(game, file); if (result) { return AddGarbage(game, result); } + + sub = strstr(file, ".jpg"); + if (sub) { + sub[0] = '.'; + sub[1] = 'w'; + sub[2] = 'b'; + sub[3] = 'p'; + sub[4] = 0; + } + result = TestDataFilePath(game, file); + if (result) { + return AddGarbage(game, result); + } + + sub = strstr(file, ".JPG"); + if (sub) { + sub[0] = '.'; + sub[1] = 'w'; + sub[2] = 'b'; + sub[3] = 'p'; + sub[4] = 0; + } + result = TestDataFilePath(game, file); + if (result) { + return AddGarbage(game, result); + } + + sub = strstr(file, ".webp"); + if (sub) { + sub[0] = '.'; + sub[1] = 'w'; + sub[2] = 'b'; + sub[3] = 'p'; + sub[4] = 0; + } + result = TestDataFilePath(game, file); + if (result) { + return AddGarbage(game, result); + } + #endif result = TestDataFilePath(game, filename);