switch to async system
[vg.git] / vg_tex.h
index f570b11684fe367af76f8819dec2501050b5e307..e857aa85858c436558aa69d9887808661b9db6c9 100644 (file)
--- a/vg_tex.h
+++ b/vg_tex.h
 #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 QOI_IMPLEMENTATION
-#define QOI_NO_STDIO
-#define QOI_MALLOC(sz) vg_qoi_malloc( sz )
-#define QOI_FREE(p) vg_qoi_free( p )
-
-#include "submodules/qoi/qoi.h"
-
-struct vg_tex2d
-{
-       const char *path;
-       u32 flags;
-       GLuint name;
-};
-
 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
 
-       glActiveTexture( GL_TEXTURE0 + id );
-       glBindTexture( GL_TEXTURE_2D, tex->name );
-}
+static u8 const_vg_tex2d_err[] =
+{
+   0xff, 0xff, 0x00, 0xff,
+   0xff, 0x00, 0x00, 0x00,
+   0xff, 0x00, 0x00, 0x00,
+   0xff, 0xff, 0x00, 0xff
+};
 
-static inline void vg_tex2d_mipmap(void) 
-{ 
-       glGenerateMipmap( GL_TEXTURE_2D );
+#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_linear(void)
-{
-       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
-       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
-}
+struct texture_load_info{
+   GLuint *dest;
+   u32 width, height, flags;
+   u8 *rgba;
+};
 
-static inline void vg_tex2d_nearest(void)
+VG_STATIC void async_vg_tex2d_upload( void *payload, u32 size )
 {
-       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 )
+VG_STATIC void vg_tex2d_replace_with_error( GLuint *dest )
 {
-   qoi_desc info;
-   u8 *tex_buffer = qoi_decode( mem, size, &info, 4 );
+   u32 hdr_size = vg_align8(sizeof(struct texture_load_info));
 
-   if( tex_buffer )
-   {
-      vg_info( "Texture decoded: [%u %u] %s\n", 
-                                    info.width, info.height, name );
-
-      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 = 2;
+   info->height = 2;
+   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 );
 }
 
-/*
- * TODO: This blocks while we read from file 
- */
-VG_STATIC GLuint vg_tex2d_rgba( const char *path )
+VG_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; i<num; i ++ )
-       {
-               vg_tex2d *tex = textures[i];
-               tex->name = 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 )
+VG_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 */