1 /************************************************************************************
2
3 AstroMenace
4 Hardcore 3D space scroll-shooter with spaceship upgrade possibilities.
5 Copyright (c) 2006-2019 Mikhail Kurinnoi, Viewizard
6
7
8 AstroMenace is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 AstroMenace is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with AstroMenace. If not, see <https://www.gnu.org/licenses/>.
20
21
22 Website: https://viewizard.com/
23 Project: https://github.com/viewizard/astromenace
24 E-mail: viewizard@viewizard.com
25
26 *************************************************************************************/
27
28 // NOTE SDL2 could be used for OpenGL context setup. "request" OpenGL context version:
29 // SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
30 // SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
31
32 // NOTE glGetStringi() for GL_EXTENSIONS (since OpenGL 3.0)
33 // glGetString() usage with GL_EXTENSIONS is deprecated
34 //
35 // int NumberOfExtensions;
36 // glGetIntegerv(GL_NUM_EXTENSIONS, &NumberOfExtensions);
37 // for (int i = 0; i < NumberOfExtensions; i++) {
38 // const GLubyte *one_string = glGetStringi(GL_EXTENSIONS, i);
39 // }
40
41 // NOTE GL_MAX_TEXTURE_MAX_ANISOTROPY (since OpenGL 4.6)
42 // could be used to replace GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
43
44 #include "graphics_internal.h"
45 #include "graphics.h"
46 #include "extensions.h"
47
48 namespace viewizard {
49
50 namespace {
51
52 // hardware device capabilities
53 sDevCaps DevCaps{};
54
55 // for 2D we need fixed internal resolution, since window's size could be
56 // different and we should guaranty, that 2D looks same for all window's sizes,
57 // it is important to understand, that we don't use this values directly,
58 // but only for 'mapping' internal window coordinates into real window coordinates
59 float InternalWidth{0.0f};
60 float InternalHeight{0.0f};
61 bool InternalResolution{false};
62
63 // pointer to main window structure
64 SDL_Window *SDLWindow{nullptr};
65 // pointer to OpenGL context
66 SDL_GLContext GLContext{nullptr};
67
68 // main FBO
69 std::shared_ptr<sFBO> MainFBO{};
70 // resolve FBO (for blit main FBO with multisample)
71 std::shared_ptr<sFBO> ResolveFBO{};
72
73 } // unnamed namespace
74
75
76 /*
77 * Get SDL window handle.
78 */
vw_GetSDLWindow()79 SDL_Window *vw_GetSDLWindow()
80 {
81 assert(SDLWindow);
82
83 return SDLWindow;
84 }
85
86 /*
87 * Check supported OpenGL extension.
88 */
ExtensionSupported(const char * Extension)89 static bool ExtensionSupported(const char *Extension)
90 {
91 char *extensions;
92 extensions = (char *) glGetString(GL_EXTENSIONS); // WARNING fix conversion
93 if (strstr(extensions, Extension) != nullptr)
94 return true;
95 return false;
96 }
97
98 /*
99 * Create window.
100 */
vw_CreateWindow(const char * Title,int Width,int Height,bool Fullscreen,int DisplayIndex)101 bool vw_CreateWindow(const char *Title, int Width, int Height, bool Fullscreen, int DisplayIndex)
102 {
103 Uint32 Flags{SDL_WINDOW_OPENGL};
104
105 if (Fullscreen)
106 Flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
107
108 SDLWindow = SDL_CreateWindow(Title,
109 SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayIndex),
110 SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayIndex),
111 Width, Height, Flags);
112 if (!SDLWindow) {
113 std::cerr << __func__ << "(): " << "SDL_CreateWindow() failed: " << SDL_GetError() << "\n";
114 std::cerr << __func__ << "(): " << "Can't set video mode " << Width << " x " << Height << "\n\n";
115 return false;
116 }
117
118 if (Fullscreen)
119 std::cout << "Fullscreen mode: ";
120 else
121 std::cout << "Windowed mode: ";
122 std::cout << Width << " x " << Height << "\n\n";
123
124 SDL_DisableScreenSaver();
125
126 return true;
127 }
128
129 /*
130 * Destroy window.
131 */
vw_DestroyWindow()132 void vw_DestroyWindow()
133 {
134 if (!SDLWindow)
135 return;
136
137 SDL_DestroyWindow(SDLWindow);
138 SDLWindow = nullptr;
139 }
140
141 /*
142 * Create OpenGL context.
143 */
vw_CreateOpenGLContext(int VSync)144 bool vw_CreateOpenGLContext(int VSync)
145 {
146 if (!SDLWindow) {
147 std::cerr << __func__ << "(): " << "Can't create OpenGL context, create window first.\n";
148 return false;
149 }
150
151 GLContext = SDL_GL_CreateContext(SDLWindow);
152
153 if (!GLContext) {
154 std::cerr << __func__ << "(): " << "SDL_GL_CreateContext() failed: " << SDL_GetError() << "\n";
155 std::cerr << __func__ << "(): " << "Can't create OpenGL context.\n";
156 return false;
157 }
158
159 if (SDL_GL_SetSwapInterval(VSync) == -1)
160 std::cerr << __func__ << "(): " << "SDL_GL_SetSwapInterval() failed: " << SDL_GetError() << "\n";
161
162 DevCaps.OpenGLmajorVersion = 1;
163 DevCaps.OpenGLminorVersion = 0;
164 DevCaps.MaxTextureWidth = 0;
165 DevCaps.MaxTextureHeight = 0;
166 DevCaps.MaxActiveLights = 0;
167 DevCaps.MaxAnisotropyLevel = 0;
168 DevCaps.FramebufferObjectDepthSize = 0;
169
170 DevCaps.OpenGL_1_3_supported = Initialize_OpenGL_1_3();
171 DevCaps.OpenGL_1_5_supported = Initialize_OpenGL_1_5();
172 DevCaps.OpenGL_2_0_supported = Initialize_OpenGL_2_0();
173 DevCaps.OpenGL_2_1_supported = Initialize_OpenGL_2_1();
174 DevCaps.OpenGL_3_0_supported = Initialize_OpenGL_3_0();
175 DevCaps.OpenGL_4_2_supported = Initialize_OpenGL_4_2();
176 Initialize_GL_NV_framebuffer_multisample_coverage(); // we don't have it in DevCaps, this is 1 function check only
177
178 DevCaps.EXT_texture_compression_s3tc = ExtensionSupported("GL_EXT_texture_compression_s3tc");
179 DevCaps.ARB_texture_compression_bptc = ExtensionSupported("GL_ARB_texture_compression_bptc");
180 DevCaps.ARB_texture_non_power_of_two = ExtensionSupported("GL_ARB_texture_non_power_of_two");
181 DevCaps.SGIS_generate_mipmap = ExtensionSupported("GL_SGIS_generate_mipmap");
182
183 if (ExtensionSupported("GL_EXT_texture_filter_anisotropic")) {
184 glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &DevCaps.MaxAnisotropyLevel);
185 std::cout << "Max anisotropy: " << DevCaps.MaxAnisotropyLevel << "\n";
186 }
187
188 std::cout << "Vendor : " << glGetString(GL_VENDOR) << "\n";
189 std::cout << "Renderer : " << glGetString(GL_RENDERER) << "\n";
190 std::cout << "Version : " << glGetString(GL_VERSION) << "\n";
191 glGetIntegerv(GL_MAJOR_VERSION, &DevCaps.OpenGLmajorVersion);
192 glGetIntegerv(GL_MINOR_VERSION, &DevCaps.OpenGLminorVersion);
193 glGetError(); // reset errors
194 std::cout << "OpenGL Version : " << DevCaps.OpenGLmajorVersion << "." << DevCaps.OpenGLminorVersion << "\n\n";
195 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &DevCaps.MaxTextureHeight);
196 std::cout << "Max texture height: " << DevCaps.MaxTextureHeight << "\n";
197 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &DevCaps.MaxTextureWidth);
198 std::cout << "Max texture width: " << DevCaps.MaxTextureWidth << "\n";
199 glGetIntegerv(GL_MAX_LIGHTS, &DevCaps.MaxActiveLights);
200 std::cout << "Max lights: " << DevCaps.MaxActiveLights << "\n";
201
202 // since we support FBO, should check supported samples
203 if (DevCaps.OpenGL_3_0_supported) {
204 int MaxSamples{0};
205 glGetIntegerv(GL_MAX_SAMPLES_EXT, &MaxSamples);
206 std::cout << "Max Samples: " << MaxSamples << "\n";
207
208 // MSAA
209 for (int i = 2; i <= MaxSamples; i *= 2) {
210 DevCaps.MultisampleCoverageModes.emplace_back(i, i);
211 }
212
213 // CSAA
214 if (ExtensionSupported("GL_NV_framebuffer_multisample_coverage")) {
215 int NumModes{0};
216 glGetIntegerv(GL_MAX_MULTISAMPLE_COVERAGE_MODES_NV, &NumModes);
217 std::cout << "Coverage modes: " << NumModes << "\n";
218 std::vector<int> modes(NumModes * 2);
219 glGetIntegerv(GL_MULTISAMPLE_COVERAGE_MODES_NV, modes.data());
220 // fill MultisampleCoverageModes with MSAA/CSAA modes
221 DevCaps.MultisampleCoverageModes.clear();
222 for (int i = 0; i < (NumModes * 2); i += 2) {
223 DevCaps.MultisampleCoverageModes.emplace_back(modes[i + 1], modes[i]);
224 }
225 }
226 }
227
228 #ifndef NDEBUG
229 // print all supported OpenGL extensions (one per line)
230 if (glGetString(GL_EXTENSIONS) != nullptr) {
231 std::string extensions{(char *)glGetString(GL_EXTENSIONS)}; // WARNING fix conversion
232 if (!extensions.empty()) {
233 std::replace(extensions.begin(), extensions.end(), ' ', '\n'); // replace all ' ' to '\n'
234 std::cout << "Supported OpenGL extensions:\n" << extensions << "\n";
235 }
236 }
237 #endif // NDEBUG
238
239 std::cout << "\n";
240
241 return true;
242 }
243
244 /*
245 * Delete OpenGL context.
246 */
vw_DeleteOpenGLContext()247 void vw_DeleteOpenGLContext()
248 {
249 if (!GLContext)
250 return;
251
252 SDL_GL_DeleteContext(GLContext);
253 GLContext = nullptr;
254 }
255
256 /*
257 * Initialize (or reinitialize) and setup OpenGL related stuff.
258 */
vw_InitOpenGLStuff(int Width,int Height,int * MSAA,int * CSAA)259 void vw_InitOpenGLStuff(int Width, int Height, int *MSAA, int *CSAA)
260 {
261 glPixelStorei(GL_PACK_ALIGNMENT, 1);
262 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
263 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
264 glEnable(GL_CULL_FACE);
265 glPolygonMode(GL_FRONT, GL_FILL);
266 glEnable(GL_TEXTURE_2D);
267 glEnable(GL_DEPTH_TEST);
268 glShadeModel(GL_SMOOTH);
269 glClearDepth(1.0);
270 glClearStencil(0);
271 glDepthFunc(GL_LEQUAL);
272 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
273 glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
274 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
275 glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
276
277 if (DevCaps.OpenGL_3_0_supported) {
278 MainFBO = vw_BuildFBO(Width, Height, true, true, *MSAA, CSAA);
279 ResolveFBO = vw_BuildFBO(Width, Height, true, false);
280 if (!MainFBO || !ResolveFBO) {
281 *MSAA = 0;
282 MainFBO.reset();
283 ResolveFBO.reset();
284 DevCaps.FramebufferObjectDepthSize = 0;
285 }
286 } else {
287 *MSAA = 0;
288 MainFBO.reset();
289 ResolveFBO.reset();
290 DevCaps.FramebufferObjectDepthSize = 0;
291 }
292 }
293
294 /*
295 * Release OpenGL related stuff.
296 */
vw_ReleaseOpenGLStuff()297 void vw_ReleaseOpenGLStuff()
298 {
299 vw_ReleaseAllShaders();
300
301 MainFBO.reset();
302 ResolveFBO.reset();
303 }
304
305 /*
306 * Get device capability.
307 */
vw_DevCaps()308 const sDevCaps &vw_DevCaps()
309 {
310 return DevCaps;
311 }
312
313 /*
314 * Internal access to DevCaps, with write access.
315 */
ChangeDevCaps()316 sDevCaps &ChangeDevCaps()
317 {
318 return DevCaps;
319 }
320
321 /*
322 * Resize scene.
323 */
vw_ResizeScene(float FieldOfViewAngle,float AspectRatio,float zNearClip,float zFarClip)324 void vw_ResizeScene(float FieldOfViewAngle, float AspectRatio, float zNearClip, float zFarClip)
325 {
326 glMatrixMode(GL_PROJECTION);
327 glLoadIdentity();
328
329 gluPerspective(FieldOfViewAngle, AspectRatio, zNearClip, zFarClip);
330
331 glMatrixMode(GL_MODELVIEW);
332 glLoadIdentity();
333 }
334
335 /*
336 * Clear buffers.
337 */
vw_Clear(int mask)338 void vw_Clear(int mask)
339 {
340 GLbitfield glmask{0};
341
342 if (mask & 0x1000)
343 glmask = glmask | GL_COLOR_BUFFER_BIT;
344 if (mask & 0x0100)
345 glmask = glmask | GL_DEPTH_BUFFER_BIT;
346 if (mask & 0x0010)
347 glmask = glmask | GL_ACCUM_BUFFER_BIT;
348 if (mask & 0x0001)
349 glmask = glmask | GL_STENCIL_BUFFER_BIT;
350
351 glClear(glmask);
352 }
353
354 /*
355 * Begin rendering.
356 */
vw_BeginRendering(int mask)357 void vw_BeginRendering(int mask)
358 {
359 vw_BindFBO(MainFBO);
360
361 vw_Clear(mask);
362
363 glMatrixMode(GL_MODELVIEW);
364 glLoadIdentity();
365 }
366
367 /*
368 * End rendering.
369 */
vw_EndRendering()370 void vw_EndRendering()
371 {
372 if (MainFBO) {
373 std::shared_ptr<sFBO> tmpEmptyFBO{};
374
375 if (MainFBO->ColorTexture)
376 vw_DrawColorFBO(MainFBO, tmpEmptyFBO);
377 else {
378 // if we use multisamples, should blit to color buffer first
379 vw_BlitFBO(MainFBO, ResolveFBO);
380 vw_DrawColorFBO(ResolveFBO, tmpEmptyFBO);
381 }
382 }
383
384 assert(SDLWindow);
385
386 SDL_GL_SwapWindow(SDLWindow);
387 }
388
389 /*
390 * Set virtual internal resolution size and status.
391 */
vw_SetInternalResolution(float Width,float Height,bool Status)392 void vw_SetInternalResolution(float Width, float Height, bool Status)
393 {
394 InternalResolution = Status;
395
396 if (Status) {
397 InternalWidth = Width;
398 InternalHeight = Height;
399 }
400 }
401
402 /*
403 * Get virtual internal resolution.
404 */
vw_GetInternalResolution(float * Width,float * Height)405 bool vw_GetInternalResolution(float *Width, float *Height)
406 {
407 if (Width)
408 *Width = InternalWidth;
409 if (Height)
410 *Height = InternalHeight;
411
412 return InternalResolution;
413 }
414
415 /*
416 * Set depth range.
417 */
vw_DepthRange(GLdouble zNear,GLdouble zFar)418 void vw_DepthRange(GLdouble zNear, GLdouble zFar)
419 {
420 glDepthRange(zNear, zFar);
421 }
422
423 /*
424 * Set viewport data.
425 */
vw_SetViewport(GLint x,GLint y,GLsizei width,GLsizei height,eOrigin Origin)426 void vw_SetViewport(GLint x, GLint y, GLsizei width, GLsizei height, eOrigin Origin)
427 {
428 assert(SDLWindow);
429
430 if (Origin == eOrigin::upper_left) {
431 int SDLWindowWidth, SDLWindowHeight;
432 SDL_GetWindowSize(SDLWindow, &SDLWindowWidth, &SDLWindowHeight);
433 y = SDLWindowHeight - y - height;
434 }
435
436 glViewport(x, y, width, height);
437 }
438
439 /*
440 * Get viewport data.
441 */
vw_GetViewport(float * x,float * y,float * width,float * height)442 void vw_GetViewport(float *x, float *y, float *width, float *height)
443 {
444 GLfloat buff[4];
445 glGetFloatv(GL_VIEWPORT, buff);
446
447 if (x)
448 *x = buff[0];
449 if (y)
450 *y = buff[1];
451 if (width)
452 *width = buff[2];
453 if (height)
454 *height = buff[3];
455 }
456
457 /*
458 * Get viewport data.
459 */
vw_GetViewport(int * x,int * y,int * width,int * height)460 void vw_GetViewport(int *x, int *y, int *width, int *height)
461 {
462 GLint buff[4];
463 glGetIntegerv(GL_VIEWPORT, buff);
464
465 if (x)
466 *x = buff[0];
467 if (y)
468 *y = buff[1];
469 if (width)
470 *width = buff[2];
471 if (height)
472 *height = buff[3];
473 }
474
475 /*
476 * Set what facets can be culled.
477 */
vw_CullFace(eCullFace mode)478 void vw_CullFace(eCullFace mode)
479 {
480 if (mode == eCullFace::NONE) {
481 glDisable(GL_CULL_FACE);
482 return;
483 }
484
485 glEnable(GL_CULL_FACE);
486 glCullFace(static_cast<GLenum>(mode));
487 }
488
489 /*
490 * Set the scale and units used to calculate depth values.
491 */
vw_PolygonOffset(bool status,GLfloat factor,GLfloat units)492 void vw_PolygonOffset(bool status, GLfloat factor, GLfloat units)
493 {
494 if (status)
495 glEnable(GL_POLYGON_OFFSET_FILL);
496 else
497 glDisable(GL_POLYGON_OFFSET_FILL);
498
499 glPolygonOffset(factor, units);
500 }
501
502 /*
503 * Specify clear values for the color buffers.
504 */
vw_SetClearColor(GLclampf red,GLclampf green,GLclampf blue,GLclampf alpha)505 void vw_SetClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)
506 {
507 glClearColor(red, green, blue, alpha);
508 }
509
510 /*
511 * Set color.
512 */
vw_SetColor(GLfloat red,GLfloat green,GLfloat blue,GLfloat alpha)513 void vw_SetColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
514 {
515 glColor4f(red, green, blue, alpha);
516 }
517
518 /*
519 * Specifies whether the individual color components in the frame buffer can or cannot be written.
520 */
vw_SetColorMask(GLboolean red,GLboolean green,GLboolean blue,GLboolean alpha)521 void vw_SetColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)
522 {
523 glColorMask(red, green, blue, alpha);
524 }
525
526 /*
527 * Set depth buffer.
528 */
vw_DepthTest(bool mode,eCompareFunc func)529 void vw_DepthTest(bool mode, eCompareFunc func)
530 {
531 if (mode) {
532 glEnable(GL_DEPTH_TEST);
533 glDepthFunc(static_cast<GLenum>(func));
534 } else
535 glDisable(GL_DEPTH_TEST);
536 }
537
538 } // viewizard namespace
539