replace VG_STATIC -> static
[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 {
68 0x00, 0xff, 0xff, 0xff,
69 0xff, 0xff, 0x00, 0xff,
70 0x00, 0x00, 0xff, 0xff,
71 0xff, 0xff, 0x00, 0xff
72 };
73
74 #define QOI_SRGB 0
75 #define QOI_LINEAR 1
76
77 typedef struct {
78 unsigned int width;
79 unsigned int height;
80 unsigned char channels;
81 unsigned char colorspace;
82 } qoi_desc;
83
84 #ifndef QOI_ZEROARR
85 #define QOI_ZEROARR(a) memset((a),0,sizeof(a))
86 #endif
87
88 #define QOI_OP_INDEX 0x00 /* 00xxxxxx */
89 #define QOI_OP_DIFF 0x40 /* 01xxxxxx */
90 #define QOI_OP_LUMA 0x80 /* 10xxxxxx */
91 #define QOI_OP_RUN 0xc0 /* 11xxxxxx */
92 #define QOI_OP_RGB 0xfe /* 11111110 */
93 #define QOI_OP_RGBA 0xff /* 11111111 */
94
95 #define QOI_MASK_2 0xc0 /* 11000000 */
96
97 #define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
98 #define QOI_MAGIC \
99 (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
100 ((unsigned int)'i') << 8 | ((unsigned int)'f'))
101 #define QOI_HEADER_SIZE 14
102
103 /* 2GB is the max file size that this implementation can safely handle. We guard
104 against anything larger than that, assuming the worst case with 5 bytes per
105 pixel, rounded down to a nice clean value. 400 million pixels ought to be
106 enough for anybody. */
107 #define QOI_PIXELS_MAX ((unsigned int)400000000)
108
109 typedef union {
110 struct { unsigned char r, g, b, a; } rgba;
111 unsigned int v;
112 } qoi_rgba_t;
113
114 static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
115 static u32 qoi_read_32( const u8 *bytes, int *p ) {
116 u32 a = bytes[(*p)++];
117 u32 b = bytes[(*p)++];
118 u32 c = bytes[(*p)++];
119 u32 d = bytes[(*p)++];
120 return a << 24 | b << 16 | c << 8 | d;
121 }
122
123 struct texture_load_info{
124 GLuint *dest;
125 u32 width, height, flags;
126 u8 *rgba;
127 };
128
129 static void async_vg_tex2d_upload( void *payload, u32 size )
130 {
131 if( vg_thread_purpose() != k_thread_purpose_main ){
132 vg_fatal_error( "Catastrophic programming error.\n" );
133 }
134
135 struct texture_load_info *info = payload;
136
137 glGenTextures( 1, info->dest );
138 glBindTexture( GL_TEXTURE_2D, *info->dest );
139 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, info->width, info->height,
140 0, GL_RGBA, GL_UNSIGNED_BYTE, info->rgba );
141
142 if( !(info->flags & VG_TEX2D_NOMIP) ){
143 glGenerateMipmap( GL_TEXTURE_2D );
144 }
145
146 if( info->flags & VG_TEX2D_LINEAR ){
147 if( info->flags & VG_TEX2D_NOMIP ){
148 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
149 }
150 else{
151 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
152 GL_LINEAR_MIPMAP_LINEAR );
153 }
154 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
155 }
156
157 if( info->flags & VG_TEX2D_NEAREST ){
158 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
159 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
160 }
161
162 if( info->flags & VG_TEX2D_CLAMP ){
163 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
164 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
165 }
166
167 if( info->flags & VG_TEX2D_REPEAT ){
168 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
169 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
170 }
171 }
172
173 static void vg_tex2d_replace_with_error( GLuint *dest )
174 {
175 u32 hdr_size = vg_align8(sizeof(struct texture_load_info));
176
177 vg_async_item *call = vg_async_alloc( hdr_size );
178 struct texture_load_info *info = call->payload;
179
180 info->dest = dest;
181 info->flags = VG_TEX2D_NEAREST|VG_TEX2D_REPEAT|VG_TEX2D_NOMIP;
182 info->width = 2;
183 info->height = 2;
184 info->rgba = const_vg_tex2d_err;
185
186 vg_async_dispatch( call, async_vg_tex2d_upload );
187 }
188
189 static
190 void vg_tex2d_load_qoi_async( const u8 *bytes, u32 size,
191 u32 flags, GLuint *dest )
192 {
193 u32 header_magic;
194 qoi_rgba_t index[64];
195 qoi_rgba_t px;
196 int px_len, chunks_len, px_pos;
197 int p = 0, run = 0;
198
199 u32 channels = 4; /* TODO */
200
201 qoi_desc desc;
202
203 if (
204 bytes == NULL ||
205 (channels != 0 && channels != 3 && channels != 4) ||
206 size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)
207 ) {
208 vg_error( "Error while decoding qoi file: illegal parameters\n" );
209 vg_tex2d_replace_with_error( dest );
210 return;
211 }
212
213 header_magic = qoi_read_32(bytes, &p);
214 desc.width = qoi_read_32(bytes, &p);
215 desc.height = qoi_read_32(bytes, &p);
216 desc.channels = bytes[p++];
217 desc.colorspace = bytes[p++];
218
219 if (
220 desc.width == 0 || desc.height == 0 ||
221 desc.channels < 3 || desc.channels > 4 ||
222 desc.colorspace > 1 ||
223 header_magic != QOI_MAGIC ||
224 desc.height >= QOI_PIXELS_MAX / desc.width
225 ) {
226 vg_error( "Error while decoding qoi file: invalid file\n" );
227 vg_tex2d_replace_with_error( dest );
228 return;
229 }
230
231 if (channels == 0) {
232 channels = desc.channels;
233 }
234
235 px_len = desc.width * desc.height * channels;
236
237 /* allocate async call
238 * --------------------------
239 */
240 u32 hdr_size = vg_align8(sizeof(struct texture_load_info)),
241 tex_size = vg_align8(px_len);
242
243 vg_async_item *call = vg_async_alloc( hdr_size + tex_size );
244 struct texture_load_info *info = call->payload;
245
246 info->dest = dest;
247 info->flags = flags;
248 info->width = desc.width;
249 info->height = desc.height;
250 info->rgba = ((u8*)call->payload) + hdr_size;
251
252 /*
253 * Decode
254 * ----------------------------
255 */
256
257 u8 *pixels = info->rgba;
258
259 QOI_ZEROARR(index);
260 px.rgba.r = 0;
261 px.rgba.g = 0;
262 px.rgba.b = 0;
263 px.rgba.a = 255;
264
265 chunks_len = size - (int)sizeof(qoi_padding);
266 for (px_pos = 0; px_pos < px_len; px_pos += channels) {
267 if (run > 0) {
268 run--;
269 }
270 else if (p < chunks_len) {
271 int b1 = bytes[p++];
272
273 if (b1 == QOI_OP_RGB) {
274 px.rgba.r = bytes[p++];
275 px.rgba.g = bytes[p++];
276 px.rgba.b = bytes[p++];
277 }
278 else if (b1 == QOI_OP_RGBA) {
279 px.rgba.r = bytes[p++];
280 px.rgba.g = bytes[p++];
281 px.rgba.b = bytes[p++];
282 px.rgba.a = bytes[p++];
283 }
284 else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
285 px = index[b1];
286 }
287 else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
288 px.rgba.r += ((b1 >> 4) & 0x03) - 2;
289 px.rgba.g += ((b1 >> 2) & 0x03) - 2;
290 px.rgba.b += ( b1 & 0x03) - 2;
291 }
292 else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
293 int b2 = bytes[p++];
294 int vg = (b1 & 0x3f) - 32;
295 px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
296 px.rgba.g += vg;
297 px.rgba.b += vg - 8 + (b2 & 0x0f);
298 }
299 else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
300 run = (b1 & 0x3f);
301 }
302
303 index[QOI_COLOR_HASH(px) % 64] = px;
304 }
305
306 pixels[px_pos + 0] = px.rgba.r;
307 pixels[px_pos + 1] = px.rgba.g;
308 pixels[px_pos + 2] = px.rgba.b;
309
310 if (channels == 4) {
311 pixels[px_pos + 3] = px.rgba.a;
312 }
313 }
314
315 /*
316 * Complete the call
317 * --------------------------
318 */
319
320 vg_async_dispatch( call, async_vg_tex2d_upload );
321 }
322
323 static
324 void vg_tex2d_load_qoi_async_file( const char *path, u32 flags, GLuint *dest )
325 {
326 vg_linear_clear( vg_mem.scratch );
327
328 u32 size;
329 const void *data = vg_file_read( vg_mem.scratch, path, &size );
330 vg_tex2d_load_qoi_async( data, size, flags, dest );
331 }
332
333 #endif /* VG_TEX_H */