build system revision
[vg.git] / vg_tex.c
1 #include "vg_tex.h"
2 #include "vg_engine.h"
3 #include "vg_async.h"
4 #include "vg_io.h"
5 #include <string.h>
6
7 static u8 const_vg_tex2d_err[] ={
8 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff,
9 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff,
10 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff,
11 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff,
12 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff,
13 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff,
14 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff,
15 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff,
16 };
17
18 #define QOI_SRGB 0
19 #define QOI_LINEAR 1
20
21 typedef struct {
22 unsigned int width;
23 unsigned int height;
24 unsigned char channels;
25 unsigned char colorspace;
26 } qoi_desc;
27
28 #ifndef QOI_ZEROARR
29 #define QOI_ZEROARR(a) memset((a),0,sizeof(a))
30 #endif
31
32 #define QOI_OP_INDEX 0x00 /* 00xxxxxx */
33 #define QOI_OP_DIFF 0x40 /* 01xxxxxx */
34 #define QOI_OP_LUMA 0x80 /* 10xxxxxx */
35 #define QOI_OP_RUN 0xc0 /* 11xxxxxx */
36 #define QOI_OP_RGB 0xfe /* 11111110 */
37 #define QOI_OP_RGBA 0xff /* 11111111 */
38
39 #define QOI_MASK_2 0xc0 /* 11000000 */
40
41 #define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
42 #define QOI_MAGIC \
43 (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
44 ((unsigned int)'i') << 8 | ((unsigned int)'f'))
45 #define QOI_HEADER_SIZE 14
46
47 /* 2GB is the max file size that this implementation can safely handle. We guard
48 against anything larger than that, assuming the worst case with 5 bytes per
49 pixel, rounded down to a nice clean value. 400 million pixels ought to be
50 enough for anybody. */
51 #define QOI_PIXELS_MAX ((unsigned int)400000000)
52
53 typedef union {
54 struct { unsigned char r, g, b, a; } rgba;
55 unsigned int v;
56 } qoi_rgba_t;
57
58 static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
59 static u32 qoi_read_32( const u8 *bytes, int *p ) {
60 u32 a = bytes[(*p)++];
61 u32 b = bytes[(*p)++];
62 u32 c = bytes[(*p)++];
63 u32 d = bytes[(*p)++];
64 return a << 24 | b << 16 | c << 8 | d;
65 }
66
67 struct texture_load_info{
68 GLuint *dest;
69 u32 width, height, flags;
70 u8 *rgba;
71 };
72
73 static void async_vg_tex2d_upload( void *payload, u32 size )
74 {
75 if( vg_thread_purpose() != k_thread_purpose_main ){
76 vg_fatal_error( "Catastrophic programming error.\n" );
77 }
78
79 struct texture_load_info *info = payload;
80
81 glGenTextures( 1, info->dest );
82 glBindTexture( GL_TEXTURE_2D, *info->dest );
83 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, info->width, info->height,
84 0, GL_RGBA, GL_UNSIGNED_BYTE, info->rgba );
85
86 if( !(info->flags & VG_TEX2D_NOMIP) ){
87 glGenerateMipmap( GL_TEXTURE_2D );
88 }
89
90 if( info->flags & VG_TEX2D_LINEAR ){
91 if( info->flags & VG_TEX2D_NOMIP ){
92 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
93 }
94 else{
95 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
96 GL_LINEAR_MIPMAP_LINEAR );
97 }
98 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
99 }
100
101 if( info->flags & VG_TEX2D_NEAREST ){
102 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
103 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
104 }
105
106 if( info->flags & VG_TEX2D_CLAMP ){
107 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
108 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
109 }
110
111 if( info->flags & VG_TEX2D_REPEAT ){
112 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
113 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
114 }
115 }
116
117 void vg_tex2d_replace_with_error_async( GLuint *dest )
118 {
119 u32 hdr_size = vg_align8(sizeof(struct texture_load_info));
120
121 vg_async_item *call = vg_async_alloc( hdr_size );
122 struct texture_load_info *info = call->payload;
123
124 info->dest = dest;
125 info->flags = VG_TEX2D_NEAREST|VG_TEX2D_REPEAT|VG_TEX2D_NOMIP;
126 info->width = 4;
127 info->height = 4;
128 info->rgba = const_vg_tex2d_err;
129
130 vg_async_dispatch( call, async_vg_tex2d_upload );
131 }
132
133 void vg_tex2d_load_qoi_async( const u8 *bytes, u32 size,
134 u32 flags, GLuint *dest )
135 {
136 u32 header_magic;
137 qoi_rgba_t index[64];
138 qoi_rgba_t px;
139 int px_len, chunks_len, px_pos;
140 int p = 0, run = 0;
141
142 u32 channels = 4; /* TODO */
143
144 qoi_desc desc;
145
146 if (
147 bytes == NULL ||
148 (channels != 0 && channels != 3 && channels != 4) ||
149 size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)
150 ) {
151 vg_error( "Error while decoding qoi file: illegal parameters\n" );
152 vg_tex2d_replace_with_error_async( dest );
153 return;
154 }
155
156 header_magic = qoi_read_32(bytes, &p);
157 desc.width = qoi_read_32(bytes, &p);
158 desc.height = qoi_read_32(bytes, &p);
159 desc.channels = bytes[p++];
160 desc.colorspace = bytes[p++];
161
162 if (
163 desc.width == 0 || desc.height == 0 ||
164 desc.channels < 3 || desc.channels > 4 ||
165 desc.colorspace > 1 ||
166 header_magic != QOI_MAGIC ||
167 desc.height >= QOI_PIXELS_MAX / desc.width
168 ) {
169 vg_error( "Error while decoding qoi file: invalid file\n" );
170 vg_tex2d_replace_with_error_async( dest );
171 return;
172 }
173
174 if (channels == 0) {
175 channels = desc.channels;
176 }
177
178 px_len = desc.width * desc.height * channels;
179
180 /* allocate async call
181 * --------------------------
182 */
183 u32 hdr_size = vg_align8(sizeof(struct texture_load_info)),
184 tex_size = vg_align8(px_len);
185
186 vg_async_item *call = vg_async_alloc( hdr_size + tex_size );
187 struct texture_load_info *info = call->payload;
188
189 info->dest = dest;
190 info->flags = flags;
191 info->width = desc.width;
192 info->height = desc.height;
193 info->rgba = ((u8*)call->payload) + hdr_size;
194
195 /*
196 * Decode
197 * ----------------------------
198 */
199
200 u8 *pixels = info->rgba;
201
202 QOI_ZEROARR(index);
203 px.rgba.r = 0;
204 px.rgba.g = 0;
205 px.rgba.b = 0;
206 px.rgba.a = 255;
207
208 chunks_len = size - (int)sizeof(qoi_padding);
209 for (px_pos = 0; px_pos < px_len; px_pos += channels) {
210 if (run > 0) {
211 run--;
212 }
213 else if (p < chunks_len) {
214 int b1 = bytes[p++];
215
216 if (b1 == QOI_OP_RGB) {
217 px.rgba.r = bytes[p++];
218 px.rgba.g = bytes[p++];
219 px.rgba.b = bytes[p++];
220 }
221 else if (b1 == QOI_OP_RGBA) {
222 px.rgba.r = bytes[p++];
223 px.rgba.g = bytes[p++];
224 px.rgba.b = bytes[p++];
225 px.rgba.a = bytes[p++];
226 }
227 else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
228 px = index[b1];
229 }
230 else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
231 px.rgba.r += ((b1 >> 4) & 0x03) - 2;
232 px.rgba.g += ((b1 >> 2) & 0x03) - 2;
233 px.rgba.b += ( b1 & 0x03) - 2;
234 }
235 else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
236 int b2 = bytes[p++];
237 int vg = (b1 & 0x3f) - 32;
238 px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
239 px.rgba.g += vg;
240 px.rgba.b += vg - 8 + (b2 & 0x0f);
241 }
242 else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
243 run = (b1 & 0x3f);
244 }
245
246 index[QOI_COLOR_HASH(px) % 64] = px;
247 }
248
249 pixels[px_pos + 0] = px.rgba.r;
250 pixels[px_pos + 1] = px.rgba.g;
251 pixels[px_pos + 2] = px.rgba.b;
252
253 if (channels == 4) {
254 pixels[px_pos + 3] = px.rgba.a;
255 }
256 }
257
258 /*
259 * Complete the call
260 * --------------------------
261 */
262
263 vg_async_dispatch( call, async_vg_tex2d_upload );
264 }
265
266 void vg_tex2d_load_qoi_async_file( const char *path, u32 flags, GLuint *dest )
267 {
268 if( vg_thread_purpose() != k_thread_purpose_loader )
269 vg_fatal_error( "wrong thread\n" );
270
271 vg_linear_clear( vg_mem.scratch );
272
273 u32 size;
274 const void *data = vg_file_read( vg_mem.scratch, path, &size );
275 vg_tex2d_load_qoi_async( data, size, flags, dest );
276 }