--- /dev/null
+#include "vg_tex.h"
+#include "vg_engine.h"
+#include "vg_async.h"
+#include "vg_io.h"
+#include <string.h>
+
+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 );
+}