#if defined(_WIN32) || defined(_WIN64) # define SOKOL_D3D11 # define _CRT_SECURE_NO_WARNINGS # include "quad_hlsl.vert.h" # include "quad_hlsl.frag.h" # include "quad_cubemap_hlsl.frag.h" # define SOKOL_LOG(s) OutputDebugStringA(s) #elif defined(__linux__) # include "quad_glsl.vert.h" # include "quad_glsl.frag.h" # include "quad_cubemap_glsl.frag.h" # define SOKOL_GLCORE33 #elif defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) # include "quad_metal.vert.h" # include "quad_metal.frag.h" # include "quad_cubemap_metal.frag.h" # define SOKOL_METAL #endif #define SOKOL_IMPL #define DDSKTX_IMPLEMENT #define SOKOL_API_DECL static #include "sokol_app.h" #include "sokol_gfx.h" #include "sokol_glue.h" #define DDSKTX_API static #include "../dds-ktx.h" #ifndef __APPLE__ # include #endif #include #include #include #define SOKOL_DEBUGTEXT_IMPL #include "sokol_debugtext.h" #define FONT_SCALE 1.1f #define CHECKER_SIZE 8 typedef struct uniforms_fs { float color[4]; float args[4]; } uniforms_fs; typedef struct uniforms_vs { float proj_mat[16]; } uniforms_vs; typedef struct vertex { float x; float y; float u; float v; float w; // reserved for cubemapping } vertex; typedef struct ctexview_state { sg_pass_action pass_action; void* file_data; int file_size; ddsktx_texture_info texinfo; sg_image tex; sg_shader shader; sg_shader shader_cubemap; sg_pipeline pip; sg_pipeline pip_cubemap; sg_pipeline pip_checker; sg_buffer vb; sg_buffer ib; sg_buffer vb_checker; sg_image checker; bool inv_text_color; uniforms_fs vars_fs; int cur_mip; int cur_slice; int cube_face; } ctexview_state; ctexview_state g_state; static const vertex k_vertices[] = { { -1.0f, -1.0f, 0.0f, 1.0f, 0 }, { 1.0f, -1.0f, 1.0f, 1.0f, 0 }, { 1.0f, 1.0f, 1.0f, 0.0f, 0 }, { -1.0f, 1.0f, 0.0f, 0.0f, 0 }, }; static const uint16_t k_indices[] = { 0, 2, 1, 2, 0, 3 }; static const char* k_cube_face_names[DDSKTX_CUBE_FACE_COUNT] = { "X+", "X-", "Y+", "Y-", "Z+", "Z-" }; #if defined(_WIN32) || defined(_WIN64) static desktop_size(int* width, int* height) { RECT desktop; const HWND hDesktop = GetDesktopWindow(); GetWindowRect(hDesktop, &desktop); *width = desktop.right; *height = desktop.bottom; } #endif static int nearest_pow2(int n) { n--; n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; n++; return n; } // https://en.wikipedia.org/wiki/Cube_mapping static void convert_cube_uv_to_xyz(int index, float u, float v, float* x, float* y, float* z) { // convert range 0 to 1 to -1 to 1 float uc = 2.0f * u - 1.0f; float vc = 2.0f * v - 1.0f; switch (index) { case 0: *x = 1.0f; *y = vc; *z = -uc; break; // POSITIVE X case 1: *x = -1.0f; *y = vc; *z = uc; break; // NEGATIVE X case 2: *x = uc; *y = 1.0f; *z = -vc; break; // POSITIVE Y case 3: *x = uc; *y = -1.0f; *z = vc; break; // NEGATIVE Y case 4: *x = uc; *y = vc; *z = 1.0f; break; // POSITIVE Z case 5: *x = -uc; *y = vc; *z = -1.0f; break; // NEGATIVE Z } } static void set_cube_face(int index) { if (g_state.texinfo.flags & DDSKTX_TEXTURE_FLAG_CUBEMAP) { assert(index >= 0 && index < DDSKTX_CUBE_FACE_COUNT); vertex vertices[4]; memcpy(vertices, k_vertices, sizeof(k_vertices)); for (int i = 0; i < 4; i++) { float x, y, z; convert_cube_uv_to_xyz(index, vertices[i].u, vertices[i].v, &x, &y, &z); vertices[i].u = x; vertices[i].v = y; vertices[i].w = z; } sg_update_buffer(g_state.vb, vertices, sizeof(vertices)); } } static void adjust_checker_coords(int width, int height) { int count_x = width/CHECKER_SIZE; int count_y = height/CHECKER_SIZE; float ratio = (float)width / (float)height; float u, v; if (width > height) { u = (float)count_x; v = (float)count_y * ratio; } else { v = (float)count_y; u = (float)count_x / ratio; } vertex vertices[4]; memcpy(vertices, k_vertices, sizeof(k_vertices)); for (int i = 0; i < 4; i++) { vertices[i].u = vertices[i].u != 0 ? u : 0; vertices[i].v = vertices[i].v != 0 ? v : 0; } sg_update_buffer(g_state.vb_checker, vertices, sizeof(vertices)); } static void sx_mat4_ortho(float mat[16], float width, float height, float zn, float zf, float offset, bool ogl_ndc) { const float d = zf - zn; const float cc = (ogl_ndc ? 2.0f : 1.0f) / d; const float ff = ogl_ndc ? -(zn + zf) / d : -zn / d; mat[0] = 2.0f / width; mat[1] = 0; mat[2] = 0; mat[3] = 0; mat[4] = 0; mat[5] = 2.0f / height; mat[6] = 0; mat[7] = 0; mat[8] = 0; mat[9] = 0; mat[10] = -cc; mat[11] = 0; mat[12] = offset; mat[13] = 0; mat[14] = ff; mat[15] = 1.0f; } static void sx_mat4_ident(float mat[16]) { mat[0] = 1.0f; mat[1] = 0; mat[2] = 0; mat[3] = 0; mat[4] = 0; mat[5] = 1.0f; mat[6] = 0; mat[7] = 0; mat[8] = 0; mat[9] = 0; mat[10] = 1.0f; mat[11] = 0; mat[12] = 0; mat[13] = 0; mat[14] = 0; mat[15] = 1.0f; } static sg_image create_checker_texture(int checker_size, int size, uint32_t colors[2]) { assert(size % 4 == 0 && "size must be multiple of four"); assert(size % checker_size == 0 && "checker_size must be dividable by size"); int size_bytes = size * size * sizeof(uint32_t); uint32_t* pixels = malloc(size_bytes); assert(pixels); // split into tiles and color them int tiles_x = size / checker_size; int tiles_y = size / checker_size; int num_tiles = tiles_x * tiles_y; int* poss = malloc(sizeof(int) * 2 * num_tiles); assert(poss); int _x = 0, _y = 0; for (int i = 0; i < num_tiles; i++) { poss[i*2] = _x; poss[i*2 + 1] = _y; _x += checker_size; if (_x >= size) { _x = 0; _y += checker_size; } } int color_idx = 0; for (int i = 0; i < num_tiles; i++) { int* p = poss + i*2; uint32_t c = colors[color_idx]; if (i == 0 || ((i + 1) % tiles_x) != 0) color_idx = !color_idx; int end_x = p[0] + checker_size; int end_y = p[1] + checker_size; for (int y = p[1]; y < end_y; y++) { for (int x = p[0]; x < end_x; x++) { int pixel = x + y * size; pixels[pixel] = c; } } } sg_image tex = sg_make_image(&(sg_image_desc) { .width = size, .height = size, .num_mipmaps = 1, .pixel_format = SG_PIXELFORMAT_RGBA8, .content = (sg_image_content){.subimage[0][0].ptr = pixels, .subimage[0][0].size = size_bytes } }); free(poss); free(pixels); return tex; } static void print_msg(const char* fmt, ...) { char msg[1024]; va_list args; va_start(args, fmt); vsnprintf(msg, sizeof(msg), fmt, args); va_end(args); #if defined(_WIN32) || defined(_WIN64) MessageBoxA(NULL, msg, "DDS/KTX viewer", MB_OK); #else puts(msg); #endif } static sg_shader_desc get_shader_desc(const void* vs_data, uint32_t vs_size, const void* fs_data, uint32_t fs_size, sg_image_type imgtype) { return (sg_shader_desc){ .attrs = { [0] = {.name = "a_pos", .sem_name = "POSITION" }, [1] = {.name = "a_uv", .sem_name = "TEXCOORD" } }, .vs = { #ifdef SOKOL_D3D11 .byte_code = (const uint8_t*)vs_data, .byte_code_size = vs_size, #else .source = (const char*)vs_data, #endif #ifdef SOKOL_METAL .entry = "main0", #endif .uniform_blocks[0] = { .size = sizeof(uniforms_vs), .uniforms = { [0] = {.name = "proj_mat", .type = SG_UNIFORMTYPE_MAT4 }, } } }, .fs = { #ifdef SOKOL_D3D11 .byte_code = (const uint8_t*)fs_data, .byte_code_size = fs_size, #else .source = (const char*)fs_data, #endif #ifdef SOKOL_METAL .entry = "main0", #endif .images[0] = { .name = "tex_image", .type = imgtype }, .uniform_blocks[0] = { .size = sizeof(uniforms_fs), .uniforms = { [0] = {.name = "color", .type = SG_UNIFORMTYPE_FLOAT4 }, [1] = {.name = "target_lod", SG_UNIFORMTYPE_FLOAT4 } } } } }; } static void init(void) { sg_setup(&(sg_desc) { .context = sapp_sgcontext() }); sg_image_type imgtype = (g_state.texinfo.flags & DDSKTX_TEXTURE_FLAG_CUBEMAP) ? SG_IMAGETYPE_CUBE : SG_IMAGETYPE_2D; g_state.pass_action = (sg_pass_action) { .colors[0] = { .action = SG_ACTION_CLEAR, .val = {0, 0, 0, 1.0f} } }; g_state.vb = sg_make_buffer(&(sg_buffer_desc) { .usage = SG_USAGE_DYNAMIC, .type = SG_BUFFERTYPE_VERTEXBUFFER, .size = sizeof(k_vertices) }); g_state.vb_checker = sg_make_buffer(&(sg_buffer_desc) { .usage = SG_USAGE_DYNAMIC, .type = SG_BUFFERTYPE_VERTEXBUFFER, .size = sizeof(k_vertices) }); g_state.ib = sg_make_buffer(&(sg_buffer_desc) { .usage = SG_USAGE_IMMUTABLE, .type = SG_BUFFERTYPE_INDEXBUFFER, .size = sizeof(k_indices), .content = k_indices }); { sg_shader_desc desc = get_shader_desc(quad_vs_data, quad_vs_size, quad_fs_data, quad_fs_size, SG_IMAGETYPE_2D); g_state.shader = sg_make_shader(&desc); } { sg_shader_desc desc = get_shader_desc(quad_vs_data, quad_vs_size, quad_cubemap_fs_data, quad_cubemap_fs_size, SG_IMAGETYPE_CUBE); g_state.shader_cubemap = sg_make_shader(&desc); } sg_pipeline_desc pip_desc = (sg_pipeline_desc) { .layout = { .buffers[0] = { .stride = sizeof(vertex), }, .attrs = { [0] = {.offset = 0, .format = SG_VERTEXFORMAT_FLOAT2 }, [1] = {.offset = 8, .format = SG_VERTEXFORMAT_FLOAT3 } } }, .primitive_type = SG_PRIMITIVETYPE_TRIANGLES, .index_type = SG_INDEXTYPE_UINT16, .rasterizer = { .cull_mode = SG_CULLMODE_BACK }, .blend = { .enabled = true, .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA, .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA } }; { pip_desc.shader = g_state.shader; g_state.pip = sg_make_pipeline(&pip_desc); } { pip_desc.shader = g_state.shader_cubemap; g_state.pip_cubemap = sg_make_pipeline(&pip_desc); } // main texture (dds-ktx) if (imgtype == SG_IMAGETYPE_CUBE) { set_cube_face(0); } else { sg_update_buffer(g_state.vb, k_vertices, sizeof(k_vertices)); } adjust_checker_coords(sapp_width(), sapp_height()); sg_image_desc desc = { .type = imgtype, .width = g_state.texinfo.width, .height = g_state.texinfo.height, .depth = 1, .num_mipmaps = g_state.texinfo.num_mips, .min_filter = SG_FILTER_NEAREST, .mag_filter = SG_FILTER_NEAREST }; switch (g_state.texinfo.format) { case DDSKTX_FORMAT_BC1: desc.pixel_format = SG_PIXELFORMAT_BC1_RGBA; break; case DDSKTX_FORMAT_BC2: desc.pixel_format = SG_PIXELFORMAT_BC2_RGBA; break; case DDSKTX_FORMAT_BC3: desc.pixel_format = SG_PIXELFORMAT_BC3_RGBA; break; case DDSKTX_FORMAT_BC4: desc.pixel_format = SG_PIXELFORMAT_BC4_R; break; case DDSKTX_FORMAT_BC5: desc.pixel_format = SG_PIXELFORMAT_BC5_RG; break; case DDSKTX_FORMAT_BC6H: desc.pixel_format = SG_PIXELFORMAT_BC6H_RGBF; break; case DDSKTX_FORMAT_BC7: desc.pixel_format = SG_PIXELFORMAT_BC7_RGBA; break; case DDSKTX_FORMAT_A8: case DDSKTX_FORMAT_R8: desc.pixel_format = SG_PIXELFORMAT_R8; break; case DDSKTX_FORMAT_RGBA8: case DDSKTX_FORMAT_RGBA8S: desc.pixel_format = SG_PIXELFORMAT_RGBA8; break; case DDSKTX_FORMAT_RG16: desc.pixel_format = SG_PIXELFORMAT_RG16; break; case DDSKTX_FORMAT_RGB8: desc.pixel_format = SG_PIXELFORMAT_RGBA8; break; case DDSKTX_FORMAT_R16: desc.pixel_format = SG_PIXELFORMAT_R16; break; case DDSKTX_FORMAT_R32F: desc.pixel_format = SG_PIXELFORMAT_R32F; break; case DDSKTX_FORMAT_R16F: desc.pixel_format = SG_PIXELFORMAT_R16F; break; case DDSKTX_FORMAT_RG16F: desc.pixel_format = SG_PIXELFORMAT_RG16F; break; case DDSKTX_FORMAT_RG16S: desc.pixel_format = SG_PIXELFORMAT_RG16; break; case DDSKTX_FORMAT_RGBA16F: desc.pixel_format = SG_PIXELFORMAT_RGBA16F; break; case DDSKTX_FORMAT_RGBA16: desc.pixel_format = SG_PIXELFORMAT_RGBA16; break; case DDSKTX_FORMAT_BGRA8: desc.pixel_format = SG_PIXELFORMAT_BGRA8; break; case DDSKTX_FORMAT_RGB10A2: desc.pixel_format = SG_PIXELFORMAT_RGB10A2; break; case DDSKTX_FORMAT_RG11B10F: desc.pixel_format = SG_PIXELFORMAT_RG11B10F; break; case DDSKTX_FORMAT_RG8: desc.pixel_format = SG_PIXELFORMAT_RG8; break; case DDSKTX_FORMAT_RG8S: desc.pixel_format = SG_PIXELFORMAT_RG8; break; default: assert(0); exit(-1); } int num_faces = imgtype == SG_IMAGETYPE_CUBE ? 6 : 1; for (int face = 0; face < num_faces; face++) { for (int mip = 0; mip < g_state.texinfo.num_mips; mip++) { ddsktx_sub_data subdata; ddsktx_get_sub(&g_state.texinfo, &subdata, g_state.file_data, g_state.file_size, 0, face, mip); desc.content.subimage[face][mip].ptr = subdata.buff; desc.content.subimage[face][mip].size = subdata.size_bytes; } } g_state.tex = sg_make_image(&desc); sdtx_setup(&(sdtx_desc_t) { .fonts = { [0] = sdtx_font_c64(), }, }); sdtx_set_context(SDTX_DEFAULT_CONTEXT); sdtx_canvas((float)sapp_width() * (1.0f/FONT_SCALE), (float)sapp_height() * (1.0f/FONT_SCALE)); uint32_t checker_colors[] = { 0xff999999, 0xff666666 }; g_state.checker = create_checker_texture(8, 16, checker_colors); g_state.vars_fs.color[0] = g_state.vars_fs.color[1] = g_state.vars_fs.color[2] = g_state.vars_fs.color[3] = 1.0f; } static const char* texture_type_info() { static char info[128]; const char* type = "2D"; if (g_state.texinfo.flags & DDSKTX_TEXTURE_FLAG_CUBEMAP) { snprintf(info, sizeof(info), "Cube (%s)", k_cube_face_names[g_state.cube_face]); } else if (g_state.texinfo.depth > 1) { snprintf(info, sizeof(info), "3D (%d/%d)", g_state.cur_slice, g_state.texinfo.depth); } else { strcpy(info, "2D"); } return info; } static void frame(void) { sdtx_home(); sdtx_origin(1, 1); sdtx_pos(0, 0); sdtx_color3b(!g_state.inv_text_color ? 255 : 0, !g_state.inv_text_color ? 255 : 0, 0); sdtx_printf("%s\t%dx%d (mip %d/%d)", ddsktx_format_str(g_state.texinfo.format), g_state.texinfo.width, g_state.texinfo.height, g_state.cur_mip + 1, g_state.texinfo.num_mips); sdtx_crlf(); sdtx_printf("%s\tmask: %c%c%c%c\t", texture_type_info(), g_state.vars_fs.color[0] == 1.0f ? 'R' : 'X', g_state.vars_fs.color[1] == 1.0f ? 'G' : 'X', g_state.vars_fs.color[2] == 1.0f ? 'B' : 'X', g_state.vars_fs.color[3] == 1.0f ? 'A' : 'X'); sdtx_crlf(); g_state.vars_fs.args[0] = (float)g_state.cur_mip; sg_begin_default_pass(&g_state.pass_action, sapp_width(), sapp_height()); if (g_state.tex.id) { sg_bindings bindings = { .index_buffer = g_state.ib, }; if (g_state.checker.id) { bindings.fs_images[0] = g_state.checker; bindings.vertex_buffers[0] = g_state.vb_checker; uniforms_fs ufs = { {1.0f, 1.0f, 1.0f, 1.0f}, {0, 0, 0, 0} }; uniforms_vs uvs; float ratio = (float)sapp_width() / (float)sapp_height(); float w = 1.0f, h = 1.0f; if (sapp_width() > sapp_height()) { h = w/ratio; } else { w = h*ratio; } sx_mat4_ortho(uvs.proj_mat, w, h, -1.0f, 1.0f, 0, false); sg_apply_pipeline(g_state.pip); sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &uvs, sizeof(uvs)); sg_apply_uniforms(SG_SHADERSTAGE_FS, 0, &ufs, sizeof(ufs)); sg_apply_bindings(&bindings); sg_draw(0, 6, 1); } uniforms_vs uvs; sx_mat4_ident(uvs.proj_mat); bindings.fs_images[0] = g_state.tex; bindings.vertex_buffers[0] = g_state.vb; // for the image to the window and keep the ratio int w = g_state.texinfo.width; int h = g_state.texinfo.height; { float ratio_outer = (float)sapp_width() / (float)sapp_height(); float ratio_inner = (float)w / (float)h; float scale = (ratio_inner >= ratio_outer) ? ((float)sapp_width()/(float)w) : ((float)sapp_height()/(float)h); w = (int)((float)w * scale); h = (int)((float)h * scale); } sg_apply_viewport((sapp_width() - w)/2, (sapp_height() - h)/2, w, h, true); sg_apply_pipeline((g_state.texinfo.flags & DDSKTX_TEXTURE_FLAG_CUBEMAP) ? g_state.pip_cubemap : g_state.pip); sg_apply_uniforms(SG_SHADERSTAGE_VS, 0, &uvs, sizeof(uvs)); sg_apply_uniforms(SG_SHADERSTAGE_FS, 0, &g_state.vars_fs, sizeof(g_state.vars_fs)); sg_apply_bindings(&bindings); sg_draw(0, 6, 1); } sg_apply_viewport(0, 0, sapp_width(), sapp_height(), true); sdtx_draw(); sg_end_pass(); sg_commit(); } static void release(void) { free(g_state.file_data); sg_destroy_pipeline(g_state.pip); sg_destroy_pipeline(g_state.pip_checker); sg_destroy_pipeline(g_state.pip_cubemap); sg_destroy_shader(g_state.shader); sg_destroy_shader(g_state.shader_cubemap); sg_destroy_buffer(g_state.vb); sg_destroy_buffer(g_state.vb_checker); sg_destroy_buffer(g_state.ib); sg_destroy_image(g_state.tex); sg_destroy_image(g_state.checker); sdtx_shutdown(); sg_shutdown(); } static void on_events(const sapp_event* e) { switch (e->type) { case SAPP_EVENTTYPE_RESIZED: sdtx_canvas((float)sapp_width() * (1.0f/FONT_SCALE), (float)sapp_height() * (1.0f/FONT_SCALE)); adjust_checker_coords(e->window_width, e->window_height); break; case SAPP_EVENTTYPE_KEY_DOWN: if (e->key_code == SAPP_KEYCODE_GRAVE_ACCENT) { g_state.inv_text_color = !g_state.inv_text_color; } if (e->key_code == SAPP_KEYCODE_A) { g_state.vars_fs.color[3] = g_state.vars_fs.color[3] == 1.0f ? 0 : 1.0f; } if (e->key_code == SAPP_KEYCODE_R) { g_state.vars_fs.color[0] = g_state.vars_fs.color[0] == 1.0f ? 0 : 1.0f; } if (e->key_code == SAPP_KEYCODE_G) { g_state.vars_fs.color[1] = g_state.vars_fs.color[1] == 1.0f ? 0 : 1.0f; } if (e->key_code == SAPP_KEYCODE_B) { g_state.vars_fs.color[2] = g_state.vars_fs.color[2] == 1.0f ? 0 : 1.0f; } if (e->key_code == SAPP_KEYCODE_UP) { g_state.cur_mip = (g_state.cur_mip + 1) >= g_state.texinfo.num_mips ? (g_state.texinfo.num_mips - 1) : g_state.cur_mip + 1; } if (e->key_code == SAPP_KEYCODE_DOWN) { g_state.cur_mip = (g_state.cur_mip > 0) ? g_state.cur_mip - 1 : 0; } if (e->key_code == SAPP_KEYCODE_ESCAPE) { sapp_request_quit(); } if (e->key_code == SAPP_KEYCODE_F) { g_state.cube_face = (g_state.cube_face + 1) % DDSKTX_CUBE_FACE_COUNT; set_cube_face(g_state.cube_face); } break; } } sapp_desc sokol_main(int argc, char* argv[]) { if (argc <= 1) { print_msg("Provide a file to load as argument"); exit(-1); } FILE* f = fopen(argv[1], "rb"); if (!f) { print_msg("Error: could not open file: %s\n", argv[1]); exit(-1); } fseek(f, 0, SEEK_END); int size = (int)ftell(f); if (size == 0) { print_msg("Error: file '%s' is empty\n", argv[1]); exit(-1); } fseek(f, 0, SEEK_SET); void* data = malloc(size); if (!data) { print_msg("out of memory: requested size: %d\n", (int)size); exit(-1); } if (fread(data, 1, size, f) != size) { print_msg("could not read file data : %s\n", argv[1]); exit(-1); } g_state.file_data = data; g_state.file_size = size; fclose(f); ddsktx_texture_info tc = {0}; ddsktx_error img_err; if (!ddsktx_parse(&tc, data, size, &img_err)) { print_msg("Loading image '%s' failed: %s", argv[1], img_err.msg); exit(-1); } g_state.texinfo = tc; int window_w = tc.width; int window_h = tc.height; #if defined(_WIN32) || defined(_WIN64) int desktop_w, desktop_h; desktop_size(&desktop_w, &desktop_h); float ratio = (float)tc.width / (float)tc.height; if (window_w > (desktop_w - 50)) { window_w = desktop_w - 50; window_h = (int)((float)window_w / ratio); } if (window_h > (desktop_h - 50)) { window_h = desktop_h - 50; window_w = (int)((float)window_h * ratio); } #endif return (sapp_desc) { .init_cb = init, .frame_cb = frame, .cleanup_cb = release, .event_cb = on_events, .width = window_w, .height = window_h, .window_title = "DDS/KTX viewer", .swap_interval = 2, .sample_count = 1 }; }