From 961a144d4674543998201a7fd362b89d79ea2a4e Mon Sep 17 00:00:00 2001 From: Hannes Mann Date: Tue, 26 Jul 2016 05:52:46 +0200 Subject: [PATCH] Add a basic game controller and basic GL wrappers. --- .gitignore | 5 +- libopenrayman/src/common_stream.cc | 6 +- src/config/config.cc | 81 ++++++++--------- src/engine.cc | 13 +-- src/engine.h | 38 +++++--- src/game_controller.cc | 30 +++++++ src/game_controller.h | 46 ++++++++++ src/info.h | 31 +++++-- src/main.cc | 6 +- src/math/math.h | 48 ++++++++++ src/renderer/bind_guard.h | 30 +++++++ src/renderer/buf.cc | 102 ++++++++++++++++++++++ src/renderer/buf.h | 93 ++++++++++++++++++++ src/renderer/renderer.h | 12 +++ src/renderer/texture.cc | 136 +++++++++++++++++++++++++++++ src/renderer/texture.h | 89 +++++++++++++++++++ src/window/sdl_window.cc | 10 +-- src/window/sdl_window.h | 2 +- 18 files changed, 700 insertions(+), 78 deletions(-) create mode 100644 src/game_controller.cc create mode 100644 src/game_controller.h create mode 100644 src/math/math.h create mode 100644 src/renderer/bind_guard.h create mode 100644 src/renderer/buf.cc create mode 100644 src/renderer/buf.h create mode 100644 src/renderer/renderer.h create mode 100644 src/renderer/texture.cc create mode 100644 src/renderer/texture.h diff --git a/.gitignore b/.gitignore index e2b53ea..de30b37 100755 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,7 @@ build_win/ # SDL2 windows !win32/SDL2/libSDL2.dll.a -!win32/SDL2/SDL2.dll \ No newline at end of file +!win32/SDL2/SDL2.dll + +# CLion +.idea/ diff --git a/libopenrayman/src/common_stream.cc b/libopenrayman/src/common_stream.cc index 6c54c31..3b3b470 100644 --- a/libopenrayman/src/common_stream.cc +++ b/libopenrayman/src/common_stream.cc @@ -121,7 +121,7 @@ namespace openrayman encoded_buf::int_type encoded_buf::underflow() { - int value = m_in.peek(); + auto value = m_in.peek(); if(value == traits_type::eof()) return traits_type::eof(); return traits_type::to_int_type(decode_char((char)value)); @@ -129,10 +129,10 @@ namespace openrayman encoded_buf::int_type encoded_buf::uflow() { - int value = m_in.get(); + auto value = m_in.get(); if(value == traits_type::eof()) return traits_type::eof(); - char c = decode_char((char)value); + auto c = decode_char((char)value); advance_virtual_position(1, true); return traits_type::to_int_type(c); } diff --git a/src/config/config.cc b/src/config/config.cc index a918091..b029e86 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -59,34 +59,34 @@ namespace openrayman config_stream >> config_json; if(config_json.count("game") > 0) game = config_json["game"]; - if(config_json.count("gfx") > 0) + if(config_json.count("renderer") > 0) { - vsync = config_json["gfx"]["vsync"]; - fullscreen = config_json["gfx"]["fullscreen"]; - max_fps = config_json["gfx"]["max_fps"]; + vsync = config_json["renderer"]["vsync"]; + fullscreen = config_json["renderer"]["fullscreen"]; + max_fps = config_json["renderer"]["max_fps"]; } - if(config_json.count("keyboard_map") > 0) + if(config_json.count("input") > 0 && config_json["input"].count("keyboard") > 0) { - keyboard_map["stick(strength)"] = config_json["keyboard_map"]["stick(strength)"]; + keyboard_map["stick(strength)"] = config_json["input"]["keyboard"]["stick(strength)"]; - keyboard_map["stick(x, -)"] = config_json["keyboard_map"]["stick(x, -)"]; - keyboard_map["stick(x, +)"] = config_json["keyboard_map"]["stick(x, +)"]; - keyboard_map["stick(y, -)"] = config_json["keyboard_map"]["stick(y, -)"]; - keyboard_map["stick(y, +)"] = config_json["keyboard_map"]["stick(y, +)"]; + keyboard_map["stick(x, -)"] = config_json["input"]["keyboard"]["stick(x, -)"]; + keyboard_map["stick(x, +)"] = config_json["input"]["keyboard"]["stick(x, +)"]; + keyboard_map["stick(y, -)"] = config_json["input"]["keyboard"]["stick(y, -)"]; + keyboard_map["stick(y, +)"] = config_json["input"]["keyboard"]["stick(y, +)"]; - keyboard_map["start"] = config_json["keyboard_map"]["start"]; + keyboard_map["start"] = config_json["input"]["keyboard"]["start"]; - keyboard_map["a"] = config_json["keyboard_map"]["a"]; - keyboard_map["b"] = config_json["keyboard_map"]["b"]; + keyboard_map["a"] = config_json["input"]["keyboard"]["a"]; + keyboard_map["b"] = config_json["input"]["keyboard"]["b"]; - keyboard_map["l"] = config_json["keyboard_map"]["l"]; - keyboard_map["r"] = config_json["keyboard_map"]["r"]; - keyboard_map["z"] = config_json["keyboard_map"]["z"]; + keyboard_map["l"] = config_json["input"]["keyboard"]["l"]; + keyboard_map["r"] = config_json["input"]["keyboard"]["r"]; + keyboard_map["z"] = config_json["input"]["keyboard"]["z"]; - keyboard_map["cbtn(x, -)"] = config_json["keyboard_map"]["cbtn(x, -)"]; - keyboard_map["cbtn(x, +)"] = config_json["keyboard_map"]["cbtn(x, +)"]; - keyboard_map["cbtn(y, -)"] = config_json["keyboard_map"]["cbtn(y, -)"]; - keyboard_map["cbtn(y, +)"] = config_json["keyboard_map"]["cbtn(y, +)"]; + keyboard_map["cbtn(x, -)"] = config_json["input"]["keyboard"]["cbtn(x, -)"]; + keyboard_map["cbtn(x, +)"] = config_json["input"]["keyboard"]["cbtn(x, +)"]; + keyboard_map["cbtn(y, -)"] = config_json["input"]["keyboard"]["cbtn(y, -)"]; + keyboard_map["cbtn(y, +)"] = config_json["input"]["keyboard"]["cbtn(y, +)"]; } return true; } @@ -101,34 +101,35 @@ namespace openrayman nlohmann::json config_json; config_json["game"] = game; - config_json["gfx"] = nlohmann::json::object(); + config_json["renderer"] = nlohmann::json::object(); - config_json["gfx"]["vsync"] = vsync; - config_json["gfx"]["fullscreen"] = fullscreen; - config_json["gfx"]["max_fps"] = max_fps; + config_json["renderer"]["vsync"] = vsync; + config_json["renderer"]["fullscreen"] = fullscreen; + config_json["renderer"]["max_fps"] = max_fps; - config_json["keyboard_map"] = nlohmann::json::object(); + config_json["input"] = nlohmann::json::object(); + config_json["input"]["keyboard"] = nlohmann::json::object(); - config_json["keyboard_map"]["stick(strength)"] = keyboard_map["stick(strength)"]; + config_json["input"]["keyboard"]["stick(strength)"] = keyboard_map["stick(strength)"]; - config_json["keyboard_map"]["stick(x, -)"] = keyboard_map["stick(x, -)"]; - config_json["keyboard_map"]["stick(x, +)"] = keyboard_map["stick(x, +)"]; - config_json["keyboard_map"]["stick(y, -)"] = keyboard_map["stick(y, -)"]; - config_json["keyboard_map"]["stick(y, +)"] = keyboard_map["stick(y, +)"]; + config_json["input"]["keyboard"]["stick(x, -)"] = keyboard_map["stick(x, -)"]; + config_json["input"]["keyboard"]["stick(x, +)"] = keyboard_map["stick(x, +)"]; + config_json["input"]["keyboard"]["stick(y, -)"] = keyboard_map["stick(y, -)"]; + config_json["input"]["keyboard"]["stick(y, +)"] = keyboard_map["stick(y, +)"]; - config_json["keyboard_map"]["start"] = keyboard_map["start"]; + config_json["input"]["keyboard"]["start"] = keyboard_map["start"]; - config_json["keyboard_map"]["a"] = keyboard_map["a"]; - config_json["keyboard_map"]["b"] = keyboard_map["b"]; + config_json["input"]["keyboard"]["a"] = keyboard_map["a"]; + config_json["input"]["keyboard"]["b"] = keyboard_map["b"]; - config_json["keyboard_map"]["l"] = keyboard_map["l"]; - config_json["keyboard_map"]["r"] = keyboard_map["r"]; - config_json["keyboard_map"]["z"] = keyboard_map["z"]; + config_json["input"]["keyboard"]["l"] = keyboard_map["l"]; + config_json["input"]["keyboard"]["r"] = keyboard_map["r"]; + config_json["input"]["keyboard"]["z"] = keyboard_map["z"]; - config_json["keyboard_map"]["cbtn(x, -)"] = keyboard_map["cbtn(x, -)"]; - config_json["keyboard_map"]["cbtn(x, +)"] = keyboard_map["cbtn(x, +)"]; - config_json["keyboard_map"]["cbtn(y, -)"] = keyboard_map["cbtn(y, -)"]; - config_json["keyboard_map"]["cbtn(y, +)"] = keyboard_map["cbtn(y, +)"]; + config_json["input"]["keyboard"]["cbtn(x, -)"] = keyboard_map["cbtn(x, -)"]; + config_json["input"]["keyboard"]["cbtn(x, +)"] = keyboard_map["cbtn(x, +)"]; + config_json["input"]["keyboard"]["cbtn(y, -)"] = keyboard_map["cbtn(y, -)"]; + config_json["input"]["keyboard"]["cbtn(y, +)"] = keyboard_map["cbtn(y, +)"]; std::ofstream config_stream(config_json_file, std::ofstream::trunc); if(config_stream.is_open()) diff --git a/src/engine.cc b/src/engine.cc index 555a3f8..9d6c2d4 100644 --- a/src/engine.cc +++ b/src/engine.cc @@ -72,7 +72,7 @@ namespace openrayman // Provide more complete info for the user. std::stringstream title; title << "OpenRayman " << openrayman::version << " " - << (this_platform == platform::windows ? "Win32" : "Linux") + << map_platform(this_platform) << " (OpenGL " << gl_major << "." << gl_minor << ")" << " (Game \"" << load_game << "\")"; m_window.set_title(title.str()); @@ -82,13 +82,15 @@ namespace openrayman if(!lodepng::decode(icon_data, width, height, file::fix_string(m_backend_specifics.data_path() + "/common/icon.png"))) m_window.set_icon(icon_data.data(), width, height); + m_game_controller = std::unique_ptr(new game_controller(*this, *m_game, m_renderer)); + m_last_timer_value = m_backend_specifics.time(); input_provider& provider = m_window.create_input_provider(); while(!m_exit_requested) { double current_timer_value = m_backend_specifics.time(); m_current_delta_time = current_timer_value - m_last_timer_value; - m_accumulated_time_fixed += m_current_delta_time; + m_accumulated_time_timed += m_current_delta_time; m_accumulated_time_fps += m_current_delta_time; m_total_time += m_current_delta_time; m_last_timer_value = current_timer_value; @@ -99,6 +101,7 @@ namespace openrayman m_window.poll_events(); m_last_input = m_current_input; + const input_state& st = provider.poll(); m_current_input = input_state(st.buttons, st.commands, st.stick_x, st.stick_y); if(m_current_input.command(input_command::toggle_fullscreen) && !m_last_input.command(input_command::toggle_fullscreen)) @@ -112,10 +115,10 @@ namespace openrayman m_fps = m_accumulated_frames_fps; m_accumulated_time_fps = m_accumulated_frames_fps = 0; } - while(m_accumulated_time_fixed >= 1 / 60.0) + while(m_accumulated_time_timed >= 1 / 60.0) { - m_total_fixed_updates++; - m_accumulated_time_fixed -= 1 / 60.0; + m_total_timed_updates++; + m_accumulated_time_timed -= 1 / 60.0; } m_window.present(); diff --git a/src/engine.h b/src/engine.h index 6c72a5a..f0cc32f 100644 --- a/src/engine.h +++ b/src/engine.h @@ -13,13 +13,16 @@ #include #include #include +#include +#include #include +#include #include #include namespace openrayman { - // Core of OpenRayman. Handles timing, input and GL contexts. + // The engine controls game initialization, timing and renderer/input/window management. class engine { public: @@ -36,12 +39,14 @@ public: m_last_timer_value(0), m_current_delta_time(0), m_total_time(0), - m_accumulated_time_fixed(0), + m_accumulated_time_timed(0), m_accumulated_time_fps(0), m_total_frames(0), - m_total_fixed_updates(0), + m_total_timed_updates(0), m_accumulated_frames_fps(0), m_fps(0), + m_game(nullptr), + m_game_controller(nullptr), #ifdef LIBRETRO_CORE m_window(*(new libretro_window())) #else @@ -70,13 +75,13 @@ public: } // Returns a reference to the engine window. - inline window& current_window() const + inline window& active_window() const { return m_window; } // Returns a reference to the backend specifics that are currently in use. - inline backend_specifics& current_backend_specifics() const + inline backend_specifics& active_backend_specifics() const { return m_backend_specifics; } @@ -111,40 +116,49 @@ public: return m_total_frames; } - // Returns the total amount of fixed updates that have passed since the start of the game. - inline std::uint64_t total_fixed_updates() const + // Returns the total amount of timed updates that have passed since the start of the game. + inline std::uint64_t total_timed_updates() const { - return m_total_fixed_updates; + return m_total_timed_updates; } // Returns the amount of frames that were executed during the previous second. inline double fps() const { + if(std::abs((int)(m_fps - (1 / m_current_delta_time))) > 2) + return 1 / m_current_delta_time; // this is more accurate return (m_fps + (1 / m_current_delta_time)) / 2; } // Returns a reference to the active config. - inline const config& current_config() const + inline const config& active_config() const { return m_config; } // Returns a reference to the active game. - inline const game& current_game() const + inline const game& active_game() const { return *m_game; } + // Returns a reference to the active game controller. + inline game_controller& active_game_controller() + { + return *m_game_controller; + } private: window& m_window; backend_specifics& m_backend_specifics; input_state m_current_input; input_state m_last_input; - double m_last_timer_value, m_current_delta_time, m_total_time, m_accumulated_time_fixed, m_accumulated_time_fps; - std::uint64_t m_total_frames, m_total_fixed_updates, m_accumulated_frames_fps, m_fps; + double m_last_timer_value, m_current_delta_time, m_total_time, m_accumulated_time_timed, m_accumulated_time_fps; + std::uint64_t m_total_frames, m_total_timed_updates, m_accumulated_frames_fps, m_fps; config m_config; std::unique_ptr m_game; + renderer m_renderer; + std::unique_ptr m_game_controller; bool m_exit_requested; }; } diff --git a/src/game_controller.cc b/src/game_controller.cc new file mode 100644 index 0000000..1e4a7c7 --- /dev/null +++ b/src/game_controller.cc @@ -0,0 +1,30 @@ +#include + +namespace openrayman +{ + game_controller::game_controller(engine &active_engine, game &active_game, renderer &active_renderer) : + m_engine(active_engine), m_game(active_game), m_renderer(active_renderer) + { + + } + + game_controller::~game_controller() + { + + } + + void game_controller::tick(double delta, std::uint64_t update, input_state &input, input_state &last_input) + { + + } + + void game_controller::timed_tick(std::uint64_t update, input_state &input, input_state &last_input) + { + + } + + void game_controller::render() + { + + } +} \ No newline at end of file diff --git a/src/game_controller.h b/src/game_controller.h new file mode 100644 index 0000000..55b9cfd --- /dev/null +++ b/src/game_controller.h @@ -0,0 +1,46 @@ +#ifndef GAME_CONTROLLER_H +#define GAME_CONTROLLER_H + +#include +#include +#include +#include + +namespace openrayman +{ + class engine; + + // The game controller controls the world, collision and entities within it. + // It is also responsible for loading resources and showing "vignettes" (images, loading screens). + class game_controller + { +public: + game_controller(engine& active_engine, game& active_game, renderer& active_renderer); + ~game_controller(); + + void tick(double delta, std::uint64_t update, input_state& input, input_state& last_input); + void timed_tick(std::uint64_t update, input_state& input, input_state& last_input); + void render(); + + inline engine& active_engine() + { + return m_engine; + } + + inline game& active_game() + { + return m_game; + } + + inline renderer& active_renderer() + { + return m_renderer; + } +private: + engine& m_engine; + game& m_game; + renderer& m_renderer; + }; +} + +#endif diff --git a/src/info.h b/src/info.h index 426839c..dc4edc8 100644 --- a/src/info.h +++ b/src/info.h @@ -2,6 +2,7 @@ #define INFO_H #include +#include namespace openrayman { @@ -12,18 +13,32 @@ namespace openrayman const int version_code = 10; // Platform definitions. -#ifdef linux -#undef linux -#endif - enum class platform { windows, linux }; + enum class platform { win32, linux_desktop, libretro, android }; + + inline std::string map_platform(platform p) + { + switch(p) + { + case platform::win32: + return "Windows (Win32)"; + + case platform::linux_desktop: + return "Linux"; + + case platform::libretro: + return "libretro"; + + case platform::android: + return "Android"; + } + } // The platform that engine is running on (that it was compiled for). - // Assume we're on linux unless _WIN32 is specified, as those are the only two platforms we support. - // TODO: platform for libretro?!? + // Assume we're on linux unless _WIN32 is specified, as those are the only two platforms we support right now. #ifdef _WIN32 - const platform this_platform = platform::windows; + const platform this_platform = platform::win32; #else - const platform this_platform = platform::linux; + const platform this_platform = platform::linux_desktop; #endif } diff --git a/src/main.cc b/src/main.cc index b30b57a..35219b5 100755 --- a/src/main.cc +++ b/src/main.cc @@ -80,7 +80,7 @@ int main(int argc, char** argv) return openrayman::gf_tools::print_info(path); return fail_and_print("Invalid format was specified"); } - if(str == "--force-reset-rayman2") + if(str == "--force-reset-base-rayman2") { openrayman::standalone_backend_specifics backend_specifics; openrayman::file::delete_directory(backend_specifics.storage_path() + "/games/rayman2"); @@ -122,7 +122,7 @@ int main(int argc, char** argv) std::cout << " \"cnt\": Prints file hierarchy" << std::endl; std::cout << " \"gf\": Prints width, height and number of channels" << std::endl; std::cout << " --extract-cnt-to \"archive\" \"path\" \"target\" Extracts a file from a CNT archive" << std::endl; - std::cout << " --force-reset-rayman2 Forces a removal of the rayman2 base game, if it exists" << std::endl; + std::cout << " --force-reset-base-rayman2 Forces a removal of the rayman2 base game, if it exists" << std::endl; return EXIT_SUCCESS; } if(str == "--version") @@ -136,7 +136,7 @@ int main(int argc, char** argv) } } std::cout << "OpenRayman " << openrayman::version << std::endl; - std::cout << "Running on " << (openrayman::this_platform == openrayman::platform::windows ? "Windows" : "Linux") << std::endl; + std::cout << "Running on " << openrayman::map_platform(openrayman::this_platform) << std::endl; std::cout << "Using game " << (selected_game == "" ? "from config" : "\"" + selected_game + "\"") << std::endl; openrayman::engine engine; return engine.run(selected_game, selected_install_folder); diff --git a/src/math/math.h b/src/math/math.h new file mode 100644 index 0000000..84711e3 --- /dev/null +++ b/src/math/math.h @@ -0,0 +1,48 @@ +#ifndef MATH_H +#define MATH_H + +#include +#include +#include + +namespace openrayman +{ + // Math helpers. + namespace math + { + // Interpolates between values "from" - "to". + inline float interp(float from, float to, float value) + { + return from + (to - from) * value; + } + + // Interpolates between values "from" - "to". + inline glm::vec2 interp(glm::vec2 from, glm::vec2 to, float value) + { + return glm::vec2( + interp(from.x, to.x, value), + interp(from.y, to.y, value)); + } + + // Interpolates between values "from" - "to". + inline glm::vec3 interp(glm::vec3 from, glm::vec3 to, float value) + { + return glm::vec3( + interp(from.x, to.x, value), + interp(from.y, to.y, value), + interp(from.z, to.z, value)); + } + + // Interpolates between values "from" - "to". + inline glm::vec4 interp(glm::vec4 from, glm::vec4 to, float value) + { + return glm::vec4( + interp(from.r, to.r, value), + interp(from.g, to.g, value), + interp(from.b, to.b, value), + interp(from.a, to.a, value)); + } + } +} + +#endif \ No newline at end of file diff --git a/src/renderer/bind_guard.h b/src/renderer/bind_guard.h new file mode 100644 index 0000000..59aebf3 --- /dev/null +++ b/src/renderer/bind_guard.h @@ -0,0 +1,30 @@ +#ifndef BIND_GUARD_H +#define BIND_GUARD_H + +#include +#include + +namespace openrayman +{ + // Holds a bind on a GL resource until it goes out of scope. + template + class bind_guard + { + static_assert(std::is_same::value || + std::is_same::value || + std::is_same::value, + "Not a GL resource type"); +public: + bind_guard(const T& t) + { + t.bind(); + } + + ~bind_guard() + { + T::bind_null(); + } + }; +} + +#endif diff --git a/src/renderer/buf.cc b/src/renderer/buf.cc new file mode 100644 index 0000000..b58480c --- /dev/null +++ b/src/renderer/buf.cc @@ -0,0 +1,102 @@ +#include +#include + +namespace openrayman +{ + thread_local GLuint vertex_buf::m_current_bound = 0; + thread_local GLuint element_buf::m_current_bound = 0; + + vertex_buf::vertex_buf(std::vector& initial_data, buf_mod_freq frequency) : + m_local_data(initial_data), m_frequency(frequency) + { + glGenBuffers(1, &m_gl_object); + commit(); + } + + void vertex_buf::commit() + { + bind_guard guard(*this); + GLenum usage; + switch(m_frequency) + { + case buf_mod_freq::never: + usage = GL_STATIC_DRAW; + break; + + case buf_mod_freq::some: + usage = GL_DYNAMIC_DRAW; + break; + + case buf_mod_freq::every_frame: + usage = GL_STREAM_DRAW; + break; + } + glBufferData(GL_ARRAY_BUFFER, m_local_data.size() * 4, m_local_data.data(), usage); + } + + vertex_buf::~vertex_buf() + { + glDeleteBuffers(1, &m_gl_object); + } + + void vertex_buf::bind() const + { + if(!is_bound()) + glBindBuffer(GL_ARRAY_BUFFER, m_gl_object); + m_current_bound = m_gl_object; + } + + void vertex_buf::bind_null() + { + if(m_current_bound != 0) + glBindBuffer(GL_ARRAY_BUFFER, 0); + m_current_bound = 0; + } + + element_buf::element_buf(std::vector& initial_data, buf_mod_freq frequency) : + m_local_data(initial_data), m_frequency(frequency) + { + glGenBuffers(1, &m_gl_object); + commit(); + } + + void element_buf::commit() + { + bind_guard guard(*this); + GLenum usage; + switch(m_frequency) + { + case buf_mod_freq::never: + usage = GL_STATIC_DRAW; + break; + + case buf_mod_freq::some: + usage = GL_DYNAMIC_DRAW; + break; + + case buf_mod_freq::every_frame: + usage = GL_STREAM_DRAW; + break; + } + glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_local_data.size(), m_local_data.data(), usage); + } + + void element_buf::bind() const + { + if(!is_bound()) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_gl_object); + m_current_bound = m_gl_object; + } + + void element_buf::bind_null() + { + if(m_current_bound != 0) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + m_current_bound = 0; + } + + element_buf::~element_buf() + { + glDeleteBuffers(1, &m_gl_object); + } +} diff --git a/src/renderer/buf.h b/src/renderer/buf.h new file mode 100644 index 0000000..be8b296 --- /dev/null +++ b/src/renderer/buf.h @@ -0,0 +1,93 @@ +#ifndef BUF_H +#define BUF_H + +#include +#include +#include +#include + +namespace openrayman +{ + // The suggested frequency at which a buffer will be modified, to enable for driver optimizations. + enum class buf_mod_freq + { + // The buffer will have data uploaded in its constructor and then preferably never again. + never, + // The buffer will be modified from time to time, but drawn many more times than that. + some, + // The buffer will be modified as many times at it is drawn (usually once every frame). + every_frame + }; + + // Buffer on the GPU that holds vertices. + class vertex_buf + { +public: + vertex_buf(std::vector& initial_data, buf_mod_freq frequency = buf_mod_freq::some); + ~vertex_buf(); + + // Data stored in RAM. This can be modified and then committed via commit(). + inline std::vector& local_data() + { + return m_local_data; + } + + // Commits applied changes to the GPU. + void commit(); + + // Binds this element buffer as the currently active buffer. + void bind() const; + + // Returns true if this buffer is the currently bound one. + inline bool is_bound() const + { + return m_current_bound == m_gl_object; + } + + // Binds a null buffer as the currently active buffer. + static void bind_null(); +private: + GLuint m_gl_object; + thread_local static GLuint m_current_bound; + + std::vector m_local_data; + buf_mod_freq m_frequency; + }; + + // Buffer on the GPU that holds elements. + class element_buf + { +public: + element_buf(std::vector& initial_data, buf_mod_freq frequency = buf_mod_freq::some); + ~element_buf(); + + // Data stored in RAM. This can be modified and then committed via commit(). + inline std::vector& local_data() + { + return m_local_data; + } + + // Commits applied changes to the GPU. + void commit(); + + // Binds this element buffer as the currently active buffer. + void bind() const; + + // Returns true if this buffer is the currently bound one. + inline bool is_bound() const + { + return m_current_bound == m_gl_object; + } + + // Binds a null buffer as the currently active buffer. + static void bind_null(); +private: + GLuint m_gl_object; + thread_local static GLuint m_current_bound; + + std::vector m_local_data; + buf_mod_freq m_frequency; + }; +} + +#endif diff --git a/src/renderer/renderer.h b/src/renderer/renderer.h new file mode 100644 index 0000000..097a22e --- /dev/null +++ b/src/renderer/renderer.h @@ -0,0 +1,12 @@ +#ifndef RENDERER_H +#define RENDERER_H + +namespace openrayman +{ + class renderer + { + + }; +} + +#endif diff --git a/src/renderer/texture.cc b/src/renderer/texture.cc new file mode 100644 index 0000000..a800b76 --- /dev/null +++ b/src/renderer/texture.cc @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include + +namespace openrayman +{ + thread_local GLuint texture::m_current_bound = 0; + + texture::texture(glm::vec2 size, texture_filter filter = texture_filter::linear) : + texture::texture(size, glm::vec4(1, 1, 1, 1), filter) + { + } + + texture::texture(glm::vec2 size, glm::vec4 fill_color, texture_filter filter = texture_filter::linear) : + m_size((int)size.x, (int)size.y), + m_filter(filter), m_is_expected_data(false), + m_local_cpy((int)size.x * (int)size.y, fill_color) + { + glGenTextures(1, &m_gl_object); + bind_guard guard(*this); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)size.x, (int)size.y, 0, GL_RGBA, GL_FLOAT, m_local_cpy.data()); + m_is_expected_data = true; + } + + texture::texture(glm::vec2 size, const std::vector& image, texture_filter filter = texture_filter::linear) : + m_size((int)size.x, (int)size.y), + m_filter(filter), + m_is_expected_data(false), + m_local_cpy(image) + { + glGenTextures(1, &m_gl_object); + bind_guard guard(*this); + if(m_size.x * m_size.y == m_local_cpy.size()) + { + m_is_expected_data = false; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)size.x, (int)size.y, 0, GL_RGBA, GL_FLOAT, m_local_cpy.data()); + } + else + { + std::cout << "[openrayman::renderer::texture (constructor)] input data does not match input size" << std::endl; + fill_with_white(); + } + } + + texture::texture(std::istream& stream, texture_filter filter = texture_filter::linear) : + m_size(1, 1), + m_filter(filter), + m_is_expected_data(false) + { + glGenTextures(1, &m_gl_object); + bind_guard guard(*this); + std::vector data; + stream.seekg(0); + + unsigned char buffer[1024 * 1024]; + std::streamsize read = 0; + while((read = stream.read(&buffer, 1024 * 1024)) > 0) + { + if(stream.fail()) + { + std::cout << "[openrayman::renderer::texture (constructor)] stream.fail()" << std::endl; + fill_with_white(); + return; + } + data.insert(data.end(), buffer, buffer + read); + } + + if(data.size() == 0) + { + std::cout << "[openrayman::renderer::texture (constructor)] no data in stream" << std::endl; + fill_with_white(); + return; + } + + std::vector result; + int result_w, result_h; + std::uint32_t err = lodepng::decode(result, result_w, result_h, data); + if(err) + { + fill_with_white(); + std::cout << "[openrayman::renderer::texture (constructor)] LodePNG error (" << lodepng_error_text(err) << ")" << std::endl; + } + else + { + m_size.w = result_w; + m_size.h = result_h; + m_local_cpy.resize(result_w * result_h); + for(std::size_t n = 0; n < result_w * result_h, n++) + m_local_cpy[n] = glm::vec4( + result[(n * 4)] / 255.0, + result[(n * 4) + 1] / 255.0, + result[(n * 4) + 2] / 255.0, + result[(n * 4) + 3] / 255.0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)size.x, (int)size.y, 0, GL_RGBA, GL_FLOAT, m_local_cpy.data()); + m_is_expected_data = true; + } + } + + void texture::fill_with_white() + { + m_local_cpy.clear(); + m_local_cpy.resize((int)size.x * (int)size.y); + std::fill(m_local_cpy.begin(), m_local_cpy.end(), glm::vec4(1, 1, 1, 1)); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)size.x, (int)size.y, 0, GL_RGBA, GL_FLOAT, m_local_cpy.data()); + } + + glm::vec4 texture::sample_color(glm::vec2 at) const + { + at = glm::vec2( + std::fmod(at.x, size.w), + std::fmod(at.y, size.h)); + return m_local_cpy[((int)at.y * size.w) + (int)at.x]; + } + + void texture::bind() const + { + if(!is_bound()) + glBindTexture(GL_TEXTURE_2D, m_gl_object); + m_current_bound = m_gl_object; + } + + void texture::bind_null() const + { + if(m_current_bound != 0) + glBindTexture(GL_TEXTURE_2D, 0); + m_current_bound = 0; + } + + texture::~texture() + { + glDeleteTexture(1, &m_gl_object); + } +} diff --git a/src/renderer/texture.h b/src/renderer/texture.h new file mode 100644 index 0000000..bbaf315 --- /dev/null +++ b/src/renderer/texture.h @@ -0,0 +1,89 @@ +#ifndef TEXTURE_H +#define TEXTURE_H + +#include +#include +#include +#include + +namespace openrayman +{ + enum class texture_filter + { + nearest = GL_NEAREST, + linear = GL_LINEAR + }; + + // RGBA32 texture on the GPU. + class texture + { + // Creates a new texture filled with white pixels (1, 1, 1, 1). + texture(glm::vec2 size, texture_filter filter = texture_filter::linear); + + // Creates a new texture filled with the color specified by "fill_color". + texture(glm::vec2 size, glm::vec4 fill_color, texture_filter filter = texture_filter::linear); + + // Creates a new texture filled with the data in the vector "image". + texture(glm::vec2 size, const std::vector& image, texture_filter filter = texture_filter::linear); + + // Creates a new texture from a stream. + // Current supported file formats are PNG. + texture(std::istream& stream, texture_filter filter = texture_filter::linear); + + ~texture(); + + // Returns true if this texture contains the expected data. + // This can return false if a file failed to load, + // size x/y == 0, or creation from a vector did not match the size parameter. + // In this case the texture will contain all white pixels (1, 1, 1, 1). + inline bool is_expected_data() const + { + return m_is_expected_data; + } + + // Returns the size of this texture. + inline glm::vec2 size() const + { + return m_size; + } + + // Returns the filtering of this texture. + inline texture_filter filter() const + { + return m_filter; + } + + // Returns the color of the pixel at the sample position. + glm::vec4 sample_color(glm::vec2 at) const; + + // Returns an array of all colors in this texture. + inline const std::vector& sample_all() const + { + return m_local_cpy; + } + + // Binds this texture as the currently active texture. + void bind() const; + + // Returns true if this texture is the currently bound one. + inline bool is_bound() const + { + return m_current_bound == m_gl_object; + } + + // Binds a null texture as the currently active texture. + static void bind_null(); + private: + GLuint m_gl_object; + thread_local static GLuint m_current_bound; + + void fill_with_white(); + + bool m_is_expected_data; + glm::vec2 m_size; + texture_filter m_filter; + std::vector m_local_cpy; + }; +} + +#endif diff --git a/src/window/sdl_window.cc b/src/window/sdl_window.cc index 0ebe0dc..c54d3ff 100644 --- a/src/window/sdl_window.cc +++ b/src/window/sdl_window.cc @@ -8,7 +8,7 @@ namespace openrayman sdl_window::sdl_window(config& config) : m_window(nullptr), - m_current_fullscreen(false), + m_is_fullscreen(false), m_vsync_enabled(false), m_wants_close(false), m_input_provider(config) @@ -64,7 +64,7 @@ namespace openrayman SDL_GL_SetSwapInterval(0); SDL_ShowCursor(SDL_DISABLE); - m_current_fullscreen = initial_fullscreen; + m_is_fullscreen = initial_fullscreen; return true; } @@ -166,7 +166,7 @@ namespace openrayman void sdl_window::set_size(int w, int h) { - if(!m_current_fullscreen) + if(!m_is_fullscreen) SDL_SetWindowSize(m_window, w, h); } @@ -184,10 +184,10 @@ namespace openrayman void sdl_window::set_fullscreen(bool fullscreen) { - if(fullscreen == m_current_fullscreen) + if(fullscreen == m_is_fullscreen) return; SDL_SetWindowFullscreen(m_window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0x00); - m_current_fullscreen = fullscreen; + m_is_fullscreen = fullscreen; } void sdl_window::set_icon(std::uint8_t* rgba32_data, int w, int h) diff --git a/src/window/sdl_window.h b/src/window/sdl_window.h index f9305f8..6017c85 100644 --- a/src/window/sdl_window.h +++ b/src/window/sdl_window.h @@ -42,7 +42,7 @@ public: private: SDL_Window* m_window; SDL_GLContext m_gl_ctx; - bool m_current_fullscreen, m_vsync_enabled, m_wants_close; + bool m_is_fullscreen, m_vsync_enabled, m_wants_close; standalone_input_provider m_input_provider; static int m_sdl_ref_count;