#include #include #include #include #include #include namespace psemek::app { using clock = std::chrono::high_resolution_clock; struct app::impl { app * parent; std::shared_ptr sdl_init; SDL_Window * window = nullptr; SDL_GLContext gl_context = nullptr; std::vector> scene_stack; bool running = false; bool had_initial_resize = false; bool had_scene_exit = false; clock::time_point start_time; impl(app * parent) : parent(parent) , sdl_init(sdl2::init(SDL_INIT_VIDEO)) {} ~impl() { if (gl_context) SDL_GL_DeleteContext(gl_context); if (window) SDL_DestroyWindow(window); } std::shared_ptr get_scene() { if (!scene_stack.empty()) return scene_stack.back(); return std::shared_ptr(parent, [](scene *){}); } }; app::app(std::string const & name) : app(name, 0) {} app::app(std::string const & name, int multisampling) : app(name, options{multisampling}) {} app::app(std::string const & name, options const & opts) : pimpl_{make_impl(this)} { impl().start_time = clock::now(); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, gl::sys::major_version()); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl::sys::minor_version()); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); if (opts.multisampling == 0) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); } else { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, opts.multisampling); } std::uint32_t flags = SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN; if (!opts.fixed_resolution) flags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED; if (opts.highdpi) flags |= SDL_WINDOW_ALLOW_HIGHDPI; int width = opts.fixed_resolution ? (*opts.fixed_resolution)[0] : 1024; int height = opts.fixed_resolution ? (*opts.fixed_resolution)[1] : 768; impl().window = SDL_CreateWindow(name.data(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, flags); if (!impl().window) sdl2::fail("Failed to create window: "); impl().gl_context = SDL_GL_CreateContext(impl().window); if (!impl().gl_context) sdl2::fail("Failed to create OpenGL context: "); SDL_GL_MakeCurrent(impl().window, impl().gl_context); if (!gl::sys::initialize()) throw std::runtime_error("Failed to load OpenGL functions"); auto vendor = gl::GetString(gl::VENDOR); auto renderer = gl::GetString(gl::RENDERER); int major, minor; gl::GetIntegerv(gl::MAJOR_VERSION, &major); gl::GetIntegerv(gl::MINOR_VERSION, &minor); log::info() << "Initialized OpenGL " << major << '.' << minor << ", " << vendor << ", " << renderer; SDL_GL_GetDrawableSize(impl().window, &width, &height); scene_base::on_resize(width, height); log::info() << "Initial window size: " << width << 'x' << height; SDL_StopTextInput(); } app::~app() {} bool app::running() const { return impl().running; } void app::stop() { impl().running = false; } void app::on_resize(int width, int height) { scene_base::on_resize(width, height); gl::Viewport(0, 0, width, height); } void app::on_quit() { impl().running = false; } void app::poll_events() { auto handler = [this]{ return impl().get_scene(); }; for (SDL_Event e; SDL_PollEvent(&e);) switch (e.type) { case SDL_QUIT: on_quit(); break; case SDL_WINDOWEVENT: switch (e.window.event) { case SDL_WINDOWEVENT_RESIZED: impl().had_initial_resize = true; { int width, height; SDL_GL_GetDrawableSize(impl().window, &width, &height); handler()->on_resize(width, height); log::info() << "Window resized to " << width << 'x' << height; } break; case SDL_WINDOWEVENT_FOCUS_GAINED: handler()->on_focus_gained(); break; case SDL_WINDOWEVENT_FOCUS_LOST: handler()->on_focus_lost(); break; } break; case SDL_MOUSEMOTION: handler()->on_mouse_move(e.motion.x, e.motion.y, e.motion.xrel, e.motion.yrel); break; case SDL_MOUSEWHEEL: handler()->on_mouse_wheel(e.wheel.y); break; case SDL_MOUSEBUTTONDOWN: switch (e.button.button) { case SDL_BUTTON_LEFT: handler()->on_left_button_down(); break; case SDL_BUTTON_MIDDLE: handler()->on_middle_button_down(); break; case SDL_BUTTON_RIGHT: handler()->on_right_button_down(); break; } break; case SDL_MOUSEBUTTONUP: switch (e.button.button) { case SDL_BUTTON_LEFT: handler()->on_left_button_up(); break; case SDL_BUTTON_MIDDLE: handler()->on_middle_button_up(); break; case SDL_BUTTON_RIGHT: handler()->on_right_button_up(); break; } break; case SDL_KEYDOWN: handler()->on_key_down(e.key.keysym.sym); break; case SDL_KEYUP: handler()->on_key_up(e.key.keysym.sym); break; case SDL_TEXTINPUT: handler()->on_text_input(e.text.text); break; } } void app::present() { gl::ClearColor(0.7f, 0.7f, 1.f, 1.f); gl::Clear(gl::COLOR_BUFFER_BIT); } void app::run() { SDL_ShowWindow(impl().window); impl().running = true; if (impl().get_scene().get() == this) on_scene_enter(this); while (running()) { poll_events(); auto handler = [this]{ return impl().get_scene(); }; if (!impl().had_initial_resize) { int w, h; SDL_GetWindowSize(impl().window, &w, &h); impl().had_initial_resize = true; handler()->on_resize(w, h); } if (!running()) break; handler()->update(); handler()->present(); SDL_GL_SwapWindow(impl().window); } if (!impl().had_scene_exit) { impl().had_scene_exit = true; impl().get_scene()->on_scene_exit(); } } void app::push_scene(std::shared_ptr s) { if (!impl().had_scene_exit) { impl().had_scene_exit = true; impl().get_scene()->on_scene_exit(); } impl().had_initial_resize = false; impl().had_scene_exit = false; impl().scene_stack.push_back(std::move(s)); impl().get_scene()->on_scene_enter(this); } std::shared_ptr app::pop_scene() { if (impl().scene_stack.empty()) return nullptr; if (!impl().had_scene_exit) { impl().had_scene_exit = true; impl().get_scene()->on_scene_exit(); } impl().had_initial_resize = false; auto s = std::move(impl().scene_stack.back()); impl().scene_stack.pop_back(); impl().get_scene()->on_scene_enter(this); return s; } void app::show_cursor(bool show) { SDL_ShowCursor(show ? SDL_TRUE : SDL_FALSE); SDL_SetRelativeMouseMode(show ? SDL_FALSE : SDL_TRUE); } float app::time() const { return std::chrono::duration_cast>(clock::now() - impl().start_time).count(); } bool app::vsync() const { return SDL_GL_GetSwapInterval() != 0; } void app::vsync(bool on) { if (on) { // try adaptive vsync if (SDL_GL_SetSwapInterval(-1) != 0) { // failed, try usual vsync then SDL_GL_SetSwapInterval(1); } } else { SDL_GL_SetSwapInterval(0); } } }