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
// The slice of OpenGL we need, and the state dance around drawing into someone
// else's frame.
//
// We are called from inside the game's swap-buffers, so every bit of GL state
// is set up for whatever it was drawing. Two lessons, both paid for the hard
// way, are baked in here:
//
// * a sampler object bound to texture unit 0 overrides the font atlas's own
// parameters and renders the whole menu flat black;
// * a non-zero GL_UNPACK_ROW_LENGTH (or a bound pixel-unpack buffer) turns
// every texture upload into noise — which is what a garbled font is.
//
// The second one bites at *font atlas upload*, not just at draw time, so the
// neutralise has to wrap texture creation too, not only rendering.
#pragma once
#include <windows.h>
#include <cstdint>
namespace lodestone::gl {
using GLenum = unsigned int;
using GLboolean = unsigned char;
using GLint = int;
using GLuint = unsigned int;
using GLsizei = int;
constexpr GLenum TEXTURE_2D = 0x0DE1;
constexpr GLenum BLEND = 0x0BE2;
constexpr GLenum DEPTH_TEST = 0x0B71;
constexpr GLenum CULL_FACE = 0x0B44;
constexpr GLenum SCISSOR_TEST = 0x0C11;
constexpr GLenum STENCIL_TEST = 0x0B90;
constexpr GLenum FRAMEBUFFER_SRGB = 0x8DB9;
constexpr GLenum VIEWPORT = 0x0BA2;
constexpr GLenum SCISSOR_BOX = 0x0C10;
constexpr GLenum CURRENT_PROGRAM = 0x8B8D;
constexpr GLenum VERTEX_ARRAY_BINDING = 0x85B5;
constexpr GLenum ARRAY_BUFFER = 0x8892;
constexpr GLenum ARRAY_BUFFER_BINDING = 0x8894;
constexpr GLenum PIXEL_UNPACK_BUFFER = 0x88EC;
constexpr GLenum PIXEL_UNPACK_BUFFER_BINDING = 0x88EF;
constexpr GLenum TEXTURE_BINDING_2D = 0x8069;
constexpr GLenum ACTIVE_TEXTURE = 0x84E0;
constexpr GLenum TEXTURE0 = 0x84C0;
constexpr GLenum SAMPLER_BINDING = 0x8919;
constexpr GLenum DRAW_FRAMEBUFFER = 0x8CA9;
constexpr GLenum DRAW_FRAMEBUFFER_BINDING = 0x8CA6;
constexpr GLenum UNPACK_ALIGNMENT = 0x0CF5;
constexpr GLenum UNPACK_ROW_LENGTH = 0x0CF2;
constexpr GLenum UNPACK_SKIP_PIXELS = 0x0CF4;
constexpr GLenum UNPACK_SKIP_ROWS = 0x0CF3;
constexpr GLenum BLEND_SRC_RGB = 0x80C9;
constexpr GLenum BLEND_DST_RGB = 0x80C8;
constexpr GLenum BLEND_SRC_ALPHA = 0x80CB;
constexpr GLenum BLEND_DST_ALPHA = 0x80CA;
constexpr GLenum BLEND_EQUATION_RGB = 0x8009;
constexpr GLenum BLEND_EQUATION_ALPHA = 0x883D;
constexpr GLenum FRONT_AND_BACK = 0x0408;
constexpr GLenum FILL = 0x1B02;
constexpr GLenum NEAREST = 0x2600;
constexpr GLenum TEXTURE_MIN_FILTER = 0x2801;
constexpr GLenum TEXTURE_MAG_FILTER = 0x2800;
// Core 1.1, exported by opengl32 itself.
using PFNGetIntegerv = void(APIENTRY*)(GLenum, GLint*);
using PFNIsEnabled = GLboolean(APIENTRY*)(GLenum);
using PFNEnable = void(APIENTRY*)(GLenum);
using PFNDisable = void(APIENTRY*)(GLenum);
using PFNPixelStorei = void(APIENTRY*)(GLenum, GLint);
using PFNBindTexture = void(APIENTRY*)(GLenum, GLuint);
using PFNViewport = void(APIENTRY*)(GLint, GLint, GLsizei, GLsizei);
using PFNScissor = void(APIENTRY*)(GLint, GLint, GLsizei, GLsizei);
using PFNColorMask = void(APIENTRY*)(GLboolean, GLboolean, GLboolean, GLboolean);
using PFNPolygonMode = void(APIENTRY*)(GLenum, GLenum);
using PFNTexParameteri = void(APIENTRY*)(GLenum, GLenum, GLint);
// Later additions, which only wglGetProcAddress knows about.
using PFNActiveTexture = void(APIENTRY*)(GLenum);
using PFNBindSampler = void(APIENTRY*)(GLuint, GLuint);
using PFNBindBuffer = void(APIENTRY*)(GLenum, GLuint);
using PFNBindVertexArray = void(APIENTRY*)(GLuint);
using PFNUseProgram = void(APIENTRY*)(GLuint);
using PFNBindFramebuffer = void(APIENTRY*)(GLenum, GLuint);
using PFNBlendFuncSeparate = void(APIENTRY*)(GLenum, GLenum, GLenum, GLenum);
using PFNBlendEquationSeparate = void(APIENTRY*)(GLenum, GLenum);
struct Api {
PFNGetIntegerv GetIntegerv = nullptr;
PFNIsEnabled IsEnabled = nullptr;
PFNEnable Enable = nullptr;
PFNDisable Disable = nullptr;
PFNPixelStorei PixelStorei = nullptr;
PFNBindTexture BindTexture = nullptr;
PFNViewport Viewport = nullptr;
PFNScissor Scissor = nullptr;
PFNColorMask ColorMask = nullptr;
PFNPolygonMode PolygonMode = nullptr;
PFNTexParameteri TexParameteri = nullptr;
PFNActiveTexture ActiveTexture = nullptr;
PFNBindSampler BindSampler = nullptr;
PFNBindBuffer BindBuffer = nullptr;
PFNBindVertexArray BindVertexArray = nullptr;
PFNUseProgram UseProgram = nullptr;
PFNBindFramebuffer BindFramebuffer = nullptr;
PFNBlendFuncSeparate BlendFuncSeparate = nullptr;
PFNBlendEquationSeparate BlendEquationSeparate = nullptr;
};
inline Api g_api;
/// Extensions come from wglGetProcAddress, core 1.1 only from the opengl32
/// export table — and wglGetProcAddress reports failure as 1, 2, 3 or -1 as
/// well as null, which is a classic way to end up calling into nothing.
inline void* proc(const char* name) {
static HMODULE opengl32 = GetModuleHandleA("opengl32.dll");
if (!opengl32) return nullptr;
using PFNWglGetProcAddress = PROC(APIENTRY*)(LPCSTR);
static auto wgl = reinterpret_cast<PFNWglGetProcAddress>(
reinterpret_cast<void*>(GetProcAddress(opengl32, "wglGetProcAddress")));
if (wgl) {
void* p = reinterpret_cast<void*>(wgl(name));
auto bad = p == nullptr || p == reinterpret_cast<void*>(1) ||
p == reinterpret_cast<void*>(2) || p == reinterpret_cast<void*>(3) ||
p == reinterpret_cast<void*>(-1);
if (!bad) return p;
}
return reinterpret_cast<void*>(GetProcAddress(opengl32, name));
}
inline void load() {
auto get = [](const char* n) { return proc(n); };
g_api.GetIntegerv = reinterpret_cast<PFNGetIntegerv>(get("glGetIntegerv"));
g_api.IsEnabled = reinterpret_cast<PFNIsEnabled>(get("glIsEnabled"));
g_api.Enable = reinterpret_cast<PFNEnable>(get("glEnable"));
g_api.Disable = reinterpret_cast<PFNDisable>(get("glDisable"));
g_api.PixelStorei = reinterpret_cast<PFNPixelStorei>(get("glPixelStorei"));
g_api.BindTexture = reinterpret_cast<PFNBindTexture>(get("glBindTexture"));
g_api.Viewport = reinterpret_cast<PFNViewport>(get("glViewport"));
g_api.Scissor = reinterpret_cast<PFNScissor>(get("glScissor"));
g_api.ColorMask = reinterpret_cast<PFNColorMask>(get("glColorMask"));
g_api.PolygonMode = reinterpret_cast<PFNPolygonMode>(get("glPolygonMode"));
g_api.TexParameteri = reinterpret_cast<PFNTexParameteri>(get("glTexParameteri"));
g_api.ActiveTexture = reinterpret_cast<PFNActiveTexture>(get("glActiveTexture"));
g_api.BindSampler = reinterpret_cast<PFNBindSampler>(get("glBindSampler"));
g_api.BindBuffer = reinterpret_cast<PFNBindBuffer>(get("glBindBuffer"));
g_api.BindVertexArray = reinterpret_cast<PFNBindVertexArray>(get("glBindVertexArray"));
g_api.UseProgram = reinterpret_cast<PFNUseProgram>(get("glUseProgram"));
g_api.BindFramebuffer = reinterpret_cast<PFNBindFramebuffer>(get("glBindFramebuffer"));
g_api.BlendFuncSeparate = reinterpret_cast<PFNBlendFuncSeparate>(get("glBlendFuncSeparate"));
g_api.BlendEquationSeparate =
reinterpret_cast<PFNBlendEquationSeparate>(get("glBlendEquationSeparate"));
}
/// Everything the game might have left set that we disturb, so its next frame
/// renders exactly as it would have.
struct State {
GLint draw_framebuffer, program, vao, array_buffer;
GLint active_texture, texture, sampler0;
GLint unpack_buffer, unpack_alignment, unpack_row_length;
GLint unpack_skip_pixels, unpack_skip_rows;
GLint viewport[4], scissor[4];
GLint blend_src_rgb, blend_dst_rgb, blend_src_alpha, blend_dst_alpha;
GLint blend_eq_rgb, blend_eq_alpha;
bool blend, depth_test, cull_face, scissor_test, stencil_test, srgb;
static State save() {
const Api& a = g_api;
State s{};
a.GetIntegerv(DRAW_FRAMEBUFFER_BINDING, &s.draw_framebuffer);
a.GetIntegerv(CURRENT_PROGRAM, &s.program);
a.GetIntegerv(VERTEX_ARRAY_BINDING, &s.vao);
a.GetIntegerv(ARRAY_BUFFER_BINDING, &s.array_buffer);
a.GetIntegerv(ACTIVE_TEXTURE, &s.active_texture);
a.GetIntegerv(TEXTURE_BINDING_2D, &s.texture);
a.GetIntegerv(SAMPLER_BINDING, &s.sampler0);
a.GetIntegerv(PIXEL_UNPACK_BUFFER_BINDING, &s.unpack_buffer);
a.GetIntegerv(UNPACK_ALIGNMENT, &s.unpack_alignment);
a.GetIntegerv(UNPACK_ROW_LENGTH, &s.unpack_row_length);
a.GetIntegerv(UNPACK_SKIP_PIXELS, &s.unpack_skip_pixels);
a.GetIntegerv(UNPACK_SKIP_ROWS, &s.unpack_skip_rows);
a.GetIntegerv(VIEWPORT, s.viewport);
a.GetIntegerv(SCISSOR_BOX, s.scissor);
a.GetIntegerv(BLEND_SRC_RGB, &s.blend_src_rgb);
a.GetIntegerv(BLEND_DST_RGB, &s.blend_dst_rgb);
a.GetIntegerv(BLEND_SRC_ALPHA, &s.blend_src_alpha);
a.GetIntegerv(BLEND_DST_ALPHA, &s.blend_dst_alpha);
a.GetIntegerv(BLEND_EQUATION_RGB, &s.blend_eq_rgb);
a.GetIntegerv(BLEND_EQUATION_ALPHA, &s.blend_eq_alpha);
s.blend = a.IsEnabled(BLEND);
s.depth_test = a.IsEnabled(DEPTH_TEST);
s.cull_face = a.IsEnabled(CULL_FACE);
s.scissor_test = a.IsEnabled(SCISSOR_TEST);
s.stencil_test = a.IsEnabled(STENCIL_TEST);
s.srgb = a.IsEnabled(FRAMEBUFFER_SRGB);
return s;
}
/// The plain state ImGui assumes. Also used around font-atlas upload, which
/// is the other place the pixel-transfer settings matter.
static void neutralise() {
const Api& a = g_api;
a.BindFramebuffer(DRAW_FRAMEBUFFER, 0);
a.Disable(DEPTH_TEST);
a.Disable(CULL_FACE);
a.Disable(STENCIL_TEST);
a.Disable(SCISSOR_TEST);
// Plain sRGB values are written; a second hardware conversion washes the
// whole menu out.
a.Disable(FRAMEBUFFER_SRGB);
a.ColorMask(1, 1, 1, 1);
a.PolygonMode(FRONT_AND_BACK, FILL);
a.ActiveTexture(TEXTURE0);
if (a.BindSampler) a.BindSampler(0, 0);
a.BindBuffer(PIXEL_UNPACK_BUFFER, 0);
a.PixelStorei(UNPACK_ALIGNMENT, 4);
a.PixelStorei(UNPACK_ROW_LENGTH, 0);
a.PixelStorei(UNPACK_SKIP_PIXELS, 0);
a.PixelStorei(UNPACK_SKIP_ROWS, 0);
}
void restore() const {
const Api& a = g_api;
a.BindFramebuffer(DRAW_FRAMEBUFFER, static_cast<GLuint>(draw_framebuffer));
a.UseProgram(static_cast<GLuint>(program));
a.BindVertexArray(static_cast<GLuint>(vao));
a.BindBuffer(ARRAY_BUFFER, static_cast<GLuint>(array_buffer));
a.BindBuffer(PIXEL_UNPACK_BUFFER, static_cast<GLuint>(unpack_buffer));
a.PixelStorei(UNPACK_ALIGNMENT, unpack_alignment);
a.PixelStorei(UNPACK_ROW_LENGTH, unpack_row_length);
a.PixelStorei(UNPACK_SKIP_PIXELS, unpack_skip_pixels);
a.PixelStorei(UNPACK_SKIP_ROWS, unpack_skip_rows);
a.ActiveTexture(TEXTURE0);
if (a.BindSampler) a.BindSampler(0, static_cast<GLuint>(sampler0));
a.BindTexture(TEXTURE_2D, static_cast<GLuint>(texture));
a.ActiveTexture(static_cast<GLenum>(active_texture));
a.Viewport(viewport[0], viewport[1], viewport[2], viewport[3]);
a.Scissor(scissor[0], scissor[1], scissor[2], scissor[3]);
set(BLEND, blend);
a.BlendEquationSeparate(static_cast<GLenum>(blend_eq_rgb),
static_cast<GLenum>(blend_eq_alpha));
a.BlendFuncSeparate(static_cast<GLenum>(blend_src_rgb), static_cast<GLenum>(blend_dst_rgb),
static_cast<GLenum>(blend_src_alpha),
static_cast<GLenum>(blend_dst_alpha));
set(DEPTH_TEST, depth_test);
set(CULL_FACE, cull_face);
set(SCISSOR_TEST, scissor_test);
set(STENCIL_TEST, stencil_test);
set(FRAMEBUFFER_SRGB, srgb);
}
private:
static void set(GLenum cap, bool on) {
if (on) {
g_api.Enable(cap);
} else {
g_api.Disable(cap);
}
}
};
} // namespace lodestone::gl