1/*!
2 * \file sdl_demo.cpp
3 * \brief file sdl_demo.cpp
4 *
5 * Adapted from: sdl_demo.cpp of WRATH:
6 *
7 * Copyright 2013 by Nomovok Ltd.
8 * Contact: info@nomovok.com
9 * This Source Code Form is subject to the
10 * terms of the Mozilla Public License, v. 2.0.
11 * If a copy of the MPL was not distributed with
12 * this file, You can obtain one at
13 * http://mozilla.org/MPL/2.0/.
14 *
15 * \author Kevin Rogovin <kevin.rogovin@nomovok.com>
16 * \author Kevin Rogovin <kevin.rogovin@gmail.com>
17 *
18 */
19
20
21#include <typeinfo>
22#include <fstream>
23#include <SDL_syswm.h>
24#include <SDL_video.h>
25#include <SDL_render.h>
26#include <SDL_surface.h>
27
28#include <fastuidraw/util/vecN.hpp>
29#include <fastuidraw/util/fastuidraw_memory.hpp>
30#include <fastuidraw/gl_backend/gl_binding.hpp>
31#include <fastuidraw/gl_backend/gl_get.hpp>
32
33#include "generic_command_line.hpp"
34#include "simple_time.hpp"
35#include "stream_holder.hpp"
36#include "sdl_demo.hpp"
37
38namespace
39{
40 int
41 GetSDLGLValue(SDL_GLattr arg)
42 {
43 int R(0);
44 SDL_GL_GetAttribute(arg, &R);
45 return R;
46 }
47
48 void
49 print_gl_extensions(std::ostream &dst)
50 {
51 int cnt;
52
53 cnt = fastuidraw::gl::context_get<GLint>(GL_NUM_EXTENSIONS);
54 dst << "\nGL_EXTENSIONS(" << cnt << "):";
55 for(int i = 0; i < cnt; ++i)
56 {
57 dst << "\n\t" << fastuidraw_glGetStringi(GL_EXTENSIONS, i);
58 }
59 }
60
61 bool
62 is_help_request(const std::string &v)
63 {
64 return v==std::string("-help")
65 or v==std::string("--help")
66 or v==std::string("-h");
67 }
68
69 void
70 reverse_y_of_sdl_event(int h, SDL_Event &ev)
71 {
72 switch(ev.type)
73 {
74 case SDL_MOUSEBUTTONUP:
75 case SDL_MOUSEBUTTONDOWN:
76 ev.button.y = h - ev.button.y;
77 break;
78
79 case SDL_MOUSEMOTION:
80 ev.motion.y = h - ev.motion.y;
81 ev.motion.yrel = -ev.motion.yrel;
82 break;
83 }
84 }
85
86 void*
87 get_proc(fastuidraw::c_string proc_name)
88 {
89 return SDL_GL_GetProcAddress(proc_name);
90 }
91
92 class OstreamLogger:public fastuidraw::gl_binding::CallbackGL
93 {
94 public:
95 explicit
96 OstreamLogger(const fastuidraw::reference_counted_ptr<StreamHolder> &str):
97 m_str(str)
98 {}
99
100 virtual
101 void
102 pre_call(fastuidraw::c_string call_string_values,
103 fastuidraw::c_string call_string_src,
104 fastuidraw::c_string function_name,
105 void *function_ptr,
106 fastuidraw::c_string src_file, int src_line);
107
108 virtual
109 void
110 post_call(fastuidraw::c_string call_string_values,
111 fastuidraw::c_string call_string_src,
112 fastuidraw::c_string function_name,
113 fastuidraw::c_string error_string,
114 void *function_ptr,
115 fastuidraw::c_string src_file, int src_line);
116
117 virtual
118 void
119 message(fastuidraw::c_string message,
120 fastuidraw::c_string src_file, int src_line);
121
122 private:
123 fastuidraw::reference_counted_ptr<StreamHolder> m_str;
124 };
125}
126
127//////////////////////////
128// OstreamLogger methods
129void
130OstreamLogger::
131pre_call(fastuidraw::c_string call_string_values,
132 fastuidraw::c_string call_string_src,
133 fastuidraw::c_string function_name,
134 void *function_ptr,
135 fastuidraw::c_string src_file, int src_line)
136{
137 FASTUIDRAWunused(call_string_src);
138 FASTUIDRAWunused(function_name);
139 FASTUIDRAWunused(function_ptr);
140 m_str->stream() << "Pre: [" << src_file << "," << src_line << "] "
141 << call_string_values << "\n";
142}
143
144void
145OstreamLogger::
146post_call(fastuidraw::c_string call_string_values,
147 fastuidraw::c_string call_string_src,
148 fastuidraw::c_string function_name,
149 fastuidraw::c_string error_string,
150 void *function_ptr,
151 fastuidraw::c_string src_file, int src_line)
152{
153 FASTUIDRAWunused(call_string_src);
154 FASTUIDRAWunused(function_name);
155 FASTUIDRAWunused(function_ptr);
156 FASTUIDRAWunused(error_string);
157 m_str->stream() << "Post: [" << src_file << "," << src_line << "] "
158 << call_string_values;
159
160 if (error_string && *error_string)
161 {
162 m_str->stream() << "{" << error_string << "}";
163 }
164 m_str->stream() << "\n";
165}
166
167void
168OstreamLogger::
169message(fastuidraw::c_string message,
170 fastuidraw::c_string src_file,
171 int src_line)
172{
173 m_str->stream() << "Message: [" << src_file << "," << src_line << "] "
174 << message << "\n";
175}
176
177////////////////////////////
178// sdl_demo methods
179sdl_demo::
180sdl_demo(const std::string &about_text, bool dimensions_must_match_default_value):
181 m_handle_events(true),
182 m_about(command_line_argument::tabs_to_spaces(command_line_argument::format_description_string("", about_text))),
183 m_common_label("Screen and Context Option", *this),
184 m_red_bits(8, "red_bits",
185 "Bpp of red channel, non-positive values mean use SDL defaults",
186 *this),
187 m_green_bits(8, "green_bits",
188 "Bpp of green channel, non-positive values mean use SDL defaults",
189 *this),
190 m_blue_bits(8, "blue_bits",
191 "Bpp of blue channel, non-positive values mean use SDL defaults",
192 *this),
193 m_alpha_bits(8, "alpha_bits",
194 "Bpp of alpha channel, non-positive values mean use SDL defaults",
195 *this),
196 m_depth_bits(24, "depth_bits",
197 "Bpp of depth buffer, non-positive values mean use SDL defaults",
198 *this),
199 m_stencil_bits(8, "stencil_bits",
200 "Bpp of stencil buffer, non-positive values mean use SDL defaults",
201 *this),
202 m_fullscreen(false, "fullscreen", "fullscreen mode", *this),
203 m_hide_cursor(false, "hide_cursor", "If true, hide the mouse cursor with a SDL call", *this),
204 m_use_msaa(false, "enable_msaa", "If true enables MSAA", *this),
205 m_msaa(4, "msaa_samples",
206 "If greater than 0, specifies the number of samples "
207 "to request for MSAA. If not, SDL will choose the "
208 "sample count as the highest available value",
209 *this),
210 m_width(800, "width", "window width", *this),
211 m_height(480, "height", "window height", *this),
212 m_dimensions_must_match(dimensions_must_match_default_value, "dimensions_must_match",
213 "If true, then will abort if the created window dimensions do not "
214 "match precisely the width and height parameters", *this),
215 m_bpp(32, "bpp", "bits per pixel", *this),
216 m_log_gl_commands("", "log_gl", "if non-empty, GL commands are logged to the named file. "
217 "If value is stderr then logged to stderr, if value is stdout logged to stdout", *this),
218 m_print_gl_info(false, "print_gl_info", "If true print to stdout GL information", *this),
219 m_swap_interval(-1, "swap_interval",
220 "If set, pass the specified value to SDL_GL_SetSwapInterval, "
221 "a value of 0 means no vsync, a value of 1 means vsync and "
222 "a value of -1, if the platform supports, late swap tearing "
223 "as found in extensions GLX_EXT_swap_control_tear and "
224 "WGL_EXT_swap_control_tear. STRONG REMINDER: the value is "
225 "only passed to SDL_GL_SetSwapInterval if the value is set "
226 "at command line", *this),
227 #ifdef FASTUIDRAW_GL_USE_GLES
228 m_gl_major(3, "gles_major", "GLES major version", *this),
229 m_gl_minor(0, "gles_minor", "GLES minor version", *this),
230 #else
231 m_gl_major(3, "gl_major", "GL major version", *this),
232 m_gl_minor(3, "gl_minor", "GL minor version", *this),
233 m_gl_forward_compatible_context(false, "foward_context", "if true request forward compatible context", *this),
234 m_gl_debug_context(false, "debug_context", "if true request a context with debug", *this),
235 m_gl_core_profile(true, "core_context", "if true request a context which is core profile", *this),
236 m_try_to_get_latest_gl_version(true, "try_to_get_latest_gl_version",
237 "If true, first create a GL context the old fashioned way "
238 "and query its context version and then max that value with "
239 "the requested version before making the context used by the application",
240 *this),
241 #endif
242
243 m_show_framerate(false, "show_framerate", "if true show the cumulative framerate at end", *this),
244 m_num_warm_up_frames(10, "num_warm_up_frames",
245 "Number of warm-up frames to ignore in timing the average framerate", *this),
246 m_reverse_event_y(false),
247 m_window(nullptr),
248 m_ctx(nullptr)
249{
250
251}
252
253sdl_demo::
254~sdl_demo()
255{
256 if (m_window)
257 {
258 if (m_ctx)
259 {
260 SDL_GL_MakeCurrent(m_window, nullptr);
261 SDL_GL_DeleteContext(m_ctx);
262 }
263
264 SDL_ShowCursor(SDL_ENABLE);
265 SDL_SetWindowGrab(m_window, SDL_FALSE);
266
267 SDL_DestroyWindow(m_window);
268 SDL_Quit();
269 }
270}
271
272void
273sdl_demo::
274set_sdl_gl_context_attributes(void)
275{
276 #ifdef FASTUIDRAW_GL_USE_GLES
277 {
278 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, m_gl_major.value());
279 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, m_gl_minor.value());
280 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
281 }
282 #else
283 {
284 if (m_gl_major.value() >= 3)
285 {
286 int context_flags(0);
287 int profile_mask(0);
288
289 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, m_gl_major.value());
290 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, m_gl_minor.value());
291
292 if (m_gl_forward_compatible_context.value())
293 {
294 context_flags |= SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG;
295 }
296
297 if (m_gl_debug_context.value())
298 {
299 context_flags |= SDL_GL_CONTEXT_DEBUG_FLAG;
300 }
301
302 if (m_gl_core_profile.value())
303 {
304 profile_mask = SDL_GL_CONTEXT_PROFILE_CORE;
305 }
306 else
307 {
308 profile_mask = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
309 }
310 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, context_flags);
311 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profile_mask);
312 }
313 }
314 #endif
315}
316
317void
318sdl_demo::
319create_sdl_gl_context(void)
320{
321 #ifdef FASTUIDRAW_GL_USE_GLES
322 {
323 set_sdl_gl_context_attributes();
324 m_ctx = SDL_GL_CreateContext(m_window);
325 }
326 #else
327 {
328 if (!m_try_to_get_latest_gl_version.value())
329 {
330 set_sdl_gl_context_attributes();
331 m_ctx = SDL_GL_CreateContext(m_window);
332 return;
333 }
334
335 /* Some WGL/GLX implementations will only give the exact GL
336 * version requested, but for our purposes we really want the
337 * latest version we can get. Very often, by having SDL create
338 * a context the old-fashioned way, we can get a context of
339 * the greatest version for compatibility profiles. We get SDL to
340 * make a GL context the old-fashioned way by NOT setting any
341 * of the SDL-GL attributes related to context versions/profiles
342 */
343 m_ctx = SDL_GL_CreateContext(m_window);
344 if (m_ctx == nullptr)
345 {
346 std::cerr << "Unable to create vanilla GL context: " << SDL_GetError() << "\n";
347 return;
348 }
349 SDL_GL_MakeCurrent(m_window, m_ctx);
350
351 /* Query the version of a context made the old-way and max
352 * that value with the requested version; note that we
353 * CANNOT use ngl_ system because (1) the get_proc function
354 * is not yet assigned and (2) some GL implementation on
355 * MS-Windows have that the functions returned by
356 * wglGetProcAddress are only good for the context that made
357 * them (shudders).
358 */
359 PFNGLGETINTEGERVPROC get_integer;
360 get_integer = (PFNGLGETINTEGERVPROC)get_proc("glGetIntegerv");
361 if (get_integer)
362 {
363 fastuidraw::ivec2 ver(0, 0);
364 fastuidraw::ivec2 req(m_gl_major.value(), m_gl_minor.value());
365
366 get_integer(GL_MAJOR_VERSION, &ver.x());
367 get_integer(GL_MINOR_VERSION, &ver.y());
368 req = fastuidraw::t_max(ver, req);
369 m_gl_major.value() = req.x();
370 m_gl_minor.value() = req.y();
371 }
372
373 SDL_GL_MakeCurrent(m_window, nullptr);
374 SDL_GL_DeleteContext(m_ctx);
375 set_sdl_gl_context_attributes();
376 m_ctx = SDL_GL_CreateContext(m_window);
377 }
378 #endif
379}
380
381enum fastuidraw::return_code
382sdl_demo::
383init_sdl(void)
384{
385 #ifdef _WIN32
386 {
387 SetProcessDPIAware();
388 }
389 #endif
390
391 if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
392 {
393 std::cerr << "\nFailed on SDL_Init\n";
394 return fastuidraw::routine_fail;
395 }
396
397 int video_flags;
398 video_flags = SDL_WINDOW_RESIZABLE;
399
400 if (m_fullscreen.value())
401 {
402 video_flags = video_flags | SDL_WINDOW_FULLSCREEN;
403 }
404
405 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
406 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, m_stencil_bits.value());
407 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, m_depth_bits.value());
408 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, m_red_bits.value());
409 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, m_green_bits.value());
410 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, m_blue_bits.value());
411 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, m_alpha_bits.value());
412 if (m_use_msaa.value())
413 {
414 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
415 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, m_msaa.value());
416 }
417
418 video_flags |= SDL_WINDOW_OPENGL;
419 m_window = SDL_CreateWindow("",
420 SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
421 m_width.value(),
422 m_height.value(),
423 video_flags);
424
425 if (m_window == nullptr)
426 {
427 std::cerr << "\nFailed on SDL_SetVideoMode\n";
428 return fastuidraw::routine_fail;
429 }
430
431 if (m_dimensions_must_match.value())
432 {
433 int w, h;
434 bool is_fullscreen;
435 is_fullscreen = (SDL_GetWindowFlags(m_window) & SDL_WINDOW_FULLSCREEN) != 0;
436 SDL_GetWindowSize(m_window, &w, &h);
437 if (w != m_width.value() || h != m_height.value() || is_fullscreen != m_fullscreen.value())
438 {
439 std::cerr << "\nDimensions did not match and required to match\n";
440 return fastuidraw::routine_fail;
441 }
442 }
443
444 fastuidraw::reference_counted_ptr<StreamHolder> str;
445 if (!m_log_gl_commands.value().empty())
446 {
447 str = FASTUIDRAWnew StreamHolder(m_log_gl_commands.value());
448 }
449
450 create_sdl_gl_context();
451 if (m_ctx == nullptr)
452 {
453 std::cerr << "Unable to create GL context: " << SDL_GetError() << "\n";
454 return fastuidraw::routine_fail;
455 }
456 SDL_GL_MakeCurrent(m_window, m_ctx);
457
458 if (m_swap_interval.set_by_command_line())
459 {
460 if (SDL_GL_SetSwapInterval(m_swap_interval.value()) != 0)
461 {
462 std::cerr << "Warning unable to set swap interval: "
463 << SDL_GetError() << "\n";
464 }
465 }
466 fastuidraw::gl_binding::get_proc_function(get_proc);
467
468 if (m_hide_cursor.value())
469 {
470 SDL_ShowCursor(SDL_DISABLE);
471 }
472
473 if (str)
474 {
475 m_gl_logger = FASTUIDRAWnew OstreamLogger(str);
476 }
477
478 if (m_print_gl_info.value())
479 {
480 std::cout << "\nSwapInterval: " << SDL_GL_GetSwapInterval()
481 << "\ndepth bits: " << GetSDLGLValue(SDL_GL_DEPTH_SIZE)
482 << "\nstencil bits: " << GetSDLGLValue(SDL_GL_STENCIL_SIZE)
483 << "\nred bits: " << GetSDLGLValue(SDL_GL_RED_SIZE)
484 << "\ngreen bits: " << GetSDLGLValue(SDL_GL_GREEN_SIZE)
485 << "\nblue bits: " << GetSDLGLValue(SDL_GL_BLUE_SIZE)
486 << "\nalpha bits: " << GetSDLGLValue(SDL_GL_ALPHA_SIZE)
487 << "\ndouble buffered: " << GetSDLGLValue(SDL_GL_DOUBLEBUFFER)
488 << "\nGL_MAJOR_VERSION: " << fastuidraw::gl::context_get<GLint>(GL_MAJOR_VERSION)
489 << "\nGL_MINOR_VERSION: " << fastuidraw::gl::context_get<GLint>(GL_MINOR_VERSION)
490 << "\nGL_VERSION string:" << fastuidraw_glGetString(GL_VERSION)
491 << "\nGL_VENDOR:" << fastuidraw_glGetString(GL_VENDOR)
492 << "\nGL_RENDERER:" << fastuidraw_glGetString(GL_RENDERER)
493 << "\nGL_SHADING_LANGUAGE_VERSION:" << fastuidraw_glGetString(GL_SHADING_LANGUAGE_VERSION)
494 << "\nGL_MAX_VARYING_COMPONENTS:" << fastuidraw::gl::context_get<GLint>(GL_MAX_VARYING_COMPONENTS)
495 << "\nGL_MAX_VERTEX_ATTRIBS:" << fastuidraw::gl::context_get<GLint>(GL_MAX_VERTEX_ATTRIBS)
496 << "\nGL_MAX_VERTEX_TEXTURE_IMAGE_UNITS:" << fastuidraw::gl::context_get<GLint>(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS)
497 << "\nGL_MAX_VERTEX_UNIFORM_BLOCKS:" << fastuidraw::gl::context_get<GLint>(GL_MAX_VERTEX_UNIFORM_BLOCKS)
498 << "\nGL_MAX_FRAGMENT_UNIFORM_BLOCKS:" << fastuidraw::gl::context_get<GLint>(GL_MAX_FRAGMENT_UNIFORM_BLOCKS)
499 << "\nGL_MAX_COMBINED_UNIFORM_BLOCKS:" << fastuidraw::gl::context_get<GLint>(GL_MAX_COMBINED_UNIFORM_BLOCKS)
500 << "\nGL_MAX_UNIFORM_BLOCK_SIZE:" << fastuidraw::gl::context_get<GLint>(GL_MAX_UNIFORM_BLOCK_SIZE)
501 << "\nGL_MAX_TEXTURE_SIZE: " << fastuidraw::gl::context_get<GLint>(GL_MAX_TEXTURE_SIZE)
502 << "\nGL_MAX_ARRAY_TEXTURE_LAYERS: " << fastuidraw::gl::context_get<GLint>(GL_MAX_ARRAY_TEXTURE_LAYERS)
503 << "\nGL_MAX_TEXTURE_BUFFER_SIZE: " << fastuidraw::gl::context_get<GLint>(GL_MAX_TEXTURE_BUFFER_SIZE);
504
505
506 #ifndef FASTUIDRAW_GL_USE_GLES
507 {
508 std::cout << "\nGL_MAX_GEOMETRY_UNIFORM_BLOCKS:" << fastuidraw::gl::context_get<GLint>(GL_MAX_GEOMETRY_UNIFORM_BLOCKS)
509 << "\nGL_MAX_CLIP_DISTANCES:" << fastuidraw::gl::context_get<GLint>(GL_MAX_CLIP_DISTANCES);
510 }
511 #endif
512
513 print_gl_extensions(std::cout);
514 std::cout << "\n";
515 }
516
517 return fastuidraw::routine_success;
518}
519
520void
521sdl_demo::
522reverse_event_y(bool v)
523{
524 m_reverse_event_y = v;
525}
526
527void
528sdl_demo::
529swap_buffers(unsigned int count)
530{
531 for(unsigned int i = 0; i < count; ++i)
532 {
533 SDL_GL_SwapWindow(m_window);
534 }
535}
536
537
538int
539sdl_demo::
540main(int argc, char **argv)
541{
542 simple_time render_time;
543 unsigned int num_frames;
544
545 if (argc == 2 && is_help_request(argv[1]))
546 {
547 std::cout << m_about << "\n\nUsage: " << argv[0];
548 print_help(std::cout);
549 print_detailed_help(std::cout);
550 return 0;
551 }
552
553 std::cout << "\n\nRunning: \"";
554 for(int i = 0; i < argc; ++i)
555 {
556 std::cout << argv[i] << " ";
557 }
558
559 parse_command_line(argc, argv);
560 std::cout << "\n\n" << std::flush;
561
562 enum fastuidraw::return_code R;
563 R = init_sdl();
564 int w, h;
565
566 if (R == fastuidraw::routine_fail)
567 {
568 return -1;
569 }
570
571 m_run_demo = true;
572 SDL_GetWindowSize(m_window, &w, &h);
573 init_gl(w, h);
574
575 num_frames = 0;
576 while(m_run_demo)
577 {
578 if (num_frames == m_num_warm_up_frames.value())
579 {
580 render_time.restart();
581 }
582
583 pre_draw_frame();
584 draw_frame();
585 post_draw_frame();
586 swap_buffers();
587 ++num_frames;
588
589 if (m_run_demo && m_handle_events)
590 {
591 SDL_Event ev;
592 while(m_run_demo && m_handle_events && SDL_PollEvent(&ev))
593 {
594 if (m_reverse_event_y)
595 {
596 int w, h;
597 FASTUIDRAWassert(m_window);
598 SDL_GetWindowSize(m_window, &w, &h);
599 reverse_y_of_sdl_event(h, ev);
600 }
601 handle_event(ev);
602 }
603 }
604 }
605
606 if (m_show_framerate.value() && num_frames > m_num_warm_up_frames.value())
607 {
608 int32_t ms;
609 float msf, numf;
610
611 num_frames -= m_num_warm_up_frames.value();
612
613 ms = render_time.elapsed();
614 numf = static_cast<float>(std::max(1u, num_frames));
615 msf = static_cast<float>(std::max(1, ms));
616 std::cout << "Rendered " << num_frames << " in " << ms << " ms.\n"
617 << "ms/frame = " << msf / numf << "\n"
618 << "FPS = " << 1000.0f * numf / msf << "\n";
619 }
620
621 return m_return_value;
622}
623
624fastuidraw::ivec2
625sdl_demo::
626dimensions(void)
627{
628 fastuidraw::ivec2 return_value;
629
630 FASTUIDRAWassert(m_window);
631 SDL_GetWindowSize(m_window, &return_value.x(), &return_value.y());
632 return return_value;
633}
634