#endif
};
-#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))
+ #define QOI_ZEROARR(a) memset((a),0,sizeof(a))
#endif
#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
#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'))
+ (((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
#define QOI_PIXELS_MAX ((unsigned int)400000000)
typedef union {
- struct { unsigned char r, g, b, a; } rgba;
- unsigned int v;
+ 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;
+ 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 void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
+ bytes[(*p)++] = (0xff000000 & v) >> 24;
+ bytes[(*p)++] = (0x00ff0000 & v) >> 16;
+ bytes[(*p)++] = (0x0000ff00 & v) >> 8;
+ bytes[(*p)++] = (0x000000ff & v);
}
struct texture_load_info{
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 );
+ glGenTextures( 1, info->dest );
- if( !(info->flags & VG_TEX2D_NOMIP) ){
- glGenerateMipmap( GL_TEXTURE_2D );
- }
+ if( info->flags & VG_TEX2D_CUBEMAP )
+ {
+ bool not_error = !(info->flags & VG_TEX2D_ERROR);
+ u32 w = info->width,
+ h = not_error? info->height/6: info->height;
- 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 );
+ glBindTexture( GL_TEXTURE_CUBE_MAP, *info->dest );
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+
+ for( u32 j=0; j<6; j ++ )
+ {
+ u32 offset = not_error? w*h*j*4: 0;
+
+ glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, 0, GL_RGBA,
+ w, h,
+ 0, GL_RGBA, GL_UNSIGNED_BYTE, info->rgba + offset );
}
- glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
}
+ else
+ {
+ 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_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_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_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_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 );
+ 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 )
+void vg_tex2d_replace_with_error_async( u32 original_flags, GLuint *dest )
{
u32 hdr_size = vg_align8(sizeof(struct texture_load_info));
struct texture_load_info *info = call->payload;
info->dest = dest;
- info->flags = VG_TEX2D_NEAREST|VG_TEX2D_REPEAT|VG_TEX2D_NOMIP;
+ info->flags = VG_TEX2D_ERROR|VG_TEX2D_NEAREST|VG_TEX2D_REPEAT|VG_TEX2D_NOMIP;
+
+ if( original_flags & VG_TEX2D_CUBEMAP )
+ info->flags |= VG_TEX2D_CUBEMAP;
+
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 )
+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 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)
- ) {
+ 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_tex2d_replace_with_error_async( flags, 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;
- }
+ vg_tex2d_replace_with_error_async( flags, dest );
+ return;
+ }
- if (channels == 0) {
- channels = desc.channels;
- }
+ if (channels == 0) {
+ channels = desc.channels;
+ }
- px_len = desc.width * desc.height * channels;
+ px_len = desc.width * desc.height * channels;
/* allocate async call
* --------------------------
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;
- }
- }
+ 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
const void *data = vg_file_read( vg_mem.scratch, path, &size );
vg_tex2d_load_qoi_async( data, size, flags, dest );
}
+
+u32 vg_query_qoi_storage_size( const qoi_desc *desc )
+{
+ return
+ desc->width * desc->height * (desc->channels + 1) +
+ QOI_HEADER_SIZE + sizeof(qoi_padding);
+}
+
+bool vg_encode_qoi2( const u8 *pixels, const qoi_desc *desc, u8 *out_bytes, int *out_len )
+{
+ int i = 0, p = 0, run = 0;
+ int px_len, px_end, px_pos, channels;
+ qoi_rgba_t index[64];
+ qoi_rgba_t px, px_prev;
+
+ if( desc->width == 0 || desc->height == 0 )
+ {
+ vg_error( "vg_write_qoi2: 0 width/height: %dx%d\n", desc->width, desc->height );
+ return 0;
+ }
+
+ if( desc->channels < 3 || desc->channels > 4 )
+ {
+ vg_error( "vg_write_qoi2: invalid channels: %d (min: 3, max: 4)\n", desc->channels );
+ return 0;
+ }
+
+ if( desc->colorspace > 1 )
+ {
+ vg_error( "vg_write_qoi2: invalid colourspace: %d (min:0 SRGB, max:1 LINEAR)\n", desc->colorspace );
+ return 0;
+ }
+
+ int total_pixels = desc->height * desc->width;
+ if( total_pixels >= QOI_PIXELS_MAX )
+ {
+ vg_error( "vg_write_qoi2: too many pixels (%d exceeds limit of %d)\n", total_pixels, QOI_PIXELS_MAX );
+ return 0;
+ }
+
+ qoi_write_32( out_bytes, &p, QOI_MAGIC );
+ qoi_write_32( out_bytes, &p, desc->width );
+ qoi_write_32( out_bytes, &p, desc->height );
+ out_bytes[p++] = desc->channels;
+ out_bytes[p++] = desc->colorspace;
+
+ QOI_ZEROARR(index);
+
+ run = 0;
+ px_prev.rgba.r = 0;
+ px_prev.rgba.g = 0;
+ px_prev.rgba.b = 0;
+ px_prev.rgba.a = 255;
+ px = px_prev;
+
+ px_len = desc->width * desc->height * desc->channels;
+ px_end = px_len - desc->channels;
+ channels = desc->channels;
+
+ for( px_pos = 0; px_pos < px_len; px_pos += channels )
+ {
+ px.rgba.r = pixels[px_pos + 0];
+ px.rgba.g = pixels[px_pos + 1];
+ px.rgba.b = pixels[px_pos + 2];
+
+ if( channels == 4 )
+ px.rgba.a = pixels[px_pos + 3];
+
+ if( px.v == px_prev.v )
+ {
+ run++;
+ if( run == 62 || px_pos == px_end )
+ {
+ out_bytes[p++] = QOI_OP_RUN | (run - 1);
+ run = 0;
+ }
+ }
+ else
+ {
+ int index_pos;
+ if( run > 0 )
+ {
+ out_bytes[p++] = QOI_OP_RUN | (run - 1);
+ run = 0;
+ }
+
+ index_pos = QOI_COLOR_HASH(px) % 64;
+
+ if( index[index_pos].v == px.v )
+ out_bytes[p++] = QOI_OP_INDEX | index_pos;
+ else
+ {
+ index[index_pos] = px;
+
+ if( px.rgba.a == px_prev.rgba.a )
+ {
+ i8 vr = px.rgba.r - px_prev.rgba.r,
+ vg = px.rgba.g - px_prev.rgba.g,
+ vb = px.rgba.b - px_prev.rgba.b,
+ vg_r = vr - vg,
+ vg_b = vb - vg;
+
+ if( vr > -3 && vr < 2 &&
+ vg > -3 && vg < 2 &&
+ vb > -3 && vb < 2 )
+ {
+ out_bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
+ }
+ else if( vg_r > -9 && vg_r < 8 &&
+ vg > -33 && vg < 32 &&
+ vg_b > -9 && vg_b < 8 )
+ {
+ out_bytes[p++] = QOI_OP_LUMA | (vg + 32);
+ out_bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8);
+ }
+ else
+ {
+ out_bytes[p++] = QOI_OP_RGB;
+ out_bytes[p++] = px.rgba.r;
+ out_bytes[p++] = px.rgba.g;
+ out_bytes[p++] = px.rgba.b;
+ }
+ }
+ else
+ {
+ out_bytes[p++] = QOI_OP_RGBA;
+ out_bytes[p++] = px.rgba.r;
+ out_bytes[p++] = px.rgba.g;
+ out_bytes[p++] = px.rgba.b;
+ out_bytes[p++] = px.rgba.a;
+ }
+ }
+ }
+ px_prev = px;
+ }
+
+ for( i = 0; i < (int)sizeof(qoi_padding); i ++ )
+ out_bytes[p ++] = qoi_padding[i];
+
+ *out_len = p;
+ return 1;
+}