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