3f9cd9850eb8ae45191d0dd5ddb24250e58d2bb9
[vg.git] / vg_tex.h
1 /* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved
2 *
3 * A portion of this file is copied and altered from the QOI projects' source,
4 * Originally written by Dominic Szablewski. It is slightly modified.
5 * For the original unaltered QOI header, you can find it here:
6 * https://github.com/phoboslab/qoi/blob/master/qoi.h
7 *
8 * Copyright (C) 2021, Dominic Szablewski
9 * SPDX-License-Identifier: MIT
10 *
11 * MIT License
12 Copyright (c) 2022 Dominic Szablewski - https://phoboslab.org
13
14 Permission is hereby granted, free of charge, to any person obtaining a copy
15 of this software and associated documentation files (the "Software"), to deal
16 in the Software without restriction, including without limitation the rights
17 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 copies of the Software, and to permit persons to whom the Software is
19 furnished to do so, subject to the following conditions:
20
21 The above copyright notice and this permission notice shall be included in all
22 copies or substantial portions of the Software.
23
24 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 SOFTWARE.
31 */
32
33 #ifndef VG_TEX_H
34 #define VG_TEX_H
35
36 #define VG_GAME
37 #include "vg/vg.h"
38 #include "vg/vg_log.h"
39
40 #define STB_IMAGE_WRITE_IMPLEMENTATION
41 #include "vg/submodules/stb/stb_image_write.h"
42
43 /* its a sad day. */
44 #if 0
45 #define STBI_MALLOC(X)
46 #define STBI_REALLOC(X,Y)
47 #define STBI_FREE(X)
48 #endif
49
50 #define STBI_ONLY_JPEG
51 #define STBI_NO_THREAD_LOCALS
52 #define STB_IMAGE_IMPLEMENTATION
53 #include "vg/submodules/stb/stb_image.h"
54
55 struct vg_sprite
56 {
57 v4f uv_xywh;
58 };
59
60 #define VG_TEX2D_LINEAR 0x1
61 #define VG_TEX2D_NEAREST 0x2
62 #define VG_TEX2D_REPEAT 0x4
63 #define VG_TEX2D_CLAMP 0x8
64 #define VG_TEX2D_NOMIP 0x10
65
66 static u8 const_vg_tex2d_err[] ={
67 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff,
68 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff,
69 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff,
70 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff,
71 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff,
72 0xff,0x00,0xff,0xff, 0x00,0x00,0x00,0xff,
73 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff,
74 0x00,0x00,0x00,0xff, 0xff,0x00,0xff,0xff,
75 };
76
77 #define QOI_SRGB 0
78 #define QOI_LINEAR 1
79
80 typedef struct {
81 unsigned int width;
82 unsigned int height;
83 unsigned char channels;
84 unsigned char colorspace;
85 } qoi_desc;
86
87 #ifndef QOI_ZEROARR
88 #define QOI_ZEROARR(a) memset((a),0,sizeof(a))
89 #endif
90
91 #define QOI_OP_INDEX 0x00 /* 00xxxxxx */
92 #define QOI_OP_DIFF 0x40 /* 01xxxxxx */
93 #define QOI_OP_LUMA 0x80 /* 10xxxxxx */
94 #define QOI_OP_RUN 0xc0 /* 11xxxxxx */
95 #define QOI_OP_RGB 0xfe /* 11111110 */
96 #define QOI_OP_RGBA 0xff /* 11111111 */
97
98 #define QOI_MASK_2 0xc0 /* 11000000 */
99
100 #define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
101 #define QOI_MAGIC \
102 (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
103 ((unsigned int)'i') << 8 | ((unsigned int)'f'))
104 #define QOI_HEADER_SIZE 14
105
106 /* 2GB is the max file size that this implementation can safely handle. We guard
107 against anything larger than that, assuming the worst case with 5 bytes per
108 pixel, rounded down to a nice clean value. 400 million pixels ought to be
109 enough for anybody. */
110 #define QOI_PIXELS_MAX ((unsigned int)400000000)
111
112 typedef union {
113 struct { unsigned char r, g, b, a; } rgba;
114 unsigned int v;
115 } qoi_rgba_t;
116
117 static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
118 static u32 qoi_read_32( const u8 *bytes, int *p ) {
119 u32 a = bytes[(*p)++];
120 u32 b = bytes[(*p)++];
121 u32 c = bytes[(*p)++];
122 u32 d = bytes[(*p)++];
123 return a << 24 | b << 16 | c << 8 | d;
124 }
125
126 struct texture_load_info{
127 GLuint *dest;
128 u32 width, height, flags;
129 u8 *rgba;
130 };
131
132 static void async_vg_tex2d_upload( void *payload, u32 size )
133 {
134 if( vg_thread_purpose() != k_thread_purpose_main ){
135 vg_fatal_error( "Catastrophic programming error.\n" );
136 }
137
138 struct texture_load_info *info = payload;
139
140 glGenTextures( 1, info->dest );
141 glBindTexture( GL_TEXTURE_2D, *info->dest );
142 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, info->width, info->height,
143 0, GL_RGBA, GL_UNSIGNED_BYTE, info->rgba );
144
145 if( !(info->flags & VG_TEX2D_NOMIP) ){
146 glGenerateMipmap( GL_TEXTURE_2D );
147 }
148
149 if( info->flags & VG_TEX2D_LINEAR ){
150 if( info->flags & VG_TEX2D_NOMIP ){
151 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
152 }
153 else{
154 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
155 GL_LINEAR_MIPMAP_LINEAR );
156 }
157 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
158 }
159
160 if( info->flags & VG_TEX2D_NEAREST ){
161 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
162 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
163 }
164
165 if( info->flags & VG_TEX2D_CLAMP ){
166 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
167 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
168 }
169
170 if( info->flags & VG_TEX2D_REPEAT ){
171 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
172 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
173 }
174 }
175
176 static void vg_tex2d_replace_with_error( GLuint *dest )
177 {
178 u32 hdr_size = vg_align8(sizeof(struct texture_load_info));
179
180 vg_async_item *call = vg_async_alloc( hdr_size );
181 struct texture_load_info *info = call->payload;
182
183 info->dest = dest;
184 info->flags = VG_TEX2D_NEAREST|VG_TEX2D_REPEAT|VG_TEX2D_NOMIP;
185 info->width = 4;
186 info->height = 4;
187 info->rgba = const_vg_tex2d_err;
188
189 vg_async_dispatch( call, async_vg_tex2d_upload );
190 }
191
192 static
193 void vg_tex2d_load_qoi_async( const u8 *bytes, u32 size,
194 u32 flags, GLuint *dest )
195 {
196 u32 header_magic;
197 qoi_rgba_t index[64];
198 qoi_rgba_t px;
199 int px_len, chunks_len, px_pos;
200 int p = 0, run = 0;
201
202 u32 channels = 4; /* TODO */
203
204 qoi_desc desc;
205
206 if (
207 bytes == NULL ||
208 (channels != 0 && channels != 3 && channels != 4) ||
209 size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)
210 ) {
211 vg_error( "Error while decoding qoi file: illegal parameters\n" );
212 vg_tex2d_replace_with_error( dest );
213 return;
214 }
215
216 header_magic = qoi_read_32(bytes, &p);
217 desc.width = qoi_read_32(bytes, &p);
218 desc.height = qoi_read_32(bytes, &p);
219 desc.channels = bytes[p++];
220 desc.colorspace = bytes[p++];
221
222 if (
223 desc.width == 0 || desc.height == 0 ||
224 desc.channels < 3 || desc.channels > 4 ||
225 desc.colorspace > 1 ||
226 header_magic != QOI_MAGIC ||
227 desc.height >= QOI_PIXELS_MAX / desc.width
228 ) {
229 vg_error( "Error while decoding qoi file: invalid file\n" );
230 vg_tex2d_replace_with_error( dest );
231 return;
232 }
233
234 if (channels == 0) {
235 channels = desc.channels;
236 }
237
238 px_len = desc.width * desc.height * channels;
239
240 /* allocate async call
241 * --------------------------
242 */
243 u32 hdr_size = vg_align8(sizeof(struct texture_load_info)),
244 tex_size = vg_align8(px_len);
245
246 vg_async_item *call = vg_async_alloc( hdr_size + tex_size );
247 struct texture_load_info *info = call->payload;
248
249 info->dest = dest;
250 info->flags = flags;
251 info->width = desc.width;
252 info->height = desc.height;
253 info->rgba = ((u8*)call->payload) + hdr_size;
254
255 /*
256 * Decode
257 * ----------------------------
258 */
259
260 u8 *pixels = info->rgba;
261
262 QOI_ZEROARR(index);
263 px.rgba.r = 0;
264 px.rgba.g = 0;
265 px.rgba.b = 0;
266 px.rgba.a = 255;
267
268 chunks_len = size - (int)sizeof(qoi_padding);
269 for (px_pos = 0; px_pos < px_len; px_pos += channels) {
270 if (run > 0) {
271 run--;
272 }
273 else if (p < chunks_len) {
274 int b1 = bytes[p++];
275
276 if (b1 == QOI_OP_RGB) {
277 px.rgba.r = bytes[p++];
278 px.rgba.g = bytes[p++];
279 px.rgba.b = bytes[p++];
280 }
281 else if (b1 == QOI_OP_RGBA) {
282 px.rgba.r = bytes[p++];
283 px.rgba.g = bytes[p++];
284 px.rgba.b = bytes[p++];
285 px.rgba.a = bytes[p++];
286 }
287 else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
288 px = index[b1];
289 }
290 else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
291 px.rgba.r += ((b1 >> 4) & 0x03) - 2;
292 px.rgba.g += ((b1 >> 2) & 0x03) - 2;
293 px.rgba.b += ( b1 & 0x03) - 2;
294 }
295 else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
296 int b2 = bytes[p++];
297 int vg = (b1 & 0x3f) - 32;
298 px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
299 px.rgba.g += vg;
300 px.rgba.b += vg - 8 + (b2 & 0x0f);
301 }
302 else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
303 run = (b1 & 0x3f);
304 }
305
306 index[QOI_COLOR_HASH(px) % 64] = px;
307 }
308
309 pixels[px_pos + 0] = px.rgba.r;
310 pixels[px_pos + 1] = px.rgba.g;
311 pixels[px_pos + 2] = px.rgba.b;
312
313 if (channels == 4) {
314 pixels[px_pos + 3] = px.rgba.a;
315 }
316 }
317
318 /*
319 * Complete the call
320 * --------------------------
321 */
322
323 vg_async_dispatch( call, async_vg_tex2d_upload );
324 }
325
326 static
327 void vg_tex2d_load_qoi_async_file( const char *path, u32 flags, GLuint *dest )
328 {
329 if( vg_thread_purpose() != k_thread_purpose_loader )
330 vg_fatal_error( "wrong thread\n" );
331
332 vg_linear_clear( vg_mem.scratch );
333
334 u32 size;
335 const void *data = vg_file_read( vg_mem.scratch, path, &size );
336 vg_tex2d_load_qoi_async( data, size, flags, dest );
337 }
338
339 #endif /* VG_TEX_H */