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