Grand app refactor: move main to sdl2 lib, make the rest of the engine independent of SDL2
This commit is contained in:
parent
42f986ce4a
commit
b910d16261
29 changed files with 2435 additions and 684 deletions
|
|
@ -8,4 +8,4 @@ endif()
|
|||
|
||||
psemek_add_library(psemek-app ${PSEMEK_APP_HEADERS} ${PSEMEK_APP_SOURCES})
|
||||
target_include_directories(psemek-app PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_link_libraries(psemek-app PUBLIC psemek-log psemek-util psemek-gfx psemek-ui psemek-sdl2)
|
||||
target_link_libraries(psemek-app PUBLIC psemek-log psemek-util psemek-gfx psemek-ui)
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/scene.hpp>
|
||||
#include <psemek/app/scene_manager.hpp>
|
||||
|
||||
#include <psemek/geom/vector.hpp>
|
||||
|
||||
#include <psemek/util/pimpl.hpp>
|
||||
|
||||
#include <SDL2/SDL_keycode.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
static const geom::vector<int, 2> common_resolutions[] =
|
||||
{
|
||||
{1024, 768},
|
||||
{1280, 720},
|
||||
{1280, 1024},
|
||||
{1366, 768},
|
||||
{1440, 900},
|
||||
{1536, 864},
|
||||
{1600, 900},
|
||||
{1920, 1080},
|
||||
};
|
||||
|
||||
struct app
|
||||
: scene_base, scene_manager
|
||||
{
|
||||
struct options
|
||||
{
|
||||
int multisampling = 0;
|
||||
std::optional<geom::vector<int, 2>> fixed_resolution = std::nullopt;
|
||||
bool highdpi = false;
|
||||
};
|
||||
|
||||
app(std::string const & name);
|
||||
app(std::string const & name, int multisampling);
|
||||
app(std::string const & name, options const & opts);
|
||||
~app() override;
|
||||
|
||||
virtual bool running() const;
|
||||
virtual void stop();
|
||||
|
||||
void on_resize(int width, int height) override;
|
||||
|
||||
void present() override;
|
||||
|
||||
void poll_events();
|
||||
void run();
|
||||
|
||||
void push_scene(std::shared_ptr<scene> s) override;
|
||||
std::shared_ptr<scene> pop_scene() override;
|
||||
|
||||
void show_cursor(bool show);
|
||||
bool vsync() const;
|
||||
void vsync(bool on);
|
||||
|
||||
float time() const;
|
||||
|
||||
private:
|
||||
psemek_declare_pimpl
|
||||
};
|
||||
|
||||
}
|
||||
47
libs/app/include/psemek/app/application.hpp
Normal file
47
libs/app/include/psemek/app/application.hpp
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/event_handler.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct application
|
||||
: event_handler
|
||||
{
|
||||
// Data sent to platform backend for initialization
|
||||
struct options
|
||||
{
|
||||
std::string name;
|
||||
int multisampling = 4;
|
||||
bool highdpi = false;
|
||||
};
|
||||
|
||||
// Data received from platform backend after initialization
|
||||
struct context
|
||||
{
|
||||
std::function<void(bool)> show_cursor;
|
||||
std::function<void(bool)> vsync;
|
||||
};
|
||||
|
||||
struct factory
|
||||
{
|
||||
virtual application::options const & options() = 0;
|
||||
virtual std::unique_ptr<application> create(struct application::options const & options, context const & context) = 0;
|
||||
|
||||
virtual ~factory() {}
|
||||
};
|
||||
|
||||
virtual bool running() const = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
virtual void update() = 0;
|
||||
virtual void present() = 0;
|
||||
};
|
||||
|
||||
// Implemented by the user, called by platform backends
|
||||
std::unique_ptr<application::factory> make_application_factory();
|
||||
|
||||
}
|
||||
31
libs/app/include/psemek/app/application_base.hpp
Normal file
31
libs/app/include/psemek/app/application_base.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/application.hpp>
|
||||
#include <psemek/app/event_state.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct application_base
|
||||
: application
|
||||
{
|
||||
void on_event(resize_event const &) override;
|
||||
void on_event(focus_event const &) override;
|
||||
void on_event(mouse_move_event const &) override;
|
||||
void on_event(mouse_wheel_event const &) override;
|
||||
void on_event(mouse_button_event const &) override;
|
||||
void on_event(key_event const &) override;
|
||||
|
||||
void stop() override;
|
||||
bool running() const override;
|
||||
|
||||
event_state const & state() const { return state_; }
|
||||
|
||||
private:
|
||||
bool running_ = true;
|
||||
event_state state_;
|
||||
};
|
||||
|
||||
}
|
||||
36
libs/app/include/psemek/app/default_application_factory.hpp
Normal file
36
libs/app/include/psemek/app/default_application_factory.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/application.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
template <typename Application>
|
||||
std::unique_ptr<application::factory> default_application_factory(application::options const & options)
|
||||
{
|
||||
struct factory_impl
|
||||
: application::factory
|
||||
{
|
||||
application::options opts;
|
||||
|
||||
factory_impl(application::options const & options)
|
||||
: opts(options)
|
||||
{}
|
||||
|
||||
application::options const & options() override
|
||||
{
|
||||
return opts;
|
||||
}
|
||||
|
||||
std::unique_ptr<application> create(application::options const & options, application::context const & context) override
|
||||
{
|
||||
return std::make_unique<Application>(options, context);
|
||||
}
|
||||
};
|
||||
|
||||
return std::make_unique<factory_impl>(options);
|
||||
}
|
||||
|
||||
}
|
||||
20
libs/app/include/psemek/app/event_handler.hpp
Normal file
20
libs/app/include/psemek/app/event_handler.hpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/events.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct event_handler
|
||||
{
|
||||
virtual void on_event(resize_event const &) {}
|
||||
virtual void on_event(focus_event const &) {}
|
||||
virtual void on_event(mouse_move_event const &) {}
|
||||
virtual void on_event(mouse_wheel_event const &) {}
|
||||
virtual void on_event(mouse_button_event const &) {}
|
||||
virtual void on_event(key_event const &) {}
|
||||
|
||||
virtual ~event_handler() {}
|
||||
};
|
||||
|
||||
}
|
||||
56
libs/app/include/psemek/app/event_state.hpp
Normal file
56
libs/app/include/psemek/app/event_state.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/events.hpp>
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct event_state
|
||||
{
|
||||
geom::vector<int, 2> size = {0, 0};
|
||||
bool focus = true;
|
||||
geom::point<int, 2> mouse = {0, 0};
|
||||
int wheel = 0;
|
||||
std::unordered_set<mouse_button> mouse_button_down;
|
||||
std::unordered_set<keycode> key_down;
|
||||
};
|
||||
|
||||
inline void apply(event_state & state, resize_event const & event)
|
||||
{
|
||||
state.size = event.size;
|
||||
}
|
||||
|
||||
inline void apply(event_state & state, focus_event const & event)
|
||||
{
|
||||
state.focus = event.gained;
|
||||
}
|
||||
|
||||
inline void apply(event_state & state, mouse_move_event const & event)
|
||||
{
|
||||
state.mouse = event.position;
|
||||
}
|
||||
|
||||
inline void apply(event_state & state, mouse_wheel_event const & event)
|
||||
{
|
||||
state.wheel += event.delta;
|
||||
}
|
||||
|
||||
inline void apply(event_state & state, mouse_button_event const & event)
|
||||
{
|
||||
if (event.down)
|
||||
state.mouse_button_down.insert(event.button);
|
||||
else
|
||||
state.mouse_button_down.erase(event.button);
|
||||
}
|
||||
|
||||
inline void apply(event_state & state, key_event const & event)
|
||||
{
|
||||
if (event.down)
|
||||
state.key_down.insert(event.key);
|
||||
else
|
||||
state.key_down.erase(event.key);
|
||||
}
|
||||
|
||||
}
|
||||
52
libs/app/include/psemek/app/events.hpp
Normal file
52
libs/app/include/psemek/app/events.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/geom/point.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct resize_event
|
||||
{
|
||||
geom::vector<int, 2> size;
|
||||
};
|
||||
|
||||
struct focus_event
|
||||
{
|
||||
bool gained;
|
||||
};
|
||||
|
||||
struct mouse_move_event
|
||||
{
|
||||
geom::point<int, 2> position;
|
||||
};
|
||||
|
||||
struct mouse_wheel_event
|
||||
{
|
||||
int delta;
|
||||
};
|
||||
|
||||
enum class mouse_button
|
||||
{
|
||||
left,
|
||||
middle,
|
||||
right,
|
||||
};
|
||||
|
||||
struct mouse_button_event
|
||||
{
|
||||
mouse_button button;
|
||||
bool down;
|
||||
};
|
||||
|
||||
enum class keycode
|
||||
{
|
||||
// TODO
|
||||
};
|
||||
|
||||
struct key_event
|
||||
{
|
||||
keycode key;
|
||||
bool down;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/log/log.hpp>
|
||||
#include <psemek/util/pretty_print.hpp>
|
||||
#include <psemek/util/clock.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
template <typename App, typename ... Args>
|
||||
int main(Args && ... args) try
|
||||
{
|
||||
util::clock<std::chrono::milliseconds, std::chrono::high_resolution_clock> clock;
|
||||
|
||||
#ifdef PSEMEK_PACKAGE_MODE
|
||||
log::level stdio_log_level = log::level::info;
|
||||
#else
|
||||
log::level stdio_log_level = log::level::debug;
|
||||
#endif
|
||||
|
||||
log::add_sink(log::default_sink(io::std_out(), stdio_log_level));
|
||||
log::register_thread("main");
|
||||
|
||||
App app(std::forward<Args>(args)...);
|
||||
|
||||
log::info() << "Started in " << util::pretty(clock.duration(), std::chrono::milliseconds{1});
|
||||
|
||||
log::info() << "Running";
|
||||
app.run();
|
||||
log::info() << "Quitting";
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
log::error() << e.what();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
log::error() << "Unknown exception";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,93 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/geom/point.hpp>
|
||||
|
||||
#include <SDL2/SDL_keycode.h>
|
||||
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <psemek/app/event_handler.hpp>
|
||||
#include <psemek/app/event_state.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct app;
|
||||
|
||||
struct scene
|
||||
: event_handler
|
||||
{
|
||||
virtual ~scene() = 0;
|
||||
|
||||
virtual void on_scene_enter(app * /* parent */) {}
|
||||
virtual void on_scene_exit() {}
|
||||
|
||||
virtual void on_resize(int /* width */, int /* height */) {}
|
||||
virtual void on_focus_gained() {}
|
||||
virtual void on_focus_lost() {}
|
||||
|
||||
virtual void on_mouse_move(int /* x */, int /* y */, int /* dx */, int /* dy */) {}
|
||||
virtual void on_mouse_wheel(int /* delta */) {}
|
||||
virtual void on_left_button_down() {}
|
||||
virtual void on_left_button_up() {}
|
||||
virtual void on_middle_button_down() {}
|
||||
virtual void on_middle_button_up() {}
|
||||
virtual void on_right_button_down() {}
|
||||
virtual void on_right_button_up() {}
|
||||
|
||||
virtual void on_key_down(SDL_Keycode /* key */) {}
|
||||
virtual void on_key_up(SDL_Keycode /* key */) {}
|
||||
|
||||
virtual void on_text_input(std::string_view /* text */) {}
|
||||
virtual void on_enter(event_state const &) {}
|
||||
virtual void on_exit() {}
|
||||
|
||||
virtual void update() {}
|
||||
virtual void present() {}
|
||||
};
|
||||
|
||||
inline scene::~scene() = default;
|
||||
|
||||
struct scene_base
|
||||
: scene
|
||||
{
|
||||
void on_scene_enter(app * parent) override { parent_ = parent; }
|
||||
void on_scene_exit() override { parent_ = nullptr; }
|
||||
|
||||
void on_resize(int width, int height) override { width_ = width; height_ = height; }
|
||||
|
||||
void on_mouse_move(int x, int y, int, int) override { mouse_ = geom::point{x, y}; }
|
||||
void on_left_button_down() override { left_button_down_ = true; }
|
||||
void on_left_button_up() override { left_button_down_ = false; }
|
||||
void on_middle_button_down() override { middle_button_down_ = true; }
|
||||
void on_middle_button_up() override { middle_button_down_ = false; }
|
||||
void on_right_button_down() override { right_button_down_ = true; }
|
||||
void on_right_button_up() override { right_button_down_ = false; }
|
||||
void on_key_down(SDL_Keycode key) override { keys_.insert(key); }
|
||||
void on_key_up(SDL_Keycode key) override { keys_.erase(key); }
|
||||
|
||||
bool active() const { return parent_ != nullptr; }
|
||||
app * parent() const { return parent_; }
|
||||
|
||||
bool is_left_button_down() const { return left_button_down_; }
|
||||
bool is_middle_button_down() const { return middle_button_down_; }
|
||||
bool is_right_button_down() const { return right_button_down_; }
|
||||
|
||||
std::optional<geom::point<int, 2>> mouse() const { return mouse_; }
|
||||
|
||||
bool is_key_down(SDL_Keycode key) const { return keys_.count(key) > 0; }
|
||||
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
|
||||
private:
|
||||
app * parent_ = nullptr;
|
||||
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
|
||||
bool left_button_down_ = false;
|
||||
bool middle_button_down_ = false;
|
||||
bool right_button_down_ = false;
|
||||
|
||||
std::optional<geom::point<int, 2>> mouse_;
|
||||
|
||||
std::set<SDL_Keycode> keys_;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
29
libs/app/include/psemek/app/scene_application.hpp
Normal file
29
libs/app/include/psemek/app/scene_application.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/application_base.hpp>
|
||||
#include <psemek/app/scene.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct scene_application
|
||||
: application_base
|
||||
{
|
||||
void on_event(resize_event const &) override;
|
||||
void on_event(focus_event const &) override;
|
||||
void on_event(mouse_move_event const &) override;
|
||||
void on_event(mouse_wheel_event const &) override;
|
||||
void on_event(mouse_button_event const &) override;
|
||||
void on_event(key_event const &) override;
|
||||
|
||||
void update() override;
|
||||
void present() override;
|
||||
|
||||
virtual std::shared_ptr<scene> current_scene() = 0;
|
||||
|
||||
private:
|
||||
template <typename Event>
|
||||
void on_event_impl(Event const & event);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/scene.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
struct scene_manager
|
||||
{
|
||||
virtual void push_scene(std::shared_ptr<scene>) = 0;
|
||||
virtual std::shared_ptr<scene> pop_scene() = 0;
|
||||
|
||||
virtual ~scene_manager() {}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,326 +0,0 @@
|
|||
#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;
|
||||
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<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::poll_events()
|
||||
{
|
||||
auto handler = [this]{ return impl().get_scene(); };
|
||||
|
||||
for (SDL_Event e; SDL_PollEvent(&e);) switch (e.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
stop();
|
||||
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(); };
|
||||
|
||||
auto send_initial_resize = [this, &handler]{
|
||||
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;
|
||||
send_initial_resize();
|
||||
handler()->update();
|
||||
// The handler might have changed during the update
|
||||
send_initial_resize();
|
||||
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<scene> 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<scene> 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<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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
46
libs/app/source/application_base.cpp
Normal file
46
libs/app/source/application_base.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#include <psemek/app/application_base.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
void application_base::on_event(resize_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(focus_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(mouse_move_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(mouse_wheel_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(mouse_button_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::on_event(key_event const & event)
|
||||
{
|
||||
apply(state_, event);
|
||||
}
|
||||
|
||||
void application_base::stop()
|
||||
{
|
||||
running_ = false;
|
||||
}
|
||||
|
||||
bool application_base::running() const
|
||||
{
|
||||
return running_;
|
||||
}
|
||||
|
||||
}
|
||||
56
libs/app/source/scene_application.cpp
Normal file
56
libs/app/source/scene_application.cpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#include <psemek/app/scene_application.hpp>
|
||||
|
||||
namespace psemek::app
|
||||
{
|
||||
|
||||
void scene_application::on_event(resize_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(focus_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(mouse_move_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(mouse_wheel_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(mouse_button_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::on_event(key_event const & event)
|
||||
{
|
||||
on_event_impl(event);
|
||||
}
|
||||
|
||||
void scene_application::update()
|
||||
{
|
||||
if (auto scene = current_scene())
|
||||
scene->update();
|
||||
}
|
||||
|
||||
void scene_application::present()
|
||||
{
|
||||
if (auto scene = current_scene())
|
||||
scene->present();
|
||||
}
|
||||
|
||||
template <typename Event>
|
||||
void scene_application::on_event_impl(Event const & event)
|
||||
{
|
||||
application_base::on_event(event);
|
||||
if (auto scene = current_scene())
|
||||
scene->on_event(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,4 +3,4 @@ file(GLOB_RECURSE PSEMEK_AUDIO_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "s
|
|||
|
||||
psemek_add_library(psemek-audio ${PSEMEK_AUDIO_HEADERS} ${PSEMEK_AUDIO_SOURCES})
|
||||
target_include_directories(psemek-audio PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_link_libraries(psemek-audio PUBLIC psemek-sdl2 psemek-geom psemek-util psemek-log psemek-prof)
|
||||
target_link_libraries(psemek-audio PUBLIC psemek-random psemek-geom psemek-util psemek-log psemek-prof)
|
||||
|
|
|
|||
1592
libs/audio/include/psemek/audio/audio_file/AudioFile.h
Normal file
1592
libs/audio/include/psemek/audio/audio_file/AudioFile.h
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,30 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/audio/stream.hpp>
|
||||
#include <psemek/audio/track.hpp>
|
||||
#include <psemek/audio/channel.hpp>
|
||||
|
||||
#include <psemek/util/pimpl.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
|
||||
#include <psemek/io/stream.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
struct engine
|
||||
{
|
||||
engine();
|
||||
~engine();
|
||||
virtual channel_ptr output() = 0;
|
||||
|
||||
channel_ptr output();
|
||||
|
||||
private:
|
||||
psemek_declare_pimpl
|
||||
virtual ~engine() {}
|
||||
};
|
||||
|
||||
// Implemented by platform backend
|
||||
std::unique_ptr<engine> make_engine();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
#include <psemek/audio/engine.hpp>
|
||||
#include <psemek/audio/constants.hpp>
|
||||
#include <psemek/sdl2/init.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
#include <psemek/prof/profiler.hpp>
|
||||
#include <psemek/util/at_scope_exit.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <cmath>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
struct engine::impl
|
||||
{
|
||||
std::shared_ptr<void> sdl_init;
|
||||
|
||||
SDL_AudioDeviceID device;
|
||||
|
||||
std::vector<float> buffer;
|
||||
bool thread_registered = false;
|
||||
|
||||
channel_ptr output;
|
||||
|
||||
impl();
|
||||
|
||||
~impl()
|
||||
{
|
||||
SDL_CloseAudioDevice(device);
|
||||
}
|
||||
|
||||
static void callback(void * userdata, std::uint8_t * stream, int len);
|
||||
};
|
||||
|
||||
engine::impl::impl()
|
||||
: sdl_init(sdl2::init(SDL_INIT_AUDIO))
|
||||
{
|
||||
SDL_AudioSpec desired, obtained;
|
||||
desired.freq = frequency;
|
||||
desired.channels = 2;
|
||||
desired.format = AUDIO_S16SYS;
|
||||
desired.samples = 256;
|
||||
desired.callback = &callback;
|
||||
desired.userdata = this;
|
||||
if (device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, 0); device == 0)
|
||||
sdl2::fail("SDL_OpenAudioDevice failed:");
|
||||
|
||||
log::info() << "Initialized audio: " << static_cast<int>(obtained.channels) << " channels, " << obtained.freq << " Hz, " << obtained.samples << " samples";
|
||||
|
||||
buffer.resize(obtained.samples * obtained.channels);
|
||||
output = std::make_shared<channel>();
|
||||
SDL_PauseAudioDevice(device, 0);
|
||||
}
|
||||
|
||||
void engine::impl::callback(void * userdata, std::uint8_t * dst_u8, int len)
|
||||
{
|
||||
static std::string const profiler_str = "audio";
|
||||
prof::profiler prof(profiler_str);
|
||||
|
||||
auto self = static_cast<impl *>(userdata);
|
||||
stream_ptr output = self->output->stream();
|
||||
std::int16_t * dst = reinterpret_cast<std::int16_t *>(dst_u8);
|
||||
|
||||
if (!self->thread_registered)
|
||||
{
|
||||
log::register_thread("audio");
|
||||
self->thread_registered = true;
|
||||
}
|
||||
|
||||
std::size_t const size = len / 2;
|
||||
std::size_t read = 0;
|
||||
if (output)
|
||||
read = output->read({self->buffer.data(), size});
|
||||
std::fill(self->buffer.data() + read, self->buffer.data() + size, 0.f);
|
||||
|
||||
for (auto s : self->buffer)
|
||||
*dst++ = static_cast<std::int16_t>(std::max(std::min((65535.f * s - 1.f) / 2.f, 32767.f), -32768.f));
|
||||
}
|
||||
|
||||
engine::engine()
|
||||
: pimpl_(make_impl())
|
||||
{}
|
||||
|
||||
engine::~engine() = default;
|
||||
|
||||
channel_ptr engine::output()
|
||||
{
|
||||
return impl().output;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
#include <psemek/audio/track.hpp>
|
||||
#include <psemek/audio/constants.hpp>
|
||||
#include <psemek/audio/detail/resampler.hpp>
|
||||
#include <psemek/audio/audio_file/AudioFile.h>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
#include <psemek/util/at_scope_exit.hpp>
|
||||
#include <psemek/sdl2/init.hpp>
|
||||
|
||||
#include <SDL2/SDL_audio.h>
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
|
@ -13,38 +11,34 @@ namespace psemek::audio
|
|||
namespace
|
||||
{
|
||||
|
||||
std::vector<float> convert_audio(SDL_AudioSpec const & spec, std::uint8_t * samples, std::size_t length)
|
||||
std::vector<float> convert_audio(std::vector<std::vector<float>> const & channels, int frequency)
|
||||
{
|
||||
if (spec.channels > 2)
|
||||
throw std::runtime_error(util::to_string("Can't convert audio with ", static_cast<int>(spec.channels), " channels"));
|
||||
if (channels.empty() || channels.size() > 2)
|
||||
throw std::runtime_error(util::to_string("Can't convert audio with ", static_cast<int>(channels.size()), " channels"));
|
||||
|
||||
if (spec.format != AUDIO_S16SYS)
|
||||
throw std::runtime_error(util::to_string("Can't convert audio with format ", spec.format));
|
||||
std::vector<float> result(channels[0].size() * 2);
|
||||
auto out = result.begin();
|
||||
|
||||
auto p = reinterpret_cast<std::int16_t *>(samples);
|
||||
|
||||
std::vector<float> result;
|
||||
|
||||
if (spec.channels == 1)
|
||||
if (channels.size() == 1)
|
||||
{
|
||||
result.resize(length);
|
||||
for (std::size_t i = 0; i < length / 2; ++i)
|
||||
for (auto p = channels[0].begin(); p != channels[0].end(); ++p)
|
||||
{
|
||||
float v = (p[i] * 2.f + 1.f) / 65536.f;
|
||||
result[2 * i + 0] = v;
|
||||
result[2 * i + 1] = v;
|
||||
*out++ = *p;
|
||||
*out++ = *p;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (channels.size() == 2)
|
||||
{
|
||||
result.resize(length / 2);
|
||||
for (std::size_t i = 0; i < length / 2; ++i)
|
||||
result[i] = (p[i] * 2.f + 1.f) / 65536.f;
|
||||
for (auto p0 = channels[0].begin(), p1 = channels[1].begin(); p0 != channels[0].end(); ++p0, ++p1)
|
||||
{
|
||||
*out++ = *p0;
|
||||
*out++ = *p1;
|
||||
}
|
||||
}
|
||||
|
||||
if (spec.freq != audio::frequency)
|
||||
if (frequency != audio::frequency)
|
||||
{
|
||||
audio::resampler resampler(audio::frequency * 1.f / spec.freq);
|
||||
audio::resampler resampler(audio::frequency * 1.f / frequency);
|
||||
resampler.feed(result);
|
||||
result = resampler.grab_result();
|
||||
}
|
||||
|
|
@ -56,14 +50,17 @@ namespace psemek::audio
|
|||
|
||||
track_ptr load_wav(util::span<char const> data)
|
||||
{
|
||||
SDL_AudioSpec spec;
|
||||
std::uint8_t * samples;
|
||||
std::uint32_t length;
|
||||
if (!SDL_LoadWAV_RW(SDL_RWFromConstMem(data.data(), data.size()), 1, &spec, &samples, &length))
|
||||
sdl2::fail("SDL_LoadWAV_RW failed:");
|
||||
std::vector<std::uint8_t> data_u8(data.size());
|
||||
std::copy(data.begin(), data.end(), reinterpret_cast<char *>(data_u8.data()));
|
||||
|
||||
util::at_scope_exit release_samples([samples]{ SDL_FreeWAV(samples); });
|
||||
return load_raw(convert_audio(spec, samples, length));
|
||||
AudioFile<float> audio_file;
|
||||
audio_file.shouldLogErrorsToConsole(false);
|
||||
audio_file.onError = [](std::string const & error) {
|
||||
throw std::runtime_error("failed to load WAV file: " + error);
|
||||
};
|
||||
audio_file.loadFromMemory(data_u8);
|
||||
|
||||
return load_raw(convert_audio(audio_file.samples, audio_file.getSampleRate()));
|
||||
}
|
||||
|
||||
track_ptr load_wav(std::vector<char> const & data)
|
||||
|
|
|
|||
8
libs/gfx/include/psemek/gfx/init.hpp
Normal file
8
libs/gfx/include/psemek/gfx/init.hpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
namespace psemek::gfx
|
||||
{
|
||||
|
||||
void init();
|
||||
|
||||
}
|
||||
22
libs/gfx/source/init.cpp
Normal file
22
libs/gfx/source/init.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#include <psemek/gfx/init.hpp>
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
|
||||
namespace psemek::gfx
|
||||
{
|
||||
|
||||
void init()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,4 +5,4 @@ file(GLOB_RECURSE PSEMEK_SDL2_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "so
|
|||
|
||||
psemek_add_library(psemek-sdl2 ${PSEMEK_SDL2_HEADERS} ${PSEMEK_SDL2_SOURCES})
|
||||
target_include_directories(psemek-sdl2 PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
target_link_libraries(psemek-sdl2 PUBLIC psemek-log psemek-util psemek-gfx SDL2)
|
||||
target_link_libraries(psemek-sdl2 PUBLIC psemek-log psemek-util psemek-gfx psemek-audio psemek-app SDL2)
|
||||
|
|
|
|||
11
libs/sdl2/include/psemek/sdl2/events.hpp
Normal file
11
libs/sdl2/include/psemek/sdl2/events.hpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/event_handler.hpp>
|
||||
|
||||
namespace psemek::sdl2
|
||||
{
|
||||
|
||||
// Returns true is quit is requested
|
||||
bool poll_events(app::event_handler & handler);
|
||||
|
||||
}
|
||||
28
libs/sdl2/include/psemek/sdl2/window.hpp
Normal file
28
libs/sdl2/include/psemek/sdl2/window.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/app/application.hpp>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
namespace psemek::sdl2
|
||||
{
|
||||
|
||||
struct window
|
||||
{
|
||||
window(psemek::app::application::options const & options);
|
||||
~window();
|
||||
|
||||
geom::vector<int, 2> size() const;
|
||||
|
||||
void show();
|
||||
void swap();
|
||||
void show_cursor(bool show);
|
||||
void vsync(bool on);
|
||||
|
||||
private:
|
||||
std::shared_ptr<void> sdl_init_;
|
||||
SDL_Window * window_ = nullptr;
|
||||
SDL_GLContext gl_context_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
111
libs/sdl2/source/audio_engine.cpp
Normal file
111
libs/sdl2/source/audio_engine.cpp
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#include <psemek/audio/engine.hpp>
|
||||
#include <psemek/audio/constants.hpp>
|
||||
#include <psemek/sdl2/init.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
#include <psemek/prof/profiler.hpp>
|
||||
#include <psemek/util/at_scope_exit.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <cmath>
|
||||
|
||||
namespace psemek::sdl2
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct audio_engine_impl
|
||||
: audio::engine
|
||||
{
|
||||
audio_engine_impl();
|
||||
~audio_engine_impl();
|
||||
|
||||
audio::channel_ptr output() override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<void> sdl_init_;
|
||||
|
||||
SDL_AudioDeviceID device_;
|
||||
|
||||
std::vector<float> buffer_;
|
||||
bool thread_registered_ = false;
|
||||
|
||||
audio::channel_ptr output_;
|
||||
|
||||
static void callback(void * userdata, std::uint8_t * stream, int len);
|
||||
};
|
||||
|
||||
audio_engine_impl::audio_engine_impl()
|
||||
: sdl_init_(sdl2::init(SDL_INIT_AUDIO))
|
||||
{
|
||||
SDL_AudioSpec desired, obtained;
|
||||
desired.freq = audio::frequency;
|
||||
desired.channels = 2;
|
||||
desired.format = AUDIO_S16SYS;
|
||||
desired.samples = 256;
|
||||
desired.callback = &callback;
|
||||
desired.userdata = this;
|
||||
if (device_ = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, 0); device_ == 0)
|
||||
fail("SDL_OpenAudioDevice failed:");
|
||||
|
||||
log::info() << "Initialized audio: " << static_cast<int>(obtained.channels) << " channels, " << obtained.freq << " Hz, " << obtained.samples << " samples";
|
||||
|
||||
buffer_.resize(obtained.samples * obtained.channels);
|
||||
output_ = std::make_shared<audio::channel>();
|
||||
SDL_PauseAudioDevice(device_, 0);
|
||||
}
|
||||
|
||||
audio_engine_impl::~audio_engine_impl()
|
||||
{
|
||||
SDL_CloseAudioDevice(device_);
|
||||
}
|
||||
|
||||
void audio_engine_impl::callback(void * userdata, std::uint8_t * dst_u8, int len)
|
||||
{
|
||||
static std::string const profiler_str = "audio";
|
||||
prof::profiler prof(profiler_str);
|
||||
|
||||
auto self = static_cast<audio_engine_impl *>(userdata);
|
||||
auto stream = self->output()->stream();
|
||||
std::int16_t * dst = reinterpret_cast<std::int16_t *>(dst_u8);
|
||||
|
||||
if (!self->thread_registered_)
|
||||
{
|
||||
log::register_thread("audio");
|
||||
self->thread_registered_ = true;
|
||||
}
|
||||
|
||||
std::size_t const size = len / 2;
|
||||
std::size_t read = 0;
|
||||
if (stream)
|
||||
read = stream->read({self->buffer_.data(), size});
|
||||
std::fill(self->buffer_.data() + read, self->buffer_.data() + size, 0.f);
|
||||
|
||||
for (auto s : self->buffer_)
|
||||
*dst++ = static_cast<std::int16_t>(std::max(std::min((65535.f * s - 1.f) / 2.f, 32767.f), -32768.f));
|
||||
}
|
||||
|
||||
audio::channel_ptr audio_engine_impl::output()
|
||||
{
|
||||
return output_;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace psemek::audio
|
||||
{
|
||||
|
||||
std::unique_ptr<engine> make_engine()
|
||||
{
|
||||
return std::make_unique<sdl2::audio_engine_impl>();
|
||||
}
|
||||
|
||||
}
|
||||
85
libs/sdl2/source/events.cpp
Normal file
85
libs/sdl2/source/events.cpp
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#include <psemek/sdl2/events.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
namespace psemek::sdl2
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::optional<app::mouse_button> mouse_button(Uint8 button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case SDL_BUTTON_LEFT:
|
||||
return app::mouse_button::left;
|
||||
case SDL_BUTTON_MIDDLE:
|
||||
return app::mouse_button::middle;
|
||||
case SDL_BUTTON_RIGHT:
|
||||
return app::mouse_button::right;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<app::keycode> keycode(SDL_Keysym)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool poll_events(app::event_handler & handler)
|
||||
{
|
||||
for (SDL_Event e; SDL_PollEvent(&e);) switch (e.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
return true;
|
||||
case SDL_WINDOWEVENT: switch (e.window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
return true;
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
handler.on_event(app::resize_event{{e.window.data1, e.window.data2}});
|
||||
break;
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
handler.on_event(app::resize_event{{e.window.data1, e.window.data2}});
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
handler.on_event(app::focus_event{true});
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
handler.on_event(app::focus_event{false});
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SDL_MOUSEMOTION:
|
||||
handler.on_event(app::mouse_move_event{{e.motion.x, e.motion.y}});
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
handler.on_event(app::mouse_wheel_event{e.wheel.y});
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
if (auto button = mouse_button(e.button.button))
|
||||
handler.on_event(app::mouse_button_event{*button, true});
|
||||
break;
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
if (auto button = mouse_button(e.button.button))
|
||||
handler.on_event(app::mouse_button_event{*button, false});
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
if (auto key = keycode(e.key.keysym))
|
||||
handler.on_event(app::key_event{*key, true});
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
if (auto key = keycode(e.key.keysym))
|
||||
handler.on_event(app::key_event{*key, false});
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
68
libs/sdl2/source/main.cpp
Normal file
68
libs/sdl2/source/main.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#include <psemek/app/application.hpp>
|
||||
#include <psemek/gfx/init.hpp>
|
||||
#include <psemek/sdl2/init.hpp>
|
||||
#include <psemek/sdl2/window.hpp>
|
||||
#include <psemek/sdl2/events.hpp>
|
||||
#include <psemek/util/clock.hpp>
|
||||
#include <psemek/util/pretty_print.hpp>
|
||||
#include <psemek/log/log.hpp>
|
||||
|
||||
#undef main
|
||||
|
||||
int main() try
|
||||
{
|
||||
using namespace psemek;
|
||||
|
||||
util::clock<std::chrono::milliseconds, std::chrono::high_resolution_clock> clock;
|
||||
|
||||
#ifdef PSEMEK_PACKAGE_MODE
|
||||
log::level const stdio_log_level = log::level::info;
|
||||
#else
|
||||
log::level const stdio_log_level = log::level::debug;
|
||||
#endif
|
||||
|
||||
log::add_sink(log::default_sink(io::std_out(), stdio_log_level));
|
||||
log::register_thread("main");
|
||||
|
||||
auto const factory = app::make_application_factory();
|
||||
auto const options = factory->options();
|
||||
|
||||
sdl2::window window(options);
|
||||
|
||||
gfx::init();
|
||||
|
||||
app::application::context context;
|
||||
context.show_cursor = [&](bool show){ window.show_cursor(show); };
|
||||
context.vsync = [&](bool on){ window.vsync(on); };
|
||||
|
||||
auto application = factory->create(options, context);
|
||||
application->on_event(app::resize_event{window.size()});
|
||||
|
||||
window.show();
|
||||
|
||||
log::info() << "Started in " << util::pretty(clock.duration(), std::chrono::milliseconds{1});
|
||||
log::info() << "Running";
|
||||
|
||||
while (application->running())
|
||||
{
|
||||
if (sdl2::poll_events(*application))
|
||||
application->stop();
|
||||
if (!application->running()) break;
|
||||
application->update();
|
||||
application->present();
|
||||
window.swap();
|
||||
}
|
||||
|
||||
log::info() << "Quitting";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
psemek::log::error() << e.what();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
psemek::log::error() << "Unknown exception";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
96
libs/sdl2/source/window.cpp
Normal file
96
libs/sdl2/source/window.cpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include <psemek/sdl2/window.hpp>
|
||||
#include <psemek/sdl2/init.hpp>
|
||||
#include <psemek/gfx/gl.hpp>
|
||||
|
||||
namespace psemek::sdl2
|
||||
{
|
||||
|
||||
window::window(psemek::app::application::options const & options)
|
||||
: sdl_init_(init(SDL_INIT_EVENTS | SDL_INIT_VIDEO))
|
||||
{
|
||||
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 (options.multisampling == 0)
|
||||
{
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, options.multisampling);
|
||||
}
|
||||
|
||||
std::uint32_t flags = SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_BORDERLESS | SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
if (options.highdpi) flags |= SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
|
||||
SDL_DisplayMode display_mode;
|
||||
SDL_GetCurrentDisplayMode(0, &display_mode);
|
||||
|
||||
window_ = SDL_CreateWindow(options.name.data(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, display_mode.w, display_mode.h, flags);
|
||||
if (!window_)
|
||||
sdl2::fail("Failed to create window: ");
|
||||
|
||||
gl_context_ = SDL_GL_CreateContext(window_);
|
||||
if (!gl_context_)
|
||||
sdl2::fail("Failed to create OpenGL context: ");
|
||||
|
||||
SDL_GL_MakeCurrent(window_, gl_context_);
|
||||
}
|
||||
|
||||
geom::vector<int, 2> window::size() const
|
||||
{
|
||||
geom::vector<int, 2> result;
|
||||
SDL_GL_GetDrawableSize(window_, &result[0], &result[1]);
|
||||
return result;
|
||||
}
|
||||
|
||||
void window::show()
|
||||
{
|
||||
SDL_ShowWindow(window_);
|
||||
}
|
||||
|
||||
void window::swap()
|
||||
{
|
||||
SDL_GL_SwapWindow(window_);
|
||||
}
|
||||
|
||||
void window::show_cursor(bool show)
|
||||
{
|
||||
SDL_ShowCursor(show ? SDL_TRUE : SDL_FALSE);
|
||||
SDL_SetRelativeMouseMode(show ? SDL_FALSE : SDL_TRUE);
|
||||
}
|
||||
|
||||
void window::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);
|
||||
}
|
||||
}
|
||||
|
||||
window::~window()
|
||||
{
|
||||
if (gl_context_)
|
||||
SDL_GL_DeleteContext(gl_context_);
|
||||
if (window_)
|
||||
SDL_DestroyWindow(window_);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue