X-Git-Url: https://harrygodden.com/git/?p=vg.git;a=blobdiff_plain;f=vg_tex.c;fp=vg_tex.c;h=1ed8c96eb5cfdd29714f175fb54e5d62727f28a8;hp=0000000000000000000000000000000000000000;hb=3b14f3dcd5bf9dd3c85144f2123d667bfa4bb63f;hpb=fce86711735b15bff37de0f70716808410fcf269 diff --git a/vg_tex.c b/vg_tex.c new file mode 100644 index 0000000..1ed8c96 --- /dev/null +++ b/vg_tex.c @@ -0,0 +1,276 @@ +#include "vg_tex.h" +#include "vg_engine.h" +#include "vg_async.h" +#include "vg_io.h" +#include + +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, +}; + +#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; +} + +struct texture_load_info{ + GLuint *dest; + u32 width, height, flags; + u8 *rgba; +}; + +static void async_vg_tex2d_upload( void *payload, u32 size ) +{ + if( vg_thread_purpose() != k_thread_purpose_main ){ + vg_fatal_error( "Catastrophic programming error.\n" ); + } + + struct texture_load_info *info = payload; + + 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 ); + + if( !(info->flags & VG_TEX2D_NOMIP) ){ + glGenerateMipmap( GL_TEXTURE_2D ); + } + + 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 ); + } + + 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 ); + } + + 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 ); + } + + 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 ); + } +} + +void vg_tex2d_replace_with_error_async( GLuint *dest ) +{ + u32 hdr_size = vg_align8(sizeof(struct texture_load_info)); + + 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; + + vg_async_dispatch( call, async_vg_tex2d_upload ); +} + +void vg_tex2d_load_qoi_async( const u8 *bytes, u32 size, + u32 flags, GLuint *dest ) +{ + 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_async( dest ); + return; + } + + 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_async( dest ); + return; + } + + if (channels == 0) { + channels = desc.channels; + } + + px_len = desc.width * desc.height * channels; + + /* 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 (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; + } + + 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 ); +} + +void vg_tex2d_load_qoi_async_file( const char *path, u32 flags, GLuint *dest ) +{ + if( vg_thread_purpose() != k_thread_purpose_loader ) + vg_fatal_error( "wrong thread\n" ); + + 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 ); +}