#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; int width, height; scene * current_scene = nullptr; bool running = false; bool had_initial_resize = 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); } scene * get_scene() { if (current_scene) return current_scene; return parent; } }; app::app(std::string const & name) : app(name, 0) {} app::app(std::string const & name, int multisampling) : pimpl_{std::make_unique(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::GetLeastMajorVersion()); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl::sys::GetLeastMinorVersion()); 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); if (multisampling == 0) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); } else { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, multisampling); } impl().window = SDL_CreateWindow(name.data(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_MAXIMIZED); 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::LoadFunctions()) throw std::runtime_error("Failed to load OpenGL functions"); auto vendor = gl::GetString(gl::VENDOR); auto renderer = gl::GetString(gl::RENDERER); log::info() << "Initialized OpenGL " << gl::sys::GetMajorVersion() << '.' << gl::sys::GetMinorVersion() << ", " << vendor << ", " << renderer; SDL_GetWindowSize(impl().window, &impl().width, &impl().height); } app::~app() {} bool app::running() const { return impl().running; } void app::stop() { impl().running = false; } void app::on_resize(int width, int height) { gl::Viewport(0, 0, width, height); impl().width = width; impl().height = height; } void app::on_quit() { impl().running = false; } void app::poll_events() { auto handler = 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; handler->on_resize(e.window.data1, e.window.data2); 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; } } void app::render() { gl::ClearColor(0.7f, 0.7f, 1.f, 1.f); gl::Clear(gl::COLOR_BUFFER_BIT); } void app::run() { impl().running = true; if (impl().current_scene == nullptr) impl().get_scene()->on_scene_enter(); while (running()) { poll_events(); auto handler = 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->render(); SDL_GL_SwapWindow(impl().window); } } scene * app::set_scene(scene * s) { impl().get_scene()->on_scene_exit(); impl().had_initial_resize = false; std::swap(s, impl().current_scene); impl().get_scene()->on_scene_enter(); 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(); } int app::width() const { return impl().width; } int app::height() const { return impl().height; } }