X-Git-Url: https://harrygodden.com/git/?a=blobdiff_plain;f=vg_tex.h;h=68d9d458654721dc17a06f6acd6eea2b54a7e20c;hb=17dc0afed8913ce7756e86f1c69da4ada8ceda79;hp=5c7c7f6b26a8ca088d875b61ca96b85aa2450201;hpb=4c48fe01a5d1983be89b7dce6f08e6b708cfbb05;p=vg.git diff --git a/vg_tex.h b/vg_tex.h index 5c7c7f6..68d9d45 100644 --- a/vg_tex.h +++ b/vg_tex.h @@ -1,198 +1,336 @@ -/* Copyright (C) 2021-2022 Harry Godden (hgn) - All Rights Reserved */ +/* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved + * + * A portion of this file is copied and altered from the QOI projects' source, + * Originally written by Dominic Szablewski. It is slightly modified. + * For the original unaltered QOI header, you can find it here: + * https://github.com/phoboslab/qoi/blob/master/qoi.h + * + * Copyright (C) 2021, Dominic Szablewski + * SPDX-License-Identifier: MIT + * + * MIT License + Copyright (c) 2022 Dominic Szablewski - https://phoboslab.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + #ifndef VG_TEX_H #define VG_TEX_H +#define VG_GAME #include "vg/vg.h" #include "vg/vg_log.h" -#define VG_TEXTURE_NO_MIP 0x1 -#define VG_TEXTURE_REPEAT 0x2 -#define VG_TEXTURE_CLAMP 0x4 -#define VG_TEXTURE_NEAREST 0x8 -#define VG_TEXTURE_ALLOCATED_INTERNAL 0x10 - -VG_STATIC void *vg_qoi_malloc( size_t size ) -{ - return vg_linear_alloc( vg_mem.scratch, size ); -} - -VG_STATIC void vg_qoi_free( void *ptr ) -{ - -} +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "vg/submodules/stb/stb_image_write.h" -#define QOI_IMPLEMENTATION -#define QOI_NO_STDIO -#define QOI_MALLOC(sz) vg_qoi_malloc( sz ) -#define QOI_FREE(p) vg_qoi_free( p ) +/* its a sad day. */ +#if 0 +#define STBI_MALLOC(X) +#define STBI_REALLOC(X,Y) +#define STBI_FREE(X) +#endif -#include "submodules/qoi/qoi.h" - -struct vg_tex2d -{ - const char *path; - u32 flags; - GLuint name; -}; +#define STBI_ONLY_JPEG +#define STBI_NO_THREAD_LOCALS +#define STB_IMAGE_IMPLEMENTATION +#include "vg/submodules/stb/stb_image.h" struct vg_sprite { v4f uv_xywh; }; -VG_STATIC void vg_tex2d_bind( vg_tex2d *tex, u32 id ) -{ - if( !(tex->flags & VG_TEXTURE_ALLOCATED_INTERNAL) ) - { - vg_error( "Tried to use '%s' while unloaded!\n", tex->path ); - return; - } +#define VG_TEX2D_LINEAR 0x1 +#define VG_TEX2D_NEAREST 0x2 +#define VG_TEX2D_REPEAT 0x4 +#define VG_TEX2D_CLAMP 0x8 +#define VG_TEX2D_NOMIP 0x10 + +static u8 const_vg_tex2d_err[] ={ + 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, + 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, + 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, + 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, + 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, + 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff, + 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, + 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff, +}; - glActiveTexture( GL_TEXTURE0 + id ); - glBindTexture( GL_TEXTURE_2D, tex->name ); +#define QOI_SRGB 0 +#define QOI_LINEAR 1 + +typedef struct { + unsigned int width; + unsigned int height; + unsigned char channels; + unsigned char colorspace; +} qoi_desc; + +#ifndef QOI_ZEROARR + #define QOI_ZEROARR(a) memset((a),0,sizeof(a)) +#endif + +#define QOI_OP_INDEX 0x00 /* 00xxxxxx */ +#define QOI_OP_DIFF 0x40 /* 01xxxxxx */ +#define QOI_OP_LUMA 0x80 /* 10xxxxxx */ +#define QOI_OP_RUN 0xc0 /* 11xxxxxx */ +#define QOI_OP_RGB 0xfe /* 11111110 */ +#define QOI_OP_RGBA 0xff /* 11111111 */ + +#define QOI_MASK_2 0xc0 /* 11000000 */ + +#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) +#define QOI_MAGIC \ + (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ + ((unsigned int)'i') << 8 | ((unsigned int)'f')) +#define QOI_HEADER_SIZE 14 + +/* 2GB is the max file size that this implementation can safely handle. We guard +against anything larger than that, assuming the worst case with 5 bytes per +pixel, rounded down to a nice clean value. 400 million pixels ought to be +enough for anybody. */ +#define QOI_PIXELS_MAX ((unsigned int)400000000) + +typedef union { + struct { unsigned char r, g, b, a; } rgba; + unsigned int v; +} qoi_rgba_t; + +static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1}; +static u32 qoi_read_32( const u8 *bytes, int *p ) { + u32 a = bytes[(*p)++]; + u32 b = bytes[(*p)++]; + u32 c = bytes[(*p)++]; + u32 d = bytes[(*p)++]; + return a << 24 | b << 16 | c << 8 | d; } -static inline void vg_tex2d_mipmap(void) -{ - glGenerateMipmap( GL_TEXTURE_2D ); -} +struct texture_load_info{ + GLuint *dest; + u32 width, height, flags; + u8 *rgba; +}; -static inline void vg_tex2d_linear(void) +static void async_vg_tex2d_upload( void *payload, u32 size ) { - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); -} + if( vg_thread_purpose() != k_thread_purpose_main ){ + vg_fatal_error( "Catastrophic programming error.\n" ); + } -static inline void vg_tex2d_nearest(void) -{ - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); -} + struct texture_load_info *info = payload; -static inline void vg_tex2d_linear_mipmap(void) -{ - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, - GL_LINEAR_MIPMAP_LINEAR ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); -} + glGenTextures( 1, info->dest ); + glBindTexture( GL_TEXTURE_2D, *info->dest ); + glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, info->width, info->height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, info->rgba ); -static inline void vg_tex2d_repeat(void) -{ - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); -} + if( !(info->flags & VG_TEX2D_NOMIP) ){ + glGenerateMipmap( GL_TEXTURE_2D ); + } -static inline void vg_tex2d_clamp(void) -{ - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); -} + if( info->flags & VG_TEX2D_LINEAR ){ + if( info->flags & VG_TEX2D_NOMIP ){ + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + } + else{ + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_LINEAR_MIPMAP_LINEAR ); + } + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } -VG_STATIC GLuint vg_tex2d_new(void) -{ - GLuint texture_name; - glGenTextures( 1, &texture_name ); - glBindTexture( GL_TEXTURE_2D, texture_name ); + if( info->flags & VG_TEX2D_NEAREST ){ + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + } - return texture_name; -} + if( info->flags & VG_TEX2D_CLAMP ){ + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + } -VG_STATIC void vg_tex2d_set_error(void) -{ - u32 tex_err[4] = - { - 0xffff00ff, - 0xff000000, - 0xff000000, - 0xffff00ff - }; - - glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, - 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_err ); + if( info->flags & VG_TEX2D_REPEAT ){ + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); + } } -VG_STATIC void vg_tex2d_qoi( void *mem, u32 size, const char *name ) +static void vg_tex2d_replace_with_error( GLuint *dest ) { - qoi_desc info; - u8 *tex_buffer = qoi_decode( mem, size, &info, 4 ); - - if( tex_buffer ) - { - vg_info( "Texture decoded: [%u %u] %s\n", - info.width, info.height, name ); + u32 hdr_size = vg_align8(sizeof(struct texture_load_info)); - glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, info.width, info.height, - 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_buffer ); + vg_async_item *call = vg_async_alloc( hdr_size ); + struct texture_load_info *info = call->payload; + + info->dest = dest; + info->flags = VG_TEX2D_NEAREST|VG_TEX2D_REPEAT|VG_TEX2D_NOMIP; + info->width = 4; + info->height = 4; + info->rgba = const_vg_tex2d_err; - QOI_FREE(tex_buffer); - } - else - { - vg_error( "File size: %u\n", size ); - vg_tex2d_set_error(); - } + vg_async_dispatch( call, async_vg_tex2d_upload ); } -VG_STATIC GLuint vg_tex2d_rgba( const char *path ) +static +void vg_tex2d_load_qoi_async( const u8 *bytes, u32 size, + u32 flags, GLuint *dest ) { - GLuint texture_name = vg_tex2d_new(); + u32 header_magic; + qoi_rgba_t index[64]; + qoi_rgba_t px; + int px_len, chunks_len, px_pos; + int p = 0, run = 0; + + u32 channels = 4; /* TODO */ + + qoi_desc desc; + + if ( + bytes == NULL || + (channels != 0 && channels != 3 && channels != 4) || + size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) + ) { + vg_error( "Error while decoding qoi file: illegal parameters\n" ); + vg_tex2d_replace_with_error( dest ); + return; + } - vg_linear_clear( vg_mem.scratch ); - u32 size; - void *file = vg_file_read( vg_mem.scratch, path, &size ); + header_magic = qoi_read_32(bytes, &p); + desc.width = qoi_read_32(bytes, &p); + desc.height = qoi_read_32(bytes, &p); + desc.channels = bytes[p++]; + desc.colorspace = bytes[p++]; + + if ( + desc.width == 0 || desc.height == 0 || + desc.channels < 3 || desc.channels > 4 || + desc.colorspace > 1 || + header_magic != QOI_MAGIC || + desc.height >= QOI_PIXELS_MAX / desc.width + ) { + vg_error( "Error while decoding qoi file: invalid file\n" ); + vg_tex2d_replace_with_error( dest ); + return; + } - if( file ) - { - vg_tex2d_qoi( file, size, path ); - } - else - { - vg_error( "Loading texture failed (%s)\n", path ); - vg_tex2d_set_error(); - } + if (channels == 0) { + channels = desc.channels; + } - return texture_name; -} + px_len = desc.width * desc.height * channels; -VG_STATIC void vg_tex2d_init( vg_tex2d *textures[], int num ) -{ - for( int i=0; iname = vg_tex2d_rgba( tex->path ); - if( !(tex->flags & VG_TEXTURE_NO_MIP) ) - vg_tex2d_mipmap(); - - if( tex->flags & VG_TEXTURE_NEAREST ) - { - if( tex->flags & VG_TEXTURE_NO_MIP ) - vg_error( "Invalid texture settings\n" ); - else - vg_tex2d_nearest(); + /* allocate async call + * -------------------------- + */ + u32 hdr_size = vg_align8(sizeof(struct texture_load_info)), + tex_size = vg_align8(px_len); + + vg_async_item *call = vg_async_alloc( hdr_size + tex_size ); + struct texture_load_info *info = call->payload; + + info->dest = dest; + info->flags = flags; + info->width = desc.width; + info->height = desc.height; + info->rgba = ((u8*)call->payload) + hdr_size; + + /* + * Decode + * ---------------------------- + */ + + u8 *pixels = info->rgba; + + QOI_ZEROARR(index); + px.rgba.r = 0; + px.rgba.g = 0; + px.rgba.b = 0; + px.rgba.a = 255; + + chunks_len = size - (int)sizeof(qoi_padding); + for (px_pos = 0; px_pos < px_len; px_pos += channels) { + if (run > 0) { + run--; } - else - { - if( tex->flags & VG_TEXTURE_NO_MIP ) - vg_tex2d_linear(); - else - vg_tex2d_linear_mipmap(); + else if (p < chunks_len) { + int b1 = bytes[p++]; + + if (b1 == QOI_OP_RGB) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + } + else if (b1 == QOI_OP_RGBA) { + px.rgba.r = bytes[p++]; + px.rgba.g = bytes[p++]; + px.rgba.b = bytes[p++]; + px.rgba.a = bytes[p++]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { + px = index[b1]; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { + px.rgba.r += ((b1 >> 4) & 0x03) - 2; + px.rgba.g += ((b1 >> 2) & 0x03) - 2; + px.rgba.b += ( b1 & 0x03) - 2; + } + else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { + int b2 = bytes[p++]; + int vg = (b1 & 0x3f) - 32; + px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); + px.rgba.g += vg; + px.rgba.b += vg - 8 + (b2 & 0x0f); + } + else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { + run = (b1 & 0x3f); + } + + index[QOI_COLOR_HASH(px) % 64] = px; } - - if( tex->flags & VG_TEXTURE_CLAMP ) - vg_tex2d_clamp(); - else - vg_tex2d_repeat(); - tex->flags |= VG_TEXTURE_ALLOCATED_INTERNAL; + pixels[px_pos + 0] = px.rgba.r; + pixels[px_pos + 1] = px.rgba.g; + pixels[px_pos + 2] = px.rgba.b; + + if (channels == 4) { + pixels[px_pos + 3] = px.rgba.a; + } } + + /* + * Complete the call + * -------------------------- + */ + + vg_async_dispatch( call, async_vg_tex2d_upload ); } -VG_STATIC void vg_tex2d_free( vg_tex2d *textures[], int num ) +static +void vg_tex2d_load_qoi_async_file( const char *path, u32 flags, GLuint *dest ) { - for( int i = 0; i < num; i ++ ) - { - glDeleteTextures( 1, &textures[i]->name ); - } + vg_linear_clear( vg_mem.scratch ); + + u32 size; + const void *data = vg_file_read( vg_mem.scratch, path, &size ); + vg_tex2d_load_qoi_async( data, size, flags, dest ); } #endif /* VG_TEX_H */