-
Notifications
You must be signed in to change notification settings - Fork 103
/
gpu_console.cpp
467 lines (394 loc) · 12.1 KB
/
gpu_console.cpp
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
// Sample GPU/OpenGL powered console renderer implementation.
// Please note that this implementation is incomplete, and only normal/bold colors are implemented.
// Reverse colors, faint colors, underline or strikethrough are not implemented.
// Nevertheless, it should suffice for most purposes.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <switch.h>
#include <EGL/egl.h> // EGL library
#include <EGL/eglext.h> // EGL extensions
#include <glad/glad.h> // glad library (OpenGL loader)
#define TRACE(...) ((void)0)
static const char* const vertexShaderSource = R"text(
#version 330 core
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
out gl_PerVertex
{
vec4 gl_Position;
};
layout (location = 0) in int inAttr;
layout (location = 0) out vec4 outColor;
layout (location = 1) out vec3 outUV;
uniform ivec2 dimensions;
uniform vec4 palettes[16] = vec4[](
vec4(0.0, 0.0, 0.0, 1.0),
vec4(0.5, 0.0, 0.0, 1.0),
vec4(0.0, 0.5, 0.0, 1.0),
vec4(0.5, 0.5, 0.0, 1.0),
vec4(0.0, 0.0, 0.5, 1.0),
vec4(0.5, 0.0, 0.5, 1.0),
vec4(0.0, 0.5, 0.5, 1.0),
vec4(0.75, 0.75, 0.75, 1.0),
vec4(0.5, 0.5, 0.5, 1.0),
vec4(1.0, 0.0, 0.0, 1.0),
vec4(0.0, 1.0, 0.0, 1.0),
vec4(1.0, 1.0, 0.0, 1.0),
vec4(0.0, 0.0, 1.0, 1.0),
vec4(1.0, 0.0, 1.0, 1.0),
vec4(0.0, 1.0, 1.0, 1.0),
vec4(1.0, 1.0, 1.0, 1.0)
);
const vec2 builtin_vertices[] = vec2[](
vec2(0.0, 0.0),
vec2(0.0, -1.0),
vec2(1.0, -1.0),
vec2(0.0, 0.0),
vec2(1.0, -1.0),
vec2(1.0, 0.0)
);
void main()
{
// Extract data from the attribute
float tileId = float(inAttr & 0x3FF);
bool hFlip = ((inAttr >> 10) & 1) != 0;
bool vFlip = ((inAttr >> 11) & 1) != 0;
int palId = (inAttr >> 12) & 0xF;
vec2 vtxData = builtin_vertices[gl_VertexID];
// Position
float tileRow = floor(float(gl_InstanceID) / dimensions.x);
float tileCol = float(gl_InstanceID) - tileRow*dimensions.x;
vec2 basePos;
basePos.x = 2.0 * tileCol / dimensions.x - 1.0;
basePos.y = 2.0 * (1.0 - tileRow / dimensions.y) - 1.0;
vec2 offsetPos = vec2(2.0) / vec2(dimensions.xy);
gl_Position.xy = basePos + offsetPos * vtxData;
gl_Position.zw = vec2(0.0, 1.0);
// Color
outColor = palettes[palId];
// UVs
if (hFlip)
vtxData.x = 1.0 - vtxData.x;
if (vFlip)
vtxData.y = -1.0 - vtxData.y;
outUV.xy = vec2(0.0,1.0) + vtxData;
outUV.z = tileId;
}
)text";
static const char* const fragmentShaderSource = R"text(
#version 330 core
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout (location = 0) in vec4 inColor;
layout (location = 1) in vec3 inUV;
layout (location = 0) out vec4 outColor;
uniform sampler2DArray tileset;
void main()
{
float alpha = inColor.a * texture(tileset, inUV).r;
if (alpha < 0.1) discard;
outColor.rgb = inColor.rgb;
outColor.a = alpha;
}
)text";
namespace
{
struct GpuConsole : public ConsoleRenderer
{
constexpr GpuConsole() :
ConsoleRenderer{ _init, _deinit, _drawChar, _scrollWindow, _flushAndSwap },
s_display{}, s_context{}, s_surface{},
s_tilemapVsh{}, s_tilemapFsh{}, s_tilemapPipeline{},
s_tilemapVao{}, s_tilemapVbo{}, s_tilemap{},
s_tilesetTex{}
{ }
bool init(PrintConsole* con);
void deinit(PrintConsole* con);
void drawChar(PrintConsole* con, int x, int y, int c);
void scrollWindow(PrintConsole* con);
void flushAndSwap(PrintConsole* con);
private:
static GpuConsole* _get(PrintConsole* con)
{
return static_cast<GpuConsole*>(con->renderer);
}
static bool _init(PrintConsole* con)
{
return _get(con)->init(con);
}
static void _deinit(PrintConsole* con)
{
_get(con)->deinit(con);
}
static void _drawChar(PrintConsole* con, int x, int y, int c)
{
_get(con)->drawChar(con, x, y, c);
}
static void _scrollWindow(PrintConsole* con)
{
_get(con)->scrollWindow(con);
}
static void _flushAndSwap(PrintConsole* con)
{
_get(con)->flushAndSwap(con);
}
EGLDisplay s_display;
EGLContext s_context;
EGLSurface s_surface;
bool initEgl();
void deinitEgl();
GLuint s_tilemapVsh, s_tilemapFsh;
GLuint s_tilemapPipeline;
GLuint s_tilemapVao, s_tilemapVbo;
uint16_t* s_tilemap;
GLuint s_tilesetTex;
};
constexpr uint16_t MakeTilemapEntry(unsigned tileId, bool hFlip, bool vFlip, unsigned palId)
{
uint16_t ent = 0;
ent |= (tileId & 0x3FF);
if (hFlip)
ent |= 1u << 10;
if (vFlip)
ent |= 1u << 11;
ent |= (palId & 0xF) << 12;
return ent;
}
GLuint loadShaderProgram(GLenum type, const char* source)
{
GLint success;
GLchar msg[512];
GLuint handle = glCreateShaderProgramv(type, 1, &source);
glGetProgramiv(handle, GL_LINK_STATUS, &success);
if (success == GL_FALSE)
{
glGetProgramInfoLog(handle, sizeof(msg), nullptr, msg);
TRACE("Shader error: %s", msg);
glDeleteProgram(handle);
handle = 0;
}
return handle;
}
}
bool GpuConsole::init(PrintConsole* con)
{
// Initialize EGL and load GL routines
if (!initEgl())
return false;
gladLoadGL();
// Load our shaders
s_tilemapVsh = loadShaderProgram(GL_VERTEX_SHADER, vertexShaderSource);
s_tilemapFsh = loadShaderProgram(GL_FRAGMENT_SHADER, fragmentShaderSource);
// Configure tilemap dimensions
glProgramUniform2i(s_tilemapVsh, glGetUniformLocation(s_tilemapVsh, "dimensions"),
con->consoleWidth, con->consoleHeight
);
// Create a program pipeline and attach the programs to their respective stages
glGenProgramPipelines(1, &s_tilemapPipeline);
glUseProgramStages(s_tilemapPipeline, GL_VERTEX_SHADER_BIT, s_tilemapVsh);
glUseProgramStages(s_tilemapPipeline, GL_FRAGMENT_SHADER_BIT, s_tilemapFsh);
// Create a VAO and a VBO for the tilemap
glGenVertexArrays(1, &s_tilemapVao);
glBindVertexArray(s_tilemapVao);
// Allocate the tilemap data
glGenBuffers(1, &s_tilemapVbo);
glBindBuffer(GL_ARRAY_BUFFER, s_tilemapVbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(uint16_t)*con->consoleWidth*con->consoleHeight, nullptr, GL_DYNAMIC_DRAW);
// Configure the only vertex attribute (which is per-instance)
glVertexAttribIPointer(0, 1, GL_UNSIGNED_SHORT, sizeof(uint16_t), (void*)0);
glVertexAttribDivisor(0, 1);
glEnableVertexAttribArray(0);
// We're done with the VBO/VAO, unbind them
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// Allocate the tilemap and clear it
s_tilemap = new uint16_t[con->consoleWidth*con->consoleHeight];
memset(s_tilemap, 0, sizeof(uint16_t)*con->consoleWidth*con->consoleHeight);
// Unpack 1bpp tileset into a texture image OpenGL can load
uint8_t* tileset = new uint8_t[con->font.numChars*con->font.tileWidth*con->font.tileHeight];
unsigned bytesPerRow = (con->font.tileWidth+7)/8;
for (unsigned i = 0; i < con->font.numChars; i ++)
{
const uint8_t* tile = (const uint8_t*)con->font.gfx + con->font.tileHeight*bytesPerRow*i;
uint8_t* data = &tileset[con->font.tileWidth*con->font.tileHeight*i];
for (unsigned j = 0; j < con->font.tileHeight; j ++)
{
//const uint8_t* row = &tile[bytesPerRow*(con->font.tileHeight-1-j)];
const uint8_t* row = &tile[bytesPerRow*(con->font.tileHeight-j)];
uint8_t c = 0;
for (unsigned k = 0; k < con->font.tileWidth; k ++)
{
if (!(k & 7))
c = *--row;
*data++ = (c & 0x80) ? 0xFF : 0x00;
c <<= 1;
}
}
}
// Create tileset texture from the unpacked tileset image
glGenTextures(1, &s_tilesetTex);
glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
glBindTexture(GL_TEXTURE_2D_ARRAY, s_tilesetTex);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // can also use GL_LINEAR here
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_R8, con->font.tileWidth, con->font.tileHeight, con->font.numChars, 0, GL_RED, GL_UNSIGNED_BYTE, tileset);
delete[] tileset;
// Bind the texture unit to the fragment shader
glProgramUniform1i(s_tilemapFsh, glGetUniformLocation(s_tilemapFsh, "tileset"), 0); // texunit 0
// Other miscellaneous init
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
//glEnable(GL_BLEND);
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Configure viewport
glViewport(0, 0, 1280, 720);
return true;
}
void GpuConsole::deinit(PrintConsole* con)
{
glDeleteTextures(1, &s_tilesetTex);
glDeleteBuffers(1, &s_tilemapVbo);
glDeleteVertexArrays(1, &s_tilemapVao);
glDeleteProgramPipelines(1, &s_tilemapPipeline);
glDeleteProgram(s_tilemapFsh);
glDeleteProgram(s_tilemapVsh);
delete[] s_tilemap;
deinitEgl();
}
void GpuConsole::drawChar(PrintConsole* con, int x, int y, int c)
{
int writingColor = con->fg;
int screenColor = con->bg;
if (con->flags & CONSOLE_COLOR_BOLD) {
writingColor += 8;
} else if (con->flags & CONSOLE_COLOR_FAINT) {
// Not supported yet
//writingColor += 16;
}
if (con->flags & CONSOLE_COLOR_REVERSE) {
int tmp = writingColor;
writingColor = screenColor;
screenColor = tmp;
}
s_tilemap[y*con->consoleWidth+x] = MakeTilemapEntry(c, false, false, writingColor);
}
void GpuConsole::scrollWindow(PrintConsole* con)
{
for (int y = 0; y < con->windowHeight-1; y ++)
memcpy(
&s_tilemap[(con->windowY+y+0)*con->consoleWidth + con->windowX],
&s_tilemap[(con->windowY+y+1)*con->consoleWidth + con->windowX],
sizeof(uint16_t)*con->windowWidth);
}
void GpuConsole::flushAndSwap(PrintConsole* con)
{
// Clear the framebuffer
glClearColor(0x10/255.0f, 0x10/255.0f, 0x10/255.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Update tilemap
glBindBuffer(GL_ARRAY_BUFFER, s_tilemapVbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(uint16_t)*con->consoleWidth*con->consoleHeight, s_tilemap);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// Draw the tilemap
glBindProgramPipeline(s_tilemapPipeline);
glBindVertexArray(s_tilemapVao);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, con->consoleWidth*con->consoleHeight);
glBindVertexArray(0);
// Swap buffers
eglSwapBuffers(s_display, s_surface);
}
bool GpuConsole::initEgl()
{
// Connect to the EGL default display
s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!s_display)
{
TRACE("Could not connect to display! error: %d", eglGetError());
goto _fail0;
}
// Initialize the EGL display connection
eglInitialize(s_display, nullptr, nullptr);
// Select OpenGL (Core) as the desired graphics API
if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE)
{
TRACE("Could not set API! error: %d", eglGetError());
goto _fail1;
}
// Get an appropriate EGL framebuffer configuration
EGLConfig config;
EGLint numConfigs;
static const EGLint framebufferAttributeList[] =
{
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_NONE
};
eglChooseConfig(s_display, framebufferAttributeList, &config, 1, &numConfigs);
if (numConfigs == 0)
{
TRACE("No config found! error: %d", eglGetError());
goto _fail1;
}
// Create an EGL window surface
s_surface = eglCreateWindowSurface(s_display, config, nwindowGetDefault(), nullptr);
if (!s_surface)
{
TRACE("Surface creation failed! error: %d", eglGetError());
goto _fail1;
}
// Create an EGL rendering context
static const EGLint contextAttributeList[] =
{
EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR,
EGL_CONTEXT_MAJOR_VERSION_KHR, 4,
EGL_CONTEXT_MINOR_VERSION_KHR, 3,
EGL_NONE
};
s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList);
if (!s_context)
{
TRACE("Context creation failed! error: %d", eglGetError());
goto _fail2;
}
// Connect the context to the surface
eglMakeCurrent(s_display, s_surface, s_surface, s_context);
return true;
_fail2:
eglDestroySurface(s_display, s_surface);
s_surface = nullptr;
_fail1:
eglTerminate(s_display);
s_display = nullptr;
_fail0:
return false;
}
void GpuConsole::deinitEgl()
{
if (s_display)
{
eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (s_context)
{
eglDestroyContext(s_display, s_context);
s_context = nullptr;
}
if (s_surface)
{
eglDestroySurface(s_display, s_surface);
s_surface = nullptr;
}
eglTerminate(s_display);
s_display = nullptr;
}
}
extern "C" ConsoleRenderer* getDefaultConsoleRenderer(void)
{
static GpuConsole s_gpuConsole;
return &s_gpuConsole;
}