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
|
#define _GL_TEXTURE_MAX_LEVEL 0x813D
#define _GL_BGRA_EXT 0x80E1
#define _GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
#if defined CC_BUILD_WEB || defined CC_BUILD_ANDROID
#define PIXEL_FORMAT GL_RGBA
#else
#define PIXEL_FORMAT _GL_BGRA_EXT
#endif
#if defined CC_BIG_ENDIAN
/* Pixels are stored in memory as A,R,G,B but GL_UNSIGNED_BYTE will interpret as B,G,R,A */
/* So use GL_UNSIGNED_INT_8_8_8_8_REV instead to remedy this */
#define TRANSFER_FORMAT _GL_UNSIGNED_INT_8_8_8_8_REV
#else
/* Pixels are stored in memory as B,G,R,A and GL_UNSIGNED_BYTE will interpret as B,G,R,A */
/* So fine to just use GL_UNSIGNED_BYTE here */
#define TRANSFER_FORMAT GL_UNSIGNED_BYTE
#endif
#define uint_to_ptr(raw) ((void*)((cc_uintptr)(raw)))
#define ptr_to_uint(raw) ((GLuint)((cc_uintptr)(raw)))
/*########################################################################################################################*
*---------------------------------------------------------General---------------------------------------------------------*
*#########################################################################################################################*/
static void GL_UpdateVsync(void) {
GLContext_SetFpsLimit(gfx_vsync, gfx_minFrameMs);
}
static void GLBackend_Init(void);
void Gfx_Create(void) {
GLContext_Create();
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &Gfx.MaxTexWidth);
Gfx.MaxTexHeight = Gfx.MaxTexWidth;
Gfx.Created = true;
/* necessary for android which "loses" context when window is closed */
Gfx.LostContext = false;
GLBackend_Init();
Gfx_RestoreState();
GL_UpdateVsync();
}
cc_bool Gfx_TryRestoreContext(void) {
return GLContext_TryRestore();
}
void Gfx_Free(void) {
Gfx_FreeState();
GLContext_Free();
}
#define gl_Toggle(cap) if (enabled) { glEnable(cap); } else { glDisable(cap); }
static void* tmpData;
static int tmpSize;
static void* FastAllocTempMem(int size) {
if (size > tmpSize) {
Mem_Free(tmpData);
tmpData = Mem_Alloc(size, 1, "Gfx_AllocTempMemory");
}
tmpSize = size;
return tmpData;
}
/*########################################################################################################################*
*---------------------------------------------------------Textures--------------------------------------------------------*
*#########################################################################################################################*/
static void Gfx_DoMipmaps(int x, int y, struct Bitmap* bmp, int rowWidth, cc_bool partial) {
BitmapCol* prev = bmp->scan0;
BitmapCol* cur;
int lvls = CalcMipmapsLevels(bmp->width, bmp->height);
int lvl, width = bmp->width, height = bmp->height;
for (lvl = 1; lvl <= lvls; lvl++) {
x /= 2; y /= 2;
if (width > 1) width /= 2;
if (height > 1) height /= 2;
cur = (BitmapCol*)Mem_Alloc(width * height, 4, "mipmaps");
GenMipmaps(width, height, cur, prev, rowWidth);
if (partial) {
_glTexSubImage2D(GL_TEXTURE_2D, lvl, x, y, width, height, PIXEL_FORMAT, TRANSFER_FORMAT, cur);
} else {
_glTexImage2D(GL_TEXTURE_2D, lvl, GL_RGBA, width, height, 0, PIXEL_FORMAT, TRANSFER_FORMAT, cur);
}
if (prev != bmp->scan0) Mem_Free(prev);
prev = cur;
rowWidth = width;
}
if (prev != bmp->scan0) Mem_Free(prev);
}
/* TODO: Use GL_UNPACK_ROW_LENGTH for Desktop OpenGL instead */
#define UPDATE_FAST_SIZE (64 * 64)
static CC_NOINLINE void UpdateTextureSlow(int x, int y, struct Bitmap* part, int rowWidth, cc_bool full) {
BitmapCol buffer[UPDATE_FAST_SIZE];
void* ptr = (void*)buffer;
int count = part->width * part->height;
/* cannot allocate memory on the stack for very big updates */
if (count > UPDATE_FAST_SIZE) {
ptr = Mem_Alloc(count, 4, "Gfx_UpdateTexture temp");
}
CopyTextureData(ptr, part->width << 2, part, rowWidth << 2);
if (full) {
_glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, part->width, part->height, 0, PIXEL_FORMAT, TRANSFER_FORMAT, ptr);
} else {
_glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, part->width, part->height, PIXEL_FORMAT, TRANSFER_FORMAT, ptr);
}
if (count > UPDATE_FAST_SIZE) Mem_Free(ptr);
}
static GfxResourceID Gfx_AllocTexture(struct Bitmap* bmp, int rowWidth, cc_uint8 flags, cc_bool mipmaps) {
GfxResourceID texId = NULL;
_glGenTextures(1, (GLuint*)&texId);
_glBindTexture(GL_TEXTURE_2D, ptr_to_uint(texId));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
if (mipmaps) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
if (customMipmapsLevels) {
int lvls = CalcMipmapsLevels(bmp->width, bmp->height);
glTexParameteri(GL_TEXTURE_2D, _GL_TEXTURE_MAX_LEVEL, lvls);
}
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
if (bmp->width == rowWidth) {
_glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bmp->width, bmp->height, 0, PIXEL_FORMAT, TRANSFER_FORMAT, bmp->scan0);
} else {
UpdateTextureSlow(0, 0, bmp, rowWidth, true);
}
if (mipmaps) Gfx_DoMipmaps(0, 0, bmp, rowWidth, false);
return texId;
}
void Gfx_UpdateTexture(GfxResourceID texId, int x, int y, struct Bitmap* part, int rowWidth, cc_bool mipmaps) {
_glBindTexture(GL_TEXTURE_2D, ptr_to_uint(texId));
if (part->width == rowWidth) {
_glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, part->width, part->height, PIXEL_FORMAT, TRANSFER_FORMAT, part->scan0);
} else {
UpdateTextureSlow(x, y, part, rowWidth, false);
}
if (mipmaps) Gfx_DoMipmaps(x, y, part, rowWidth, true);
}
void Gfx_DeleteTexture(GfxResourceID* texId) {
GLuint id = ptr_to_uint(*texId);
if (id) _glDeleteTextures(1, &id);
*texId = 0;
}
void Gfx_EnableMipmaps(void) { }
void Gfx_DisableMipmaps(void) { }
/*########################################################################################################################*
*-----------------------------------------------------State management----------------------------------------------------*
*#########################################################################################################################*/
static PackedCol gfx_clearColor;
void Gfx_SetFaceCulling(cc_bool enabled) { gl_Toggle(GL_CULL_FACE); }
static void SetAlphaBlend(cc_bool enabled) { gl_Toggle(GL_BLEND); }
void Gfx_SetAlphaArgBlend(cc_bool enabled) { }
static void GL_ClearColor(PackedCol color) {
glClearColor(PackedCol_R(color) / 255.0f, PackedCol_G(color) / 255.0f,
PackedCol_B(color) / 255.0f, PackedCol_A(color) / 255.0f);
}
void Gfx_ClearColor(PackedCol color) {
if (color == gfx_clearColor) return;
GL_ClearColor(color);
gfx_clearColor = color;
}
static void SetColorWrite(cc_bool r, cc_bool g, cc_bool b, cc_bool a) {
glColorMask(r, g, b, a);
}
void Gfx_SetDepthWrite(cc_bool enabled) { glDepthMask(enabled); }
void Gfx_SetDepthTest(cc_bool enabled) { gl_Toggle(GL_DEPTH_TEST); }
/*########################################################################################################################*
*---------------------------------------------------------Matrices--------------------------------------------------------*
*#########################################################################################################################*/
void Gfx_CalcOrthoMatrix(struct Matrix* matrix, float width, float height, float zNear, float zFar) {
/* Transposed, source https://learn.microsoft.com/en-us/windows/win32/opengl/glortho */
/* The simplified calculation below uses: L = 0, R = width, T = 0, B = height */
*matrix = Matrix_Identity;
matrix->row1.x = 2.0f / width;
matrix->row2.y = -2.0f / height;
matrix->row3.z = -2.0f / (zFar - zNear);
matrix->row4.x = -1.0f;
matrix->row4.y = 1.0f;
matrix->row4.z = -(zFar + zNear) / (zFar - zNear);
}
static float Cotangent(float x) { return Math_CosF(x) / Math_SinF(x); }
void Gfx_CalcPerspectiveMatrix(struct Matrix* matrix, float fov, float aspect, float zFar) {
float zNear = 0.1f;
float c = Cotangent(0.5f * fov);
/* Transposed, source https://learn.microsoft.com/en-us/windows/win32/opengl/glfrustum */
/* For a FOV based perspective matrix, left/right/top/bottom are calculated as: */
/* left = -c * aspect, right = c * aspect, bottom = -c, top = c */
/* Calculations are simplified because of left/right and top/bottom symmetry */
*matrix = Matrix_Identity;
matrix->row1.x = c / aspect;
matrix->row2.y = c;
matrix->row3.z = -(zFar + zNear) / (zFar - zNear);
matrix->row3.w = -1.0f;
matrix->row4.z = -(2.0f * zFar * zNear) / (zFar - zNear);
matrix->row4.w = 0.0f;
}
/*########################################################################################################################*
*-----------------------------------------------------------Misc----------------------------------------------------------*
*#########################################################################################################################*/
static BitmapCol* GL_GetRow(struct Bitmap* bmp, int y, void* ctx) {
/* OpenGL stores bitmap in bottom-up order, so flip order when saving */
return Bitmap_GetRow(bmp, (bmp->height - 1) - y);
}
cc_result Gfx_TakeScreenshot(struct Stream* output) {
struct Bitmap bmp;
cc_result res;
GLint vp[4];
glGetIntegerv(GL_VIEWPORT, vp); /* { x, y, width, height } */
bmp.width = vp[2];
bmp.height = vp[3];
bmp.scan0 = (BitmapCol*)Mem_TryAlloc(bmp.width * bmp.height, 4);
if (!bmp.scan0) return ERR_OUT_OF_MEMORY;
glReadPixels(0, 0, bmp.width, bmp.height, PIXEL_FORMAT, TRANSFER_FORMAT, bmp.scan0);
res = Png_Encode(&bmp, output, GL_GetRow, false, NULL);
Mem_Free(bmp.scan0);
return res;
}
static void AppendVRAMStats(cc_string* info) {
static const cc_string memExt = String_FromConst("GL_NVX_gpu_memory_info");
GLint totalKb, curKb;
float total, cur;
/* NOTE: glGetString returns UTF8, but I just treat it as code page 437 */
cc_string exts = String_FromReadonly((const char*)glGetString(GL_EXTENSIONS));
if (!String_CaselessContains(&exts, &memExt)) return;
glGetIntegerv(0x9048, &totalKb);
glGetIntegerv(0x9049, &curKb);
if (totalKb <= 0 || curKb <= 0) return;
total = totalKb / 1024.0f; cur = curKb / 1024.0f;
String_Format2(info, "Video memory: %f2 MB total, %f2 free\n", &total, &cur);
}
void Gfx_GetApiInfo(cc_string* info) {
GLint depthBits = 0;
int pointerSize = sizeof(void*) * 8;
glGetIntegerv(GL_DEPTH_BITS, &depthBits);
#if CC_GFX_BACKEND == CC_GFX_BACKEND_GL2
String_Format1(info, "-- Using OpenGL Modern (%i bit) --\n", &pointerSize);
#else
String_Format1(info, "-- Using OpenGL (%i bit) --\n", &pointerSize);
#endif
String_Format1(info, "Vendor: %c\n", glGetString(GL_VENDOR));
String_Format1(info, "Renderer: %c\n", glGetString(GL_RENDERER));
String_Format1(info, "GL version: %c\n", glGetString(GL_VERSION));
AppendVRAMStats(info);
PrintMaxTextureInfo(info);
String_Format1(info, "Depth buffer bits: %i\n", &depthBits);
GLContext_GetApiInfo(info);
}
void Gfx_SetFpsLimit(cc_bool vsync, float minFrameMs) {
gfx_minFrameMs = minFrameMs;
gfx_vsync = vsync;
if (Gfx.Created) GL_UpdateVsync();
}
void Gfx_BeginFrame(void) { }
void Gfx_ClearBuffers(GfxBuffers buffers) {
int targets = 0;
if (buffers & GFX_BUFFER_COLOR) targets |= GL_COLOR_BUFFER_BIT;
if (buffers & GFX_BUFFER_DEPTH) targets |= GL_DEPTH_BUFFER_BIT;
glClear(targets);
}
void Gfx_EndFrame(void) {
#if CC_GFX_BACKEND == CC_GFX_BACKEND_GL1
if (Window_IsObscured()) {
TickReducedPerformance();
} else {
EndReducedPerformance();
}
#endif
/* TODO always run ?? */
if (!GLContext_SwapBuffers()) Gfx_LoseContext("GLContext lost");
if (gfx_minFrameMs) LimitFPS();
}
void Gfx_OnWindowResize(void) {
Gfx_SetViewport(0, 0, Game.Width, Game.Height);
/* With cocoa backend, in some cases [NSOpenGLContext update] will actually */
/* call glViewport with the size of the window framebuffer */
/* https://github.com/glfw/glfw/issues/80 */
/* Normally this doesn't matter, but it does when game is compiled against recent */
/* macOS SDK *and* the display is a high DPI display - where glViewport(width, height) */
/* above would otherwise result in game rendering to only 1/4 of the screen */
/* https://github.com/ClassiCube/ClassiCube/issues/888 */
GLContext_Update();
}
void Gfx_SetViewport(int x, int y, int w, int h) {
glViewport(x, y, w, h);
}
|