308 lines
6.9 KiB
C++
308 lines
6.9 KiB
C++
#include <psemek/app/app.hpp>
|
|
#include <psemek/gfx/gl.hpp>
|
|
#include <psemek/log/log.hpp>
|
|
#include <psemek/sdl2/init.hpp>
|
|
|
|
#include <SDL2/SDL.h>
|
|
|
|
#include <vector>
|
|
|
|
namespace psemek::app
|
|
{
|
|
|
|
using clock = std::chrono::high_resolution_clock;
|
|
|
|
struct app::impl
|
|
{
|
|
app * parent;
|
|
|
|
std::shared_ptr<void> sdl_init;
|
|
SDL_Window * window = nullptr;
|
|
SDL_GLContext gl_context = nullptr;
|
|
|
|
std::vector<std::shared_ptr<scene>> scene_stack;
|
|
|
|
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);
|
|
}
|
|
|
|
std::shared_ptr<scene> get_scene()
|
|
{
|
|
if (!scene_stack.empty())
|
|
return scene_stack.back();
|
|
return std::shared_ptr<scene>(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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
impl().get_scene()->on_scene_exit();
|
|
}
|
|
|
|
void app::push_scene(std::shared_ptr<scene> s)
|
|
{
|
|
impl().get_scene()->on_scene_exit();
|
|
|
|
impl().had_initial_resize = false;
|
|
impl().scene_stack.push_back(std::move(s));
|
|
|
|
impl().get_scene()->on_scene_enter(this);
|
|
}
|
|
|
|
std::shared_ptr<scene> app::pop_scene()
|
|
{
|
|
if (impl().scene_stack.empty())
|
|
return nullptr;
|
|
|
|
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<std::chrono::duration<float>>(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);
|
|
}
|
|
}
|
|
|
|
}
|