-
Notifications
You must be signed in to change notification settings - Fork 0
/
gfx.h
1186 lines (1070 loc) · 51.8 KB
/
gfx.h
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
///////////////////////////////////////////////////////////////////////////////////////////////////
/// gfx.h
///
/// Index:
/// - Version History
/// - Instructions
/// - Include
/// - API & Macros
/// - Logging
/// - Overview
/// - Disabling color output
/// - Example of usage
/// - Assert
/// - Overview
/// - Example of usage
/// - GLEnumToString
/// - Overview
/// - Example of usage
/// - GL_CALL
/// - Overview
/// - Disabling termination on error
/// - Example of usage
/// - Introspection (in development)
/// - Overview
/// - Enabling introspection
/// - Implementation
/// - Logging
/// - GL_CALL
/// - Introspection
/// - GLEnumToString (at the end, due to its size)
///
/// Version History
/// - 2019.02.23 (1.15): - Minor grammar fixes in documentation
/// - 2019.02.23 (1.14): - Add asserts to introspection functions
/// - Add support for some non-square matrices
/// - 2019.02.19 (1.13): - Add support for using colorpicker when editing vec3 and vec4
/// (Happens on an all or nothing basis as Dear ImGui don't store checkbox state)
/// - Add support for 2x2 matrix uniforms
/// - Update documentation for turning on and off gfx assert, debug, and introspection
/// - 2019.01.29 (1.12): - Make all UI modifications to uniforms drag based (ctrl+left click to input manually)
/// - Add support for 3x3 and 4x4 matrix uniforms
/// - 2019.01.21 (1.11): - #undef min max, and scope disable warnings of sprintf on windows
/// - 2019.01.21 (1.10): - Add support for printing GLenum value if unknown type
/// - 2019.01.21 (1.09): - Fix bug as result of CollapsingHeader not pushing ID on stack
/// - 2019.01.12 (1.08): - Add Introspection of Vertex Array Objects
/// - 2019.01.12 (1.07): - Use Collapsing Headers in Introspection of Shader Program,
/// - Add function to generate scrollable height
/// - 2019.01.10 (1.06): - Add Introspection of Shader Program
/// - 2019.01.09 (1.05): - Terminate on OpenGL error on default & redo indexing of documentation
/// - 2019.01.09 (1.04): - Add GL_CALL
/// - 2019.01.09 (1.03): - Fix missing () in GFX_ASSERT
/// - 2019.01.09 (1.02): - Rename GlEnumToString to GLEnumToString
/// - 2019.01.08 (1.01): - Add GlEnumToString function
/// - 2019.01.08 (1.00): - Initial "release" GFX_ assert and logs.
///
/// Instructions - Include
/// This library is contained within this single header file to make it more convenient for
/// students to include it into their projects.
/// To avoid introducing unnecessary includes and compile time overhead, this library does not
/// inline everything, but rather requires being implemented in 1 single file.
/// To do this, in 1 (ONE) .cpp file write: #define GFX_IMPLEMENTATION before including gfx.h
/// For example, in main.cpp do
/// #include // some file
/// #include // some other file
/// #define GFX_IMPLEMENTATION
/// #include "gfx.h"
///
/// Failing to do this in any file will lead to errors complaining about
/// undefined references.
/// Defining the macro in multiple files will lead to errors complaining about
/// multiple definitions
///
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
/// API - Logging
///////////////////////////////////////////////////////////////////////////////////////////////////
/// OVERVIEW
///
/// Basic functionality for categorized logging to console.
/// The logging statements will either print to stdout or to stderr depending on
/// the category of the message.
///
/// Logging should be done through the different macros,
/// NOT through the Gfx::Detail::Log function!
///
/// All messages will be following the specified format:
/// <category>: <file>: <function>: <line_number>: message.
///
/// Example:
/// [INFO ]: main.cpp : main : 7: Initialized all OpenGL components
///
/// The logs must be written with printf syntax, which can be found here:
/// http://en.cppreference.com/w/cpp/io/c/fprintf
///
/// note, you must use .c_str() or .data() when sending a std::string to "%s".
///
///////////////////////////////////////////////////////////////////////////////////////////////////
/// DISABLING COLOR OUTPUT
///
/// Color output has been confirmed to work in the Windows terminal, and in the raw terminal
/// on Ubuntu (tested Ubuntu 16.04 & 18.04).
/// However, I did notice issues when logging to terminal in Visual Studio Code,
/// and have therefore added an option for disabling the colors.
/// To do this define GFX_NO_TERMINAL_COLOR before including gfx.h in the file where gfx.h is
/// defined (the one containing #define GFX_IMPLEMENTATION)
/// For example:
/// #include // some file
/// #include // some other file
/// #define GFX_IMPLEMENTATION
/// #define GFX_NO_TERMINAL_COLOR
/// #include "gfx.h"
///
///////////////////////////////////////////////////////////////////////////////////////////////////
/// EXAMPLE OF USAGE
///
/// #include <string>
/// #include "gfx.h"
///
/// int main()
/// {
/// std::string str = "hey";
/// GFX_DEBUG("Logging debug: %s yas", str.c_str());
/// return 0;
/// }
///
/// Will result in something like:
/// [DEBUG]: main.cpp : main : 7: Logging debug: hey yas
///
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <cstdio>
#include <cstdlib>
#include <GL/glew.h> // Probably doesn't work on other platforms, test to make it work.
///////////////////////////////////////////////////////////
/// \brief
/// Prints the specified error to stderr and
/// terminates the program.
///
/// \detailed
/// This error is supposed to be used for
/// unrecoverable errors.
///////////////////////////////////////////////////////////
#define GFX_ERROR(fmt, ...) \
{ \
Gfx::Detail::Log(stderr, "ERROR", Gfx::Detail::TERMINAL_COLOR_FG_RED, __FILE__, __func__, __LINE__, fmt, ##__VA_ARGS__); \
Gfx::Detail::PauseTerminal(); \
std::exit(EXIT_FAILURE); \
}
///////////////////////////////////////////////////////////
/// \brief
/// Prints the specified warning to stderr.
/// Use for non breaking situations.
///////////////////////////////////////////////////////////
#define GFX_WARN(fmt, ...) \
Gfx::Detail::Log(stderr, "WARN", Gfx::Detail::TERMINAL_COLOR_FG_YELLOW, __FILE__, __func__, __LINE__, fmt, ##__VA_ARGS__);
///////////////////////////////////////////////////////////
/// \brief
/// Prints regular info to stdout.
///////////////////////////////////////////////////////////
#define GFX_INFO(fmt, ...) \
Gfx::Detail::Log(stdout, "INFO", Gfx::Detail::TERMINAL_COLOR_FG_WHITE, __FILE__, __func__, __LINE__, fmt, ##__VA_ARGS__);
///////////////////////////////////////////////////////////
/// \brief
/// Prints debug information to stdout.
///
/// \detailed
/// Use this for debug related information,
/// can be turned off by defining GFX_NO_DEBUG
/// on a project wide basis (i.e. in CMake)
///////////////////////////////////////////////////////////
#ifndef GFX_NO_DEBUG
#define GFX_DEBUG(fmt, ...) \
Gfx::Detail::Log(stdout, "DEBUG", Gfx::Detail::TERMINAL_COLOR_FG_CYAN, __FILE__, __func__, __LINE__, fmt, ##__VA_ARGS__);
#else
#define GFX_DEBUG(fmt, ...)
#endif
namespace Gfx
{
namespace Detail
{
extern const char* TERMINAL_COLOR_RESET;
extern const char* TERMINAL_COLOR_FG_BLACK;
extern const char* TERMINAL_COLOR_FG_RED;
extern const char* TERMINAL_COLOR_FG_GREEN;
extern const char* TERMINAL_COLOR_FG_YELLOW;
extern const char* TERMINAL_COLOR_FG_BLUE;
extern const char* TERMINAL_COLOR_FG_MAGENTA;
extern const char* TERMINAL_COLOR_FG_CYAN;
extern const char* TERMINAL_COLOR_FG_GREY;
extern const char* TERMINAL_COLOR_FG_WHITE;
extern const char* TERMINAL_COLOR_BG_BLACK;
extern const char* TERMINAL_COLOR_BG_RED;
extern const char* TERMINAL_COLOR_BG_GREEN;
extern const char* TERMINAL_COLOR_BG_YELLOW;
extern const char* TERMINAL_COLOR_BG_BLUE;
extern const char* TERMINAL_COLOR_BG_MAGENTA;
extern const char* TERMINAL_COLOR_BG_CYAN;
extern const char* TERMINAL_COLOR_BG_GREY;
extern const char* TERMINAL_COLOR_BG_WHITE;
///////////////////////////////////////////////////////////
/// \brief
/// Actual function which is called by log
/// macros.
///
/// \note
/// ATTENTION!
/// Do not use this function!
/// You are supposed to use the logger macros!
///////////////////////////////////////////////////////////
void
Log(std::FILE* file,
const char* type,
const char* color,
const char* filepath,
const char* func,
const int line,
const char* fmt,
...);
///////////////////////////////////////////////////////////
/// Convenience for letting the console "hang"
/// on windows systems.
///////////////////////////////////////////////////////////
void
PauseTerminal();
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// API - Assert
///////////////////////////////////////////////////////////////////////////////////////////////////
/// OVERVIEW
///
/// Asserts are "landmines" you place for yourself to avoid making mistakes.
/// An assert is given an expression that is expected to be true, in the case where it is not
/// the program crashes.
///
/// An assertation failure will lead to a message in the terminal with the following format.
/// <category>: <file>: <function>: <line_number>: Assertation failure: <expr>: message.
///
/// The messages must be written with printf syntax, which can be found here:
/// http://en.cppreference.com/w/cpp/io/c/fprintf
///
/// note, you must use .c_str() or .data() when sending a std::string to "%s".
///
///////////////////////////////////////////////////////////////////////////////////////////////////
/// EXAMPLE OF USAGE
///
/// #include <string>
/// #include "gfx.h"
///
/// void func(char* ptr)
/// {
/// GFX_ASSERT(ptr != nullptr, "ptr must not be nullptr");
/// // Do something with ptr
/// }
///
/// int main()
/// {
/// func(nullptr);
/// return 0;
/// }
///
/// Will result in something like:
/// [ERROR]: main.cpp : func : 6: Assertion failure: ptr != nullptr: ptr must not be nullptr
///
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
/// \brief
/// Assertation macro, send expr that is
/// expected to be true and also a formatted message.
///
/// \detailed
/// Can be turned off by defining GFX_ASSERT_OFF
/// on a project wide basis (i.e. in CMake)
///////////////////////////////////////////////////////////
#ifndef GFX_ASSERT_OFF
#define GFX_ASSERT(expr, fmt, ...) \
if ((expr)) {} \
else { GFX_ERROR("Assertion failure: " #expr ": " fmt, ##__VA_ARGS__); } \
#else
#define GFX_ASSERT(expr, fmt, ...)
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
/// API - GLEnumToString
///////////////////////////////////////////////////////////////////////////////////////////////////
/// OVERVIEW
///
/// OpenGL has a large amount defined hex enum values, which can be hard to read when
/// you don't have access to the name.
/// GLEnumToString is a utility function that will turn those enum values into constant strings.
/// Enums will also include the errors that you get back from OpenGL.
/// Important: This function does not allocate any memory, so DO NOT call delete[]
/// on the strings you get back from this function.
///
/// Note: Since OpenGL contains so many enums, some are obviously missing.
/// I have mainly included those needed for gfx.h to function.
/// If you miss any, please request it in an issue or something :)
///
///////////////////////////////////////////////////////////////////////////////////////////////////
/// EXAMPLE OF USAGE
///
/// #include <string>
/// #include "gfx.h"
///
/// int main()
/// {
/// std::string str = "hey";
/// GFX_DEBUG("Logging debug: %s", Gfx::GLEnumToString(GL_FLOAT_VEC2));
/// return 0;
/// }
///
/// Will result in something like:
/// [DEBUG]: main.cpp : main : 7: Logging debug: GL_FLOAT_VEC2
///
///////////////////////////////////////////////////////////////////////////////////////////////////
namespace Gfx
{
const char* GLEnumToString(GLenum e);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// API - GL_Call
///////////////////////////////////////////////////////////////////////////////////////////////////
/// OVERVIEW
///
/// Error handling is important in OpenGL, but it can be easy to forget.
/// If you make an OpenGL call with the GL_Call macro any errors detected from that call
/// and where the call happened will be reported to you through the Gfx logging system.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
/// DISABLING COLOR OUTPUT
///
/// By default we log and terminate the application if we detect an OpenGL error,
/// however, if you do not want that functionality you can remove it.
/// To do this define GFX_NO_TERMINATE_ON_GL_ERROR before including gfx.h in the file where
/// gfx.h is defined (the one containing #define GFX_IMPLEMENTATION).
/// For example:
/// #include // some file
/// #include // some other file
/// #define GFX_IMPLEMENTATION
/// #define GFX_NO_TERMINATION_ON_GL_ERROR
/// #include "gfx.h"
///
///////////////////////////////////////////////////////////////////////////////////////////////////
/// EXAMPLE OF USAGE
///
/// #include "gfx.h"
///
/// int main()
/// {
/// GLuint invalid_program_id = 15;
/// GFX_GL_CALL(glUseProgram(invalid_program_id))
/// return 0;
/// }
///
/// Will result in something like:
/// [GL_ERROR]: main.cpp : main : 7: (0x0501): GL_INVALID_VALUE
///
///////////////////////////////////////////////////////////////////////////////////////////////////
#define GFX_GL_CALL(glfunc) \
{ \
while (glGetError() != GL_NO_ERROR) {} \
glfunc; \
while (GLenum errc = glGetError()) \
{ \
Gfx::Detail::Log(stderr, "GL_ERROR", Gfx::Detail::TERMINAL_COLOR_FG_RED, __FILE__, __func__, __LINE__, "(0x%04x): %s", errc, Gfx::GLEnumToString(errc)); \
Gfx::Detail::GLCallTerminateImpl(); \
} \
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// API - Introspection (In development)
///////////////////////////////////////////////////////////////////////////////////////////////////
/// OVERVIEW
///
/// The introspection API is supposed to help you get a better view of what is currently
/// going on in the OpenGL world, by being allowed to inspect (and sometimes manipulate)
/// different values.
///
/// To use these utility tools you need to have Dear ImGui (https://github.com/ocornut/imgui)
/// integrated into your projects, and I as far as possible try to follow the same interface
/// style as that used in Dear ImGui.
///
/// These functions will respect the current OpenGL pipeline, and turn back any modifications
/// made.
/// They do not require setting up the pipeline in a specific manner,
/// unless otherwise noted.
///
///////////////////////////////////////////////////////////////////////////////////////////////////
/// ENABLING INTROSPECTION
///
/// Since the introspection functionality requires Dear ImGui as a dependency
/// (and since it is under development) it needs to be enabled before it can be used.
/// To do this GFX_ENABLE_INTROSPECTION must be defined on a project wide bases (i.e. in CMake)
///////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef GFX_ENABLE_INTROSPECTION
namespace Gfx
{
///////////////////////////////////////////////////////
/// \brief
/// Outputs information related to the shader-
/// program <program>.
/// Information is presented using ImGui, in the
/// collapsing header labeled <label>.
///
/// \param label
/// The label used to identify the program in
/// ImGui. Must be unique.
///
/// \param program
/// The program to output information about.
/// The program needs to be a valid and linked
/// OpenGL program.
/// To get source output of the different shaders
/// they must not have been deleted or detached.
///
///////////////////////////////////////////////////////
void IntrospectShader(const char* label, GLuint program);
///////////////////////////////////////////////////////
/// \brief
/// Outputs information related to the vertex-
/// array <vao>.
/// Information is presented using ImGui, in the
/// collapsing header labeled <label>.
///
/// \param label
/// The label used to identify the vao in
/// ImGui. Must be unique.
///
/// \param vao
/// The vertex array to output information about.
/// The vao needs to be a valid vertex array
/// object.
/// The element buffer object belonging to the vao
/// must be of type GL_UNSIGNED_INT.
///
///////////////////////////////////////////////////////
void IntrospectVertexArray(const char* label, GLuint vao);
}
#endif // GFX_ENABLE_INTROSPECTION
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Implementation
///////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef GFX_IMPLEMENTATION
#include <cstdarg>
#include <cstring>
#include <vector>
#ifdef _WIN32
#include <Windows.h>
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#pragma warning (push)
#pragma warning (disable: 4996)
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Implementation - Logging
///////////////////////////////////////////////////////////////////////////////////////////////////
namespace Gfx
{
namespace Detail
{
#ifdef GFX_NO_TERMINAL_COLOR
const char* TERMINAL_COLOR_RESET = "";
const char* TERMINAL_COLOR_FG_BLACK = "";
const char* TERMINAL_COLOR_FG_RED = "";
const char* TERMINAL_COLOR_FG_GREEN = "";
const char* TERMINAL_COLOR_FG_YELLOW = "";
const char* TERMINAL_COLOR_FG_BLUE = "";
const char* TERMINAL_COLOR_FG_MAGENTA = "";
const char* TERMINAL_COLOR_FG_CYAN = "";
const char* TERMINAL_COLOR_FG_GREY = "";
const char* TERMINAL_COLOR_FG_WHITE = "";
const char* TERMINAL_COLOR_BG_BLACK = "";
const char* TERMINAL_COLOR_BG_RED = "";
const char* TERMINAL_COLOR_BG_GREEN = "";
const char* TERMINAL_COLOR_BG_YELLOW = "";
const char* TERMINAL_COLOR_BG_BLUE = "";
const char* TERMINAL_COLOR_BG_MAGENTA = "";
const char* TERMINAL_COLOR_BG_CYAN = "";
const char* TERMINAL_COLOR_BG_GREY = "";
const char* TERMINAL_COLOR_BG_WHITE = "";
#else
const char* TERMINAL_COLOR_RESET = "\033[0m";
const char* TERMINAL_COLOR_FG_BLACK = "\033[0;30m";
const char* TERMINAL_COLOR_FG_RED = "\033[0;31m";
const char* TERMINAL_COLOR_FG_GREEN = "\033[0;32m";
const char* TERMINAL_COLOR_FG_YELLOW = "\033[0;33m";
const char* TERMINAL_COLOR_FG_BLUE = "\033[0;34m";
const char* TERMINAL_COLOR_FG_MAGENTA = "\033[0;35m";
const char* TERMINAL_COLOR_FG_CYAN = "\033[0;36m";
const char* TERMINAL_COLOR_FG_GREY = "\033[0;37m";
const char* TERMINAL_COLOR_FG_WHITE = "\033[0m";
const char* TERMINAL_COLOR_BG_BLACK = "\033[0;40m";
const char* TERMINAL_COLOR_BG_RED = "\033[0;41m";
const char* TERMINAL_COLOR_BG_GREEN = "\033[0;42m";
const char* TERMINAL_COLOR_BG_YELLOW = "\033[0;43m";
const char* TERMINAL_COLOR_BG_BLUE = "\033[0;44m";
const char* TERMINAL_COLOR_BG_MAGENTA = "\033[0;45m";
const char* TERMINAL_COLOR_BG_CYAN = "\033[0;46m";
const char* TERMINAL_COLOR_BG_GREY = "\033[0;47m";
const char* TERMINAL_COLOR_BG_WHITE = "\033[0m";
#endif
void
Log(std::FILE* file,
const char* type,
const char* color,
const char* filepath,
const char* func,
const int line,
const char* fmt,
...)
{
// Enable virtual terminal in windows for text color
#ifdef _WIN32
static bool initialized = false;
if (!initialized)
{
const auto init_windows = []()
{
const auto outputs = { STD_OUTPUT_HANDLE, STD_ERROR_HANDLE };
for (const auto& item : outputs)
{
HANDLE handle = GetStdHandle(item);
if (handle == INVALID_HANDLE_VALUE)
return false;
DWORD mode;
if (!GetConsoleMode(handle, &mode))
return false;
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
if (!SetConsoleMode(handle, mode))
return false;
}
return true;
};
if (!init_windows())
{
std::fprintf(stderr, "Could not enable virtual terminal processing, you will not get any terminal colors\n");
}
initialized = true;
}
#endif
// Logging logic
va_list args1;
va_start(args1, fmt);
va_list args2;
va_copy(args2, args1);
std::size_t size = 1 + std::vsnprintf(nullptr, 0, fmt, args1);
std::vector<char> buffer(size);
va_end(args1);
std::vsnprintf(buffer.data(), size, fmt, args2);
va_end(args2);
#ifdef _WIN32
const char* filename = std::strrchr(filepath, '\\');
#else
const char* filename = std::strrchr(filepath, '/');
#endif
filename = (filename)
? filename + 1 // Increment past '/' or '\\'
: filepath;
std::fprintf(file, "%s[%-5s]%s: %-10s: %-10s:%3d: %s\n",
color, type, TERMINAL_COLOR_RESET,
filename, func,
line, buffer.data());
std::fflush(file);
}
void
PauseTerminal()
{
#ifdef _WIN32
system("pause");
#endif
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Implementation - GL_Call
///////////////////////////////////////////////////////////////////////////////////////////////////
namespace Gfx
{
namespace Detail
{
void
GLCallTerminateImpl()
{
#ifndef GFX_NO_TERMINATION_ON_GL_ERROR
std::exit(EXIT_FAILURE);
#endif
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/// Implementation - Introspection (In development)
///////////////////////////////////////////////////////////////////////////////////////////////////
#ifdef GFX_ENABLE_INTROSPECTION
#include "imgui.h"
#include <algorithm>
#include <vector>
#include <cinttypes>
namespace Gfx
{
namespace Detail
{
// Generator macro to avoid duplicating code all the time.
#define GFX_INTROSPECTION_GENERATE_VARIABLE_RENDER(cputype, count, gltype, glread, glwrite, imguifunc) \
{ \
ImGui::Text(#gltype" %s:", name); \
cputype value[count]; \
glread(program, location, &value[0]); \
if (imguifunc("", &value[0], 0.25f)) \
glwrite(program, location, 1, &value[0]); \
}
#define GFX_INTROSPECTION_GENERATE_MATRIX_RENDER(cputype, rows, columns, gltype, glread, glwrite, imguifunc) \
{ \
ImGui::Text(#gltype" %s:", name); \
cputype value[rows * columns]; \
int size = rows * columns; \
glread(program, location, &value[0]); \
int modified = 0; \
for (int i = 0; i < size; i += rows) \
{ \
ImGui::PushID(i); \
modified += imguifunc("", &value[i], 0.25f); \
ImGui::PopID(); \
} \
if (modified) \
glwrite(program, location, 1, GL_FALSE, value); \
}
void
RenderUniformVariable(GLuint program, GLenum type, const char* name, GLint location)
{
static bool is_color = false;
switch (type)
{
case GL_FLOAT:
GFX_INTROSPECTION_GENERATE_VARIABLE_RENDER(GLfloat, 1, GL_FLOAT, glGetUniformfv, glProgramUniform1fv, ImGui::DragFloat);
break;
case GL_FLOAT_VEC2:
GFX_INTROSPECTION_GENERATE_VARIABLE_RENDER(GLfloat, 2, GL_FLOAT_VEC2, glGetUniformfv, glProgramUniform2fv, ImGui::DragFloat2);
break;
case GL_FLOAT_VEC3:
{
ImGui::Checkbox("##is_color", &is_color); ImGui::SameLine();
ImGui::Text("GL_FLOAT_VEC3 %s", name); ImGui::SameLine();
float value[3];
glGetUniformfv(program, location, &value[0]);
if ((!is_color && ImGui::DragFloat3("", &value[0])) || (is_color && ImGui::ColorEdit3("Color", &value[0], ImGuiColorEditFlags_NoLabel)))
glProgramUniform3fv(program, location, 1, &value[0]);
}
break;
case GL_FLOAT_VEC4:
{
ImGui::Checkbox("##is_color", &is_color); ImGui::SameLine();
ImGui::Text("GL_FLOAT_VEC4 %s", name); ImGui::SameLine();
float value[4];
glGetUniformfv(program, location, &value[0]);
if ((!is_color && ImGui::DragFloat4("", &value[0])) || (is_color && ImGui::ColorEdit4("Color", &value[0], ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf)))
glProgramUniform4fv(program, location, 1, &value[0]);
}
break;
case GL_INT:
GFX_INTROSPECTION_GENERATE_VARIABLE_RENDER(GLint, 1, GL_INT, glGetUniformiv, glProgramUniform1iv, ImGui::DragInt);
break;
case GL_INT_VEC2:
GFX_INTROSPECTION_GENERATE_VARIABLE_RENDER(GLint, 2, GL_INT, glGetUniformiv, glProgramUniform2iv, ImGui::DragInt2);
break;
case GL_INT_VEC3:
GFX_INTROSPECTION_GENERATE_VARIABLE_RENDER(GLint, 3, GL_INT, glGetUniformiv, glProgramUniform3iv, ImGui::DragInt3);
break;
case GL_INT_VEC4:
GFX_INTROSPECTION_GENERATE_VARIABLE_RENDER(GLint, 4, GL_INT, glGetUniformiv, glProgramUniform4iv, ImGui::DragInt4);
break;
case GL_SAMPLER_2D:
GFX_INTROSPECTION_GENERATE_VARIABLE_RENDER(GLint, 1, GL_SAMPLER_2D, glGetUniformiv, glProgramUniform1iv, ImGui::DragInt);
break;
case GL_FLOAT_MAT2:
GFX_INTROSPECTION_GENERATE_MATRIX_RENDER(GLfloat, 2, 2, GL_FLOAT_MAT2, glGetUniformfv, glProgramUniformMatrix2fv, ImGui::DragFloat2);
break;
case GL_FLOAT_MAT3:
GFX_INTROSPECTION_GENERATE_MATRIX_RENDER(GLfloat, 3, 3, GL_FLOAT_MAT3, glGetUniformfv, glProgramUniformMatrix3fv, ImGui::DragFloat3);
break;
case GL_FLOAT_MAT4:
GFX_INTROSPECTION_GENERATE_MATRIX_RENDER(GLfloat, 4, 4, GL_FLOAT_MAT4, glGetUniformfv, glProgramUniformMatrix4fv, ImGui::DragFloat4);
break;
case GL_FLOAT_MAT2x3:
GFX_INTROSPECTION_GENERATE_MATRIX_RENDER(GLfloat, 3, 2, GL_FLOAT_MAT2x3, glGetUniformfv, glProgramUniformMatrix2x3fv, ImGui::DragFloat3);
break;
case GL_FLOAT_MAT2x4:
GFX_INTROSPECTION_GENERATE_MATRIX_RENDER(GLfloat, 4, 2, GL_FLOAT_MAT2x4, glGetUniformfv, glProgramUniformMatrix2x4fv, ImGui::DragFloat4);
break;
case GL_FLOAT_MAT3x2:
GFX_INTROSPECTION_GENERATE_MATRIX_RENDER(GLfloat, 2, 3, GL_FLOAT_MAT3x2, glGetUniformfv, glProgramUniformMatrix3x2fv, ImGui::DragFloat2);
break;
case GL_FLOAT_MAT3x4:
GFX_INTROSPECTION_GENERATE_MATRIX_RENDER(GLfloat, 4, 3, GL_FLOAT_MAT3x4, glGetUniformfv, glProgramUniformMatrix3x2fv, ImGui::DragFloat4);
break;
default:
ImGui::Text("%s has type %s, which isn't supported yet!", name, GLEnumToString(type));
break;
}
}
#undef GFX_INTROSPECTION_GENERATE_VARIABLE_RENDER
#undef GFX_INTROSPECTION_GENERATE_MATRIX_RENDER
float
GetScrollableHeight()
{
return ImGui::GetTextLineHeight() * 16;
}
}
void
IntrospectShader(const char* label, GLuint program)
{
GFX_ASSERT(label != nullptr, "The label supplied with program: %u is nullptr", program);
GFX_ASSERT(glIsProgram(program), "The program: %u is not a valid shader program", program);
ImGui::PushID(label);
if (ImGui::CollapsingHeader(label))
{
// Uniforms
ImGui::Indent();
if (ImGui::CollapsingHeader("Uniforms", ImGuiTreeNodeFlags_DefaultOpen))
{
GLint uniform_count;
glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniform_count);
// Read the length of the longest active uniform.
GLint max_name_length;
glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_name_length);
static std::vector<char> name;
name.resize(max_name_length);
for (int i = 0; i < uniform_count; i++)
{
GLint ignored;
GLenum type;
glGetActiveUniform(program, i, max_name_length, nullptr, &ignored, &type, name.data());
const auto location = glGetUniformLocation(program, name.data());
ImGui::Indent();
ImGui::PushID(i);
ImGui::PushItemWidth(-1.0f);
Detail::RenderUniformVariable(program, type, name.data(), location);
ImGui::PopItemWidth();
ImGui::PopID();
ImGui::Unindent();
}
}
ImGui::Unindent();
// Shaders
ImGui::Indent();
if (ImGui::CollapsingHeader("Shaders"))
{
GLint shader_count;
glGetProgramiv(program, GL_ATTACHED_SHADERS, &shader_count);
static std::vector<GLuint> attached_shaders;
attached_shaders.resize(shader_count);
glGetAttachedShaders(program, shader_count, nullptr, attached_shaders.data());
for (const auto& shader : attached_shaders)
{
GLint source_length = 0;
glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &source_length);
static std::vector<char> source;
source.resize(source_length);
glGetShaderSource(shader, source_length, nullptr, source.data());
GLint type = 0;
glGetShaderiv(shader, GL_SHADER_TYPE, &type);
ImGui::Indent();
auto string_type = GLEnumToString(type);
ImGui::PushID(string_type);
if (ImGui::CollapsingHeader(string_type))
{
auto y_size = std::min(ImGui::CalcTextSize(source.data()).y, Detail::GetScrollableHeight());
ImGui::InputTextMultiline("", source.data(), source.size(), ImVec2(-1.0f, y_size), ImGuiInputTextFlags_ReadOnly);
}
ImGui::PopID();
ImGui::Unindent();
}
}
ImGui::Unindent();
}
ImGui::PopID();
}
void
IntrospectVertexArray(const char* label, GLuint vao)
{
GFX_ASSERT(label != nullptr, "The label supplied with VAO: %u is nullptr", vao);
GFX_ASSERT(glIsVertexArray(vao), "The VAO: %u is not a valid vertex array object", vao);
ImGui::PushID(label);
if (ImGui::CollapsingHeader(label))
{
ImGui::Indent();
// Get current bound vertex buffer object so we can reset it back once we are finished.
GLint current_vbo = 0;
glGetIntegerv(GL_ARRAY_BUFFER_BINDING, ¤t_vbo);
// Get current bound vertex array object so we can reset it back once we are finished.
GLint current_vao = 0;
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, ¤t_vao);
glBindVertexArray(vao);
// Get the maximum number of vertex attributes,
// minimum is 4, I have 16, means that whatever number of attributes is here, it should be reasonable to iterate over.
GLint max_vertex_attribs = 0;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_vertex_attribs);
GLint ebo = 0;
glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &ebo);
// EBO Visualization
char buffer[128];
std::sprintf(buffer, "Element Array Buffer: %d", ebo);
ImGui::PushID(buffer);
if (ImGui::CollapsingHeader(buffer))
{
ImGui::Indent();
// Assuming unsigned int atm, as I have not found a way to get out the type of the element array buffer.
int size = 0;
glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);
size /= sizeof(GLuint);
ImGui::Text("Size: %d", size);
if (ImGui::TreeNode("Buffer Contents"))
{
// TODO: Find a better way to put this out on screen, because this solution will probably not scale good when we get a lot of indices.
// Possible solution: Make it into columns, like the VBO's, and present the indices as triangles.
auto ptr = (GLuint*)glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_READ_ONLY);
for (int i = 0; i < size; i++)
{
ImGui::Text("%u", ptr[i]);
ImGui::SameLine();
if ((i + 1) % 3 == 0)
ImGui::NewLine();
}
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
ImGui::TreePop();
}
ImGui::Unindent();
}
ImGui::PopID();
// VBO Visualization
for (intptr_t i = 0; i < max_vertex_attribs; i++)
{
GLint enabled = 0;
glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &enabled);
if (!enabled)
continue;
std::sprintf(buffer, "Attribute: %" PRIdPTR "", i);
ImGui::PushID(buffer);
if (ImGui::CollapsingHeader(buffer))
{
ImGui::Indent();
// Display meta data
GLint buffer = 0;
glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &buffer);
ImGui::Text("Buffer: %d", buffer);
GLint type = 0;
glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_TYPE, &type);
ImGui::Text("Type: %s", GLEnumToString(type));
GLint dimensions = 0;
glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_SIZE, &dimensions);
ImGui::Text("Dimensions: %d", dimensions);
// Need to bind buffer to get access to parameteriv, and for mapping later
glBindBuffer(GL_ARRAY_BUFFER, buffer);
GLint size = 0;
glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);
ImGui::Text("Size in bytes: %d", size);
GLint stride = 0;
glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &stride);
ImGui::Text("Stride in bytes: %d", stride);
GLvoid* offset = nullptr;
glGetVertexAttribPointerv(i, GL_VERTEX_ATTRIB_ARRAY_POINTER, &offset);
ImGui::Text("Offset in bytes: %" PRIdPTR "", (intptr_t)offset);
GLint usage = 0;
glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_USAGE, &usage);
ImGui::Text("Usage: %s", GLEnumToString(usage));
// Create table with indexes and actual contents
if (ImGui::TreeNode("Buffer Contents"))
{
ImGui::BeginChild(ImGui::GetID("vbo contents"), ImVec2(-1.0f, Detail::GetScrollableHeight()), true, ImGuiWindowFlags_AlwaysVerticalScrollbar);
ImGui::Columns(dimensions + 1);
const char* descriptors[] = {"index", "x", "y", "z", "w"};
for (int j = 0; j < dimensions + 1; j++)
{
ImGui::Text("%s", descriptors[j]);
ImGui::NextColumn();
}
ImGui::Separator();
auto ptr = (char*)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_ONLY) + (intptr_t)offset;
for (int j = 0, c = 0; j < size; j += stride, c++)
{
ImGui::Text("%d", c);
ImGui::NextColumn();
for (int k = 0; k < dimensions; k++)
{
switch (type)
{
case GL_BYTE: ImGui::Text("% d", *(GLbyte*) &ptr[j + k * sizeof(GLbyte)]); break;
case GL_UNSIGNED_BYTE: ImGui::Text("%u", *(GLubyte*) &ptr[j + k * sizeof(GLubyte)]); break;
case GL_SHORT: ImGui::Text("% d", *(GLshort*) &ptr[j + k * sizeof(GLshort)]); break;
case GL_UNSIGNED_SHORT: ImGui::Text("%u", *(GLushort*) &ptr[j + k * sizeof(GLushort)]); break;
case GL_INT: ImGui::Text("% d", *(GLint*) &ptr[j + k * sizeof(GLint)]); break;
case GL_UNSIGNED_INT: ImGui::Text("%u", *(GLuint*) &ptr[j + k * sizeof(GLuint)]); break;
case GL_FLOAT: ImGui::Text("% f", *(GLfloat*) &ptr[j + k * sizeof(GLfloat)]); break;
case GL_DOUBLE: ImGui::Text("% f", *(GLdouble*) &ptr[j + k * sizeof(GLdouble)]); break;