build system revision
[vg.git] / vg_tex.c
diff --git a/vg_tex.c b/vg_tex.c
new file mode 100644 (file)
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 <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 );
+}