GCC Code Coverage Report


Directory: ./
File: lib/geogram_gfx/basic/GL.cpp
Date: 2026-09-07 02:28:19
Exec Total Coverage
Lines: 0 235 0.0%
Functions: 0 15 0.0%
Branches: 0 136 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <geogram_gfx/basic/GL.h>
41 #include <geogram_gfx/basic/GLSL.h>
42 #include <geogram_gfx/GLUP/GLUP.h>
43 #include <geogram_gfx/GLUP/GLUP_context.h>
44 #include <geogram/basic/logger.h>
45 #include <geogram/basic/command_line.h>
46
47 namespace {
48 using namespace GEO;
49
50 /**
51 * \brief Converts a mat4 into a matrix for OpenGL.
52 * \param[in] m a const reference to the matrix
53 * \return a const pointer to the converted matrix,
54 * stored as a static array of 16 doubles.
55 * \note The matrix is transposed before being sent to OpenGL
56 * because Geogram uses the convention with column
57 * vectors and OpenGL the convention with row vectors
58 * to represent the transformed points.
59 */
60 const GLdouble* convert_matrix(
61 const mat4& m
62 ) {
63 static double result[16] ;
64 index_t k = 0 ;
65 for(index_t i=0; i<4; i++) {
66 for(index_t j=0; j<4; j++) {
67 result[k] = m(i,j) ;
68 k++ ;
69 }
70 }
71 return result ;
72 }
73
74 GLuint quad_VAO = 0;
75 GLuint quad_vertices_VBO = 0;
76 GLuint quad_tex_coords_VBO = 0;
77 GLuint quad_program = 0;
78 GLuint quad_program_BW = 0;
79 GLuint quad_program_DEPTH = 0;
80
81 /**
82 * \brief Creates the VAO, VBOs and program used to draw
83 * a single textured quad.
84 * \details We often have to do that, for instance for blitting
85 * a FrameBufferObject onto the screen (or onto another FrameBufferObject)
86 */
87 void create_quad_VAO_and_program() {
88 // All that stuff just to draw a single textured square,
89 // the new OpenGL is really painful !!!
90 // (one could use glBegin()/glVertex()/glEnd() instead, but
91 // this would not work with pure Core OpenGL profile and
92 // neither with OpenGL ES !!
93
94
95 // Will be drawn with a triangle strip (supported in both
96 // OpenGL and OpenGL ES)
97
98 static GLfloat coords[4][2] = {
99 {-1.0f, -1.0f},
100 { 1.0f, -1.0f},
101 {-1.0f, 1.0f},
102 { 1.0f, 1.0f}
103 };
104
105 static GLfloat tex_coords[4][2] = {
106 { 0.0f, 0.0f},
107 { 1.0f, 0.0f},
108 { 0.0f, 1.0f},
109 { 1.0f, 1.0f}
110 };
111
112 update_buffer_object(
113 quad_vertices_VBO, GL_ARRAY_BUFFER,
114 sizeof(GL_FLOAT)*2*4, coords
115 );
116 update_buffer_object(
117 quad_tex_coords_VBO, GL_ARRAY_BUFFER,
118 sizeof(GL_FLOAT)*2*4, tex_coords
119 );
120 glupGenVertexArrays(1, &quad_VAO);
121 glupBindVertexArray(quad_VAO);
122
123 glBindBuffer(GL_ARRAY_BUFFER, quad_vertices_VBO);
124 glEnableVertexAttribArray(0);
125 glVertexAttribPointer(
126 0, // Attribute 0
127 2, // nb coordinates per vertex
128 GL_FLOAT, // input coordinates representation
129 GL_FALSE, // do not normalize
130 0, // offset between two consecutive vertices (0 = packed)
131 nullptr // addr. relative to bound VBO
132 );
133
134 glBindBuffer(GL_ARRAY_BUFFER, quad_tex_coords_VBO);
135 glEnableVertexAttribArray(1);
136 glVertexAttribPointer(
137 1, // Attribute 1
138 2, // nb coordinates per vertex
139 GL_FLOAT, // input coordinates representation
140 GL_FALSE, // do not normalize
141 0, // offset between two consecutive vertices (0 = packed)
142 nullptr // addr. relative to bound VBO
143 );
144
145 glupBindVertexArray(0);
146 glBindBuffer(GL_ARRAY_BUFFER, 0);
147
148 #if defined(GEO_OS_EMSCRIPTEN) || defined(GEO_OS_ANDROID)
149 #define NO_DEPTH_SHADER
150
151 static const char* vshader_source =
152 "#version 100 \n"
153 "attribute vec2 vertex_in; \n"
154 "attribute vec2 tex_coord_in; \n"
155 "varying vec2 tex_coord; \n"
156 "void main() { \n"
157 " tex_coord = tex_coord_in; \n"
158 " gl_Position = vec4(vertex_in, 0.0, 1.0); \n"
159 "} \n"
160 ;
161
162 static const char* fshader_source =
163 "#version 100 \n"
164 "precision mediump float; \n"
165 "varying vec2 tex_coord; \n"
166 "uniform sampler2D tex; \n"
167 "void main() { \n"
168 " gl_FragColor = texture2D( \n"
169 " tex, tex_coord \n"
170 " ); \n"
171 "} \n"
172 ;
173
174 static const char* fshader_BW_source =
175 "#version 100 \n"
176 "precision mediump float; \n"
177 "varying vec2 tex_coord; \n"
178 "uniform sampler2D tex; \n"
179 "void main() { \n"
180 " gl_FragColor = vec4(texture2D( \n"
181 " tex, tex_coord \n"
182 " ).xxx,1.0); \n"
183 "} \n"
184 ;
185
186 #else
187
188 static const char* vshader_source =
189 # ifdef GEO_OS_APPLE
190 "#version 330 \n"
191 # else
192 "#version 130 \n"
193 # endif
194 "in vec2 vertex_in; \n"
195 "in vec2 tex_coord_in; \n"
196 "out vec2 tex_coord; \n"
197 "void main() { \n"
198 " tex_coord = tex_coord_in; \n"
199 " gl_Position = vec4(vertex_in, 0.0, 1.0); \n"
200 "} \n"
201 ;
202
203 static const char* fshader_source =
204 # ifdef GEO_OS_APPLE
205 "#version 330 \n"
206 # else
207 "#version 130 \n"
208 # endif
209 "out vec4 frag_color ; \n"
210 "in vec2 tex_coord; \n"
211 "uniform sampler2D tex; \n"
212 "void main() { \n"
213 " frag_color = texture( \n"
214 " tex, tex_coord \n"
215 " ); \n"
216 "} \n"
217 ;
218
219 static const char* fshader_BW_source =
220 # ifdef GEO_OS_APPLE
221 "#version 330 \n"
222 # else
223 "#version 130 \n"
224 # endif
225 "out vec4 frag_color ; \n"
226 "in vec2 tex_coord; \n"
227 "uniform sampler2D tex; \n"
228 "void main() { \n"
229 " frag_color = vec4(texture( \n"
230 " tex, tex_coord \n"
231 " ).xxx,1.0); \n"
232 "} \n"
233 ;
234
235 // NOTE: does not seem to work !
236 static const char* fshader_DEPTH_source =
237 # ifdef GEO_OS_APPLE
238 "#version 330 \n"
239 # else
240 "#version 130 \n"
241 # endif
242 "in vec2 tex_coord; \n"
243 "uniform sampler2D tex; \n"
244 "void main() { \n"
245 " float z = texture(tex,tex_coord).r; \n"
246 " gl_FragDepth = \n"
247 " (1.0-z)*gl_DepthRange.near + z*gl_DepthRange.far; \n"
248 "} \n"
249 ;
250 #endif
251
252 GLuint vshader = GLSL::compile_shader(
253 GL_VERTEX_SHADER, vshader_source, nullptr
254 );
255
256 GLuint fshader = GLSL::compile_shader(
257 GL_FRAGMENT_SHADER, fshader_source, nullptr
258 );
259
260 GLuint fshader_BW = GLSL::compile_shader(
261 GL_FRAGMENT_SHADER, fshader_BW_source, nullptr
262 );
263
264 #ifndef NO_DEPTH_SHADER
265 GLuint fshader_DEPTH = GLSL::compile_shader(
266 GL_FRAGMENT_SHADER, fshader_DEPTH_source, nullptr
267 );
268 #endif
269
270 quad_program = GLSL::create_program_from_shaders_no_link(
271 vshader, fshader, nullptr
272 );
273
274 quad_program_BW = GLSL::create_program_from_shaders_no_link(
275 vshader, fshader_BW, nullptr
276 );
277
278 #ifndef NO_DEPTH_SHADER
279 quad_program_DEPTH = GLSL::create_program_from_shaders_no_link(
280 vshader, fshader_DEPTH, nullptr
281 );
282 #endif
283
284 glBindAttribLocation(quad_program, 0, "vertex_in");
285 glBindAttribLocation(quad_program, 1, "tex_coord_in");
286 GLSL::link_program(quad_program);
287
288
289 glBindAttribLocation(quad_program_BW, 0, "vertex_in");
290 glBindAttribLocation(quad_program_BW, 1, "tex_coord_in");
291 GLSL::link_program(quad_program_BW);
292 GLSL::set_program_uniform_by_name(quad_program_BW, "tex", 0);
293
294 #ifndef NO_DEPTH_SHADER
295 glBindAttribLocation(quad_program_DEPTH, 0, "vertex_in");
296 glBindAttribLocation(quad_program_DEPTH, 1, "tex_coord_in");
297 GLSL::link_program(quad_program_DEPTH);
298 GLSL::set_program_uniform_by_name(quad_program_DEPTH, "tex", 0);
299 #endif
300 glDeleteShader(vshader);
301 glDeleteShader(fshader);
302 glDeleteShader(fshader_BW);
303
304 #ifndef NO_DEPTH_SHADER
305 glDeleteShader(fshader_DEPTH);
306 #endif
307 }
308
309 const char* error_string(GLenum error_code) {
310 const char* result = nullptr;
311 switch(error_code) {
312 case GL_NO_ERROR:
313 result = "no error";
314 break;
315 case GL_INVALID_ENUM:
316 result = "invalid enum";
317 break;
318 case GL_INVALID_VALUE:
319 result = "invalid value";
320 break;
321 case GL_INVALID_OPERATION:
322 result = "invalid operation";
323 break;
324 case GL_INVALID_FRAMEBUFFER_OPERATION:
325 result = "invalid framebuffer operation";
326 break;
327 case GL_OUT_OF_MEMORY:
328 result = "out of memory";
329 break;
330 /*
331 case GL_STACK_UNDERFLOW:
332 result = "stack underflow";
333 break;
334 case GL_STACK_OVERFLOW:
335 result = "stack over";
336 break;
337 */
338 default:
339 result = "unknown errorcode";
340 }
341 return result;
342 }
343 }
344
345 namespace GEO {
346
347 namespace GL {
348
349 void initialize() {
350 }
351
352 void terminate() {
353 if(quad_program != 0) {
354 glDeleteProgram(quad_program);
355 quad_program = 0;
356 }
357 if(quad_program_BW != 0) {
358 glDeleteProgram(quad_program_BW);
359 quad_program_BW = 0;
360 }
361 if(quad_program_DEPTH != 0) {
362 glDeleteProgram(quad_program_DEPTH);
363 quad_program_DEPTH = 0;
364 }
365 if(quad_VAO != 0) {
366 glupDeleteVertexArrays(1, &quad_VAO);
367 quad_VAO = 0;
368 }
369 if(quad_vertices_VBO != 0) {
370 glDeleteBuffers(1, &quad_vertices_VBO);
371 quad_vertices_VBO = 0;
372 }
373 if(quad_tex_coords_VBO != 0) {
374 glDeleteBuffers(1, &quad_tex_coords_VBO);
375 quad_tex_coords_VBO = 0;
376 }
377 }
378 }
379
380 void glupMapTexCoords1d(double minval, double maxval, index_t mult) {
381 glupMatrixMode(GLUP_TEXTURE_MATRIX);
382 GLUPdouble M[16];
383 Memory::clear(M, sizeof(GLUPdouble)*16);
384 double d = maxval - minval;
385 if(::fabs(d) < 1e-10) {
386 d = 0.0;
387 } else {
388 d = 1.0 / d;
389 }
390 d *= double(std::max(mult, 1u));
391 M[0] = d;
392 M[12] = -d*minval;
393 M[15] = 1.0;
394 glupLoadMatrixd(M);
395 glupMatrixMode(GLUP_MODELVIEW_MATRIX);
396 }
397
398 void glupLoadMatrix(const mat4& m) {
399 glupLoadMatrixd(convert_matrix(m));
400 }
401
402 void glupMultMatrix(const mat4& m) {
403 glupMultMatrixd(convert_matrix(m));
404 }
405
406
407 GLint64 get_size_of_bound_buffer_object(GLenum target) {
408
409 #ifdef GLUP_DEBUG
410 {
411 GLint buffer = 0;
412 switch(target) {
413 case GL_ARRAY_BUFFER:
414 glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &buffer);
415 break;
416 case GL_ELEMENT_ARRAY_BUFFER:
417 glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &buffer);
418 break;
419 default:
420 geo_assert_not_reached;
421 break;
422 }
423 geo_assert(buffer != 0);
424 }
425 #endif
426
427 GLint64 result=0;
428
429 #ifdef GEO_GL_440
430
431 static bool init = false;
432 static bool use_glGetBufferParameteri64v = false;
433
434 // Note: there is a version of glGetBufferParameteriv that uses
435 // 64 bit parameters. Since array data larger than 4Gb will be
436 // common place, it is this version that should be used. However,
437 // it is not supported by the Intel driver (therefore we fallback
438 // to the standard 32 bits version if such a driver is detected).
439 // It is not implemented by Gallium either... Oh well, for now
440 // I deactivate it if the driver is not NVIDIA.
441
442 if(!init) {
443 init = true;
444 const char* vendor = (const char*)glGetString(GL_VENDOR);
445 use_glGetBufferParameteri64v = (
446 strlen(vendor) >= 6 && !strncmp(vendor, "NVIDIA", 6) &&
447 (glGetBufferParameteri64v != nullptr)
448 );
449 // Does not seem to be implemented under OpenGL ES
450 if(CmdLine::get_arg("gfx:GL_profile") == "ES") {
451 use_glGetBufferParameteri64v = false;
452 }
453 }
454 if(use_glGetBufferParameteri64v) {
455 glGetBufferParameteri64v(target,GL_BUFFER_SIZE,&result);
456 } else
457 #endif
458 {
459 GLint result32=0;
460 glGetBufferParameteriv(target,GL_BUFFER_SIZE,&result32);
461 result = GLint64(result32);
462 }
463 return result;
464 }
465
466
467 void update_buffer_object(
468 GLuint& buffer_id, GLenum target, size_t new_size, const void* data
469 ) {
470 if(new_size == 0) {
471 if(buffer_id != 0) {
472 glDeleteBuffers(1, &buffer_id);
473 buffer_id = 0;
474 }
475 return;
476 }
477
478 GLint64 size = 0;
479 if(buffer_id == 0) {
480 glGenBuffers(1, &buffer_id);
481 glBindBuffer(target, buffer_id);
482 } else {
483 glBindBuffer(target, buffer_id);
484 size = get_size_of_bound_buffer_object(target);
485 }
486
487 if(new_size == size_t(size)) {
488 glBufferSubData(target, 0, GLsizeiptr(size), data);
489 } else {
490 glBufferData(
491 target, GLsizeiptr(new_size), data, GL_STATIC_DRAW
492 );
493 }
494 }
495
496 void stream_buffer_object(
497 GLuint& buffer_id, GLenum target, size_t new_size, const void* data
498 ) {
499 if(new_size == 0) {
500 if(buffer_id != 0) {
501 glDeleteBuffers(1, &buffer_id);
502 buffer_id = 0;
503 }
504 return;
505 }
506
507 GLint64 size = 0;
508 if(buffer_id == 0) {
509 glGenBuffers(1, &buffer_id);
510 glBindBuffer(target, buffer_id);
511 } else {
512 glBindBuffer(target, buffer_id);
513 size = get_size_of_bound_buffer_object(target);
514 }
515
516 if(new_size == size_t(size)) {
517 // Binding nullptr makes the GPU-side allocated buffer "orphan",
518 // if there was a rendering operation currently using it, then
519 // it can safely continue.
520 glBufferData(target, GLsizeiptr(size), nullptr, GL_STREAM_DRAW);
521 // And here we bind a fresh new block of GPU-side memory.
522 // See https://www.opengl.org/wiki/Buffer_Object_Streaming
523 glBufferData(target, GLsizeiptr(size), data, GL_STREAM_DRAW);
524 } else {
525 glBufferData(
526 target, GLsizeiptr(new_size), data, GL_STATIC_DRAW
527 );
528 }
529 }
530
531
532 void update_or_check_buffer_object(
533 GLuint& buffer_id, GLenum target, size_t new_size, const void* data,
534 bool update
535 ) {
536 if(update) {
537 update_buffer_object(buffer_id, target, new_size, data);
538 } else {
539 glBindBuffer(target, buffer_id);
540 if(new_size != size_t(get_size_of_bound_buffer_object(target))) {
541 Logger::warn("OpenGL")
542 << "Buffer Object does not have the expected size."
543 << std::endl;
544 Logger::warn("OpenGL")
545 << "An object was probably changed "
546 << "without notifying/updating the graphics."
547 << std::endl;
548 Logger::warn("OpenGL")
549 << "Forcing Buffer Object update."
550 << std::endl;
551 update_buffer_object(buffer_id, target, new_size, data);
552 }
553 }
554 }
555
556 void check_gl(const char* file, int line, bool warning_only) {
557
558 GLenum error_code = glGetError() ;
559 bool has_opengl_errors = false ;
560 while(error_code != GL_NO_ERROR) {
561 has_opengl_errors = true ;
562 if(warning_only) {
563 Logger::warn("OpenGL")
564 << file << ":" << line << " "
565 << error_string(error_code)
566 << " (ignored)"
567 << std::endl;
568 } else {
569 Logger::err("OpenGL")
570 << file << ":" << line << " "
571 << error_string(error_code)
572 << std::endl;
573 }
574 error_code = glGetError() ;
575 }
576 geo_argused(has_opengl_errors);
577 }
578
579 void clear_gl_error_flags(const char* file, int line) {
580 #ifdef GEO_DEBUG
581 check_gl(file,line,true);
582 #else
583 geo_argused(file);
584 geo_argused(line);
585 while(glGetError() != GL_NO_ERROR);
586 #endif
587 }
588
589 void draw_unit_textured_quad(TexturedQuadMode mode) {
590 if(quad_VAO == 0) {
591 create_quad_VAO_and_program();
592 }
593 GLint current_program = 0;
594 glGetIntegerv(GL_CURRENT_PROGRAM, &current_program);
595 if(current_program == 0) {
596 switch(mode) {
597 case TEX_QUAD_RGBA:
598 glUseProgram(quad_program);
599 break;
600 case TEX_QUAD_RRR1:
601 glUseProgram(quad_program_BW);
602 break;
603 case TEX_QUAD_DEPTH:
604 glUseProgram(quad_program_DEPTH);
605 break;
606 }
607 }
608 glupBindVertexArray(quad_VAO);
609 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
610 glupBindVertexArray(0);
611 if(current_program == 0) {
612 glUseProgram(0);
613 }
614 }
615
616 /****************************************************************/
617
618
619 static int htoi(char digit) {
620 if(digit >= '0' && digit <= '9') {
621 return digit - '0';
622 }
623 if(digit >= 'a' && digit <= 'f') {
624 return digit - 'a' + 10;
625 }
626 if(digit >= 'A' && digit <= 'F') {
627 return digit - 'A' + 10;
628 }
629 fprintf(stderr, "xpm: unknown digit\n");
630 return 0;
631 }
632
633 /* The colormap. */
634 static unsigned char i2r[1024];
635 static unsigned char i2g[1024];
636 static unsigned char i2b[1024];
637 static unsigned char i2a[1024];
638
639 /*
640 * Converts a two-digit XPM color code into
641 * a color index.
642 */
643 static int char_to_index[256][256];
644
645 void glTexImage2Dxpm(char const* const* xpm_data) {
646 int width, height, nb_colors, chars_per_pixel;
647 int line = 0;
648 int color = 0;
649 int key1 = 0, key2 = 0;
650 const char* colorcode;
651 int x, y;
652 unsigned char* rgba;
653 unsigned char* pixel;
654
655 sscanf(
656 xpm_data[line], "%6d%6d%6d%6d",
657 &width, &height, &nb_colors, &chars_per_pixel
658 );
659 line++;
660 if(nb_colors > 1024) {
661 fprintf(stderr, "xpm with more than 1024 colors\n");
662 return;
663 }
664 if(chars_per_pixel != 1 && chars_per_pixel != 2) {
665 fprintf(stderr, "xpm with more than 2 chars per pixel\n");
666 return;
667 }
668 for(color = 0; color < nb_colors; color++) {
669 int r, g, b;
670 int none ;
671
672 key1 = xpm_data[line][0];
673 key2 = (chars_per_pixel == 2) ? xpm_data[line][1] : 0;
674 colorcode = strstr(xpm_data[line], "c #");
675 none = 0;
676 if(colorcode == nullptr) {
677 colorcode = "c #000000";
678 if(strstr(xpm_data[line], "None") != nullptr) {
679 none = 1;
680 } else {
681 fprintf(
682 stderr, "unknown xpm color entry (replaced with black)\n"
683 );
684 }
685 }
686 colorcode += 3;
687
688 if(strlen(colorcode) == 12) {
689 r = 16 * htoi(colorcode[0]) + htoi(colorcode[1]);
690 g = 16 * htoi(colorcode[4]) + htoi(colorcode[5]);
691 b = 16 * htoi(colorcode[8]) + htoi(colorcode[9]);
692 } else {
693 r = 16 * htoi(colorcode[0]) + htoi(colorcode[1]);
694 g = 16 * htoi(colorcode[2]) + htoi(colorcode[3]);
695 b = 16 * htoi(colorcode[4]) + htoi(colorcode[5]);
696 }
697
698 i2r[color] = (unsigned char) r;
699 i2g[color] = (unsigned char) g;
700 i2b[color] = (unsigned char) b;
701 if(none) {
702 i2a[color] = 0;
703 } else {
704 i2a[color] = 255;
705 }
706 char_to_index[key1][key2] = color;
707 line++;
708 }
709 rgba = (unsigned char*) malloc((size_t) (width * height * 4));
710 pixel = rgba;
711 for(y = 0; y < height; y++) {
712 for(x = 0; x < width; x++) {
713 if(chars_per_pixel == 2) {
714 key1 = xpm_data[line][2 * x];
715 key2 = xpm_data[line][2 * x + 1];
716 } else {
717 key1 = xpm_data[line][x];
718 key2 = 0;
719 }
720 color = char_to_index[key1][key2];
721 pixel[0] = i2r[color];
722 pixel[1] = i2g[color];
723 pixel[2] = i2b[color];
724 pixel[3] = i2a[color];
725 pixel += 4;
726 }
727 line++;
728 }
729
730 glTexImage2D(
731 GL_TEXTURE_2D, 0,
732 GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba
733 );
734 #ifndef __EMSCRIPTEN__
735 glGenerateMipmap(GL_TEXTURE_2D);
736 #endif
737 free(rgba);
738 }
739
740
741 }
742