diff --git a/contrib/single_file_transcoder/README.md b/contrib/single_file_transcoder/README.md new file mode 100644 index 0000000..95f6ae9 --- /dev/null +++ b/contrib/single_file_transcoder/README.md @@ -0,0 +1,19 @@ +# Single File Basis Universal Transcoder + +The script `combine.sh` creates an _amalgamated_ source file that can be used with or without `basisu_transcoder.h`. This isn't a _header-only_ file but it does offer a similar level of simplicity when integrating into a project. + +Create `basisutranslib.cpp` from the transcoder source using: +``` +cd basis_universal/contrib/single_file_transcoder +./combine.sh -r ../../transcoder -o basisutranslib.cpp basisutranslib-in.cpp +``` +Then add the resulting file to your project (see the [example files](examples)). + +`create_translib.sh` will run the above script, creating the file `basisutranslib.cpp`. + +Why? +---- + +Because all it now takes to support Basis Universal is the addition of a single file, two if using the header, with no configuration or further build steps. + +The library is small, adding, for example, 181kB to an Emscripten compiled WebAssembly project (with transcoding disabled for BC7, ASTC and ATC). diff --git a/contrib/single_file_transcoder/basisutranslib-in.cpp b/contrib/single_file_transcoder/basisutranslib-in.cpp index 0595f10..a684ea6 100644 --- a/contrib/single_file_transcoder/basisutranslib-in.cpp +++ b/contrib/single_file_transcoder/basisutranslib-in.cpp @@ -3,10 +3,26 @@ * \code * ./combine.sh -r ../../transcoder -o basisutranslib.cpp basisutranslib-in.cpp * \endcode - * We can add any build options here, e.g.: - * \code - * #define BASISD_SUPPORT_BC7 0 - * \endcode + * + * \todo -Wunused-parameter errors when removing BASISD_SUPPORT_ETC2_EAC_A8 + * \todo enable and test BASISD_SUPPORT_ASTC in browsers + * \todo BASISD_SUPPORT_DXT5A needs enabling for BC3? */ +/* + * Build options for known platforms. + */ +#ifdef __APPLE__ +#include +#endif +#if TARGET_OS_IPHONE +#define BASISD_SUPPORT_DXT1 0 +#define BASISD_SUPPORT_DXT5A 0 +#endif +#if TARGET_OS_IPHONE || defined(__EMSCRIPTEN__) +#define BASISD_SUPPORT_BC7 0 +#define BASISD_SUPPORT_ASTC 0 +#define BASISD_SUPPORT_ATC 0 +#endif + #include "basisu_transcoder.cpp" diff --git a/contrib/single_file_transcoder/create_transcoder.sh b/contrib/single_file_transcoder/create_translib.sh similarity index 100% rename from contrib/single_file_transcoder/create_transcoder.sh rename to contrib/single_file_transcoder/create_translib.sh diff --git a/contrib/single_file_transcoder/examples/README.md b/contrib/single_file_transcoder/examples/README.md new file mode 100644 index 0000000..c27901f --- /dev/null +++ b/contrib/single_file_transcoder/examples/README.md @@ -0,0 +1,7 @@ +# Single File Basis Universal Examples + +The examples `#include` the generated `basisutranslib.cpp` directly but work equally as well when including `basisu_transcoder.h` and compiling the amalgamated source separately. + +`emscripten.cpp` is a bare-bones [Emscripten](https://github.com/emscripten-core/emscripten) compiled WebGL demo picking the best transcoder format for the sample texture (see the [original PNG image](testcard.png)). + +The example files in this directory are released under a [Creative Commons Zero license](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/contrib/single_file_transcoder/examples/emscripten.cpp b/contrib/single_file_transcoder/examples/emscripten.cpp index 7688c32..dc82f14 100644 --- a/contrib/single_file_transcoder/examples/emscripten.cpp +++ b/contrib/single_file_transcoder/examples/emscripten.cpp @@ -1,8 +1,7 @@ /** - * \file emscripten.c - * Emscripten example of using the single-file \c zstddeclib. Draws a rotating - * textured quad with data from the in-line Zstd compressed DXT1 texture (DXT1 - * being hardware compression, further compressed with Zstd). + * \file emscripten.cpp + * Emscripten example of using the single-file \c basisutranslib. Draws a + * rotating textured quad with data from the in-line compressed texture. * \n * Compile using: * \code @@ -10,12 +9,13 @@ * export EM_FLAGS="-s ENVIRONMENT=web -s WASM=1 --shell-file shell.html --closure 1" * emcc $CC_FLAGS $EM_FLAGS -o out.html emscripten.cpp * \endcode + * + * Released under a CC0 license. */ -#include -#include -#include -#include +#include +#include +#include #include #include @@ -23,17 +23,7 @@ #include #include -#define BASISD_SUPPORT_DXT5A 0 -#define BASISD_SUPPORT_BC7 0 -//#define BASISD_SUPPORT_ETC2_EAC_A8 0 <-- todo: -Wunused-parameter errors -#define BASISD_SUPPORT_ASTC 0 -#define BASISD_SUPPORT_ATC 0 - -#include "../basisutranslib.cpp" - -using namespace basist; - -static etc1_global_selector_codebook* globalCodebook = NULL; +#include "../basisutranslib.cpp" //<-- can be built without to determine the size //********************************* Test Data ********************************/ @@ -793,7 +783,7 @@ static GLuint fragId = 0; static GLint uRotId = -1; /** - * Draw colour ID. + * Texture ID. */ static GLint uTx0Id = -1; @@ -907,6 +897,9 @@ struct posTex2d { #define GL_HAS_EXT(ctx, ext) emscripten_webgl_enable_extension(ctx, ext) #endif +/* + * Possibly missing GL enums. + */ #ifndef GL_COMPRESSED_RGB8_ETC2 #define GL_COMPRESSED_RGB8_ETC2 0x9274 #endif @@ -915,6 +908,155 @@ struct posTex2d { #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 #endif +//***************************** Basis Universal /*****************************/ + +/* + * All of the BasisU code is within this block to enable building with or + * without the library. Not including the transcoder will build a dummy + * implementation to (roughly) determine the size. + */ +#ifdef BASISD_LIB_VERSION + +using namespace basist; + +/** + * Shared codebook instance. + */ +static etc1_global_selector_codebook* globalCodebook = NULL; + +/** + * Returns a supported compressed texture format for a given context. + * + * \param[in] ctx WebGL context + * \param[in] alpha \c true if the texture has an alpha channel + * \return corresponding Basis format + */ +static transcoder_texture_format supports(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE const ctx, bool const alpha) { + /* + * Test for both prefixed and non-prefixed versions. This should grab iOS + * and other ImgTec GPUs first as a preference. + */ + static bool const pvr = GL_HAS_EXT(ctx, "WEBKIT_WEBGL_compressed_texture_pvrtc") + || GL_HAS_EXT(ctx, "WEBGL_compressed_texture_pvrtc"); + if (pvr) { + return (alpha) + ? cTFPVRTC1_4_RGBA // 9 + : cTFPVRTC1_4_RGB; // 8 + } + /* + * We choose DXT next, since a worry is the browser will claim ETC support + * then transcode (transcoding slower and with more artefacts) + */ + static bool const dxt = GL_HAS_EXT(ctx, "WEBGL_compressed_texture_s3tc"); + if (dxt) { + return (alpha) + ? cTFBC3 // 3 + : cTFBC1; // 2 + } + /* + * Finally ETC then falling back on RGB. + */ + static bool const etc = GL_HAS_EXT(ctx, "WEBGL_compressed_texture_etc"); + if (etc) { + return (alpha) + ? cTFETC2 // 1 + : cTFETC1; // 0 + } + /* + * We choose 8888 over 4444 and 565 (in the hope that is is never chosen). + */ + return cTFRGBA32; +} + +/** + * Returns the equivalent GL type given a BasisU type. + * + * \param[in] type BasisU transcode target + * \return equivalent GL type + */ +static GLenum toGlType(transcoder_texture_format const type) { + switch (type) { + case cTFETC1: + return GL_COMPRESSED_RGB8_ETC2; + case cTFETC2: + return GL_COMPRESSED_RGBA8_ETC2_EAC; + case cTFBC1: + return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; + case cTFBC3: + return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + case cTFPVRTC1_4_RGB: + return GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + case cTFPVRTC1_4_RGBA: + return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + case cTFRGBA32: + return GL_UNSIGNED_BYTE; + case cTFRGB565: + return GL_UNSIGNED_SHORT_5_6_5; + default: + return GL_UNSIGNED_SHORT_4_4_4_4; + } +} + +/** + * Uploads the texture. + * + * \param[in] ctx ctx WebGL context + * \param[in] name texture \e name + * \param[in] data \c .basis file content + * \param[in] size number of bytes in \a data + * \return \c true if the texture was decoded and created + */ +bool upload(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE const ctx, GLuint const name, const uint8_t* const data, size_t const size) { + bool success = false; + basisu_transcoder_init(); + if (!globalCodebook) { + globalCodebook = new etc1_global_selector_codebook(g_global_selector_cb_size, g_global_selector_cb); + } + basisu_transcoder transcoder(globalCodebook); + if (transcoder.validate_header(data, size)) { + basisu_file_info fileInfo; + if (transcoder.get_file_info(data, size, fileInfo)) { + transcoder_texture_format type = supports(ctx, fileInfo.m_has_alpha_slices); + basisu_image_info info; + if (transcoder.get_image_info(data, size, info, 0)) { + uint32_t descW, descH, blocks; + if (transcoder.get_image_level_desc(data, size, 0, 0, descW, descH, blocks)) { + if (transcoder.start_transcoding(data, size)) { + uint32_t decSize = basis_get_bytes_per_block(type) * blocks; + if (void* decBuf = malloc(decSize)) { + if (type >= cTFTotalBlockTextureFormats) { + // note that blocks becomes total number of pixels for RGB/RGBA + blocks = descW * descH; + } + if (transcoder.transcode_image_level(data, size, 0, 0, decBuf, blocks, type)) { + glBindTexture(GL_TEXTURE_2D, name); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + if (type < cTFTotalBlockTextureFormats) { + glCompressedTexImage2D(GL_TEXTURE_2D, 0, + toGlType(type), descW, descH, 0, decSize, decBuf); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + descW, descH, 0, GL_RGBA, toGlType(type), decBuf); + } + success = true; + } + free(decBuf); + } + } + } + } + } + } + return success; +} + +#else +// dummy implementation +bool upload(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE /*ctx*/, GLuint /*name*/, const uint8_t* data, size_t size) { + return (data[0] | data[size - 1]) != 0; +} +#endif + //****************************************************************************/ /** @@ -922,6 +1064,11 @@ struct posTex2d { */ static float rotDeg = 0.0f; +/** + * Decoded textures (0 = opaque, 1 = transparent). + */ +static GLuint txName[2] = {}; + /** * Emscripten (single) GL context. */ @@ -930,7 +1077,7 @@ static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE glCtx = 0; /** * Emscripten resize handler. */ -static EM_BOOL resize(int type, const EmscriptenUiEvent* e, void* data) { +static EM_BOOL resize(int /*type*/, const EmscriptenUiEvent* /*e*/, void* /*data*/) { double surfaceW; double surfaceH; if (emscripten_get_element_css_size ("#canvas", &surfaceW, &surfaceH) == EMSCRIPTEN_RESULT_SUCCESS) { @@ -939,9 +1086,6 @@ static EM_BOOL resize(int type, const EmscriptenUiEvent* e, void* data) { glViewport(0, 0, (int) surfaceW, (int) surfaceH); } } - (void) type; - (void) data; - (void) e; return EM_FALSE; } @@ -968,121 +1112,23 @@ static EM_BOOL initContext() { static void tick() { glClearColor(1.0f, 0.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - + if (uRotId >= 0) { glUniform1f(uRotId, rotDeg); rotDeg += 0.1f; if (rotDeg >= 360.0f) { rotDeg -= 360.0f; } + glBindTexture(GL_TEXTURE_2D, txName[(lround(rotDeg / 45) & 1) != 0]); } - + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); glFlush(); } /** - * Returns a supported compressed texture format for a given context. - * - * \param[in] ctx WebGL context - * \param[in] alpha \c true if the texture has an alpha channel - * \return corresponding Basis format - */ -static transcoder_texture_format supports(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE const ctx, bool const alpha = false) { - static bool const pvr = GL_HAS_EXT(ctx, "WEBKIT_WEBGL_compressed_texture_pvrtc") - || GL_HAS_EXT(ctx, "WEBGL_compressed_texture_pvrtc"); - if (pvr) { - return (alpha) - ? cTFPVRTC1_4_RGBA - : cTFPVRTC1_4_RGB; - } - static bool const etc = GL_HAS_EXT(ctx, "WEBGL_compressed_texture_etc"); - if (etc) { - return (alpha) - ? cTFETC2 - : cTFETC1; - } - static bool const dxt = GL_HAS_EXT(ctx, "WEBGL_compressed_texture_s3tc"); - if (dxt) { - return (alpha) - ? cTFBC5 - : cTFBC1; - } - return (alpha) - ? cTFRGBA4444 - : cTFRGB565; -} - -static GLenum toGlType(transcoder_texture_format const type) { - switch (type) { - case cTFETC1: - return GL_COMPRESSED_RGB8_ETC2; - case cTFETC2: - return GL_COMPRESSED_RGBA8_ETC2_EAC; - case cTFBC1: - return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; - case cTFBC5: - return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; - case cTFPVRTC1_4_RGB: - return GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; - case cTFPVRTC1_4_RGBA: - return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; - case cTFRGB565: - GL_UNSIGNED_SHORT_5_6_5; - default: - return GL_UNSIGNED_SHORT_4_4_4_4; - } -} - -/** - * Creates the texture. - */ -bool upload(GLuint name, const uint8_t * data, size_t size, transcoder_texture_format type = cTFBC1) { - basisu_transcoder_init(); - if (!globalCodebook) { - globalCodebook = new etc1_global_selector_codebook(g_global_selector_cb_size, g_global_selector_cb); - } - basisu_transcoder transcoder(globalCodebook); - if (transcoder.validate_header(data, size)) { - basisu_image_info info; - if (transcoder.get_image_info(data, size, info, 0)) { - uint32_t descW, descH, blocks; - if (transcoder.get_image_level_desc(data, size, 0, 0, descW, descH, blocks)) { - basisu_file_info fileInfo; - transcoder.get_file_info(data, size, fileInfo); - if (transcoder.start_transcoding(data, size)) { - uint32_t decSize = basis_get_bytes_per_block(type) * blocks; - if (void* decBuf = malloc(decSize)) { - if (transcoder.transcode_image_level(data, size, 0, 0, decBuf, blocks, type)) { - GLenum glType = toGlType(type); - if (glType != GL_FALSE) { - glBindTexture(GL_TEXTURE_2D, name); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - if (type < cTFTotalBlockTextureFormats) { - glCompressedTexImage2D(GL_TEXTURE_2D, 0, - glType, descW, descH, 0, decSize, decBuf); - } else { - - } - } - free(decBuf); - return glType != GL_FALSE; - } - } - } - } - } - } - return false; -} - -/** - * Creates the GL context, shaders and quad data, decompresses the Zstd data - * and 'uploads' the resulting texture. - * - * As a (naive) comparison, removing Zstd and building with "-Os -g0 s WASM=1 - * -lGL emscripten.c" results in a 15kB WebAssembly file; re-adding Zstd - * increases the Wasm by 26kB. + * Creates the GL context, shaders and quad data, decompresses the .basis files + * and 'uploads' the resulting textures. */ int main() { if (initContext()) { @@ -1114,7 +1160,6 @@ int main() { GLuint vertsBuf = 0; GLuint indexBuf = 0; - GLuint txName = 0; // Create the textured quad (vert positions then UVs) struct posTex2d verts2d[] = { {{-0.85f, -0.85f}, {0.0f, 0.0f}}, // BL @@ -1142,9 +1187,10 @@ int main() { glEnableVertexAttribArray(GL_VERT_POSXY_ID); glEnableVertexAttribArray(GL_VERT_TXUV0_ID); - (void) srcRgba; - glGenTextures(1, &txName); - if (upload(txName, srcRgb, sizeof srcRgb, supports(glCtx))) { + glGenTextures(2, txName); + if (upload(glCtx, txName[0], srcRgb, sizeof srcRgb) && + upload(glCtx, txName[1], srcRgba, sizeof srcRgba)) + { printf("Decoded!\n"); }