-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathslim_gl.h
More file actions
1836 lines (1584 loc) · 65.2 KB
/
slim_gl.h
File metadata and controls
1836 lines (1584 loc) · 65.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
SlimGL v1.0.0 - Compact OpenGL shorthand functions for common cases
Do this:
#define SLIM_GL_IMPLEMENTATION
before you include this file in *one* C file to create the implementation.
// i.e. it should look like this:
#include ...
#include ...
#include ...
#define SLIM_GL_IMPLEMENTATION
#include "slim_gl.h"
Core ideas:
- Single header file library (like stb_image.h).
- A simple API as shortcuts for the most common cases.
- Can be combined with direct OpenGL calls for more complex code (SlimGL functions return native OpenGL object IDs).
- Emphasis on ease of use, not so much on performance.
- Use a printf()/scanf() style API for uniform and attribute setup to make drawcalls more compact and easier to read.
Right now the API covers the following:
- OpenGL programs (vertex and fragment shaders): sgl_program_from_files(), sgl_program_from_strings(), sgl_program_destroy()
sgl_program_inspect() and the SGL_GLSL() macro.
- Drawcalls that draw the complete vertex buffer, uniform and vertex attribute setup: sgl_draw().
- Textures: sgl_texture_new(), sgl_texture_destroy(), sgl_texture_update(), sgl_texture_update_sub() and sgl_texture_dimensions().
- Framebuffers with just one color attachment: sgl_framebuffer_new(), sgl_framebuffer_destroy() and sgl_framebuffer_bind().
- Some useful utilities: sgl_error(), sgl_fload() and sgl_strappendf().
Example code to render a white triangle on black background:
// Compile vertex and fragment shaders into an OpenGL program
GLuint program = sgl_program_from_strings(SGL_GLSL("#version 140",
in vec2 pos;
void main() {
gl_Position = vec4(pos, 0, 1);
}
), SGL_GLSL("#version 140",
void main() {
gl_FragColor = vec4(1);
}
), NULL);
// Create a vertex buffer with one triangle in it
struct { float x, y; } vertices[] = {
{ 0, 0.5 }, // top
{ 0.5, -0.5 }, // right
{ -0.5, -0.5 } // left
};
GLuint buffer = sgl_buffer_new(vertices, sizeof(vertices));
// Draw background and triangle
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
sgl_draw(GL_TRIANGLES, program, "pos %2f", buffer);
For more details please take a look at the documentation of the individual functions.
Revision history:
v1.0.0 (2015-09-22) First released version
License: MIT License
*/
#ifndef SLIM_GL_HEADER
#define SLIM_GL_HEADER
#include <stdint.h>
//
// OpenGL program functions
//
GLuint sgl_program_new(const char* args, ...);
/**
* Compiles a vertex and fragment shader from two files, links them into an OpenGL program reports compiler errors on failure.
*
* // Without error reporting
* GLuint object_prog = sgl_program_from_files("object.vs", "object.fs", NULL);
*
* // With error reporting
* char* compiler_errors = NULL;
* GLuint object_prog = sgl_program_from_files("object.vs", "object.fs", &compiler_errors);
* if (!object_prog) {
* printf("Failed to compile object shaders:\n%s", compiler_errors);
* free(compiler_errors);
* compiler_errors = NULL;
* }
*
* On success the OpenGL program ID is returned. On failure `0` is returned and compiler errors are reported.
* If `compiler_errors` is `NULL` the errors are printed to `stderr`. Otherwise it's target is set to a string
* containing the compiler messages. That string has to be `free()`ed by the caller. On error incomplete
* shader and program objects are deleted.
*
* Changed OpenGL state: None.
*/
GLuint sgl_program_from_files(const char* vertex_shader_file, const char* fragment_shader_file, char** compiler_errors);
/**
* Same as `sgl_program_from_files()` but loads the shader code from strings instead of files.
*/
GLuint sgl_program_from_strings(const char* vertex_shader_code, const char* fragment_shader_code, char** compiler_errors);
/**
* Destroys the OpenGL program and all shaders attached to it.
*
* Changed OpenGL state: None.
*/
void sgl_program_destroy(GLuint program);
/**
* Prints all attributes and uniforms of the OpenGL program on stderr.
*
* Changed OpenGL state: None.
*/
void sgl_program_inspect(GLuint program);
/**
* A small macro that can be used to write strings containing GLSL code like normal C. Instead of
*
* "#version 130\n"
* "in vec2 pos;\n"
* "void main() {\n"
* " gl_Position = vec4(pos, 0, 1);\n"
* "}\n"
*
* you can write this
*
* SGL_GLSL("#version 130",
* in vec2 pos;
* void main() {
* gl_Position = vec4(pos, 0, 1);
* }
* )
*
* All the macro does is to convert the GLSL code to a string and add it to the preprocessor directives
* ("#version 130" in the example above). This way you don't have to take care of the quotes yourself
* and syntax-highlighting works for the GLSL code. For the macro to work properly all commas in the GLSL
* code have to be enclosed in brackets. Otherwise the different parts of the code are interpreted as
* multiple arguments to the macro:
*
* SGL_GLSL("#version 130",
* in vec2 pos, focus; // <-- breaks, use two declarations instead
* void main() {
* vec a, b, c; // <-- breaks, again, use multiple declarations
* gl_Position = vec4(pos, 0, 1); // <-- ok since commas are surrounded by brackets
* }
* )
*
* GLSL preprocessor directives have to be inside the first string argument or the C preprocessor would
* already interpret them. Note that without a #version directive GLSL version 1.10 is assumed (OpenGL 2.0).
* So you should always define the version you want (e.g. "#version 130" for GLSL 1.30 which is part of
* OpenGL 3.0).
*
* Inspired by the examples of the Chipmunk2D physics library.
*/
#define SGL_GLSL(preproc_directives, code) preproc_directives "\n" #code
//
// Buffer functions
//
/**
* Creates a new vertex buffer with the specified size and initial data uploaded. The initial data
* is uploaded with the `GL_STATIC_DRAW` usage, meant to be used for model data that does not change.
*
* If `data` is `NULL` but a size is given the buffer will be allocated but no data is uploaded.
* If `size` is `0` only the OpenGL object is created but nothing is allocated.
*
* Returns the vertex buffer on success or `0` on error.
*
* Changed OpenGL state: If data is uploaded GL_ARRAY_BUFFER binding is reset to 0.
*/
GLuint sgl_buffer_new(const void* data, size_t size);
/**
* Destroys the buffer object.
*
* Changed OpenGL state: None.
*/
void sgl_buffer_destroy(GLuint buffer);
/**
* Updates the vertex buffer with new data. The `usage` parameter is the same as of the
* `glBufferData()` function:
* GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY,
* GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY,
* GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, GL_DYNAMIC_COPY.
*
* Changed OpenGL state: GL_ARRAY_BUFFER binding is reset to 0.
*/
void sgl_buffer_update(GLuint buffer, const void* data, size_t size, GLenum usage);
//
// Textures
//
#define SGL_RECT (1 << 0)
#define SGL_SKIP_MIPMAPS (1 << 1)
/**
* Creates and optionally uploads a 2D or rectangle texture with the specified number of components (1, 2, 3 or 4).
* If `data` is `NULL` the texture will be allocated but no data is uploaded. If `stride_in_pixels` is `0` the `width`
* is used as stride.
*
* By default a GL_TEXTURE_2D with mipmaps is created and the minifing filter is set to GL_LINEAR_MIPMAP_LINEAR (better
* quality). If data is uploaded the mipmaps are generated as well. You can use SGL_SKIP_MIPMAPS to skip mipmap
* generation (but the levels are still allocated, you have to fill them later on).
*
* To make the API easier this function only supports textures with 8 bits per pixel (GL_R8, GL_RG8, GL_RGB8 and GL_RGBA8).
*
* Flags:
*
* - SGL_RECT: Create a GL_TEXTURE_RECTANGLE instead of a GL_TEXTURE_2D. Rectangle textures don't have mipmaps.
* - SGL_SKIP_MIPMAPS: Skip `glGenerateMipmap()` call after uploading data for a GL_TEXTURE_2D. For example if you want
* to upload many small images into the texture and call `glGenerateMipmap()` after that.
*
* Returns the texture object on success or `0` on error.
*
* Changed OpenGL state: None.
*/
GLuint sgl_texture_new(uint32_t width, uint32_t height, uint8_t components, const void* data, size_t stride_in_pixels, uint32_t flags);
/**
* Destroys the texture object.
*
* Changed OpenGL state: None.
*/
void sgl_texture_destroy(GLuint texture);
/**
* Uploads new data for the specified texture. The data is expected to be as large as the entire texture and to have the
* same number of components as the texture. If `stride_in_pixels` is `0` the textures width is used as stride.
*
* Flags:
*
* - SGL_RECT: Texture is a GL_TEXTURE_RECTANGLE.
* - SGL_SKIP_MIPMAPS: Skip calling `glGenerateMipmap()` after upload.
*
* Changed OpenGL state: None.
*/
void sgl_texture_update(GLuint texture, const void* data, size_t stride_in_pixels, uint32_t flags);
/**
* Uploads new data for a part of a texture. The data is expected to have the same number of components. If `w` or `h`
* is `0` they are set to the remaining width or height of the texture. If `stride_in_pixels` is `0` it's assumed to be
* the same as `w` (or the remaining width if `w` is `0`).
*
* Flags:
*
* - SGL_RECT: Texture is a GL_TEXTURE_RECTANGLE.
* - SGL_SKIP_MIPMAPS: Skip calling `glGenerateMipmap()` after upload.
*
* Changed OpenGL state: None.
*/
void sgl_texture_update_sub(GLuint texture, uint32_t x, uint32_t y, uint32_t w, uint32_t h, const void* data, size_t stride_in_pixels, uint32_t flags);
/**
* Returns the texutre's dimensions in `width` and `height`. A `width` or `height` of `NULL` is ignored and safe to use.
*
* Flags:
*
* - SGL_RECT: Texture is a GL_TEXTURE_RECTANGLE.
*
* Changed OpenGL state: None.
* Temporarily changed and restored OpenGL state: GL_TEXTURE_BINDING_2D or GL_TEXTURE_BINDING_RECTANGLE (if the SGL_RECT flag is set).
*
* Changed OpenGL state: None.
*/
void sgl_texture_dimensions(GLuint texture, uint32_t* width, uint32_t* height, uint32_t flags);
//
// Frame buffer functions
//
/**
* Creates a framebuffer with `color_buffer_texture` as its color butter attachtment.
*
* Flags:
*
* - SGL_RECT: `color_buffer_texture` is a GL_TEXTURE_RECTANGLE instead of a GL_TEXTURE_2D.
*
* Returns the framebuffer object on success or `0` on error.
*
* Changed OpenGL state: None.
*/
GLuint sgl_framebuffer_new(GLuint color_buffer_texture, uint32_t flags);
/**
* Destroys the framebuffer object.
*
* Changed OpenGL state: None.
*/
void sgl_framebuffer_destroy(GLuint framebuffer);
/**
* Binds the specified framebuffer and sets the viewport dimensions to `width` and `height`.
*
* Changed OpenGL state: GL_DRAW_FRAMEBUFFER_BINDING.
*/
void sgl_framebuffer_bind(GLuint framebuffer, GLsizei width, GLsizei height);
//
// Draw functions
//
/**
* Draws `primitive`s with a specified OpenGL `program` using glDrawElements(). If an index buffer is
* specified glDrawArrays() is used (see ... below). Uniforms and vertex data is setup according to
* directives in the `bindings` string (similar to `printf()` directives).
*
* The `bindings` string is a compact notation for the glGetUniformLocation, glUniform, glGetAttribLocation,
* glBindBuffer, glVertexAttribPointer, glBindTexture, etc. calls. For each directive `sgl_draw` calls
* these functions to setup a uniform, an attribute or other state for the draw call (like an index buffer).
*
*
* # Specifying vertex data
*
* Vertex data is specified with `printf()` style directives. The function call
*
* sgl_draw(GL_TRIANGLES, program, "pos %3f color %3f", vertex_buffer);
*
* tells OpenGL that the "pos" and "color" attributes are three component float vectors and the data should be
* fetched from the buffer `vertex_buffer`. You only have to specify an OpenGL buffer object for the first
* attribute directive, all other attributes fetch their data from the same buffer.
*
* Each directive consists of a name and a format specifier, e.g. "pos" and "%3f". For each one the function
*
* - queries the location of the attribute with glGetAttribLocation(),
* - enables it with glEnableVertexAttribArray(),
* - and tells OpenGL the format of the attribute with glVertexAttribPointer().
*
* The stride and size for each attribute are calculated automatically under the assumption that the attributes
* are tightly packed in the vertex buffer. You can use the special attribute name "_" to specify padding. For
* example "_ %4f" will skip the space of a 4 component float vector in the buffer.
*
*
* ## Vertex format specifier
*
* Each specifier is in the form: "%" <dimensions> <flags> <type>
*
* A format specifier is nothing more than a compact way to write the glVertexAttribPointer() arguments.
* Please take a look at the documentation of glVertexAttribPointer() if you ask yourself what the dimensions,
* flags and type mean.
*
* Possible dimensions: "1", "2", "3", "4"
* The dimension is mandatory. Even if you just have one float you need to write "%1f".
*
* Possible types:
*
* "f" (GL_FLOAT)
* "b" (GL_BYTE)
* "s" (GL_SHORT)
* "i" (GL_INT)
*
* Possible flags for type "f" (GL_FLOAT):
*
* "h" Use GL_HALF_FLOAT instead of GL_FLOAT
* "f" Use GL_FIXED instead of GL_FLOAT
*
* Possible flags for type "b" (GL_BYTE):
*
* "u" Use GL_UNSIGNED_BYTE instead of GL_BYTE
* "n" Values are normalized. The entire value range of the data type is mapped to 0..1 in the shader.
* "i" Upload as int (use glVertexAttribIPointer())
*
* Possible flags for type "s" (GL_SHORT):
*
* "u" Use GL_UNSIGNED_SHORT instead of GL_SHORT
* "n" Values are normalized. The entire value range of the data type is mapped to 0..1 in the shader.
* "i" Upload as int (use glVertexAttribIPointer())
*
* Possible flags for type "i" (GL_INT):
*
* "u" Use GL_UNSIGNED_INT instead of GL_INT
* "n" Values are normalized. The entire value range of the data type is mapped to 0..1 in the shader.
* "i" Upload as int (use glVertexAttribIPointer())
*
* For example the format specifier "%4unb" tells glVertexAttribPointer() to unpack 4 unsigned and
* normalized bytes into a 4 component vector in the shader. The 4 values {128, 255, 64, 255} in the
* vertex buffer will be mapped to vec4(0.5, 1.0, 0.25, 1.0) in the shader. By using this format specifier
* you can store colors in the vertex buffer in a very compact way.
*
*
* # Specifying uniforms
*
* Uniforms are specified similarly to attributes but they use upper case types and different flags. Each
* uniform also consumes one argument of the sgl_draw() function.
*
* float projection[16] = { ... };
* struct{ float x, y, z; } light = { ... };
* sgl_draw(GL_TRIANGLES, program, "projection %4tM light_pos %3F pos %3f color %3f", projection, &light, vertex_buffer);
*
* This call sets the uniform "projection" to the values of the "projection" array. The uniform is a 4x4
* matrix (type "M" with 4 dimensions). A float[16] on the CPU and a mat4 in the shader. The matrix is
* transposed when uploaded ("t" flag). The second uniform "light_pos" is set to a 3 component float vector
* (type "F" with 3 dimensions).
*
* Each uniform directive consumes one argument of the `sgl_draw()` function. The arguments are consumed
* in the same order as the directives are listed in the `bindings` string. The arguments for uniforms have
* to be _pointers_ to the data for the uniform (sgl_draw() always uses the glUniform*v() functions). In the
* above example the variable "projection" is already a pointer to the first byte of the float[16] array.
* But the variable "light" is a struct so you need to get the pointer to it's location in memory. This is
* the same even for a single float:
*
* float threshold = ...;
* sgl_draw(GL_TRIANGLES, program, "threshold %1F pos %3f color %3f", &threshold, vertex_buffer);
*
* You can freely mix uniform and attribute directives in any order. But keep in mind that only the first
* vertex attribute directive (lower case type) consumes an argument.
*
*
* ## Uniform format specifier
*
* Each specifier is in the form: "%" <dimensions> <flags> <type>
*
* A format specifier is nothing more than a compact way to choose one of the glUniform*v() functions and set
* some of its flags. Please take a look at the documentation of glUniform*v() if you ask yourself what the
* dimensions, flags and type mean.
*
* Possible dimensions: "1", "2", "3", "4"
* The dimension is mandatory. Even if you just have one float you need to write "%1F".
* For matrix uniforms you can also use: "2x3", "2x4", "3x2", "3x4", "4x2", "4x3"
*
* Possible types:
*
* "F" (float, glUniform*fv)
* "I" (integer, glUniform*iv)
* "U" (unsigned integer, glUniform*uiv)
* "M" (matrix, glUniformMatrix*fv)
*
* Possible flags for type "F" (float): None.
* Possible flags for type "I" (integer): None.
* Possible flags for type "U" (unsigned integer): None.
* Possible flags for type "M" (matrix):
*
* "t" Transpose matrix.
*
*
* # Specifying textures
*
* Textures are specified as uniforms with the type "T". It binds a GL_TEXTURE_2D texture to a sampler uniform.
* The matching argument has to be the object ID of the texture (GLuint), not a pointer. The function automatically
* calls glActiveTexture(), glBindTexture() and glUniform1i().
*
* Possible flags for type "T" (texture):
*
* "r" Texture is a GL_TEXTURE_RECTANGLE texture instead of a GL_TEXTURE_2D texture
* "*" The uniform is a texture array. The argument list must contain a size_t (the array length) and a
* GLuint* (pointer to an array of OpenGL texture names) instead of a single GLuint.
*
*
* # Global options
*
* Global options start with "$" instead of "%". Right now there is just one:
*
* $I Draw with an index buffer of GL_UNSIGNED_INT indices. One argument of type GLuint is consumed.
*
* Possible flags:
*
* "b" Indices are of type GL_UNSIGNED_BYTE
* "s" Indices are of type GL_UNSIGNED_SHORT
*
*
* # Using data from multiple vertex buffers
*
* The first attribute directive consumes one argument that has to be an OpenGL buffer object.
* This buffer is used for all following attributes or until an ";" is encountered. The next attribute
* after ";" then consumes a new buffer argument. Stride and offsets of attributes are calculated
* automatically.
*
* Example:
*
* sgl_draw(GL_TRIANGLES, program, "pos %3f normal %3f ; color %4unb", geometry_vertex_buffer, color_vertex_buffer);
*
* This draw call takes the data for the "pos" and "normal" attributes from the "geometry_vertex_buffer"
* and the "color" attribute is read from the "color_vertex_buffer". Each buffer contains its data tighly
* packed.
*
*
* # TODO
*
* - Change unsigned integer uniforms from %1U to %1uI (as "u" flag for "I" type instead of an extra type).
* Should be more consistent with attribute format specifier (where it's "%1ui").
* - Check if cleanup of texture binding of really necessary. If so make sure we unbind the proper target.
* Basically we have to iterate over the directives again to get the target of each directive.
*
*
* # Reference
*
* Form of directives: <attribute or uniform name> "%" <dimensions> <flags> <type>
* e.g. "projection %4tM"
*
* Uniforms (and textures): uppercase types
*
* F (float)
* %1F glUniform1fv
* %2F glUniform2fv
* %3F glUniform3fv
* %4F glUniform4fv
* I (integer)
* %1I glUniform1iv
* %2I glUniform2iv
* %3I glUniform3iv
* %4I glUniform4iv
* U (unsigned integer)
* %1U glUniform1uiv
* %2U glUniform2uiv
* %3U glUniform3uiv
* %4U glUniform4uiv
* M (matrix)
* %2M glUniformMatrix2fv
* %2x3M glUniformMatrix2x3fv
* %2x4M glUniformMatrix2x4fv
* %3M glUniformMatrix3fv
* %3x2M glUniformMatrix3x2fv
* %3x4M glUniformMatrix3x4fv
* %4M glUniformMatrix4fv
* %4x2M glUniformMatrix4x2fv
* %4x3M glUniformMatrix4x3fv
*
* Flags:
* t transpose matrix
*
* T (textures)
* %T bind a GL_TEXTURE_2D texture to a sampler uniform
*
* Flags:
* r texture is a GL_TEXTURE_RECTANGLE texture
* * the uniform is a texture array. The argument list must contain a size_t (the array length) and a
* GLuint* (pointer to an array of OpenGL texture names) instead of a single GLuint.
*
*
* Attributes: lower case types
*
* The first attribute consumes one argument that has to be an OpenGL buffer object.
* This buffer is used for all following attributes or until an ";" is encountered.
* The next attribute after ";" consumes a new buffer argument.
* Stride and offsets of attributes are calculated automatically.
* The attribute name "_" is used for padding.
*
* Dimensions: 1, 2, 3, 4
*
* f GL_FLOAT
* h GL_HALF_FLOAT
* f GL_FIXED
*
* b GL_BYTE
* u GL_UNSIGNED_BYTE
* n normalized
* i upload as int (use glVertexAttribIPointer())
*
* s GL_SHORT
* u GL_UNSIGNED_SHORT
* n normalized
* i upload as int (use glVertexAttribIPointer())
*
* i GL_INT
* u GL_UNSIGNED_INT
* n normalized
* i upload as int (use glVertexAttribIPointer())
*
* Global options: start with "$" instead of "%"
*
* $I draw with an index buffer of GL_UNSIGNED_INT indices
* b indices are of type GL_UNSIGNED_BYTE
* s indices are of type GL_UNSIGNED_SHORT
*
*/
int sgl_draw(GLenum primitive, GLuint program, const char* bindings, ...);
//
// Utilities
//
/**
* Returns the last OpenGL error and outputs an error message to stderr (a bit like `perror()`). If no
* error happened `0` is returned and nothing is printed. To make the code more readable `sgl_error()`
* is meant to be used in the `if` that checks for errors:
*
* location = glGetUniformLocation(program, name);
* if ( sgl_error("Failed to lookup uniform %s. glGetUniformLocation()", name) ) {
* // react to error
* }
*
* If there is a last OpenGL error this function prints `description` followed by ": " and the last
* pending OpenGL error to stderr. It behaves like `printf()` so you can use stuff like %s in `description`
* to print additional error information.
*/
GLenum sgl_error(const char* description, ...);
/**
* Returns a pointer to the zero terminated `malloc()`ed contents of the file. If size is
* not `NULL` it's target is set to the size of the file not including the zero terminator
* at the end of the memory block.
*
* On error `NULL` is returned and `errno` is set accordingly.
*/
void* sgl_fload(const char* filename, size_t* size);
/**
* Appends a printf style string to an already existing string pointed to by `dest`. The
* existing string is realloced to be large enough. If `dest` is `NULL` a new string is
* allocated. Returns the result string or `NULL` on error.
*
* char* a = NULL;
* sgl_strappendf(&a, "Hello %s!\n", "World");
* sgl_strappendf(&a, "x: %f y: %f\n", 7.0, 13.7);
*
* char* b = sgl_strappendf(NULL, "%d %s\n", 42, "is the answer");
*
*/
char* sgl_strappendf(char** dest, const char* format, ...);
#endif // SLIM_GL_HEADER
#ifdef SLIM_GL_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <stdbool.h>
#include <ctype.h>
//
// OpenGL program functions
//
static const char* sgl__type_to_string(GLenum type);
static GLuint sgl__create_and_compile_program(const char* vertex_shader_code, const char* fragment_shader_code, const char* vertex_shader_name, const char* fragment_shader_name, char** compiler_errors);
static GLuint sgl__create_and_compile_shader(GLenum shader_type, const char* code, const char* filename_for_errors, char** compiler_errors);
typedef struct {
const char* error_at;
const char* error_message;
// We need a zero terminated string for glGetAttribLocation() so use a buffer instead of pointer + length into the
// argument string.
char name[128];
char modifiers[16];
char type;
} sgl_arg_t, *sgl_arg_p;
/**
* Returns true if a character is a whitespace as defined by the GLSL spec (3.1 Character Set).
* That are spaces and the ASCII range containing horizontal tab, new line, vertical tab, form feed and carriage return.
* If you look at the ASCII table (man ascii) you'll see that all whitespaces except space are consecutive ASCII codes.
* So we cover all these characters with one range check.
*/
static inline int sgl__is_whitespace(char c) {
return c == ' ' || (c >= '\t' && c <= '\r');
}
/**
* Returns true if a character is valid for an GLSL identifier (spec chapter 3.7 Identifiers) or a minus sign (-).
* Names starting with a minus are internal options (e.g. -index or -padding) instead of attribute or uniform names.
*/
static inline int sgl__is_name(char c) {
return (c >= 'a' && c <= 'z') || c == '_' || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-';
}
#define SGL__NAMED_ARGS (1 << 0)
#define SGL__BUFFER_DIRECTIVES (1 << 1)
static const char* sgl__next_argument(const char* string, int flags, sgl_arg_p arg) {
const char* c = string;
#define EXIT_WITH_ERROR_IF(condition, message) if(condition){ arg->error_at = c; arg->error_message = (message); return NULL; }
// A return value of NULL signals the end of arguments or an error. Return NULL when we're called with a NULL string
// so we can be called repeatedly at the end. It also makes sure we can dereference the string after this.
if (c == NULL)
return NULL;
// Skip whitespaces
while( sgl__is_whitespace(*c) )
c++;
// Return NULL when we're at the zero terminator to signal the end of arguments
if (*c == '\0')
return NULL;
if (flags & SGL__BUFFER_DIRECTIVES) {
if (*c == ';') {
// Got a buffer end directive
arg->type = *c++;
return c;
}
}
if (flags & SGL__NAMED_ARGS) {
// Read the name into the name buffer and add the zero-terminator
size_t name_len = 0;
while ( sgl__is_name(*c) ) {
// We can only fill the name buffer up until one byte it left. We need that byte for the zero terminator.
EXIT_WITH_ERROR_IF(name_len >= sizeof(arg->name) - 1, "Name is to long");
arg->name[name_len++] = *c++;
}
arg->name[name_len] = '\0';
// If the name isn't followed by a whitespace char we either got an invalid char in a name or the zero terminator.
// Either way we can't continue.
EXIT_WITH_ERROR_IF(!sgl__is_whitespace(*c), "Got invalid character in name");
// Skip whitespaces after name
while( sgl__is_whitespace(*c) )
c++;
}
EXIT_WITH_ERROR_IF(*c != '%', "Expected at '%' at the start of a directive");
c++;
// Read all following chars as modifiers (including the last one for now)
size_t mod_len = 0;
while ( !(sgl__is_whitespace(*c) || *c == '\0') ) {
EXIT_WITH_ERROR_IF(mod_len >= sizeof(arg->modifiers), "To many modifiers for directive");
arg->modifiers[mod_len++] = *c++;
}
EXIT_WITH_ERROR_IF(mod_len < 1, "At least one character for the type is necessary after a '%'");
// The last char in the modifiers buffer is our type so put it into the type field. Then overwrite the last char
// with the zero terminator.
arg->type = arg->modifiers[mod_len-1];
arg->modifiers[mod_len-1] = '\0';
return c;
#undef EXIT_WITH_ERROR_IF
}
GLuint sgl_program_from_files(const char* vertex_shader_file, const char* fragment_shader_file, char** compiler_errors) {
char* vertex_shader_code = sgl_fload(vertex_shader_file, NULL);
if (vertex_shader_code == NULL) {
if (compiler_errors)
*compiler_errors = sgl_strappendf(NULL, "Can't read vertex shader file %s: %s\n", vertex_shader_file, strerror(errno));
return 0;
}
char* fragment_shader_code = sgl_fload(fragment_shader_file, NULL);
if (fragment_shader_code == NULL) {
free(vertex_shader_code);
if (compiler_errors)
*compiler_errors = sgl_strappendf(NULL, "Can't read fragment shader file %s: %s\n", fragment_shader_file, strerror(errno));
return 0;
}
GLuint program = sgl__create_and_compile_program(vertex_shader_code, fragment_shader_code, vertex_shader_file, fragment_shader_file, compiler_errors);
free(vertex_shader_code);
free(fragment_shader_code);
return program;
}
GLuint sgl_program_from_strings(const char* vertex_shader_code, const char* fragment_shader_code, char** compiler_errors) {
return sgl__create_and_compile_program(vertex_shader_code, fragment_shader_code, "vertex shader", "fragment shader", compiler_errors);
}
void sgl_program_destroy(GLuint program) {
GLint shader_count = 0;
glGetProgramiv(program, GL_ATTACHED_SHADERS, &shader_count);
GLuint shaders[shader_count];
glGetAttachedShaders(program, shader_count, NULL, shaders);
glDeleteProgram(program);
for(ssize_t i = 0; i < shader_count; i++)
glDeleteShader(shaders[i]);
}
void sgl_program_inspect(GLuint program) {
GLint size;
GLenum type;
{
GLint attrib_count = 0, buffer_size = 0;
glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &attrib_count);
fprintf(stderr, "%d attributes:\n", attrib_count);
glGetProgramiv(program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &buffer_size);
char buffer[buffer_size];
for(ssize_t i = 0; i < attrib_count; i++){
glGetActiveAttrib(program, i, buffer_size, NULL, &size, &type, buffer);
fprintf(stderr, "- %s %s", buffer, sgl__type_to_string(type));
if (size > 1)
fprintf(stderr, "[%d]", size);
fprintf(stderr, "\n");
}
}
{
GLint uniform_count = 0, buffer_size = 0;
glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniform_count);
fprintf(stderr, "%d uniforms:\n", uniform_count);
glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &buffer_size);
char buffer[buffer_size];
for(ssize_t i = 0; i < uniform_count; i++){
glGetActiveUniform(program, i, buffer_size, NULL, &size, &type, buffer);
fprintf(stderr, "- %s %s", buffer, sgl__type_to_string(type));
if (size > 1)
fprintf(stderr, "[%d]", size);
fprintf(stderr, "\n");
}
}
}
/**
* Helper function to return the printable name for an OpenGL data type.
*/
static const char* sgl__type_to_string(GLenum type) {
switch(type){
case GL_FLOAT: return "float";
case GL_FLOAT_VEC2: return "vec2";
case GL_FLOAT_VEC3: return "vec3";
case GL_FLOAT_VEC4: return "vec4";
case GL_INT: return "int";
case GL_INT_VEC2: return "ivec2";
case GL_INT_VEC3: return "ivec3";
case GL_INT_VEC4: return "ivec4";
case GL_UNSIGNED_INT: return "unsigned int";
case GL_UNSIGNED_INT_VEC2: return "uvec2";
case GL_UNSIGNED_INT_VEC3: return "uvec3";
case GL_UNSIGNED_INT_VEC4: return "uvec4";
case GL_BOOL: return "bool";
case GL_BOOL_VEC2: return "bvec2";
case GL_BOOL_VEC3: return "bvec3";
case GL_BOOL_VEC4: return "bvec4";
case GL_FLOAT_MAT2: return "mat2";
case GL_FLOAT_MAT3: return "mat3";
case GL_FLOAT_MAT4: return "mat4";
case GL_FLOAT_MAT2x3: return "mat2x3";
case GL_FLOAT_MAT2x4: return "mat2x4";
case GL_FLOAT_MAT3x2: return "mat3x2";
case GL_FLOAT_MAT3x4: return "mat3x4";
case GL_FLOAT_MAT4x2: return "mat4x2";
case GL_FLOAT_MAT4x3: return "mat4x3";
case GL_SAMPLER_1D: return "sampler1D";
case GL_SAMPLER_2D: return "sampler2D";
case GL_SAMPLER_3D: return "sampler3D";
case GL_SAMPLER_CUBE: return "samplerCube";
case GL_SAMPLER_1D_SHADOW: return "sampler1DShadow";
case GL_SAMPLER_2D_SHADOW: return "sampler2DShadow";
case GL_SAMPLER_1D_ARRAY: return "sampler1DArray";
case GL_SAMPLER_2D_ARRAY: return "sampler2DArray";
case GL_SAMPLER_1D_ARRAY_SHADOW: return "sampler1DArrayShadow";
case GL_SAMPLER_2D_ARRAY_SHADOW: return "sampler2DArrayShadow";
case GL_SAMPLER_CUBE_SHADOW: return "samplerCubeShadow";
case GL_SAMPLER_BUFFER: return "samplerBuffer";
case GL_SAMPLER_2D_RECT: return "sampler2DRect";
case GL_SAMPLER_2D_RECT_SHADOW: return "sampler2DRectShadow";
case GL_INT_SAMPLER_1D: return "isampler1D";
case GL_INT_SAMPLER_2D: return "isampler2D";
case GL_INT_SAMPLER_3D: return "isampler3D";
case GL_INT_SAMPLER_CUBE: return "isamplerCube";
case GL_INT_SAMPLER_1D_ARRAY: return "isampler1DArray";
case GL_INT_SAMPLER_2D_ARRAY: return "isampler2DArray";
case GL_INT_SAMPLER_BUFFER: return "isamplerBuffer";
case GL_INT_SAMPLER_2D_RECT: return "isampler2DRect";
case GL_UNSIGNED_INT_SAMPLER_1D: return "usampler1D";
case GL_UNSIGNED_INT_SAMPLER_2D: return "usampler2D";
case GL_UNSIGNED_INT_SAMPLER_3D: return "usampler3D";
case GL_UNSIGNED_INT_SAMPLER_CUBE: return "usamplerCube";
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return "usampler2DArray";
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return "usampler2DArray";
case GL_UNSIGNED_INT_SAMPLER_BUFFER: return "usamplerBuffer";
case GL_UNSIGNED_INT_SAMPLER_2D_RECT: return "usampler2DRect";
# ifdef GL_VERSION_3_2
case GL_SAMPLER_2D_MULTISAMPLE: return "sampler2DMS";
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return "sampler2DMSArray";
case GL_INT_SAMPLER_2D_MULTISAMPLE: return "isampler2DMS";
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return "isampler2DMSArray";
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return "usampler2DMS";
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return "usampler2DMSArray";
# endif
default: return "unknown";
}
}
/**
* Helper function to compile and link a complete program.
*
* Returns the program object ID or `0` on error. Compiler errors are returned in `compiler_errors` if it's not `NULL`.
*/
static GLuint sgl__create_and_compile_program(const char* vertex_shader_code, const char* fragment_shader_code, const char* vertex_shader_name, const char* fragment_shader_name, char** compiler_errors) {
char* errors = NULL;
GLuint vertex_shader = sgl__create_and_compile_shader(GL_VERTEX_SHADER, vertex_shader_code, vertex_shader_name, &errors);
GLuint fragment_shader = sgl__create_and_compile_shader(GL_FRAGMENT_SHADER, fragment_shader_code, fragment_shader_name, &errors);
if (vertex_shader == 0 || fragment_shader == 0)
goto shaders_failed;
GLuint prog = glCreateProgram();
glAttachShader(prog, vertex_shader);
glAttachShader(prog, fragment_shader);
glLinkProgram(prog);
GLint result = GL_TRUE;
glGetProgramiv(prog, GL_LINK_STATUS, &result);
if (result == GL_FALSE){
result = 0;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &result);
char buffer[result];
glGetProgramInfoLog(prog, result, NULL, buffer);
sgl_strappendf(&errors, "Can't link vertex and pixel shader:\n%s\n", buffer);
goto program_failed;
}
return prog;
program_failed:
if (prog)
glDeleteProgram(prog);
shaders_failed:
if (vertex_shader)
glDeleteShader(vertex_shader);
if (fragment_shader)
glDeleteShader(fragment_shader);
if (*errors) {
if (compiler_errors) {
*compiler_errors = errors;
} else {
fprintf(stderr, "%s", errors);
free(errors);
}
}
return 0;
}
/**
* Helper function to load and compile source code as a shader.
*
* Returns the shaders object ID on success or `0` on error. Compiler errors in the shader are appended to the
* `compiler_errors` string (if it's not `NULL`).
*/
static GLuint sgl__create_and_compile_shader(GLenum shader_type, const char* code, const char* filename_for_errors, char** compiler_errors) {
GLuint shader = glCreateShader(shader_type);
glShaderSource(shader, 1, (const char*[]){ code }, (const int[]){ -1 });
glCompileShader(shader);
GLint result = GL_TRUE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
if (result)
return shader;
if (compiler_errors) {
result = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &result);
char buffer[result];
glGetShaderInfoLog(shader, result, NULL, buffer);
sgl_strappendf(compiler_errors, "Can't compile %s:\n%s\n", filename_for_errors, buffer);
}
glDeleteShader(shader);
return 0;
}
//
// Buffer functions
//
GLuint sgl_buffer_new(const void* data, size_t size) {
GLuint buffer = 0;
glGenBuffers(1, &buffer);
if (buffer == 0)
return 0;