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