-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRenderer.cpp
More file actions
378 lines (318 loc) · 13.4 KB
/
Copy pathRenderer.cpp
File metadata and controls
378 lines (318 loc) · 13.4 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
#include "Renderer.h"
#include "FCT/Runtime/Android_native_app_glue.h"
#include <GLES3/gl3.h>
#include <memory>
#include <vector>
#include <android/imagedecoder.h>
#include "FCT/DebugTools/Android_Out.h"
#include "Shader.h"
#include "Utility.h"
#include "TextureAsset.h"
//! executes glGetString and outputs the result to logcat
#define PRINT_GL_STRING(s) {aout << #s": "<< glGetString(s) << std::endl;}
/*!
* @brief if glGetString returns a space separated list of elements, prints each one on a new line
*
* This works by creating an istringstream of the input c-style string. Then that is used to create
* a vector -- each element of the vector is a new element in the input string. Finally a foreach
* loop consumes this and outputs it to logcat using @a aout
*/
#define PRINT_GL_STRING_AS_LIST(s) { \
std::istringstream extensionStream((const char *) glGetString(s));\
std::vector<std::string> extensionList(\
std::istream_iterator<std::string>{extensionStream},\
std::istream_iterator<std::string>());\
aout << #s":\n";\
for (auto& extension: extensionList) {\
aout << extension << "\n";\
}\
aout << std::endl;\
}
//! Color for cornflower blue. Can be sent directly to glClearColor
#define CORNFLOWER_BLUE 100 / 255.f, 149 / 255.f, 237 / 255.f, 1
// Vertex shader, you'd typically load this from assets
static const char *vertex = R"vertex(#version 300 es
in vec3 inPosition;
in vec2 inUV;
out vec2 fragUV;
uniform mat4 uProjection;
void main() {
fragUV = inUV;
gl_Position = uProjection * vec4(inPosition, 1.0);
}
)vertex";
// Fragment shader, you'd typically load this from assets
static const char *fragment = R"fragment(#version 300 es
precision mediump float;
in vec2 fragUV;
uniform sampler2D uTexture;
out vec4 outColor;
void main() {
outColor = texture(uTexture, fragUV);
}
)fragment";
/*!
* Half the height of the projection matrix. This gives you a renderable area of height 4 ranging
* from -2 to 2
*/
static constexpr float kProjectionHalfHeight = 2.f;
/*!
* The near plane distance for the projection matrix. Since this is an orthographic projection
* matrix, it's convenient to have negative values for sorting (and avoiding z-fighting at 0).
*/
static constexpr float kProjectionNearPlane = -1.f;
/*!
* The far plane distance for the projection matrix. Since this is an orthographic porjection
* matrix, it's convenient to have the far plane equidistant from 0 as the near plane.
*/
static constexpr float kProjectionFarPlane = 1.f;
Renderer::~Renderer() {
if (display_ != EGL_NO_DISPLAY) {
eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (context_ != EGL_NO_CONTEXT) {
eglDestroyContext(display_, context_);
context_ = EGL_NO_CONTEXT;
}
if (surface_ != EGL_NO_SURFACE) {
eglDestroySurface(display_, surface_);
surface_ = EGL_NO_SURFACE;
}
eglTerminate(display_);
display_ = EGL_NO_DISPLAY;
}
}
void Renderer::render() {
// Check to see if the surface has changed size. This is _necessary_ to do every frame when
// using immersive mode as you'll get no other notification that your renderable area has
// changed.
updateRenderArea();
// When the renderable area changes, the projection matrix has to also be updated. This is true
// even if you change from the sample orthographic projection matrix as your aspect ratio has
// likely changed.
if (shaderNeedsNewProjectionMatrix_) {
// a placeholder projection matrix allocated on the stack. Column-major memory layout
float projectionMatrix[16] = {0};
// build an orthographic projection matrix for 2d rendering
Utility::buildOrthographicMatrix(
projectionMatrix,
kProjectionHalfHeight,
float(width_) / height_,
kProjectionNearPlane,
kProjectionFarPlane);
// send the matrix to the shader
// Note: the shader must be active for this to work. Since we only have one shader for this
// demo, we can assume that it's active.
shader_->setProjectionMatrix(projectionMatrix);
// make sure the matrix isn't generated every frame
shaderNeedsNewProjectionMatrix_ = false;
}
// clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
// Render all the models. There's no depth testing in this sample so they're accepted in the
// order provided. But the sample EGL setup requests a 24 bit depth buffer so you could
// configure it at the end of initRenderer
if (!models_.empty()) {
for (const auto &model: models_) {
shader_->drawModel(model);
}
}
// Present the rendered image. This is an implicit glFlush.
auto swapResult = eglSwapBuffers(display_, surface_);
assert(swapResult == EGL_TRUE);
}
void Renderer::initRenderer() {
// Choose your render attributes
constexpr EGLint attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_NONE
};
// The default display is probably what you want on Android
auto display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, nullptr, nullptr);
// figure out how many configs there are
EGLint numConfigs;
eglChooseConfig(display, attribs, nullptr, 0, &numConfigs);
// get the list of configurations
std::unique_ptr<EGLConfig[]> supportedConfigs(new EGLConfig[numConfigs]);
eglChooseConfig(display, attribs, supportedConfigs.get(), numConfigs, &numConfigs);
// Find a config we like.
// Could likely just grab the first if we don't care about anything else in the config.
// Otherwise hook in your own heuristic
auto config = *std::find_if(
supportedConfigs.get(),
supportedConfigs.get() + numConfigs,
[&display](const EGLConfig &config) {
EGLint red, green, blue, depth;
if (eglGetConfigAttrib(display, config, EGL_RED_SIZE, &red)
&& eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &green)
&& eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &blue)
&& eglGetConfigAttrib(display, config, EGL_DEPTH_SIZE, &depth)) {
aout << "Found config with " << red << ", " << green << ", " << blue << ", "
<< depth << std::endl;
return red == 8 && green == 8 && blue == 8 && depth == 24;
}
return false;
});
aout << "Found " << numConfigs << " configs" << std::endl;
aout << "Chose " << config << std::endl;
// create the proper window surface
EGLint format;
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
EGLSurface surface = eglCreateWindowSurface(display, config, app_->window, nullptr);
// Create a GLES 3 context
EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
EGLContext context = eglCreateContext(display, config, nullptr, contextAttribs);
// get some window metrics
auto madeCurrent = eglMakeCurrent(display, surface, surface, context);
assert(madeCurrent);
display_ = display;
surface_ = surface;
context_ = context;
// make width and height invalid so it gets updated the first frame in @a updateRenderArea()
width_ = -1;
height_ = -1;
PRINT_GL_STRING(GL_VENDOR);
PRINT_GL_STRING(GL_RENDERER);
PRINT_GL_STRING(GL_VERSION);
PRINT_GL_STRING_AS_LIST(GL_EXTENSIONS);
shader_ = std::unique_ptr<Shader>(
Shader::loadShader(vertex, fragment, "inPosition", "inUV", "uProjection"));
assert(shader_);
// Note: there's only one shader in this demo, so I'll activate it here. For a more complex game
// you'll want to track the active shader and activate/deactivate it as necessary
shader_->activate();
// setup any other gl related global states
glClearColor(CORNFLOWER_BLUE);
// enable alpha globally for now, you probably don't want to do this in a game
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// get some demo models into memory
createModels();
}
void Renderer::updateRenderArea() {
EGLint width;
eglQuerySurface(display_, surface_, EGL_WIDTH, &width);
EGLint height;
eglQuerySurface(display_, surface_, EGL_HEIGHT, &height);
if (width != width_ || height != height_) {
width_ = width;
height_ = height;
glViewport(0, 0, width, height);
// make sure that we lazily recreate the projection matrix before we render
shaderNeedsNewProjectionMatrix_ = true;
}
}
/**
* @brief Create any demo models we want for this demo.
*/
void Renderer::createModels() {
/*
* This is a square:
* 0 --- 1
* | \ |
* | \ |
* | \ |
* 3 --- 2
*/
std::vector<Vertex> vertices = {
Vertex(Vector3{1, 1, 0}, Vector2{0, 0}), // 0
Vertex(Vector3{-1, 1, 0}, Vector2{1, 0}), // 1
Vertex(Vector3{-1, -1, 0}, Vector2{1, 1}), // 2
Vertex(Vector3{1, -1, 0}, Vector2{0, 1}) // 3
};
std::vector<Index> indices = {
0, 1, 2, 0, 2, 3
};
// loads an image and assigns it to the square.
//
// Note: there is no texture management in this sample, so if you reuse an image be careful not
// to load it repeatedly. Since you get a shared_ptr you can safely reuse it in many models.
auto assetManager = app_->activity->assetManager;
auto spAndroidRobotTexture = TextureAsset::loadAsset(assetManager, "android_robot.png");
// Create a model and put it in the back of the render list.
models_.emplace_back(vertices, indices, spAndroidRobotTexture);
}
void Renderer::handleInput() {
// handle all queued inputs
auto *inputBuffer = android_app_swap_input_buffers(app_);
if (!inputBuffer) {
// no inputs yet.
return;
}
// handle motion events (motionEventsCounts can be 0).
for (auto i = 0; i < inputBuffer->motionEventsCount; i++) {
auto &motionEvent = inputBuffer->motionEvents[i];
auto action = motionEvent.action;
// Find the pointer index, mask and bitshift to turn it into a readable value.
auto pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
>> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
aout << "Pointer(s): ";
// get the x and y position of this event if it is not ACTION_MOVE.
auto &pointer = motionEvent.pointers[pointerIndex];
auto x = GameActivityPointerAxes_getX(&pointer);
auto y = GameActivityPointerAxes_getY(&pointer);
// determine the action type and process the event accordingly.
switch (action & AMOTION_EVENT_ACTION_MASK) {
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_POINTER_DOWN:
aout << "(" << pointer.id << ", " << x << ", " << y << ") "
<< "Pointer Down";
break;
case AMOTION_EVENT_ACTION_CANCEL:
// treat the CANCEL as an UP event: doing nothing in the app, except
// removing the pointer from the cache if pointers are locally saved.
// code pass through on purpose.
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_POINTER_UP:
aout << "(" << pointer.id << ", " << x << ", " << y << ") "
<< "Pointer Up";
break;
case AMOTION_EVENT_ACTION_MOVE:
// There is no pointer index for ACTION_MOVE, only a snapshot of
// all active pointers; app needs to cache previous active pointers
// to figure out which ones are actually moved.
for (auto index = 0; index < motionEvent.pointerCount; index++) {
pointer = motionEvent.pointers[index];
x = GameActivityPointerAxes_getX(&pointer);
y = GameActivityPointerAxes_getY(&pointer);
aout << "(" << pointer.id << ", " << x << ", " << y << ")";
if (index != (motionEvent.pointerCount - 1)) aout << ",";
aout << " ";
}
aout << "Pointer Move";
break;
default:
aout << "Unknown MotionEvent Action: " << action;
}
aout << std::endl;
}
// clear the motion input count in this buffer for main thread to re-use.
android_app_clear_motion_events(inputBuffer);
// handle input key events.
for (auto i = 0; i < inputBuffer->keyEventsCount; i++) {
auto &keyEvent = inputBuffer->keyEvents[i];
aout << "Key: " << keyEvent.keyCode <<" ";
switch (keyEvent.action) {
case AKEY_EVENT_ACTION_DOWN:
aout << "Key Down";
break;
case AKEY_EVENT_ACTION_UP:
aout << "Key Up";
break;
case AKEY_EVENT_ACTION_MULTIPLE:
// Deprecated since Android API level 29.
aout << "Multiple Key Actions";
break;
default:
aout << "Unknown KeyEvent Action: " << keyEvent.action;
}
aout << std::endl;
}
// clear the key input count too.
android_app_clear_key_events(inputBuffer);
}