diff --git a/.gitignore b/.gitignore index 8e0ede845a..58d85fe013 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ Makefile callgrind.out.* perf.data* .gdb_history +.ycm_extra_conf.py diff --git a/CMakeLists.txt b/CMakeLists.txt index e028e768e2..67584b346e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,14 @@ if(NOT DEFINED WANT_INOTIFY) set(WANT_INOTIFY if_available) endif() +if(NOT DEFINED WANT_OPENGL) + set(WANT_OPENGL if_available) +endif() + +if(NOT DEFINED WANT_VULKAN) + set(WANT_VULKAN if_available) +endif() + if(NOT DEFINED WANT_GPERFTOOLS_PROFILER) set(WANT_GPERFTOOLS_PROFILER if_available) endif() @@ -59,7 +67,9 @@ endif() if(NOT DEFINED WANT_GPERFTOOLS_TCMALLOC) set(WANT_GPERFTOOLS_TCMALLOC false) endif() +# -- options +# set cmake paths set(BUILDSYSTEM_DIR "${CMAKE_SOURCE_DIR}/buildsystem") set(CMAKE_MODULE_PATH "${BUILDSYSTEM_DIR}" "${BUILDSYSTEM_DIR}/modules/") diff --git a/README.md b/README.md index f41566a946..1be1cfaacf 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Technology | Component **Python3** | Scripting, media conversion, in-game console, code generation **Cython** | Glue code **CMake** | Build system -**OpenGL2.1** | Rendering, shaders +**OpenGL3.3** | Rendering, shaders **SDL2** | Cross-platform Audio/Input/Window handling **Opus** | Audio codec **Humans** | Mixing together all of the above diff --git a/buildsystem/modules/FindInotify.cmake b/buildsystem/modules/FindInotify.cmake index 18addb5202..4b0cd367e3 100644 --- a/buildsystem/modules/FindInotify.cmake +++ b/buildsystem/modules/FindInotify.cmake @@ -1,4 +1,4 @@ -# Copyright 2014-2014 the openage authors. See copying.md for legal info. +# Copyright 2014-2015 the openage authors. See copying.md for legal info. # This module defines # @@ -8,6 +8,6 @@ find_path(INOTIFY_INCLUDE_DIR sys/inotify.h HINTS /usr/include/${CMAKE_LIBRARY_ARCHITECTURE}) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(INOTIFY DEFAULT_MSG INOTIFY_INCLUDE_DIR) +find_package_handle_standard_args(inotify DEFAULT_MSG INOTIFY_INCLUDE_DIR) mark_as_advanced(INOTIFY_INCLUDE_DIR) diff --git a/buildsystem/modules/FindVulkan.cmake b/buildsystem/modules/FindVulkan.cmake new file mode 100644 index 0000000000..6f5b3909ed --- /dev/null +++ b/buildsystem/modules/FindVulkan.cmake @@ -0,0 +1,50 @@ +# Copyright 2015-2015 the openage authors. See copying.md for legal info. +# +# (To distribute this file outside of openage, extend the above +# copyright line with an appropriate GPLv3 or later reference, +# as you probably don't have a our copying.md) + + +#================================================================= +# Locate Vulkan graphics library +# +# usage: +# find_package(Vulkan) +# +# This module sets the following variables: +# VULKAN_FOUND True, if the system has Vulkan. +# VULKAN_INCLUDE_DIR Path to the Vulkan include directory. +# VULKAN_LIBRARIES Paths to the Vulkan libraries. +#================================================================= + +set(_Vulkan_REQUIRED_VARS "VULKAN_vk_LIBRARY" "VULKAN_INCLUDE_DIR") + +find_path(VULKAN_INCLUDE_DIR + VK/vk.h + /opt/graphics/Vulkan/include +) + +find_library(VULKAN_vk_LIBRARY + NAMES VK Vulkan + PATHS + /opt/graphics/Vulkan/lib +) + +if(VULKAN_vk_LIBRARY) + set(VULKAN_LIBRARIES ${VULKAN_vk_LIBRARY}) +endif() + + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + Vulkan + REQUIRED_VARS ${_Vulkan_REQUIRED_VARS} + FAIL_MESSAGE "Vulkan NOT found." +) + +unset(_Vulkan_REQUIRED_VARS) + +mark_as_advanced( + VULKAN_INCLUDE_DIR + VULKAN_vk_LIBRARY +) diff --git a/configure b/configure index e9c110ff25..12b7fcc9b8 100755 --- a/configure +++ b/configure @@ -69,6 +69,8 @@ def features(args, parser): options = { "backtrace": "if_available", "inotify": "if_available", + "opengl": "if_available", + "vulkan": "if_available", "gperftools-tcmalloc": False, "gperftools-profiler": "if_available", } diff --git a/copying.md b/copying.md index c2ab7e5c6f..6b74f8d382 100644 --- a/copying.md +++ b/copying.md @@ -89,6 +89,7 @@ _the openage authors_ are: | Johannes Walcher | tomatower | johannes.walcher@stusta.de | | Akritas Akritidis | MaanooAk | akritasak@gmail.com | | Edgard Mota | edgardmota | edgardmota@gmail.com | +| Wojciech Nawrocki | Vtec234 | wjnawrocki@protonmail.com | If you're a first-time commiter, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. diff --git a/libopenage/CMakeLists.txt b/libopenage/CMakeLists.txt index c1653f29c6..887e5cfa3a 100644 --- a/libopenage/CMakeLists.txt +++ b/libopenage/CMakeLists.txt @@ -40,8 +40,8 @@ add_subdirectory("gui") add_subdirectory("error") add_subdirectory("gamestate") add_subdirectory("input") -add_subdirectory("log") add_subdirectory("job") +add_subdirectory("log") add_subdirectory("pathfinding") add_subdirectory("pyinterface") add_subdirectory("renderer") @@ -51,7 +51,7 @@ add_subdirectory("terrain") add_subdirectory("testing") add_subdirectory("unit") add_subdirectory("util") - +add_subdirectory("watch") # run codegen, add files to executable codegen_run() @@ -79,7 +79,6 @@ if(NOT WIN32) endif() find_package(Freetype REQUIRED) -find_package(OpenGL REQUIRED) find_package(SDL2 REQUIRED) find_package(SDL2Image REQUIRED) find_package(Opusfile REQUIRED) @@ -134,6 +133,35 @@ else() have_config_option(inotify INOTIFY false) endif() +# opengl support +if(WANT_OPENGL) + find_package(OpenGL) +endif() + +# vulkan support +if(WANT_VULKAN) + find_package(Vulkan) +endif() + +if(WANT_OPENGL AND OPENGL_FOUND) + have_config_option(opengl OPENGL true) + include_directories(${OPENGL_INCLUDE_DIR}) +else() + have_config_option(opengl OPENGL false) +endif() + +if(WANT_VULKAN AND VULKAN_FOUND) + have_config_option(vulkan VULKAN true) + include_directories(${VULKAN_INCLUDE_DIR}) +else() + have_config_option(vulkan VULKAN false) +endif() + +if(NOT (OPENGL_FOUND OR VULKAN_FOUND)) + message(FATAL_ERROR "One of OpenGL or Vulkan is required!") +endif() + + get_config_option_string() configure_file(config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/config.h) diff --git a/libopenage/assetmanager.cpp b/libopenage/assetmanager.cpp index adc99e44cf..798886d991 100644 --- a/libopenage/assetmanager.cpp +++ b/libopenage/assetmanager.cpp @@ -2,18 +2,17 @@ #include "assetmanager.h" -#if WITH_INOTIFY +#ifdef WITH_INOTIFY #include -#include -#include /* for NAME_MAX */ #endif -#include "util/compiler.h" -#include "util/file.h" #include "error/error.h" #include "log/log.h" - #include "texture.h" +#include "util/compiler.h" +#include "util/file.h" +#include "watch/watch.h" + namespace openage { @@ -23,13 +22,7 @@ AssetManager::AssetManager(qtsdl::GuiItemLink *gui_link) missing_tex{nullptr}, gui_link{gui_link} { -#if WITH_INOTIFY - // initialize the inotify instance - this->inotify_fd = inotify_init1(IN_NONBLOCK); - if (this->inotify_fd < 0) { - throw Error{MSG(err) << "Failed to initialize inotify!"}; - } -#endif + this->watch_manager = watch::WatchManager::create(); } util::Dir *AssetManager::get_data_dir() { @@ -70,14 +63,12 @@ std::shared_ptr AssetManager::load_texture(const std::string &name, boo // create the texture! tex = std::make_shared(filename, use_metafile); -#if WITH_INOTIFY - // create inotify update trigger for the requested file - int wd = inotify_add_watch(this->inotify_fd, filename.c_str(), IN_CLOSE_WRITE); - if (wd < 0) { - throw Error{MSG(warn) << "Failed to add inotify watch for " << filename}; - } - this->watch_fds[wd] = tex; -#endif + this->watch_manager->watch_file( + filename, + [=](watch::event_type, std::string) { + tex->reload(); + } + ); } // insert the texture into the map and return the texture. @@ -100,39 +91,7 @@ Texture *AssetManager::get_texture(const std::string &name, bool use_metafile) { } void AssetManager::check_updates() { -#if WITH_INOTIFY - // buffer for at least 4 inotify events - char buf[4 * (sizeof(struct inotify_event) + NAME_MAX + 1)]; - ssize_t len; - - while (true) { - // fetch all events, the kernel won't write "half" structs. - len = read(this->inotify_fd, buf, sizeof(buf)); - - if (len == -1 and errno == EAGAIN) { - // no events, nothing to do. - break; - } - else if (len == -1) { - throw Error{MSG(err) << "Failed to read inotify events!"}; - } - - // process fetched events, - // the kernel guarantees complete events in the buffer. - char *ptr = buf; - while (ptr < buf + len) { - struct inotify_event *event = (struct inotify_event *)ptr; - - if (event->mask & IN_CLOSE_WRITE) { - // TODO: this should invoke callback functions - this->watch_fds[event->wd]->reload(); - } - - // move the buffer ptr to the next event. - ptr += sizeof(struct inotify_event) + event->len; - } - } -#endif + this->watch_manager->check_changes(); } std::shared_ptr AssetManager::get_missing_tex() { diff --git a/libopenage/assetmanager.h b/libopenage/assetmanager.h index e72ebbe08c..94ca766ace 100644 --- a/libopenage/assetmanager.h +++ b/libopenage/assetmanager.h @@ -2,13 +2,14 @@ #pragma once -#include "config.h" - #include #include #include #include "util/dir.h" +#include "watch/watch.h" +#include "config.h" + namespace qtsdl { class GuiItemLink; @@ -53,6 +54,11 @@ class AssetManager final { void check_updates(); protected: + /** + * File change monitoring and automatic reloading. + */ + std::unique_ptr watch_manager; + /** * Create an internal texture handle. */ diff --git a/libopenage/audio/dynamic_resource.cpp b/libopenage/audio/dynamic_resource.cpp index f20db246f5..0e9ba3f781 100644 --- a/libopenage/audio/dynamic_resource.cpp +++ b/libopenage/audio/dynamic_resource.cpp @@ -4,6 +4,7 @@ #include "../engine.h" #include "../log/log.h" +#include "../job/job_manager.h" namespace openage { namespace audio { diff --git a/libopenage/config.h.in b/libopenage/config.h.in index ded482392f..fb22991587 100644 --- a/libopenage/config.h.in +++ b/libopenage/config.h.in @@ -8,6 +8,8 @@ #define WITH_BACKTRACE ${WITH_BACKTRACE} #define WITH_INOTIFY ${WITH_INOTIFY} +#define WITH_OPENGL ${WITH_OPENGL} +#define WITH_VULKAN ${WITH_VULKAN} #define WITH_GPERFTOOLS_PROFILER ${WITH_GPERFTOOLS_PROFILER} #define WITH_GPERFTOOLS_TCMALLOC ${WITH_GPERFTOOLS_TCMALLOC} diff --git a/libopenage/datastructure/constexpr_map.h b/libopenage/datastructure/constexpr_map.h new file mode 100644 index 0000000000..8138eccfa1 --- /dev/null +++ b/libopenage/datastructure/constexpr_map.h @@ -0,0 +1,300 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_DATASTRUCTURE_CONSTEXPR_MAP_H_ +#define OPENAGE_DATASTRUCTURE_CONSTEXPR_MAP_H_ + +#include +#include + +namespace openage { +namespace datastructure { + +/** + * One entry in the lookup map. + */ +template +class ConstMapEntry { +public: + constexpr ConstMapEntry(const K &key, const V &value) + : + key{key}, + value{value} {} + + constexpr ConstMapEntry(std::pair pair) + : + key{pair.first}, + value{pair.second} {} + + /** + * Map entry key. + */ + const K key; + + /** + * Map entry value. + */ + const V value; +}; + +/** + * Compiletime generic lookup map. + * + * This is a recursive template that decreases `pos` which each + * iteration step. Each `pos` stores one map entry. + * The lookup recursively descends those entries down to the zero-element. + * + * If you experience compiler errors, make sure you request _existing_ keys. + * We intentionally trigger compiler failures when a key doesn't exist. + * + * Messages include: "error: ā€˜*0u’ is not a constant expression" + * -> nonexistant key + */ +template +class ConstMap { +public: + template + constexpr ConstMap(Head head, Tail... tail) + : + value{head}, + tail{tail...} {} + + /** + * Return the number of entries in this map. + */ + constexpr int inline size() const { + return this->has_duplicates(), this->position(); + } + + /** + * Tests if the given key is in this map. + */ + constexpr bool inline contains(const K &key) const { + return this->has_duplicates(), this->has_entry(key); + } + + /** + * Return the stored value for the given key. + */ + constexpr const inline V &get(const K &key) const { + return this->has_duplicates(), this->must_contain(key), this->fetch(key); + } + + /** + * Access entries by map[key]. + */ + constexpr const inline V &operator [](const K &key) const { + return this->get(key); + } + +protected: + /** + * Called at compiletime when a key was used multiple times. + */ + const V &has_duplicate_key() const { + // duplicate key in map! + return *this->null; + } + + /** + * Called when a requested key is missing. + */ + constexpr bool missing_key() const { + // the requested key is not in the map! + return *this->null; + } + + /** + * Verifies that this entry's key is not used in following entries, + * then continues checking with the next entry checking for the same. + */ + constexpr bool inline all_are_unique() const { + return this->is_unique() and this->tail.all_are_unique(); + } + + /** + * Test if the key of this entry is not used in following entries. + */ + constexpr bool inline is_unique() const { + return not this->tail.has_entry(this->value.key); + } + + /** + * Returns the current entry position. + */ + constexpr int inline position() const { + return pos; + } + + /** + * Test if if the given key is stored in this element or in following elements. + */ + constexpr bool inline has_entry(const K &key) const { + return (this->value.key == key) or this->tail.has_entry(key); + } + + /** + * Returns the value matching key from this or following elements. + */ + constexpr const inline V &fetch(const K &key) const { + return + // if we found the key + (this->value.key == key) ? + // return the value: + this->value.value + : + // else try the lookup on the remaining elements: + this->tail.fetch(key); + } + + /** + * Abort when the requested key is not in the map. + */ + constexpr bool inline must_contain(const K &key) const { + return + this->has_entry(key) ? + true + : + // the requested key is not in the map + this->missing_key(); + } + + /** + * Abort when the map uses the same key more than once. + */ + constexpr bool inline has_duplicates() const { + return + this->all_are_unique() ? + true + : + this->has_duplicate_key(); + } + + /** + * The entry associated with this map position. + */ + const ConstMapEntry value; + + /** + * The remaining map entries. + */ + const ConstMap tail; + + /** + * Nullptr to induce compiler failures. + */ + static constexpr const V *null = nullptr; + + /** + * The previous entry is our friend. + */ + friend class ConstMap; +}; + + +/** + * constexpr map end container. + * When this element is reached, all contained entries have been processed. + * + * For example, when searching a key and calling to this class means + * the key was not found. + */ +template +class ConstMap { +public: + constexpr ConstMap() {} + + /** + * The size of the end element. + */ + constexpr int inline size() const { + return this->position(); + } + + /** + * The end element can't contain the key. Returns false. + */ + constexpr bool inline contains(const K &key) const { + return this->has_entry(key); + } + + /** + * The end element doesn't contain a value. Aborts. + */ + constexpr const inline V &get(const K &key) const { + return this->fetch(key); + } + +protected: + /** + * A key is not in the map. + * This function should never be called. If it is called, + * you requested a nonexisting key. + */ + constexpr const V &missing_key() const { + // intentional failure. + return *this->null; + } + + /** + * Reaching the last entry of the uniqueness check means + * we had no duplicates. + */ + constexpr bool inline all_are_unique() const { + return this->is_unique(); + } + + /** + * The end element doesn't have a key, so it's unique. + */ + constexpr bool inline is_unique() const { + return true; + } + + /** + * The end element is the last entry. + */ + constexpr int inline position() const { + return 0; + } + + /** + * The end element doesn't have an entry, + * so it can't supply a requested key. + */ + constexpr bool inline has_entry(const K &) const { + return false; + } + + /** + * Fetching a value from the end element aborts as it has no entry. + */ + constexpr const inline V &fetch(const K &) const { + return this->missing_key(); + } + + /** + * Null pointer dereference helper to trigger failures. + */ + static constexpr const V *null = nullptr; + + /** + * The previous entry class is our friend. + */ + friend class ConstMap; +}; + + +/** + * Creates a compiletime lookup table from + * K to V, where all entries of K must be unique. + * + * usage: constexpr auto bla = create_const_map(entry0, entry1, ...); + */ +template +constexpr ConstMap create_const_map(Entries&&... entry) { + return ConstMap(ConstMapEntry{std::forward(entry)}...); +} + +}} // openage::datastructure + +#endif diff --git a/libopenage/datastructure/tests.cpp b/libopenage/datastructure/tests.cpp index 6fb68afeea..106d79f1e6 100644 --- a/libopenage/datastructure/tests.cpp +++ b/libopenage/datastructure/tests.cpp @@ -2,8 +2,11 @@ #include "tests.h" +#include + #include "../testing/testing.h" +#include "constexpr_map.h" #include "doubly_linked_list.h" #include "pairing_heap.h" @@ -157,6 +160,46 @@ void doubly_linked_list() { (list.size() == 0) or TESTFAIL; } -} // namespace tests -} // namespace datastructure -} // namespace openage +// exported test +void constexpr_map() { + static_assert(create_const_map().size() == 0, "wrong size"); + static_assert(create_const_map(std::make_pair(0, 42)).size() == 1, + "wrong size"); + static_assert(create_const_map(std::make_pair(0, 42), + std::make_pair(13, 37)).size() == 2, + "wrong size"); + + static_assert(not create_const_map().contains(9001), + "empty map doesn't contain anything"); + static_assert(create_const_map(std::make_pair(42, 0), + std::make_pair(13, 37)).contains(42), + "contained element missing"); + static_assert(create_const_map(std::make_pair(42, 0), + std::make_pair(13, 37)).contains(13), + "contained element missing"); + static_assert(not create_const_map(std::make_pair(42, 0), + std::make_pair(13, 37)).contains(9001), + "uncontained element seems to be contained."); + + static_assert(create_const_map(std::make_pair(42, 9001), + std::make_pair(13, 37)).get(42) == 9001, + "fetched wrong value"); + static_assert(create_const_map(std::make_pair(42, 9001), + std::make_pair(13, 37)).get(13) == 37, + "fetched wrong value"); + + constexpr auto cmap = create_const_map( + std::make_pair(0, 0), + std::make_pair(13, 37), + std::make_pair(42, 9001) + ); + + cmap.contains(0) or TESTFAIL; + not cmap.contains(18) or TESTFAIL; + + cmap.size() == 3 or TESTFAIL; + cmap.get(13) == 37 or TESTFAIL; + cmap.get(42) == 9001 or TESTFAIL; +} + +}}} // openage::datastructure::tests diff --git a/libopenage/engine.cpp b/libopenage/engine.cpp index 3bcd168524..f952bf23d0 100644 --- a/libopenage/engine.cpp +++ b/libopenage/engine.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include "error/error.h" #include "error/gl_debug.h" @@ -19,16 +18,20 @@ #include "gamestate/game_main.h" #include "gamestate/generator.h" - +#include "job/job_manager.h" +#include "log/log.h" +#include "renderer/font/font.h" +#include "renderer/font/font_manager.h" +#include "renderer/renderer.h" +#include "renderer/text.h" +#include "renderer/window.h" +#include "screenshot.h" +#include "texture.h" #include "util/color.h" #include "util/fps.h" -#include "util/opengl.h" #include "util/strings.h" #include "util/timer.h" -#include "renderer/text.h" -#include "renderer/font/font.h" -#include "renderer/font/font_manager.h" /** * stores all things that have to do with the game. @@ -104,91 +107,22 @@ Engine::Engine(util::Dir *data_dir, int32_t fps_limit, bool gl_debug, const char // execution list. this->register_resize_action(this); - if (SDL_Init(SDL_INIT_VIDEO) < 0) { - throw Error(MSG(err) << "SDL video initialization: " << SDL_GetError()); - } else { - log::log(MSG(info) << "Initialized SDL video subsystems."); - } - - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); - SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - - int32_t window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED; - this->window = SDL_CreateWindow( - windowtitle, - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, - this->engine_coord_data->window_size.x, - this->engine_coord_data->window_size.y, - window_flags - ); - - if (this->window == nullptr) { - throw Error(MSG(err) << "Failed to create SDL window: " << SDL_GetError()); - } - - // load support for the PNG image formats, jpg bit: IMG_INIT_JPG - int wanted_image_formats = IMG_INIT_PNG; - int sdlimg_inited = IMG_Init(wanted_image_formats); - if ((sdlimg_inited & wanted_image_formats) != wanted_image_formats) { - throw Error(MSG(err) << "Failed to init PNG support: " << IMG_GetError()); - } - - if (gl_debug) - this->glcontext = error::create_debug_context(this->window); - else - this->glcontext = SDL_GL_CreateContext(this->window); - - if (this->glcontext == nullptr) { - throw Error(MSG(err) << "Failed creating OpenGL context: " << SDL_GetError()); - } - - // check the OpenGL version, for shaders n stuff - if (!epoxy_is_desktop_gl() || epoxy_gl_version() < 21) { - throw Error(MSG(err) << "OpenGL 2.1 not available"); - } - - // to quote the standard doc: - // 'The value gives a rough estimate - // of the largest texture that the GL can handle' - // -> wat? - // anyways, we need at least 1024x1024. - int max_texture_size; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); - log::log(MSG(dbg) << "Maximum supported texture size: " << max_texture_size); - if (max_texture_size < 1024) { - throw Error(MSG(err) << "Maximum supported texture size too small: " << max_texture_size); - } - - int max_texture_units; - glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &max_texture_units); - log::log(MSG(dbg) << "Maximum supported texture units: " << max_texture_units); - if (max_texture_units < 2) { - throw Error(MSG(err) << "Your GPU has not enough texture units: " << max_texture_units); - } - - // vsync on - SDL_GL_SetSwapInterval(1); - - // enable alpha blending - glEnable(GL_BLEND); + // register the engines input manager + this->register_input_action(&this->input_manager); - // order of drawing relevant for depth - // what gets drawn last is displayed on top. - glDisable(GL_DEPTH_TEST); + // create the graphical display + this->window = std::make_unique(windowtitle); + this->renderer = std::make_unique(this->window->get_context()); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + // renderer has to be notified of window size changes + this->register_resize_action(this->renderer.get()); // qml sources will be installed to the asset dir // otherwise assume that launched from the source dir using namespace std::string_literals; auto qml_search_paths = {this->data_dir->basedir, "libopenage/gui"s}; - this->gui = std::make_unique(this->window, "qml/main.qml", &this->singletons_info, qml_search_paths); + this->gui = std::make_unique(this->window->get_raw_window(), "qml/main.qml", &this->singletons_info, qml_search_paths); this->register_resize_action(this->gui.get()); this->register_input_action(this->gui.get()); this->register_drawhud_action(this->gui.get()); @@ -201,7 +135,7 @@ Engine::Engine(util::Dir *data_dir, int32_t fps_limit, bool gl_debug, const char if (number_of_worker_threads <= 0) { number_of_worker_threads = 1; } - this->job_manager = new job::JobManager{number_of_worker_threads}; + this->job_manager = std::make_unique(number_of_worker_threads); // initialize audio auto devices = audio::AudioManager::get_devices(); @@ -219,7 +153,7 @@ Engine::Engine(util::Dir *data_dir, int32_t fps_limit, bool gl_debug, const char this->drawing_huds.value = !this->drawing_huds.value; }); global_input_context.bind(action.get("SCREENSHOT"), [this](const input::action_arg_t &) { - this->get_screenshot_manager().save_screenshot(); + this->get_screenshot_manager()->save_screenshot(); }); global_input_context.bind(action.get("TOGGLE_DEBUG_OVERLAY"), [this](const input::action_arg_t &) { this->drawing_debug_overlay.value = !this->drawing_debug_overlay.value; @@ -249,12 +183,6 @@ Engine::~Engine() { this->profiler.unregister_all(); this->gui.reset(); - - delete this->job_manager; - SDL_GL_DeleteContext(glcontext); - SDL_DestroyWindow(window); - IMG_Quit(); - SDL_Quit(); } bool Engine::on_resize(coord::window new_size) { @@ -263,9 +191,6 @@ bool Engine::on_resize(coord::window new_size) { // update engine window size this->engine_coord_data->window_size = new_size; - // tell the screenshot manager about the new size - this->screenshot_manager.window_size = new_size; - // update camgame window position, set it to center. this->engine_coord_data->camgame_window = this->engine_coord_data->window_size / 2; @@ -276,9 +201,6 @@ bool Engine::on_resize(coord::window new_size) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); - // update OpenGL viewport: the renderin area - glViewport(0, 0, this->engine_coord_data->window_size.x, this->engine_coord_data->window_size.y); - // set orthographic projection: left, right, bottom, top, near_val, far_val glOrtho(0, this->engine_coord_data->window_size.x, 0, this->engine_coord_data->window_size.y, 9001, -1); @@ -434,7 +356,7 @@ void Engine::loop() { } glPopMatrix(); - util::gl_check_error(); + this->renderer->check_error(); glPushMatrix(); { // the hud coordinate system is automatically established @@ -443,7 +365,6 @@ void Engine::loop() { if (this->drawing_debug_overlay.value) { this->draw_debug_overlay(); - } if (this->drawing_huds.value) { @@ -459,7 +380,7 @@ void Engine::loop() { } glPopMatrix(); - util::gl_check_error(); + this->renderer->check_error(); this->profiler.end_measure("rendering"); @@ -467,7 +388,7 @@ void Engine::loop() { // the rendering is done // swap the drawing buffers to actually show the frame - SDL_GL_SwapWindow(window); + this->window->swap(); if (this->ns_per_frame != 0) { uint64_t ns_for_current_frame = cap_timer.getval(); @@ -476,6 +397,7 @@ void Engine::loop() { } } + // vsync wait time is over. this->profiler.end_measure("idle"); this->profiler.end_frame_measure(); @@ -511,15 +433,15 @@ GameMain *Engine::get_game() { } job::JobManager *Engine::get_job_manager() { - return this->job_manager; + return this->job_manager.get(); } audio::AudioManager &Engine::get_audio_manager() { return this->audio_manager; } -ScreenshotManager &Engine::get_screenshot_manager() { - return this->screenshot_manager; +ScreenshotManager *Engine::get_screenshot_manager() { + return this->screenshot_manager.get(); } input::ActionManager &Engine::get_action_manager() { diff --git a/libopenage/engine.h b/libopenage/engine.h index 1fea0559e9..5d0426e73b 100644 --- a/libopenage/engine.h +++ b/libopenage/engine.h @@ -5,11 +5,8 @@ #include #include #include - #include -#include - #include #include "log/log.h" @@ -23,43 +20,53 @@ #include "cvar/cvar.h" #include "game_singletons_info.h" #include "handlers.h" -#include "options.h" -#include "job/job_manager.h" +#include "input/action.h" // pxd: from libopenage.input.input_manager cimport InputManager #include "input/input_manager.h" -#include "input/action.h" -#include "util/externalprofiler.h" +#include "job/job_manager.h" +#include "options.h" +#include "renderer/text.h" +#include "renderer/window.h" +#include "screenshot.h" #include "util/dir.h" +#include "util/externalprofiler.h" #include "util/fps.h" #include "util/profiler.h" #include "unit/selection.h" #include "screenshot.h" + namespace openage { namespace gui { class GuiBasic; -} +class GuiItemLink; +} // openage::gui namespace renderer { - class Font; class FontManager; class TextRenderer; - +class Renderer; +class Window; } // openage::renderer +namespace job { +class JobManager; +} // openage::job + class DrawHandler; class TickHandler; class ResizeHandler; -class Generator; -class GameSpec; +class Font; class GameMain; -namespace gui { -class GuiItemLink; -} // openage::gui +class GameSpec; +class Generator; +class Player; +class ScreenshotManager; + struct coord_data { coord::window window_size{800, 600}; @@ -242,7 +249,7 @@ class Engine : public ResizeHandler, public options::OptionNode { /** * return this engine's screenshot manager. */ - ScreenshotManager &get_screenshot_manager(); + ScreenshotManager *get_screenshot_manager(); /** * return this engine's action manager. @@ -385,7 +392,7 @@ class Engine : public ResizeHandler, public options::OptionNode { /** * the engine's screenshot manager. */ - ScreenshotManager screenshot_manager; + std::unique_ptr screenshot_manager; /** * the engine's cvar manager. @@ -405,7 +412,7 @@ class Engine : public ResizeHandler, public options::OptionNode { /** * the engine's job manager, for asynchronous background task queuing. */ - job::JobManager *job_manager; + std::unique_ptr job_manager; /** * the engine's keybind manager. @@ -424,32 +431,40 @@ class Engine : public ResizeHandler, public options::OptionNode { std::unordered_map fonts; /** - * SDL window where everything is displayed within. + * The render window. Everything is drawn in here. + * Also contains the context. */ - SDL_Window *window; - - /** - * SDL OpenGL context, we'll only have one, - * but it would allow having multiple ones. - */ - SDL_GLContext glcontext; + std::unique_ptr window; /** * the gui binding */ std::unique_ptr gui; - /* - * the engines profiler + /** + * The renderer. Accepts all tasks to be drawn on screen. */ - util::Profiler profiler; + std::unique_ptr renderer; + /** + * The font manager to provide different sized and styled fonts. + */ std::unique_ptr font_manager; + + /** + * The engine's text renderer. To be integrated into the main renderer. + */ std::unique_ptr text_renderer; public: EngineSignals gui_signals; + gui::GuiItemLink *gui_link; + + /** + * the engines profiler + */ + util::Profiler profiler; }; } // namespace openage diff --git a/libopenage/error/error.h b/libopenage/error/error.h index 3c8ed868d1..a758d15f34 100644 --- a/libopenage/error/error.h +++ b/libopenage/error/error.h @@ -14,24 +14,22 @@ // pxd: from libopenage.error.backtrace cimport Backtrace namespace openage { -namespace error { -// forward-declaration to avoid the header include. -class Backtrace; -}} -namespace openage { namespace pyinterface { // forward-declaration for use in the 'friend' declaration below. class PyException; -}} - +} -namespace openage { +/** + * Contains the exception handling and backtracing subsystem. + */ namespace error { +class Backtrace; + /** - * Openage base exception type; the constructor usage is analogous to - * log::log(). + * openage base exception type. + * the constructor usage is analogous to log::log(). * * pxd: * @@ -174,4 +172,4 @@ inline std::string no_ensuring_message() using error::Error; -} // openage::error +} // openage diff --git a/libopenage/game_renderer.cpp b/libopenage/game_renderer.cpp index e80870104e..681dbbc30a 100644 --- a/libopenage/game_renderer.cpp +++ b/libopenage/game_renderer.cpp @@ -38,7 +38,6 @@ RenderOptions::RenderOptions() GameRenderer::GameRenderer(openage::Engine *e) : - engine{e} { // set options structure @@ -56,7 +55,7 @@ GameRenderer::GameRenderer(openage::Engine *e) std::vector player_color_lines; util::read_csv_file(asset_dir.join("player_palette.docx"), player_color_lines); - GLfloat *playercolors = new GLfloat[player_color_lines.size() * 4]; + std::unique_ptr playercolors = std::make_unique(player_color_lines.size() * 4); for (size_t i = 0; i < player_color_lines.size(); i++) { auto line = &player_color_lines[i]; playercolors[i*4] = line->r / 255.0; @@ -68,50 +67,39 @@ GameRenderer::GameRenderer(openage::Engine *e) // shader initialisation // read shader source codes and create shader objects for wrapping them. const char *shader_header_code = "#version 120\n"; - char *equalsEpsilon_code; - util::read_whole_file(&equalsEpsilon_code, data_dir->join("shaders/equalsEpsilon.glsl")); + std::string equals_epsilon_code = util::read_whole_file(data_dir->join("shaders/equalsEpsilon.glsl")); - char *texture_vert_code; - util::read_whole_file(&texture_vert_code, data_dir->join("shaders/maptexture.vert.glsl")); - auto plaintexture_vert = new shader::Shader(GL_VERTEX_SHADER, { shader_header_code, texture_vert_code }); - delete[] texture_vert_code; + std::string texture_vert_code = util::read_whole_file(data_dir->join("shaders/maptexture.vert.glsl")); + shader::Shader plaintexture_vert{GL_VERTEX_SHADER, {shader_header_code, texture_vert_code.c_str()}}; - char *texture_frag_code; - util::read_whole_file(&texture_frag_code, data_dir->join("shaders/maptexture.frag.glsl")); - auto plaintexture_frag = new shader::Shader(GL_FRAGMENT_SHADER, { shader_header_code, texture_frag_code }); - delete[] texture_frag_code; + std::string texture_frag_code = util::read_whole_file(data_dir->join("shaders/maptexture.frag.glsl")); + shader::Shader plaintexture_frag{GL_FRAGMENT_SHADER, {shader_header_code, texture_frag_code.c_str()}}; - char *teamcolor_frag_code; - util::read_whole_file(&teamcolor_frag_code, data_dir->join("shaders/teamcolors.frag.glsl")); + std::string teamcolor_frag_code = util::read_whole_file(data_dir->join("shaders/teamcolors.frag.glsl")); std::stringstream ss; ss << player_color_lines.size(); - auto teamcolor_frag = new shader::Shader(GL_FRAGMENT_SHADER, { shader_header_code, ("#define NUM_OF_PLAYER_COLORS " + ss.str() + "\n").c_str(), equalsEpsilon_code, teamcolor_frag_code }); - delete[] teamcolor_frag_code; - - char *alphamask_vert_code; - util::read_whole_file(&alphamask_vert_code, data_dir->join("shaders/alphamask.vert.glsl")); - auto alphamask_vert = new shader::Shader(GL_VERTEX_SHADER, { shader_header_code, alphamask_vert_code }); - delete[] alphamask_vert_code; + shader::Shader teamcolor_frag{GL_FRAGMENT_SHADER, { + shader_header_code, + ("#define NUM_OF_PLAYER_COLORS " + ss.str() + "\n").c_str(), + equals_epsilon_code.c_str(), + teamcolor_frag_code.c_str() + } + }; - char *alphamask_frag_code; - util::read_whole_file(&alphamask_frag_code, data_dir->join("shaders/alphamask.frag.glsl")); - auto alphamask_frag = new shader::Shader(GL_FRAGMENT_SHADER, { shader_header_code, alphamask_frag_code }); - delete[] alphamask_frag_code; + std::string alphamask_vert_code = util::read_whole_file(data_dir->join("shaders/alphamask.vert.glsl")); + shader::Shader alphamask_vert{GL_VERTEX_SHADER, {shader_header_code, alphamask_vert_code.c_str()}}; - char *texturefont_vert_code; - util::read_whole_file(&texturefont_vert_code, data_dir->join("shaders/texturefont.vert.glsl")); - auto texturefont_vert = new shader::Shader(GL_VERTEX_SHADER, { shader_header_code, texturefont_vert_code }); - delete[] texturefont_vert_code; + std::string alphamask_frag_code = util::read_whole_file(data_dir->join("shaders/alphamask.frag.glsl")); + shader::Shader alphamask_frag{GL_FRAGMENT_SHADER, {shader_header_code, alphamask_frag_code.c_str()}}; - char *texturefont_frag_code; - util::read_whole_file(&texturefont_frag_code, data_dir->join("shaders/texturefont.frag.glsl")); - auto texturefont_frag = new shader::Shader(GL_FRAGMENT_SHADER, { shader_header_code, texturefont_frag_code }); - delete[] texturefont_frag_code; + std::string texturefont_vert_code = util::read_whole_file(data_dir->join("shaders/texturefont.vert.glsl")); + shader::Shader texturefont_vert{GL_VERTEX_SHADER, {shader_header_code, texturefont_vert_code.c_str()}}; - delete[] equalsEpsilon_code; + std::string texturefont_frag_code = util::read_whole_file(data_dir->join("shaders/texturefont.frag.glsl")); + shader::Shader texturefont_frag{GL_FRAGMENT_SHADER, {shader_header_code, texturefont_frag_code.c_str()}}; // create program for rendering simple textures - texture_shader::program = new shader::Program(plaintexture_vert, plaintexture_frag); + texture_shader::program = new shader::Program(&plaintexture_vert, &plaintexture_frag); texture_shader::program->link(); texture_shader::texture = texture_shader::program->get_uniform_id("texture"); texture_shader::tex_coord = texture_shader::program->get_attribute_id("tex_coordinates"); @@ -122,7 +110,7 @@ GameRenderer::GameRenderer(openage::Engine *e) // create program for tinting textures at alpha-marked pixels // with team colors - teamcolor_shader::program = new shader::Program(plaintexture_vert, teamcolor_frag); + teamcolor_shader::program = new shader::Program(&plaintexture_vert, &teamcolor_frag); teamcolor_shader::program->link(); teamcolor_shader::texture = teamcolor_shader::program->get_uniform_id("texture"); teamcolor_shader::tex_coord = teamcolor_shader::program->get_attribute_id("tex_coordinates"); @@ -133,13 +121,12 @@ GameRenderer::GameRenderer(openage::Engine *e) glUniform1i(teamcolor_shader::texture, 0); glUniform1f(teamcolor_shader::alpha_marker_var, 254.0/255.0); // fill the teamcolor shader's player color table: - glUniform4fv(teamcolor_shader::player_color_var, 64, playercolors); + glUniform4fv(teamcolor_shader::player_color_var, 64, playercolors.get()); teamcolor_shader::program->stopusing(); - delete[] playercolors; // create program for drawing textures that are alpha-masked before - alphamask_shader::program = new shader::Program(alphamask_vert, alphamask_frag); + alphamask_shader::program = new shader::Program(&alphamask_vert, &alphamask_frag); alphamask_shader::program->link(); alphamask_shader::base_coord = alphamask_shader::program->get_attribute_id("base_tex_coordinates"); alphamask_shader::mask_coord = alphamask_shader::program->get_attribute_id("mask_tex_coordinates"); @@ -151,9 +138,8 @@ GameRenderer::GameRenderer(openage::Engine *e) glUniform1i(alphamask_shader::mask_texture, 1); alphamask_shader::program->stopusing(); - // Create program for texture based font rendering - texturefont_shader::program = new shader::Program(texturefont_vert, texturefont_frag); + texturefont_shader::program = new shader::Program(&texturefont_vert, &texturefont_frag); texturefont_shader::program->link(); texturefont_shader::texture = texturefont_shader::program->get_uniform_id("texture"); texturefont_shader::color = texturefont_shader::program->get_uniform_id("color"); @@ -162,15 +148,6 @@ GameRenderer::GameRenderer(openage::Engine *e) glUniform1i(texturefont_shader::texture, 0); texturefont_shader::program->stopusing(); - // after linking, the shaders are no longer necessary - delete plaintexture_vert; - delete plaintexture_frag; - delete teamcolor_frag; - delete alphamask_vert; - delete alphamask_frag; - delete texturefont_vert; - delete texturefont_frag; - // Renderer keybinds // TODO: a renderer settings struct // would allow these to be put somewher better diff --git a/libopenage/gamestate/game_spec.cpp b/libopenage/gamestate/game_spec.cpp index f4814b0203..07242a1dc5 100644 --- a/libopenage/gamestate/game_spec.cpp +++ b/libopenage/gamestate/game_spec.cpp @@ -1,15 +1,18 @@ // Copyright 2015-2016 the openage authors. See copying.md for legal info. +#include "game_spec.h" + +#include "../assetmanager.h" +#include "../engine.h" #include "../gamedata/blending_mode.gen.h" #include "../gamedata/string_resource.gen.h" #include "../gamedata/terrain.gen.h" +#include "../job/job_manager.h" #include "../unit/producer.h" #include "../util/strings.h" #include "../rng/global_rng.h" -#include "../assetmanager.h" -#include "../engine.h" #include "civilisation.h" -#include "game_spec.h" + #include diff --git a/libopenage/gui_basic.cpp b/libopenage/gui_basic.cpp index 6dea980021..4478649829 100644 --- a/libopenage/gui_basic.cpp +++ b/libopenage/gui_basic.cpp @@ -26,15 +26,29 @@ GuiBasic::GuiBasic(SDL_Window *window, const std::string &source, qtsdl::GuiSing const char *shader_header_code = "#version 120\n"; - char *texture_vert_code; - util::read_whole_file(&texture_vert_code, static_cast(singleton_items_info)->data_dir + "/shaders/identity.vert.glsl"); - auto plaintexture_vert = std::make_unique(GL_VERTEX_SHADER, std::initializer_list{ shader_header_code, texture_vert_code }); - delete[] texture_vert_code; - - char *texture_frag_code; - util::read_whole_file(&texture_frag_code, static_cast(singleton_items_info)->data_dir + "/shaders/maptexture.frag.glsl"); - auto plaintexture_frag = std::make_unique(GL_FRAGMENT_SHADER, std::initializer_list{ shader_header_code, texture_frag_code }); - delete[] texture_frag_code; + std::string texture_vert_code = util::read_whole_file( + static_cast(singleton_items_info)->data_dir + + "/shaders/identity.vert.glsl" + ); + auto plaintexture_vert = std::make_unique( + GL_VERTEX_SHADER, + std::initializer_list{ + shader_header_code, + texture_vert_code.c_str() + } + ); + + std::string texture_frag_code = util::read_whole_file( + static_cast(singleton_items_info)->data_dir + + "/shaders/maptexture.frag.glsl" + ); + auto plaintexture_frag = std::make_unique( + GL_FRAGMENT_SHADER, + std::initializer_list{ + shader_header_code, + texture_frag_code.c_str() + } + ); this->textured_screen_quad_shader = std::make_unique(&*plaintexture_vert, &*plaintexture_frag); this->textured_screen_quad_shader->link(); diff --git a/libopenage/handlers.h b/libopenage/handlers.h index a6b481b4bf..876fe16acf 100644 --- a/libopenage/handlers.h +++ b/libopenage/handlers.h @@ -9,8 +9,6 @@ namespace openage { -class Engine; - /** * superclass for all possible drawing operations in the game. */ diff --git a/libopenage/renderer/CMakeLists.txt b/libopenage/renderer/CMakeLists.txt index fbaa233231..0a6bd15a74 100644 --- a/libopenage/renderer/CMakeLists.txt +++ b/libopenage/renderer/CMakeLists.txt @@ -1,6 +1,26 @@ add_sources(libopenage + buffer.cpp color.cpp + context.cpp + context_state.cpp + material.cpp + pipeline.cpp + program.cpp + renderer.cpp + shader.cpp + task.cpp + tests.cpp text.cpp + texture.cpp + vertex_buffer.cpp + vertex_state.cpp + window.cpp ) -add_subdirectory(font) +pxdgen( + tests.h +) + +add_subdirectory(font/) +add_subdirectory(opengl/) +add_subdirectory(shaders/) diff --git a/libopenage/renderer/buffer.cpp b/libopenage/renderer/buffer.cpp new file mode 100644 index 0000000000..f01047d38e --- /dev/null +++ b/libopenage/renderer/buffer.cpp @@ -0,0 +1,39 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "buffer.h" + +namespace openage { +namespace renderer { + +Buffer::Buffer(Context *ctx, size_t size) + : + context{ctx}, + allocd{size}, + on_gpu{false} { + + if (size > 0) { + this->create(size); + } +} + +size_t Buffer::size() const { + return this->allocd; +} + +char *Buffer::get(bool mark_dirty) { + if (mark_dirty) { + this->mark_dirty(); + } + return this->buffer.get(); +} + +void Buffer::mark_dirty() { + this->on_gpu = false; +} + +void Buffer::create(size_t size) { + this->buffer = std::make_unique(size); + this->allocd = size; +} + +}} // openage::renderer diff --git a/libopenage/renderer/buffer.h b/libopenage/renderer/buffer.h new file mode 100644 index 0000000000..70eb543be8 --- /dev/null +++ b/libopenage/renderer/buffer.h @@ -0,0 +1,99 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_BUFFER_H_ +#define OPENAGE_RENDERER_BUFFER_H_ + +#include + +namespace openage { +namespace renderer { + +class Context; + + +/** + * Context-specialized graphics system buffer. + */ +class Buffer { +public: + enum class bind_target { + vertex_attributes, + element_indices, + query, + }; + + enum class usage { + static_draw, + static_read, + stream_draw, + stream_read, + stream_copy, + static_copy, + dynamic_draw, + dynamic_read, + dynamic_copy, + }; + + Buffer(Context *ctx, size_t size=0); + virtual ~Buffer() = default; + + Buffer(const Buffer &other) = delete; + Buffer(Buffer &&other) = delete; + Buffer &operator =(const Buffer &other) = delete; + Buffer &operator =(Buffer &&other) = delete; + + /** + * Returns the raw pointer to the buffer memory area. + */ + char *get(bool mark_dirty=false); + + /** + * Mark the buffer as changed so it will be reuploaded. + */ + void mark_dirty(); + + /** + * Create an empty buffer of the given size. + */ + void create(size_t size); + + /** + * Returns the allocated buffer size. + */ + size_t size() const; + + /** + * Uploads the current buffer contents to the GPU. + */ + virtual void upload(bind_target target, usage usage) = 0; + + /** + * Bind this buffer to the given target. + */ + virtual void bind(bind_target target) const = 0; + + /** + * The associated graphics context. + */ + Context *const context; + +protected: + /** + * The raw buffer containing the data. + */ + std::unique_ptr buffer; + + /** + * Stores the allocated buffer size. + */ + size_t allocd; + + /** + * True, when the buffer is present on the GPU. + */ + bool on_gpu; +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/color.cpp b/libopenage/renderer/color.cpp index d8b53660b5..035bcf710c 100644 --- a/libopenage/renderer/color.cpp +++ b/libopenage/renderer/color.cpp @@ -10,45 +10,25 @@ Color::Color() r{0}, g{0}, b{0}, - a{255} { - // Empty -} + a{255} {} -Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +Color::Color(color_channel_t r, color_channel_t g, + color_channel_t b, color_channel_t a) : r{r}, g{g}, b{b}, - a{a} { - // Empty -} - -Color::Color(const Color &other) - : - r{other.r}, - g{other.g}, - b{other.b}, - a{other.a} { - // Empty -} - -Color &Color::operator=(const Color &other) { - if (this != &other) { - this->r = other.r; - this->g = other.g; - this->b = other.b; - this->a = other.a; - } - - return *this; -} + a{a} {} -bool Color::operator==(const Color &other) const { - return this->r == other.r && this->g == other.g && this->b == other.b && this->a == other.a; +bool Color::operator ==(const Color &other) const { + return (this->r == other.r && + this->g == other.g && + this->b == other.b && + this->a == other.a); } -bool Color::operator!=(const Color &other) const { - return this->r != other.r || this->g != other.g || this->b != other.b || this->a != other.a; +bool Color::operator !=(const Color &other) const { + return not (*this == other); } }} // openage::renderer diff --git a/libopenage/renderer/color.h b/libopenage/renderer/color.h index 93bd7406cf..8341c5841a 100644 --- a/libopenage/renderer/color.h +++ b/libopenage/renderer/color.h @@ -8,24 +8,25 @@ namespace openage { namespace renderer { class Color { + using color_channel_t = uint8_t; + public: Color(); + Color(color_channel_t r, color_channel_t g, color_channel_t b, color_channel_t a); - Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a); - - Color(const Color &other); - - Color &operator=(const Color &other); - - bool operator==(const Color &other) const; + Color(const Color &other) = default; + Color(Color &&other) = default; + Color &operator =(const Color &other) = default; + Color &operator =(Color &&other) = default; - bool operator!=(const Color &other) const; + bool operator ==(const Color &other) const; - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; + bool operator !=(const Color &other) const; + color_channel_t r; + color_channel_t g; + color_channel_t b; + color_channel_t a; }; }} // openage::renderer diff --git a/libopenage/renderer/context.cpp b/libopenage/renderer/context.cpp new file mode 100644 index 0000000000..56e1fea602 --- /dev/null +++ b/libopenage/renderer/context.cpp @@ -0,0 +1,76 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "context.h" + +#include "../config.h" +#include "../log/log.h" +#include "../error/error.h" + +#if WITH_OPENGL +#include "opengl/context.h" +#endif + +#if WITH_VULKAN +#include "vulkan/context.h" +#endif + + +namespace openage { +namespace renderer { + +Context::Context(context_type type) + : + type{type} {} + +std::unique_ptr Context::generate(context_type t) { + context_type ctx_requested = t; + + if (t == context_type::autodetect) { + // priority: vulkan > opengl + // TODO: could use some boosting, that autodetection is kinda lame. + + if (WITH_VULKAN) { + ctx_requested = context_type::vulkan; + } + else if (WITH_OPENGL) { + ctx_requested = context_type::opengl; + } + else { + throw Error{MSG(err) << "No render context available!"}; + } + } + + if (ctx_requested == context_type::opengl) { + if (not WITH_OPENGL) { + throw Error{MSG(err) << "OpenGL support not enabled!"}; + } +#if WITH_OPENGL + log::log(MSG(dbg) << "Using OpenGL context..."); + return std::make_unique(); +#endif + } + else if (ctx_requested == context_type::vulkan) { + if (not WITH_VULKAN) { + throw Error{MSG(err) << "Vulkan support not enabled!"}; + } +#if WITH_VULKAN + log::log(MSG(dbg) << "Using Vulkan context..."); + return std::make_unique(); +#endif + } + else { + throw Error{MSG(err) << "Unknown context type requested!"}; + } + + throw Error{MSG(err) << "No context was created! Veeery bad."}; + return nullptr; +} + + +void Context::resize(const coord::window &new_size) { + this->canvas_size = new_size; + this->resize_canvas(this->canvas_size); +} + + +}} // namespace openage::renderer diff --git a/libopenage/renderer/context.h b/libopenage/renderer/context.h new file mode 100644 index 0000000000..dd2397e6c2 --- /dev/null +++ b/libopenage/renderer/context.h @@ -0,0 +1,181 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_CONTEXT_H_ +#define OPENAGE_RENDERER_CONTEXT_H_ + +#include +#include + +#include "context_state.h" +#include "../coord/window.h" + +namespace openage { +namespace renderer { + +class Buffer; +class ProgramSource; +class Program; +class Texture; +class TextureData; +class VertexState; + + +/** + * Available context types. + * Specifies all available backends. + * autodetect: use vulkan > opengl. could use some tuning :D + */ +enum class context_type { + autodetect, + vulkan, + opengl, +}; + +/** + * Context features that can be enabled or disabled. + * Used in Context::set_feature + */ +enum class context_feature { + blending, //!< Alpha blending + depth_test, //!< z-buffering for fragment depth tests +}; + + +/** + * Stores information about context capabilities and limitations. + */ +struct context_capability { + int max_vertex_attributes; + int max_texture_slots; + int max_texture_size; + + int major_version; + int minor_version; + + context_type type; +}; + + +/** + * Represents the rendering context. + * This class provides functionality that is dispatched according to the + * used backend. + * + * GPUs are state machines, this context stores/manages the state for + * the requested backend driver. + * This represents, for example: OpenGL, Vulkan, OpenGLES, WebGL, ... + */ +class Context { +public: + Context(context_type type); + virtual ~Context() = default; + + /** + * Create a context according to the requested type. + */ + static std::unique_ptr generate(context_type t); + + /** + * Called before the drawing window is created. + */ + virtual void prepare() = 0; + + /** + * Returns the SDL2 window flags for the requested context type. + */ + virtual uint32_t get_window_flags() const = 0; + + /** + * Actually creates the requested context for the given SDL window. + */ + virtual void create(SDL_Window *window) = 0; + + /** + * Setup calls for the newly created context. + * After creation, the context may want to have some setup + * calls that configure functionality. + */ + virtual void setup() = 0; + + /** + * Destroy the context to unregister it. + */ + virtual void destroy() = 0; + + /** + * Return the context properties such as max texture units, + * vertex attributes, etc. + */ + virtual context_capability get_capabilities() = 0; + + /** + * Enable or disable a given context feature. + * @param feature the feature to modify + * @param on whether to set this feature on or off. + */ + virtual void set_feature(context_feature feature, bool on) = 0; + + /** + * Save this context's framebuffer as a png screenshot. + */ + virtual void screenshot(const std::string &filename) = 0; + + /** + * Perform error checking to detect backend context problems. + */ + virtual void check_error() = 0; + + /** + * Register some texture data to the context. + * @returns the newly created Texture handle. + */ + virtual std::unique_ptr register_texture(const TextureData &data) = 0; + + /** + * Register some shader pipeline program to the context. + * @returns the newly created Program handle. + */ + virtual std::unique_ptr register_program(const ProgramSource &data) = 0; + + /** + * Create a buffer handle + * @returns the newly created buffer state. + */ + virtual std::unique_ptr create_buffer(size_t size=0) = 0; + + /** + * Create a vertex state tracker + */ + virtual std::unique_ptr create_vertex_state() = 0; + + /** + * Resize the context because the surrounding window size was updated. + */ + void resize(const coord::window &new_size); + + /** + * Context state tracking. Contains state that is common + * to all actual context backends. + */ + ContextState state; + +protected: + /** + * Perform context-specific calls to resize the drawing canvas. + */ + virtual void resize_canvas(const coord::window &new_size) = 0; + + /** + * Type of this context, namely the backend variant. + */ + context_type type; + + /** + * Render surface size. + */ + coord::window canvas_size; +}; + +}} // namespace openage::renderer + +#endif diff --git a/libopenage/renderer/context_state.cpp b/libopenage/renderer/context_state.cpp new file mode 100644 index 0000000000..764d6b9619 --- /dev/null +++ b/libopenage/renderer/context_state.cpp @@ -0,0 +1,13 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "context_state.h" + +namespace openage { +namespace renderer { + +ContextState::ContextState() + : + program{nullptr}, + vertex_state{nullptr} {} + +}} // openage::renderer diff --git a/libopenage/renderer/context_state.h b/libopenage/renderer/context_state.h new file mode 100644 index 0000000000..03c20e2c4c --- /dev/null +++ b/libopenage/renderer/context_state.h @@ -0,0 +1,30 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_CONTEXT_STATE_H_ +#define OPENAGE_RENDERER_CONTEXT_STATE_H_ + +namespace openage { +namespace renderer { + +class Program; +class VertexState; + +class ContextState { +public: + ContextState(); + ~ContextState() = default; + + /** + * Active pipeline program. + */ + Program *program; + + /** + * Active vertex size, type and buffer configuration. + */ + VertexState *vertex_state; +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/geometry.cpp b/libopenage/renderer/geometry.cpp new file mode 100644 index 0000000000..1602aa0112 --- /dev/null +++ b/libopenage/renderer/geometry.cpp @@ -0,0 +1,18 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "geometry.h" + +namespace openage { +namespace renderer { + +Geometry::Geometry(mesh_t vertices) + : + vertices{vertices} {} + +mesh_t get_mesh() { + return this->vertices() +} + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/geometry.h b/libopenage/renderer/geometry.h new file mode 100644 index 0000000000..fae018ccd3 --- /dev/null +++ b/libopenage/renderer/geometry.h @@ -0,0 +1,37 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_GEOMETRY_H_ +#define OPENAGE_RENDERER_GEOMETRY_H_ + +#include "vertex_buffer.h" +#include "vertex_state.h" + +namespace openage { +namespace renderer { + +class Context; +class Material; + +/** + * Drawable geometry, stores all triangles vertices. + */ +class Geometry { +public: + Geometry(mesh_t vertices={}); + virtual ~Geometry() = default; + + /** + * Return the associated vertex list. + */ + const mesh_t &get_mesh(); + +protected: + /** + * Triangle vertex storage. + */ + mesh_t vertices; +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/material.cpp b/libopenage/renderer/material.cpp new file mode 100644 index 0000000000..25e13d9bc9 --- /dev/null +++ b/libopenage/renderer/material.cpp @@ -0,0 +1,13 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + + +#include "material.h" + +namespace openage { +namespace renderer { + +Material::Material(Program *prg) + : + Pipeline{prg} {} + +}} // openage::renderer diff --git a/libopenage/renderer/material.h b/libopenage/renderer/material.h new file mode 100644 index 0000000000..c327ce7d8b --- /dev/null +++ b/libopenage/renderer/material.h @@ -0,0 +1,37 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_MATERIAL_H_ +#define OPENAGE_RENDERER_MATERIAL_H_ + +#include + +#include "pipeline.h" +#include "../util/vector.h" + +namespace openage { +namespace renderer { + +/** + * A material is a pipeline configuration that allows to setting predefined + * properties. + */ +class Material : public Pipeline { +public: + // standard position point position. + using position_t = util::Vector<4>; + using mesh_t = std::vector; + + /** + * Creates a "type wrapper" around the pipeline program. + * This "Material" is then used to create pipeline + * properties and rendering algorithms. + */ + Material(Program *prg); + virtual ~Material() = default; + + virtual void set_positions(mesh_t positions) = 0; +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/opengl/CMakeLists.txt b/libopenage/renderer/opengl/CMakeLists.txt new file mode 100644 index 0000000000..abff85271b --- /dev/null +++ b/libopenage/renderer/opengl/CMakeLists.txt @@ -0,0 +1,9 @@ +add_sources(libopenage + buffer.cpp + context.cpp + pipeline.cpp + program.cpp + shader.cpp + texture.cpp + vertex_state.cpp +) diff --git a/libopenage/renderer/opengl/buffer.cpp b/libopenage/renderer/opengl/buffer.cpp new file mode 100644 index 0000000000..dcaeed879d --- /dev/null +++ b/libopenage/renderer/opengl/buffer.cpp @@ -0,0 +1,86 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "../../config.h" +#if WITH_OPENGL + +#include "buffer.h" + +#include + +#include "../../error/error.h" + +namespace openage { +namespace renderer { +namespace opengl { + +Buffer::Buffer(renderer::Context *ctx, size_t size) + : + renderer::Buffer{ctx, size} { + + glGenBuffers(1, &this->id); +} + +Buffer::~Buffer() { + glDeleteBuffers(1, &this->id); +} + +void Buffer::upload(bind_target target, usage usage) { + GLenum gl_usage = this->get_usage(usage); + GLenum gl_slot = this->get_target(target); + + if (not this->on_gpu) { + this->bind(target); + glBufferData(gl_slot, this->allocd, this->get(), gl_usage); + this->on_gpu = true; + } +} + +void Buffer::bind(bind_target target) const { + GLenum gl_slot = this->get_target(target); + // TODO: ask the context if already bound. + glBindBuffer(gl_slot, this->id); +} + + +GLenum Buffer::get_target(bind_target target) { + switch (target) { + case bind_target::vertex_attributes: + return GL_ARRAY_BUFFER; + case bind_target::element_indices: + return GL_ELEMENT_ARRAY_BUFFER; + case bind_target::query: + return GL_QUERY_BUFFER; + default: + throw Error{MSG(err) << "unimplemented buffer binding target!"}; + } +} + +GLenum Buffer::get_usage(usage u) { + switch (u) { + case usage::static_draw: + return GL_STATIC_DRAW; + case usage::static_read: + return GL_STATIC_READ; + case usage::stream_draw: + return GL_STREAM_DRAW; + case usage::stream_read: + return GL_STREAM_READ; + case usage::stream_copy: + return GL_STREAM_COPY; + case usage::static_copy: + return GL_STATIC_COPY; + case usage::dynamic_draw: + return GL_DYNAMIC_DRAW; + case usage::dynamic_read: + return GL_DYNAMIC_READ; + case usage::dynamic_copy: + return GL_DYNAMIC_COPY; + + default: + throw Error{MSG(err) << "unimplemented buffer usage prediction!"}; + } +} + +}}} // openage::renderer::opengl + +#endif diff --git a/libopenage/renderer/opengl/buffer.h b/libopenage/renderer/opengl/buffer.h new file mode 100644 index 0000000000..1b26c2df15 --- /dev/null +++ b/libopenage/renderer/opengl/buffer.h @@ -0,0 +1,54 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_OPENGL_BUFFER_H_ +#define OPENAGE_RENDERER_OPENGL_BUFFER_H_ + +#include "../buffer.h" + +#include +#include + +namespace openage { +namespace renderer { +namespace opengl { + +/** + * OpenGL data buffer. + */ +class Buffer : public renderer::Buffer { +public: + Buffer(renderer::Context *ctx, size_t size=0); + virtual ~Buffer(); + + Buffer(const Buffer &other) = delete; + Buffer(Buffer &&other) = delete; + Buffer &operator =(const Buffer &other) = delete; + Buffer &operator =(Buffer &&other) = delete; + + /** + * Uploads the current state of the buffer to the GPU. + */ + void upload(bind_target target, usage usage) override; + + /** + * Bind this buffer to the specified slot. + */ + void bind(bind_target target) const override; + + /** + * Fetch the OpenGL specific buffer slot identification. + */ + static GLenum get_target(bind_target target); + + /** + * Fetch the OpenGL specific buffer usage prediction id. + */ + static GLenum get_usage(usage usage); + +protected: + GLuint id; +}; + +}}} // openage::renderer::opengl + +#endif diff --git a/libopenage/renderer/opengl/context.cpp b/libopenage/renderer/opengl/context.cpp new file mode 100644 index 0000000000..da6ed8f90f --- /dev/null +++ b/libopenage/renderer/opengl/context.cpp @@ -0,0 +1,333 @@ +// Copyright 2015-2016 the openage authors. See copying.md for legal info. + +#include "../../config.h" +#if WITH_OPENGL + +#include "context.h" + +#include +#include + +#include +#include +#include + +#include "buffer.h" +#include "program.h" +#include "texture.h" +#include "vertex_state.h" +#include "../../log/log.h" +#include "../../error/error.h" + +namespace openage { +namespace renderer { +namespace opengl { + +Context::Context() + : + renderer::Context{context_type::opengl} {} + +Context::~Context() {} + +uint32_t Context::get_window_flags() const { + return SDL_WINDOW_OPENGL; +} + +// first element is the min supported version +// last element is the max supported version +constexpr std::array, 6> gl_versions = {{{3,3}, {4,0}, {4,1}, {4,2}, {4,3}, {4,4}}}; + +void Context::prepare() { + // set initially to maximum supported version so that if the for loop doesn't fail the max one is used + int opengl_version_major = gl_versions[gl_versions.size() - 1].first; + int opengl_version_minor = gl_versions[gl_versions.size() - 1].second; + + SDL_Window *test_window = SDL_CreateWindow("test", 0, 0, 2, 2, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN); + if (test_window == nullptr) { + throw Error(MSG(err) << "Failed creating window for OpenGL context testing. SDL Error: " << SDL_GetError()); + } + SDL_GLContext test_context; + + // check each version for availability + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + + for (size_t i_ver = 0; i_ver < gl_versions.size(); ++i_ver) { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, gl_versions[i_ver].first); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl_versions[i_ver].second); + test_context = SDL_GL_CreateContext(test_window); + + if (test_context == nullptr) { + if (i_ver == 0) { + throw Error(MSG(err) << "OpenGL version " << gl_versions[0].first << "." << gl_versions[0].second << " is not available. It is the minimal required version."); + } + else { + opengl_version_major = gl_versions[i_ver - 1].first; + opengl_version_minor = gl_versions[i_ver - 1].second; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, opengl_version_major); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, opengl_version_minor); + break; + } + } + + SDL_GL_DeleteContext(test_context); + } + + test_context = SDL_GL_CreateContext(test_window); + if (test_context == nullptr) { + throw Error(MSG(err) << "Failed to create OpenGL context which previously succeeded. This should not happen! SDL Error: " << SDL_GetError()); + } + + auto &cap = this->capability; + + cap.type = this->type; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &cap.max_texture_size); + glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &cap.max_texture_slots); + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &cap.max_vertex_attributes); + + glGetIntegerv(GL_MAJOR_VERSION, &cap.major_version); + glGetIntegerv(GL_MINOR_VERSION, &cap.minor_version); + + SDL_GL_DeleteContext(test_context); + SDL_DestroyWindow(test_window); +} + +void Context::create(SDL_Window *window) { + this->glcontext = SDL_GL_CreateContext(window); + + if (this->glcontext == nullptr) { + throw Error(MSG(err) << "Failed creating OpenGL context: " << SDL_GetError()); + } + + // check the OpenGL version, for shaders n stuff + // TODO: this doesn't look necessary. libepoxy supports up to 4.4, which is the maximum supported version in gl_versions + // and if GL 3.3 is not available than the context above will fail anyway and it won't get to this point + int epoxy_glv = this->capability.major_version * 10 + this->capability.minor_version; + if (not epoxy_is_desktop_gl() or epoxy_gl_version() < epoxy_glv) { + throw Error(MSG(err) << "OpenGL " + << this->capability.major_version << "." << this->capability.minor_version + << " not available"); + } + + log::log(MSG(info) << "Using OpenGL " << this->capability.major_version << "." << this->capability.minor_version); +} + +void Context::setup() { + // TODO: context capability checking + auto &caps = this->capability; + + // to quote the standard doc: 'The value gives a rough estimate of the + // largest texture that the GL can handle' + // -> wat? anyways, we need at least 1024x1024. + log::log(MSG(dbg) << "Maximum supported texture size: " + << caps.max_texture_size); + if (caps.max_texture_size < 1024) { + throw Error(MSG(err) << "Maximum supported texture size too small: " + << caps.max_texture_size); + } + + log::log(MSG(dbg) << "Maximum supported texture units: " + << caps.max_texture_slots); + if (caps.max_texture_slots < 2) { + throw Error(MSG(err) << "Your GPU has not enough texture units: " + << caps.max_texture_slots); + } + + + // vsync on + // TODO: maybe move somewhere else or to the window. + SDL_GL_SetSwapInterval(1); + + // TODO: move to somewhere else, not all contexts may want those: + + // enable alpha blending + this->set_feature(context_feature::blending, true); + + // order of drawing relevant for depth + // what gets drawn last is displayed on top. + this->set_feature(context_feature::depth_test, false); + + // TODO: generalize like set_feature. + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + + +context_capability Context::get_capabilities() { + return this->capability; +} + + +void Context::destroy() { + SDL_GL_DeleteContext(this->glcontext); +} + + +void Context::set_feature(context_feature feature, bool on) { + // what feature to change? this is the argument to glEnable and glDisable. + GLenum what; + + switch (feature) { + case context_feature::blending: + what = GL_BLEND; + break; + + case context_feature::depth_test: + what = GL_DEPTH_TEST; + break; + + default: + throw Error(MSG(err) << "unknown opengl context feature to set"); + } + + if (on) { + glEnable(what); + } else { + glDisable(what); + } +} + + +void Context::screenshot(const std::string &filename) { + log::log(MSG(info) << "Saving screenshot to " << filename); + + // surface color masks. + int32_t rmask, gmask, bmask, amask; + rmask = 0x000000FF; + gmask = 0x0000FF00; + bmask = 0x00FF0000; + amask = 0xFF000000; + + // create output surface which will be stored later. + SDL_Surface *screen = SDL_CreateRGBSurface( + SDL_SWSURFACE, + this->canvas_size.x, this->canvas_size.y, + 32, rmask, gmask, bmask, amask + ); + + size_t pxcount = screen->w * screen->h; + + auto pxdata = std::make_unique(pxcount); + + // copy the whole framebuffer to our local buffer. + glReadPixels(0, 0, + this->canvas_size.x, this->canvas_size.y, + GL_RGBA, GL_UNSIGNED_BYTE, pxdata.get()); + + uint32_t *surface_pxls = reinterpret_cast(screen->pixels); + + // now copy the raw data to the sdl surface. + // we need to invert all pixel rows, but leave column order the same. + for (ssize_t row = 0; row < screen->h; row++) { + ssize_t irow = screen->h - 1 - row; + for (ssize_t col = 0; col < screen->w; col++) { + uint32_t pxl = pxdata[irow * screen->w + col]; + + // TODO: store the alpha channels in the screenshot, + // is buggy at the moment.. + surface_pxls[row * screen->w + col] = pxl | 0xFF000000; + } + } + + // call sdl_image for saving the screenshot to png + IMG_SavePNG(screen, filename.c_str()); + SDL_FreeSurface(screen); +} + + +void Context::check_error() { + int glerrorstate = 0; + + glerrorstate = glGetError(); + if (glerrorstate != GL_NO_ERROR) { + + const char *errormsg; + + //generate error message + switch (glerrorstate) { + case GL_INVALID_ENUM: + // An unacceptable value is specified for an enumerated argument. + // The offending command is ignored + // and has no other side effect than to set the error flag. + errormsg = "invalid enum passed to opengl call"; + break; + case GL_INVALID_VALUE: + // A numeric argument is out of range. + // The offending command is ignored + // and has no other side effect than to set the error flag. + errormsg = "invalid value passed to opengl call"; + break; + case GL_INVALID_OPERATION: + // The specified operation is not allowed in the current state. + // The offending command is ignored + // and has no other side effect than to set the error flag. + errormsg = "invalid operation performed during some state"; + break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + // The framebuffer object is not complete. The offending command + // is ignored and has no other side effect than to set the error flag. + errormsg = "invalid framebuffer operation"; + break; + case GL_OUT_OF_MEMORY: + // There is not enough memory left to execute the command. + // The state of the GL is undefined, + // except for the state of the error flags, + // after this error is recorded. + errormsg = "out of memory, wtf?"; + break; + case GL_STACK_UNDERFLOW: + // An attempt has been made to perform an operation that would + // cause an internal stack to underflow. + errormsg = "stack underflow"; + break; + case GL_STACK_OVERFLOW: + // An attempt has been made to perform an operation that would + // cause an internal stack to overflow. + errormsg = "stack overflow"; + break; + default: + // unknown error state + errormsg = "unknown error"; + } + throw Error( + MSG(err) << + "OpenGL error state id: " << glerrorstate + << "\n\t" << errormsg + ); + } +} + + +std::unique_ptr Context::register_texture(const TextureData &data) { + std::unique_ptr txt = std::make_unique(this, data); + return txt; +} + +std::unique_ptr Context::register_program(const ProgramSource &data) { + std::unique_ptr prg = std::make_unique(this, data); + return prg; +} + +std::unique_ptr Context::create_buffer(size_t size) { + std::unique_ptr buf = std::make_unique(this, size); + return buf; +} + +std::unique_ptr Context::create_vertex_state() { + std::unique_ptr vstate = std::make_unique(this); + return vstate; +} + + + +void Context::resize_canvas(const coord::window &new_size) { + log::log(MSG(dbg) << "opengl viewport resize to " << new_size.x << "x" << new_size.y); + + glViewport(0, 0, new_size.x, new_size.y); +} + + +}}} // namespace openage::renderer::opengl + +#endif // if WITH_OPENGL diff --git a/libopenage/renderer/opengl/context.h b/libopenage/renderer/opengl/context.h new file mode 100644 index 0000000000..edb6e9d0a6 --- /dev/null +++ b/libopenage/renderer/opengl/context.h @@ -0,0 +1,116 @@ +// Copyright 2015-2016 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_OPENGL_CONTEXT_H_ +#define OPENAGE_RENDERER_OPENGL_CONTEXT_H_ + +#include +#include + +#include "../context.h" + + +namespace openage { +namespace renderer { + +/** + * OpenGL specific renderer code. + * Is selected if the requested backend is OpenGL. + */ +namespace opengl { + +class Context : public renderer::Context { +public: + Context(); + ~Context(); + + /** + * Called before the drawing window is created. + */ + void prepare() override; + + /** + * Returns the SDL window flags for the opengl window. + */ + uint32_t get_window_flags() const override; + + /** + * Actually creates the OpenGL context for the given SDL window. + */ + void create(SDL_Window *window) override; + + /** + * Setup calls for the newly created context. + * + * Enables opengl functions like blending etc. + */ + void setup() override; + + /** + * Deinitializes and unregisters the gl context from SDL2. + */ + void destroy() override; + + /** + * use glEnable and glDisable to toggle a given feature. + */ + void set_feature(context_feature feature, bool on) override; + + /** + * Read the opengl framebuffer and dump it to a png file. + */ + void screenshot(const std::string &filename) override; + + /** + * Check if OpenGL detected any state machine errors. + */ + void check_error() override; + + /** + * Creates the opengl texture in this context. + * @returns a handle to it. + */ + std::unique_ptr register_texture(const TextureData &data) override; + + /** + * Register a glsl shader pipeline program to the context. + * @returns a handle to the new program. + */ + std::unique_ptr register_program(const ProgramSource &data) override; + + /** + * Create a opengl buffer handle + * @returns the newly created state object. + */ + std::unique_ptr create_buffer(size_t size=0) override; + + /** + * Create an opengl vertex state tracker + */ + std::unique_ptr create_vertex_state() override; + + + /** + * Return the available OpenGL context properties and limitations. + */ + context_capability get_capabilities() override; + +protected: + /** + * Resize the opengl viewport. + */ + void resize_canvas(const coord::window &new_size) override; + + /** + * SDL opengl context state. + */ + SDL_GLContext glcontext; + + /** + * The capability of this context. + */ + context_capability capability; +}; + +}}} // namespace openage::renderer + +#endif diff --git a/libopenage/renderer/opengl/pipeline.cpp b/libopenage/renderer/opengl/pipeline.cpp new file mode 100644 index 0000000000..5081509da7 --- /dev/null +++ b/libopenage/renderer/opengl/pipeline.cpp @@ -0,0 +1,20 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +/** @file + * OpenGL specific pipeline code. + */ + +#include "../../config.h" +#if WITH_OPENGL + +#include "pipeline.h" + +namespace openage { +namespace renderer { +namespace opengl { + +// TODO + +}}} // openage::renderer::opengl + +#endif diff --git a/libopenage/renderer/opengl/pipeline.h b/libopenage/renderer/opengl/pipeline.h new file mode 100644 index 0000000000..ecd2964ce5 --- /dev/null +++ b/libopenage/renderer/opengl/pipeline.h @@ -0,0 +1,16 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_OPENGL_PIPELINE_H_ +#define OPENAGE_RENDERER_OPENGL_PIPELINE_H_ + + +#include "../pipeline.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +}}} // openage::renderer::opengl + +#endif diff --git a/libopenage/renderer/opengl/program.cpp b/libopenage/renderer/opengl/program.cpp new file mode 100644 index 0000000000..d30f853076 --- /dev/null +++ b/libopenage/renderer/opengl/program.cpp @@ -0,0 +1,238 @@ +// Copyright 2013-2015 the openage authors. See copying.md for legal info. + +#include "../../config.h" +#if WITH_OPENGL + +#include "program.h" + +#include "texture.h" +#include "../vertex_buffer.h" +#include "../../log/log.h" +#include "../../error/error.h" +#include "../../util/compiler.h" +#include "../../util/file.h" +#include "../../util/strings.h" + +namespace openage { +namespace renderer { +namespace opengl { + +Program::Program(renderer::Context *context, const ProgramSource &source) + : + renderer::Program{context}, + is_linked{false} { + + // tell OpenGL we wanna have a new pipeline program + this->id = glCreateProgram(); + + if (unlikely(this->id == 0)) { + throw Error{MSG(err) << "no gl program created! wtf?", true}; + } + + // add all shader sources + for (auto &shadersource : source.get_shaders()) { + // create the shader from the source code + Shader shader{*shadersource}; + + // attach the shader to this program + this->attach_shader(shader); + } + + // link the sources together. + this->link(); +} + +Program::~Program() { + glDeleteProgram(this->id); +} + +void Program::attach_shader(const Shader &shader) { + if (unlikely(this->is_linked)) { + throw Error{MSG(err) << "can't attach shader to linked program", true}; + } + + this->shader_ids.push_back(shader.id); + glAttachShader(this->id, shader.id); +} + +void Program::link() { + if (unlikely(this->is_linked)) { + throw Error{MSG(err) << "can't relink a program", true}; + } + + // link shaders to a program + glLinkProgram(this->id); + this->check(GL_LINK_STATUS); + + // check if program is usable + glValidateProgram(this->id); + this->check(GL_VALIDATE_STATUS); + + this->is_linked = true; + + for (auto &id : this->shader_ids) { + glDetachShader(this->id, id); + } +} + +void Program::check(GLenum what_to_check) { + GLint status; + glGetProgramiv(this->id, what_to_check, &status); + + if (status != GL_TRUE) { + GLint loglen; + glGetProgramiv(this->id, GL_INFO_LOG_LENGTH, &loglen); + + auto infolog = std::make_unique(loglen); + glGetProgramInfoLog(this->id, loglen, NULL, infolog.get()); + + const char *what_str; + switch(what_to_check) { + case GL_LINK_STATUS: + what_str = "linking"; + break; + case GL_VALIDATE_STATUS: + what_str = "validation"; + break; + case GL_COMPILE_STATUS: + what_str = "compiliation"; + break; + default: + what_str = ""; + break; + } + + throw Error{MSG(err) << "Program " << what_str << " failed:\n" << infolog, true}; + } +} + +void Program::activate() { + if (unlikely(not this->is_linked)) { + throw Error{MSG(err) << "using program before it was linked!"}; + } + + glUseProgram(this->id); +} + +void Program::stopusing() { + glUseProgram((GLuint) 0); +} + +void Program::check_is_linked(const char *info) { + if (unlikely(not this->is_linked)) { + throw Error{MSG(err) << info << " before program was linked!"}; + } +} + +GLint Program::get_uniform_id(const char *name) { + GLint loc; + + // check if the location is cached. + auto it = this->uniforms.find(name); + if (it == this->uniforms.end()) { + this->check_is_linked("Uniform id requested"); + loc = glGetUniformLocation(this->id, name); + + // save to cache + this->uniforms[name] = loc; + } else { + loc = it->second; + } + + return loc; +} + +GLint Program::get_uniformbuffer_id(const char *name) { + this->check_is_linked("Uniform buffer requested"); + return glGetUniformBlockIndex(this->id, name); +} + +GLint Program::get_attribute_id(const char *name) { + this->check_is_linked("Vertex attribute requested"); + + GLint aid = glGetAttribLocation(this->id, name); + + if (unlikely(aid == -1)) { + this->dump_attributes(); + throw Error{MSG(err) << "Attribute " << name + << " queried but not found or active" + << " (optimized out by the compiler?).", true}; + } + + return aid; +} + +void Program::set_attribute_id(const char *name, GLuint id) { + if (unlikely(this->is_linked)) { + throw Error{MSG(err) + << "you assigned attribute '" << name << " = " + << id << "' after program was linked!", true}; + } + else { + glBindAttribLocation(this->id, id, name); + } +} + +void Program::dump_attributes() { + auto msg = MSG(info); + msg << "Dumping shader program " << this->id << " active attribute list:"; + + GLint num_attribs; + glGetProgramiv(this->id, GL_ACTIVE_ATTRIBUTES, &num_attribs); + + GLint attrib_max_length; + glGetProgramiv(this->id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &attrib_max_length); + + for (int i = 0; i < num_attribs; i++) { + GLsizei attrib_length; + GLint attrib_size; + GLenum attrib_type; + auto attrib_name = std::make_unique(attrib_max_length); + + glGetActiveAttrib(this->id, i, attrib_max_length, &attrib_length, &attrib_size, &attrib_type, attrib_name.get()); + + msg << "\n -> attribute " << attrib_name + << ": type=" << attrib_type << ", size=" << attrib_size + << ", id=" << this->get_attribute_id(attrib_name.get()); + } + + log::log(msg); +} + + +void Program::set_uniform_3f(const char *name, const util::Vector<3> &value) { + this->use(); + GLint location = this->get_uniform_id(name); + glUniform3f(location, value[0], value[1], value[2]); +} + + +void Program::set_uniform_1i(const char *name, const int &value) { + this->use(); + GLint location = this->get_uniform_id(name); + glUniform1i(location, value); +} + +void Program::set_uniform_1ui(const char *name, const unsigned &value) { + this->use(); + GLint location = this->get_uniform_id(name); + glUniform1ui(location, value); +} + + +void Program::set_uniform_2dtexture(const char *name, const renderer::Texture *texture) { + this->use(); + + // set the sampler "value" to the texture slot id. + GLint location = this->get_uniform_id(name); + + // TODO: use multiple slots! use context state tracking! + // TODO: slot assignage algorithm. + int slot = 0; + glUniform1i(location, slot); + texture->bind_to(slot); +} + +}}} // openage::renderer::opengl + +#endif // WITH_OPENGL diff --git a/libopenage/renderer/opengl/program.h b/libopenage/renderer/opengl/program.h new file mode 100644 index 0000000000..6eea29bd53 --- /dev/null +++ b/libopenage/renderer/opengl/program.h @@ -0,0 +1,145 @@ +// Copyright 2013-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_OPENGL_PROGRAM_H_ +#define OPENAGE_RENDERER_OPENGL_PROGRAM_H_ + +#include +#include +#include + +#include "../program.h" +#include "shader.h" + +namespace openage { +namespace renderer { + +class Context; + +namespace opengl { + +class Program : public renderer::Program { +public: + /** + * A program is created from shader sources. + */ + Program(renderer::Context *context, const ProgramSource &source); + virtual ~Program(); + + /** + * Activate the program on the GPU. + */ + void activate() override; + + /** + * Return the opengl handle id for a given pipeline uniform variable. + */ + GLint get_uniform_id(const char *name); + + /** + * Return the opengl handle id for a given uniform buffer object name. + */ + GLint get_uniformbuffer_id(const char *name); + + /** + * Return the opengl layout id for a given vertex attribute name. + */ + int get_attribute_id(const char *name) override; + + /** + * Set vertex attribute with given name to have a custom id. + */ + void set_attribute_id(const char *name, GLuint id); + + /** + * Query OpenGL which of the vertex attributes are actually active + * and haven't been optimized out by the compiler. + */ + void dump_attributes() override; + + /* ====== */ + // shader variables + + /** + * Set a 3 dimensional float vector + */ + void set_uniform_3f(const char *name, const util::Vector<3> &value) override; + + /** + * Set a single integer value + */ + void set_uniform_1i(const char *name, const int &value) override; + + /** + * Set a single unsigned integer value + */ + void set_uniform_1ui(const char *name, const unsigned &value) override; + + /** + * Set 2d texture data. + */ + void set_uniform_2dtexture(const char *name, const renderer::Texture *value) override; + + +protected: + /** + * add a shader to be included in this program. + */ + void attach_shader(const opengl::Shader &s); + + /** + * Combine all attached shaders and link them + * to a usable program. + */ + void link(); + + /** + * Don't use the program any more. + * Return to static pipeline configuration. + */ + void stopusing(); + + /** + * The OpenGL handle id for this program. + */ + GLuint id; + + /** + * True when this program was linked. + */ + bool is_linked; + + /** + * Shaders attached to this program. + */ + std::vector shader_ids; + + /** + * Uniform id cache. + * + * Maps uniform variable name to the handle id. + */ + std::unordered_map uniforms; + + /** + * checks a given status for this program. + * + * @param what_to_check can be one of GL_LINK_STATUS + * GL_VALIDATE_STATUS GL_COMPILE_STATUS + */ + void check(GLenum what_to_check); + + /** + * check if this program was linked already. + */ + void check_is_linked(const char *info=""); + + /** + * Get information about the program. + */ + GLint get_info(GLenum pname); +}; + + +}}} // openage::renderer::opengl + +#endif diff --git a/libopenage/renderer/opengl/shader.cpp b/libopenage/renderer/opengl/shader.cpp new file mode 100644 index 0000000000..446227e5d7 --- /dev/null +++ b/libopenage/renderer/opengl/shader.cpp @@ -0,0 +1,130 @@ +// Copyright 2013-2015 the openage authors. See copying.md for legal info. + +#include "../../config.h" +#if WITH_OPENGL + +#include "shader.h" + +#include "../../error/error.h" +#include "../../log/log.h" +#include "../../util/compiler.h" +#include "../../util/file.h" +#include "../../util/strings.h" + +namespace openage { +namespace renderer { +namespace opengl { + +Shader::Shader(const ShaderSource &source) { + // assign gl shader type + switch (source.get_type()) { + case shader_type::vertex: + this->type = GL_VERTEX_SHADER; + break; + + case shader_type::fragment: + this->type = GL_FRAGMENT_SHADER; + break; + + case shader_type::geometry: + this->type = GL_GEOMETRY_SHADER; + break; + + case shader_type::tesselation_control: + this->type = GL_TESS_CONTROL_SHADER; + break; + + case shader_type::tesselation_evaluation: + this->type = GL_TESS_EVALUATION_SHADER; + break; + + default: + throw Error{MSG(err) << "Unknown shader type requested."}; + } + + // create shader from source code! + this->create_from_source(source.get_source()); +} + +Shader::Shader(GLenum type, const char *source) + : + type{type} { + + // create the shader! + this->create_from_source(source); +} + +void Shader::create_from_source(const char *source) { + // allocate shader in opengl + this->id = glCreateShader(this->type); + + if (unlikely(this->id == 0)) { + throw Error{MSG(err) << "no gl shader created! wtf?", true}; + } + + // load shader source + glShaderSource(this->id, 1, &source, NULL); + + // compile shader source + glCompileShader(this->id); + + // check compiliation result + GLint status; + glGetShaderiv(this->id, GL_COMPILE_STATUS, &status); + + if (status != GL_TRUE) { + GLint loglen; + glGetShaderiv(this->id, GL_INFO_LOG_LENGTH, &loglen); + + auto infolog = std::make_unique(loglen); + glGetShaderInfoLog(this->id, loglen, NULL, infolog.get()); + + auto errmsg = MSG(err); + errmsg << "Failed to compile shader:\n" << infolog; + + glDeleteShader(this->id); + + throw Error{errmsg, true}; + } +} + +Shader::~Shader() { + glDeleteShader(this->id); +} + +const char *Shader::typestring() { + switch (this->type) { + case GL_VERTEX_SHADER: + return "vertex shader"; + case GL_FRAGMENT_SHADER: + return "fragment shader"; + case GL_GEOMETRY_SHADER: + return "geometry shader"; + case GL_TESS_CONTROL_SHADER: + return "tesselation control shader"; + case GL_TESS_EVALUATION_SHADER: + return "tesselation evaluation shader"; + default: + return "unknown shader type"; + } +} + + +VertexShader::VertexShader(const char *source) + : + Shader{GL_VERTEX_SHADER, source} {} + + +FragmentShader::FragmentShader(const char *source) + : + Shader{GL_FRAGMENT_SHADER, source} {} + + +GeometryShader::GeometryShader(const char *source) + : + Shader{GL_GEOMETRY_SHADER, source} {} + + +}}} // openage::renderer::opengl + +#endif // WITH_OPENGL diff --git a/libopenage/renderer/opengl/shader.h b/libopenage/renderer/opengl/shader.h new file mode 100644 index 0000000000..4d0195218d --- /dev/null +++ b/libopenage/renderer/opengl/shader.h @@ -0,0 +1,82 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_OPENGL_SHADER_H_ +#define OPENAGE_RENDERER_OPENGL_SHADER_H_ + +#include + +#include "../shader.h" + +namespace openage { +namespace renderer { +namespace opengl { + +class Shader { +public: + /** + * Create an OpenGL shader from a source code string. + */ + Shader(GLenum type, const char *source); + + /** + * Create an OpenGL shader from a ShaderSource object. + */ + Shader(const ShaderSource &source); + + // don't allow copying as the shader has a context-bound opengl handle id + Shader(const Shader ©) = delete; + Shader &operator=(const Shader ©) = delete; + + // moving could be enabled i guess, but beware the destructor call. + Shader(Shader &&other) = delete; + Shader &operator=(Shader &&other) = delete; + + virtual ~Shader(); + + /** + * Return the shader type name. + */ + const char *typestring(); + + /** + * OpenGL handle id. + */ + GLuint id; + +protected: + /** + * actually create the shader. + */ + void create_from_source(const char *source); + + /** + * OpenGL shader type enum. + */ + GLenum type; +}; + + +class VertexShader : public Shader { +public: + VertexShader(const char *source); + virtual ~VertexShader() {}; +}; + + +class FragmentShader : public Shader { +public: + FragmentShader(const char *source); + virtual ~FragmentShader() {}; +}; + + +class GeometryShader : public Shader { +public: + GeometryShader(const char *source); + virtual ~GeometryShader() {}; +}; + + +}}} // openage::renderer::opengl + +#endif diff --git a/libopenage/renderer/opengl/texture.cpp b/libopenage/renderer/opengl/texture.cpp new file mode 100644 index 0000000000..f78c08a386 --- /dev/null +++ b/libopenage/renderer/opengl/texture.cpp @@ -0,0 +1,68 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "../../config.h" +#if WITH_OPENGL + +#include "texture.h" + +#include + +#include "../../error/error.h" + + +namespace openage { +namespace renderer { +namespace opengl { + + +Texture::Texture(renderer::Context *context, const TextureData &txt) + : + renderer::Texture{context} { + + // generate opengl texture handle + glGenTextures(1, &this->id); + glBindTexture(GL_TEXTURE_2D, id); + + int input_format, output_format; + + // select pixel format + switch (txt.format) { + case texture_format::rgb: + input_format = GL_RGB8; + output_format = GL_RGB; + break; + case texture_format::rgba: + input_format = GL_RGBA8; + output_format = GL_RGBA; + break; + default: + throw Error{MSG(err) << "invalid texture format passed to OpenGL."}; + } + + // store raw pixels to gpu + glTexImage2D( + GL_TEXTURE_2D, 0, + input_format, txt.w, txt.h, 0, + output_format, GL_UNSIGNED_BYTE, txt.data.get() + ); + + // drawing settings + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glBindTexture(GL_TEXTURE_2D, 0); +} + +Texture::~Texture() { + glDeleteTextures(1, &this->id); +} + +void Texture::bind_to(int slot) const { + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_2D, this->id); +} + + +}}} // openage::renderer::opengl + +#endif diff --git a/libopenage/renderer/opengl/texture.h b/libopenage/renderer/opengl/texture.h new file mode 100644 index 0000000000..a84a36131e --- /dev/null +++ b/libopenage/renderer/opengl/texture.h @@ -0,0 +1,37 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_OPENGL_TEXTURE_H_ +#define OPENAGE_RENDERER_OPENGL_TEXTURE_H_ + +#include + +#include "../texture.h" + +namespace openage { +namespace renderer { +namespace opengl { + +/** + * An OpenGL texture. + */ +class Texture : public renderer::Texture { +public: + Texture(renderer::Context *context, const TextureData &data); + ~Texture(); + + /** + * Bind this texture to the given slot id. + */ + void bind_to(int slot) const override; + +protected: + /** + * OpenGL handle id. + */ + GLuint id; +}; + + +}}} // openage::renderer::opengl + +#endif diff --git a/libopenage/renderer/opengl/vertex_state.cpp b/libopenage/renderer/opengl/vertex_state.cpp new file mode 100644 index 0000000000..f3bb12351a --- /dev/null +++ b/libopenage/renderer/opengl/vertex_state.cpp @@ -0,0 +1,94 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "../../config.h" +#if WITH_OPENGL + +#include "vertex_state.h" + +#include "../../error/error.h" +#include "../../log/log.h" + +namespace openage { +namespace renderer { +namespace opengl { + +VertexState::VertexState(renderer::Context *ctx) + : + renderer::VertexState{ctx} { + + glGenVertexArrays(1, &this->id); +} + +VertexState::~VertexState() { + glDeleteVertexArrays(1, &this->id); +} + +void VertexState::attach_buffer(VertexBuffer &buf) { + // make the vao active, it will store the section assignment + this->bind(); + + // push the buffer to the gpu, if neccessary. + buf.upload(); + + // assign the sections, set the pointers + for (auto §ion : buf.get_sections()) { + glEnableVertexAttribArray(section.attr_id); + + GLenum type = this->get_attribute_type(section.type); + + glVertexAttribPointer( + section.attr_id, + section.dimension, + type, GL_FALSE, 0, + reinterpret_cast(section.offset) + ); + + if (this->bound_attributes.count(section.attr_id) > 0) { + throw Error(MSG(err) << "attribute " << section.attr_id + << " already set!"); + } + + // add to the list of active attributes + this->bound_attributes.insert(section.attr_id); + } +} + +void VertexState::detach_buffer(VertexBuffer &buf) { + // make the vao active, it will store the section removal + this->bind(); + + // remove sections and attribute ids + for (auto §ion : buf.get_sections()) { + glDisableVertexAttribArray(section.attr_id); + + // remove from the list of active attributes + this->bound_attributes.erase(section.attr_id); + } +} + +void VertexState::bind() const { + // when binding, first enable the vao as state storage + glBindVertexArray(this->id); + + // then enable all contained attribute ids + for (auto &attr_id : this->bound_attributes) { + glEnableVertexAttribArray(attr_id); + } +} + +GLenum VertexState::get_attribute_type(vertex_attribute_type type) { + switch (type) { + case vertex_attribute_type::float_32: + return GL_FLOAT; + case vertex_attribute_type::integer_32: + return GL_INT; + + default: + throw Error{MSG(err) << "unimplemented vertex buffer data type!"}; + } +} + + +}}} // openage::renderer::opengl + +#endif diff --git a/libopenage/renderer/opengl/vertex_state.h b/libopenage/renderer/opengl/vertex_state.h new file mode 100644 index 0000000000..db0a4fc108 --- /dev/null +++ b/libopenage/renderer/opengl/vertex_state.h @@ -0,0 +1,55 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_OPENGL_VERTEX_STATE_H_ +#define OPENAGE_RENDERER_OPENGL_VERTEX_STATE_H_ + +#include + +#include "../vertex_buffer.h" +#include "../vertex_state.h" + +namespace openage { +namespace renderer { +namespace opengl { + +/** + * OpenGL-specific vertex array object management. + * This wraps a vertex array object (vao). + */ +class VertexState : public renderer::VertexState { +public: + VertexState(renderer::Context *ctx); + virtual ~VertexState(); + + /** + * Do attribute buffer section assignments. + * This uploads the buffer to the gpu. + * This sets pointers to the given buffer to prepare usage. + */ + void attach_buffer(VertexBuffer &buf) override; + + /** + * Remove attribute buffer section assignments. + */ + void detach_buffer(VertexBuffer &buf) override; + + /** + * Make this vertex array object the current one. + */ + void bind() const override; + + /** + * Convert the buffer section data type to the OpenGL enum. + */ + static GLenum get_attribute_type(vertex_attribute_type type); + +protected: + /** + * Internal OpenGL handle id. + */ + GLuint id; +}; + +}}} // openage::renderer::opengl + +#endif diff --git a/libopenage/renderer/pipeline.cpp b/libopenage/renderer/pipeline.cpp new file mode 100644 index 0000000000..8a0bcc0eaa --- /dev/null +++ b/libopenage/renderer/pipeline.cpp @@ -0,0 +1,210 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +/** @file + * common code for all pipeline programs. + */ + +#include "pipeline.h" + +#include "context.h" +#include "vertex_buffer.h" +#include "vertex_state.h" +#include "../log/log.h" +#include "../util/compiler.h" + +#include +#include + +namespace openage { +namespace renderer { + +PipelineVariable::PipelineVariable(const std::string &name, + Pipeline *pipeline) + : + pipeline{pipeline}, + name{name} {} + +const std::string &PipelineVariable::get_name() { + return this->name; +} + +const char *PipelineVariable::get_name_cstr() { + return this->name.c_str(); +} + + +template<> +void Uniform::upload_value() { + this->pipeline->get_program()->set_uniform_2dtexture(this->get_name_cstr(), this->value); +} + + +template<> +void Uniform::upload_value() { + this->pipeline->get_program()->set_uniform_1i(this->get_name_cstr(), this->value); +} + + +template<> +void Uniform::upload_value() { + unsigned val = this->value ? 1 : 0; + this->pipeline->get_program()->set_uniform_1ui(this->get_name_cstr(), val); +} + + +Pipeline::Pipeline(Program *prg) + : + program{prg} {} + + +Pipeline::Pipeline(Pipeline &&other) + : + program{other.program} { + + this->update_proplists(other); +} + +Pipeline::Pipeline(const Pipeline &other) + : + program{other.program} { + + this->update_proplists(other); +} + +Pipeline &Pipeline::operator =(Pipeline &&other) { + this->program = other.program; + this->update_proplists(other); + return *this; +} + +Pipeline &Pipeline::operator =(const Pipeline &other) { + this->program = other.program; + this->update_proplists(other); + return *this; +} + +Program *Pipeline::get_program() { + return this->program; +} + +void Pipeline::add_var(PipelineVariable *var) { + if (BaseAttribute *attr = dynamic_cast(var)) { + this->attributes.push_back(attr); + } + else if (BaseUniform *unif = dynamic_cast(var)) { + this->uniforms.push_back(unif); + } + else { + // unknown variable, ignore it. + } +} + + +VertexBuffer Pipeline::create_attribute_buffer() { + // create a fresh buffer + VertexBuffer vbuf{this->program->context}; + + // fill the buffer with the current vertex data. + this->update_buffer(&vbuf); + + return vbuf; +} + + +void Pipeline::update_buffer(VertexBuffer *vbuf) { + // TODO: use buffersubdata to only transfer the modified + // parts of the buffer. + + // determine vertex attribute buffer size + size_t buf_size = 0; + + // number of vertices configured + size_t vertex_count = 0; + + // we'll overwrite the whole buffer, so reset the metadata. + vbuf->reset(); + + // gather the expected buffer section and sizes. + for (auto &var : this->attributes) { + size_t entry_count = var->entry_count(); + + // the first attribute determines the expected size. + // all other attribute-definitions will have to provide the same + // number of entries (so that all vertices can get the attributes). + if (unlikely(vertex_count == 0)) { + vertex_count = entry_count; + } + else if (unlikely(vertex_count != entry_count)) { + throw error::Error{MSG(err) + << "inconsistent vertex attribute count: expected " + << vertex_count << " but " << var->get_name() + << " has " << entry_count << " entries."}; + } + + // a new section in the big vertex buffer + VertexBuffer::vbo_section section{ + var->get_attr_id(), + var->type(), + var->dimension(), + buf_size // current buffer size, increased for each section. + }; + vbuf->add_section(section); + + buf_size += var->entry_size() * entry_count; + } + + // allocate a buffer to hold all the values. + // the next loop will fill into that memory. + vbuf->alloc(buf_size); + + auto sections = vbuf->get_sections(); + + // pack the values to the buffer. + for (size_t i = 0; i < this->attributes.size(); i++) { + BaseAttribute *var = this->attributes[i]; + VertexBuffer::vbo_section *section = §ions[i]; + + // raw pointer to to-be-submitted data buffer. + char *pos = vbuf->get(true); + pos += section->offset; + + // store the attribute section to the buffer + var->pack(pos, var->entry_size() * vertex_count); + } +} + + +void Pipeline::upload_uniforms() { + for (auto &uniform : this->uniforms) { + uniform->upload(); + } +} + + +void Pipeline::update_proplists(const Pipeline &source) { + this->update_proplistptr<>(&this->attributes, &source, source.attributes); + this->update_proplistptr<>(&this->uniforms, &source, source.uniforms); +} + + +template +void Pipeline::update_proplistptr(std::vector *proplist_dest, + const Pipeline *source, + const std::vector &proplist_src) { + // the proplist stores pointers to class members. + // now we're a new class: the pointers need to be updated + // to point to the members of the new class. + // The offsets are the same, the base address is different (a new 'this'). + proplist_dest->clear(); + + // beware: ultra dirty hackery. + // the new variable offset is calculated from the old one. + // all because c++ has no reflection to iterate over member variables. + size_t new_base = reinterpret_cast(this); + for (auto &entry : proplist_src) { + size_t distance = reinterpret_cast(entry) - reinterpret_cast(source); + proplist_dest->push_back(reinterpret_cast(new_base + distance)); + } +} + +}} // openage::renderer diff --git a/libopenage/renderer/pipeline.h b/libopenage/renderer/pipeline.h new file mode 100644 index 0000000000..44017527d9 --- /dev/null +++ b/libopenage/renderer/pipeline.h @@ -0,0 +1,376 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_PIPELINE_H_ +#define OPENAGE_RENDERER_PIPELINE_H_ + +#include +#include +#include +#include + +#include "program.h" +#include "vertex_buffer.h" + +#include "../error/error.h" +#include "../util/compiler.h" +#include "../util/constexpr.h" +#include "../util/vector.h" + +namespace openage { +namespace renderer { + +class Pipeline; + +/** + * A pipeline property. Wraps GPU state to be set. + */ +class PipelineVariable { +public: + PipelineVariable(const std::string &name, + Pipeline *pipeline); + virtual ~PipelineVariable() = default; + + /** + * Return the associated shader variable name. + */ + const std::string &get_name(); + + /** + * Return the shader variable name as a C string. + */ + const char *get_name_cstr(); + +protected: + /** + * The associated pipeline metadata container. + */ + Pipeline *const pipeline; + + /** + * Shader variable name. + */ + std::string name; +}; + + +/** + * Base class for all uniform properties of the pipeline. + */ +class BaseUniform : public PipelineVariable { +public: + BaseUniform(const std::string &name, Pipeline *pipeline) + : + PipelineVariable{name, pipeline}, + dirty{true} {} + + virtual ~BaseUniform() = default; + + /** + * Push the value to the GPU, if necessary. + * + * TODO: another pipeline could have changed it for the same program. + */ + void upload() { + if (this->dirty) { + this->upload_value(); + this->dirty = false; + } + } + +protected: + /** + * Per-type specialization of how to set the uniform value. + * Calls the backend-dependent function to push the data to the gpu. + */ + virtual void upload_value() = 0; + + /** + * True when the value has changed and needs to be reuploaded. + */ + bool dirty; +}; + + +/** + * Pipeline uniform variable, + * which is a global value for all pipeline stages. + */ +template +class Uniform : public BaseUniform { +public: + Uniform(const std::string &name, Pipeline *pipeline) + : + BaseUniform{name, pipeline} {} + + virtual ~Uniform() = default; + + /** + * Store the uniform value so it can be applied when requested. + */ + void set(const T &value) { + this->value = value; + this->dirty = true; + } + +protected: + /** + * Per-type specialization of how to set the uniform value. + * Calls the backend-dependent function to push the data to the gpu. + */ + void upload_value() override; + + /** + * Stored value to be uploaded to the gpu. + */ + T value; +}; + + +/** + * Boilerplate base class for templated vertex attributes. + * Stores the attributes until they are packed to a uploadable buffer. + */ +class BaseAttribute : public PipelineVariable { +public: + BaseAttribute(const std::string &name, Pipeline *pipeline) + : + PipelineVariable{name, pipeline} {} + + virtual ~BaseAttribute() = default; + + /** + * Pack all the stored attributes to the given buffer. + * Write a maximum of n chars. + */ + virtual void pack(char *buffer, size_t n) = 0; + + /** + * Return the number of attribute entries, + * aka the number of configured vertices. + */ + virtual size_t entry_count() = 0; + + /** + * Return the size in chars of one attribute entry. + * This equals dimension() * sizeof(attr_type) + */ + virtual size_t entry_size() = 0; + + /** + * Return the dimension of a single attribute entry. + * For a vec2 this is two. + */ + virtual size_t dimension() = 0; + + /** + * Return the vertex attribute type. + */ + virtual vertex_attribute_type type() = 0; + + /** + * Return the glsl layout id for this attribute. + */ + virtual int get_attr_id() = 0; +}; + + +/** + * Vertex attribute property. + * + * All vertex attributes of a shader have the same number of entries! + * All of those attribtes are merged into a single buffer on request. + * The buffer merging is done in the respective context. + * This buffer is then transmitted to the GPU. + * + * You may only use types for T that can be copied by memcpy to a + * char buffer. + * + * We could extend this to support any class by specializing + * the pack method for POD types and some other magic base class type + * that provides the availability of a specific packing method. + */ +template +class Attribute : public BaseAttribute { +public: + Attribute(const std::string &name, Pipeline *pipeline) + : + BaseAttribute{name, pipeline}, + attr_id{-1} {} + + virtual ~Attribute() = default; + + // as we wanna copy the values to the gpu, they need to be + // easily copyable. + static_assert(std::is_trivially_copyable::value, + "only trivially copyable types supported as attributes"); + + /** + * Set this attribute to some value array. + */ + void set(const std::vector &values) { + this->values = values; + } + + /** + * Set the glsl layout position. + * if unset, determine the position automatically. + */ + void set_layout(unsigned int position) { + this->attr_id = position; + } + + /** + * return the glsl layout position. + * If it's unknown until now, determine it by querying the gpu. + */ + int get_attr_id() override { + if (unlikely(this->attr_id < 0)) { + this->attr_id = this->pipeline->get_program()->get_attribute_id(this->get_name_cstr()); + } + + return this->attr_id; + } + + /** + * Store the data to the given buffer. + * This could be extended to also support other than POD types. + */ + void pack(char *buffer, size_t n) override { + memcpy(buffer, &this->values[0], n); + } + + /** + * Return the number of configured vertices. + */ + size_t entry_count() override { + return this->values.size(); + } + + /** + * Compute the size of a full vertex attribute data value. + * Equals dimensions * sizeof(entry_type) + */ + size_t entry_size() override { + return this->dimension() * util::compiletime(); + } + + /** + * Return the dimension of one vertex configuration entry. + */ + size_t dimension() override { + return dimensions; + } + + /** + * Provide the type of one component of one vertex attribute entry. + */ + vertex_attribute_type type() override { + return attribute_type; + } + +protected: + /** + * The vertex attribute values to be packed and submitted. + * We need to cache the values as the buffer packing and transfer + * is done after multiple attributes were set. + */ + std::vector values; + + /** + * glsl layout position of this attribute. + */ + int attr_id; +}; + + +/** + * Inherit from this class to create statically known pipeline properties. + * + * Add members of PipelineVariable to the inherited class + * to make pipeline variables available to the outside. + * This buffer is then transmitted to the GPU when the time has come. + */ +class Pipeline { +public: + Pipeline(Program *prg); + + Pipeline(Pipeline &&other); + Pipeline(const Pipeline &other); + Pipeline &operator =(Pipeline &&other); + Pipeline &operator =(const Pipeline &other); + + virtual ~Pipeline() = default; + + /** + * Fetch the program associated with this pipeline. + */ + Program *get_program(); + + /** + * Add the given program variable to the list of maintained + * pipeline attributes. + */ + void add_var(PipelineVariable *var); + + /** + * Create a vertex buffer that stores the attribute + * values set in this pipeline. + */ + VertexBuffer create_attribute_buffer(); + + /** + * Update the given vertex buffer with attributes set in this + * pipeline instance. + */ + void update_buffer(VertexBuffer *vbuf); + + /** + * Apply all the uniform values + */ + void upload_uniforms(); + +protected: + /** + * The pipeline program associated with this property definition class. + */ + Program *program; + + /** + * Keeps track of all registered attribute properties. + * + * Used to sync attribute entry lengths. + * Each attribute has to be supplied for each vertex once. + * e.g. vec3 1337 color entries require 1337 vec4 positions. + * These have different per-attribute sizes but the same lengths. + */ + std::vector attributes; + + /** + * Keeps track of all uniform properties. + */ + std::vector uniforms; + +private: + /** + * Update all membervariable-pointer-lists with offsets + * from another pipeline to have their base to 'this'. + * + * As C++ has no reflection, this needs to be done each time + * a Pipeline object is relocated somewhere else. + */ + void update_proplists(const Pipeline &source); + + /** + * Update the oneproperty list from another pipeline. + */ + template + void update_proplistptr(std::vector *proplist_dest, + const Pipeline *source, + const std::vector &proplist_src); +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/program.cpp b/libopenage/renderer/program.cpp new file mode 100644 index 0000000000..aee2bede96 --- /dev/null +++ b/libopenage/renderer/program.cpp @@ -0,0 +1,47 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +/** @file + * common code for all shader programs. + */ + +#include "program.h" + +#include "context.h" + +namespace openage { +namespace renderer { + +ProgramSource::ProgramSource() {} + + +ProgramSource::ProgramSource(const std::vector &shaders) { + for (auto &shader : shaders) { + this->attach_shader(*shader); + } +} + + +const std::vector &ProgramSource::get_shaders() const { + return this->shaders; +} + + +void ProgramSource::attach_shader(const ShaderSource &shader) { + this->shaders.push_back(&shader); +} + + +Program::Program(Context *context) + : + context{context} {} + + +void Program::use() { + if (this->context->state.program != this) { + this->activate(); + this->context->state.program = this; + } +} + + +}} // openage::renderer diff --git a/libopenage/renderer/program.h b/libopenage/renderer/program.h new file mode 100644 index 0000000000..e74b96584c --- /dev/null +++ b/libopenage/renderer/program.h @@ -0,0 +1,121 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_PROGRAM_H_ +#define OPENAGE_RENDERER_PROGRAM_H_ + +#include +#include +#include + +#include "../util/vector.h" + +namespace openage { +namespace renderer { + +class Context; +class ShaderSource; +class Texture; +class VertexState; + + +/** + * Create this to assemble shaders to a usable program. + * Register it at the renderer to use it. + * + * A program is a graphics card kernel, consisting of multiple + * shaders used for the rendering stages. + */ +class ProgramSource { +public: + ProgramSource(); + ProgramSource(const std::vector &shaders); + virtual ~ProgramSource() {}; + + /** + * Attach the given shader to this program. + */ + void attach_shader(const ShaderSource &shader); + + /** + * Return the list of all assigned shaders to this program. + */ + const std::vector &get_shaders() const; + +protected: + /** + * All attached shaders to this program. + */ + std::vector shaders; +}; + + +/** + * A usable handle, aquired by registering the sources of the program + * to the renderer and getting back an object of this class. + */ +class Program { +protected: + Program(Context *context); + +public: + virtual ~Program() {}; + + /** + * Return the associated graphics context. + */ + Context *get_context(); + + /** + * Try to use the program on the GPU. + * Does nothing if the context already uses this program. + */ + void use(); + + /** + * Use this program now on the GPU. + */ + virtual void activate() = 0; + + /** + * Dump vertex attribute variables. + */ + virtual void dump_attributes() = 0; + + /** + * Return the glsl layout id for a given vertex attribute name. + */ + virtual int get_attribute_id(const char *name) = 0; + + /* ========================================== */ + // available pipeline properties + + /** + * Set a 3 dimensional float vector + */ + virtual void set_uniform_3f(const char *name, const util::Vector<3> &value) = 0; + + /** + * Set a single integer value + */ + virtual void set_uniform_1i(const char *name, const int &value) = 0; + + /** + * Set a single unsigned integer value + */ + virtual void set_uniform_1ui(const char *name, const unsigned &value) = 0; + /** + * Set 2d texture data. + */ + virtual void set_uniform_2dtexture(const char *name, const Texture *value) = 0; + + /* ========================================== */ + + /** + * The associated context. + */ + Context *const context; +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/quad.h b/libopenage/renderer/quad.h new file mode 100644 index 0000000000..c4bb2f839c --- /dev/null +++ b/libopenage/renderer/quad.h @@ -0,0 +1,21 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_QUAD_H_ +#define OPENAGE_RENDERER_QUAD_H_ + +#include "../coord/window.h" + +namespace openage { +namespace renderer { + +/** + * struct to submit to the renderer + */ +struct Quad { + coord::window vertices[4]; +}; + + +}} // namespace openage::renderer + +#endif diff --git a/libopenage/renderer/renderer.cpp b/libopenage/renderer/renderer.cpp new file mode 100644 index 0000000000..88f574aba8 --- /dev/null +++ b/libopenage/renderer/renderer.cpp @@ -0,0 +1,62 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "renderer.h" + +#include "context.h" +#include "program.h" +#include "texture.h" + +namespace openage { +namespace renderer { + +Renderer::Renderer(const std::shared_ptr &ctx) + : + context{ctx} {} + +Renderer::~Renderer() {} + +TaskState Renderer::add_task(Task &task) { + // sort by: + // layer, texture, shader + this->tasks.push(task); + + TaskState ret{&task, this}; + return ret; +} + + +std::unique_ptr Renderer::add_program(const ProgramSource &source) { + return this->context->register_program(source); +} + + +std::unique_ptr Renderer::add_texture(const TextureData &data) { + return this->context->register_texture(data); +} + + +void Renderer::screenshot(const std::string &filename) { + this->context->screenshot(filename); +} + + +void Renderer::check_error() { + this->context->check_error(); +} + + +bool Renderer::on_resize(coord::window new_size) { + this->context->resize(new_size); + return true; +} + + +void Renderer::render() const { + while (not this->tasks.empty()) { + // Task = this->tasks.pop(); + // apply necessary shader/texture changes; + // render instruction; + } +} + +}} // openage::renderer diff --git a/libopenage/renderer/renderer.h b/libopenage/renderer/renderer.h new file mode 100644 index 0000000000..6fd501239f --- /dev/null +++ b/libopenage/renderer/renderer.h @@ -0,0 +1,112 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_RENDERER_H_ +#define OPENAGE_RENDERER_RENDERER_H_ + +#include "../datastructure/pairing_heap.h" +#include "../handlers.h" +#include "task.h" + +namespace openage { + +/** + * Contains all components for the graphics renderer. + * Supports multiple backends and provides a uniform interface for + * interacting with the GPU and the render window. + * + * It gathers tasks to be rendered in a heap. + * This orders the instructions in an optimized order, + * so that the GPU performs as few context switches as possible. + */ +namespace renderer { + +class Context; +class ProgramSource; +class Program; +class Texture; +class TextureData; + + +/** + * Main for collecting and rendering renderable things. + * + * All tasks are added and aggregated, + * when the render is invoked, the tasks are sorted and executed. + */ +class Renderer : public ResizeHandler { +public: + /** + * A renderer has to be created for a context. + */ + Renderer(const std::shared_ptr &ctx); + virtual ~Renderer(); + + Renderer(const Renderer &other) = delete; + Renderer(Renderer &&other) = delete; + Renderer &operator =(const Renderer &other) = delete; + Renderer &operator =(Renderer &&other) = delete; + + /** + * Add a task to be rendered to the job list. + * This enqueues the task to be drawn in an optimized order. + * + * @returns the state handle for the added task. + */ + TaskState add_task(Task &task); + + /** + * Register a pipeline program to this renderer. + * + * Internally, this creates the internal backend-aware instance for + * the program. + * + * @returns a handle to the usable program for the developer. + */ + std::unique_ptr add_program(const ProgramSource &source); + + /** + * Register a texture to the renderer. + * Creates the context-aware handling structures. + * + * @returns a texture handle to be used in the code. + */ + std::unique_ptr add_texture(const TextureData &data); + + /** + * Take a screenshot of the current framebuffer. + * Save it as png to the given filename. + */ + void screenshot(const std::string &filename); + + /** + * Test if the renderer has an invalid state. + */ + void check_error(); + + /** + * Resize the renderer because the surrounding window size was updated. + */ + bool on_resize(coord::window new_size) override; + + /** + * Finally, this is the actual drawing invocation. + * Pushes all the aggregated data to the GPU and renders it. + * When this is done, the front and backbuffer are flipped. + */ + void render() const; + +protected: + /** + * All tasks the renderer has to to display on the next drawout + */ + datastructure::PairingHeap tasks; + + /** + * The context to which the renderer is attached to. + */ + std::shared_ptr context; +}; + +}} // namespace openage::renderer + +#endif diff --git a/libopenage/renderer/shader.cpp b/libopenage/renderer/shader.cpp new file mode 100644 index 0000000000..385b159be0 --- /dev/null +++ b/libopenage/renderer/shader.cpp @@ -0,0 +1,54 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +/** @file + * common code for all shaders. + */ + +#include "shader.h" + +#include "../error/error.h" +#include "../util/file.h" + +namespace openage { +namespace renderer { + +ShaderSource::ShaderSource(shader_type type) + : + type{type} {} + + +shader_type ShaderSource::get_type() const { + return this->type; +} + + +const char *ShaderSource::get_source() const { + return this->code.c_str(); +} + + +ShaderSourceFile::ShaderSourceFile(shader_type type, const std::string &path) + : + ShaderSource{type}, + path{path} { + + this->code = util::read_whole_file(this->path); +} + + +ShaderSourceCode::ShaderSourceCode(shader_type type, const char *code) + : + ShaderSource{type} { + + this->code = code; +} + + +ShaderSourceCode::ShaderSourceCode(shader_type type, const std::string &code) + : + ShaderSource{type} { + + this->code = code; +} + +}} // openage::renderer diff --git a/libopenage/renderer/shader.h b/libopenage/renderer/shader.h new file mode 100644 index 0000000000..6a9bfaf9bc --- /dev/null +++ b/libopenage/renderer/shader.h @@ -0,0 +1,89 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_SHADER_H_ +#define OPENAGE_RENDERER_SHADER_H_ + +/** @file + * gpu program creation classes reside in here. + */ + +#include +#include + +namespace openage { +namespace renderer { + +/** + * Available shader types present in modern graphics pipelines. + */ +enum class shader_type { + fragment, + vertex, + geometry, + tesselation_control, + tesselation_evaluation, +}; + + +/** + * Shader source code storage. + */ +class ShaderSource { +public: + ShaderSource(shader_type type); + virtual ~ShaderSource() {}; + + /** + * Return the shader source code. + */ + const char *get_source() const; + + /** + * Return the shader type. + */ + shader_type get_type() const; + +protected: + /** + * The shader's pipeline stage position. + */ + shader_type type; + + /** + * Stores the shader source code. + */ + std::string code; +}; + + +/** + * Shader source code obtained from a file. + */ +class ShaderSourceFile : public ShaderSource { +public: + ShaderSourceFile(shader_type type, const std::string &path); + virtual ~ShaderSourceFile() {}; + +protected: + /** + * The shader's filesystem location. + * TODO: replace by some path magic + */ + std::string path; +}; + + +/** + * Shader source code obtained directly as text. + */ +class ShaderSourceCode : public ShaderSource { +public: + ShaderSourceCode(shader_type type, const char *code); + ShaderSourceCode(shader_type type, const std::string &code); + + virtual ~ShaderSourceCode() {}; +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/shaders/CMakeLists.txt b/libopenage/renderer/shaders/CMakeLists.txt new file mode 100644 index 0000000000..c8bc7d39bb --- /dev/null +++ b/libopenage/renderer/shaders/CMakeLists.txt @@ -0,0 +1,4 @@ +add_sources(libopenage + alphamask.cpp + simpletexture.cpp +) diff --git a/libopenage/renderer/shaders/alphamask.cpp b/libopenage/renderer/shaders/alphamask.cpp new file mode 100644 index 0000000000..c6106aedcc --- /dev/null +++ b/libopenage/renderer/shaders/alphamask.cpp @@ -0,0 +1,31 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "alphamask.h" + +namespace openage { +namespace renderer { + +AlphamaskMaterial::AlphamaskMaterial(Program *prg) + : + Material{prg}, + position{"position", this}, + base_texture{"base_tex", this}, + mask_texture{"mask_tex", this}, + show_mask{"show_mask", this}, + base_tex_coordinates{"base_tex_coordinates", this}, + mask_tex_coordinates{"mask_tex_coordinates", this} { + + this->add_var(&this->position); + this->add_var(&this->base_texture); + this->add_var(&this->mask_texture); + this->add_var(&this->show_mask); + this->add_var(&this->base_tex_coordinates); + this->add_var(&this->mask_tex_coordinates); +} + +void AlphamaskMaterial::set_positions(mesh_t positions) { + this->position.set(positions); +} + + +}} // openage::renderer diff --git a/libopenage/renderer/shaders/alphamask.h b/libopenage/renderer/shaders/alphamask.h new file mode 100644 index 0000000000..a38529ba19 --- /dev/null +++ b/libopenage/renderer/shaders/alphamask.h @@ -0,0 +1,38 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_SHADERS_ALPHAMASK_H_ +#define OPENAGE_RENDERER_SHADERS_ALPHAMASK_H_ + +#include + +#include "../material.h" +#include "../../util/vector.h" + +namespace openage { +namespace renderer { + +/** + * A simple plain texture material. + */ +class AlphamaskMaterial : public Material { +public: + AlphamaskMaterial(Program *prg); + virtual ~AlphamaskMaterial() = default; + + void set_positions(mesh_t positions) override; + + // shader variables: + Attribute position; + Uniform base_texture; + Uniform mask_texture; + + Uniform show_mask; + Attribute, 2> base_tex_coordinates; + Attribute, 2> mask_tex_coordinates; +}; + + +}} // openage::renderer + + +#endif diff --git a/libopenage/renderer/shaders/simpletexture.cpp b/libopenage/renderer/shaders/simpletexture.cpp new file mode 100644 index 0000000000..ee2e791d03 --- /dev/null +++ b/libopenage/renderer/shaders/simpletexture.cpp @@ -0,0 +1,26 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "simpletexture.h" + +namespace openage { +namespace renderer { + +SimpleTextureMaterial::SimpleTextureMaterial(Program *prg) + : + Material{prg}, + tex{"tex", this}, + position{"position", this}, + texcoord{"texcoord", this} { + + // register the variables to the "active" list + this->add_var(&this->tex); + this->add_var(&this->position); + this->add_var(&this->texcoord); +} + +void SimpleTextureMaterial::set_positions(mesh_t positions) { + this->position.set(positions); +} + + +}} // openage::renderer diff --git a/libopenage/renderer/shaders/simpletexture.h b/libopenage/renderer/shaders/simpletexture.h new file mode 100644 index 0000000000..a84cff8353 --- /dev/null +++ b/libopenage/renderer/shaders/simpletexture.h @@ -0,0 +1,34 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_SHADERS_SIMPLETEXTURE_H_ +#define OPENAGE_RENDERER_SHADERS_SIMPLETEXTURE_H_ + +#include + +#include "../material.h" +#include "../../util/vector.h" + +namespace openage { +namespace renderer { + +/** + * A simple plain texture material. + */ +class SimpleTextureMaterial : public Material { +public: + SimpleTextureMaterial(Program *prg); + virtual ~SimpleTextureMaterial() = default; + + void set_positions(mesh_t positions) override; + + // shader variables: + Uniform tex; + Attribute position; + Attribute, 2> texcoord; +}; + + +}} // openage::renderer + + +#endif diff --git a/libopenage/renderer/target.h b/libopenage/renderer/target.h new file mode 100644 index 0000000000..74fa608d83 --- /dev/null +++ b/libopenage/renderer/target.h @@ -0,0 +1,21 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_TARGET_H_ +#define OPENAGE_RENDERER_TARGET_H_ + +#include "renderer.h" + +namespace openage { +namespace renderer { + +/** + * A render output destination. + */ +class Target { +public: + // TODO. +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/task.cpp b/libopenage/renderer/task.cpp new file mode 100644 index 0000000000..4a8f170c3b --- /dev/null +++ b/libopenage/renderer/task.cpp @@ -0,0 +1,24 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +/** @file + * render task implementation to tell the renderer to + * perform any drawing on the screen. + */ + +#include "task.h" + + +namespace openage { +namespace renderer { + + +bool Task::operator <(const Task &other) const { + return true; +} + +TaskState::TaskState(Task *task, Renderer *renderer) + : + task{task}, + renderer{renderer} {} + +}} // openage::renderer diff --git a/libopenage/renderer/task.h b/libopenage/renderer/task.h new file mode 100644 index 0000000000..ecb6a51634 --- /dev/null +++ b/libopenage/renderer/task.h @@ -0,0 +1,72 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_TASK_H_ +#define OPENAGE_RENDERER_TASK_H_ + +#include "../coord/phys3.h" +#include "../util/quaternion.h" + +namespace openage { +namespace renderer { + +class Geometry; +class Material; +class Renderer; + + +/** + * Instructs the renderer to draw something. + */ +class Task { +public: + /** + * The geometry to draw. + */ + Geometry *const geometry; + + /** + * The material to use for drawing the geometry. + * Stores to-be-set uniforms. + * Can create vertex buffers. + */ + //MaterialApplication material; + + /** + * Position to draw the geometry at. + */ + coord::phys3 position; + + /** + * Rotation for the geometry. + */ + util::Quaternion<> rotation; + + /** + * Ordering condition to draw tasks in the correct order. + */ + bool operator <(const Task &other) const; +}; + + +/** + * corresponding state storage for a render task. + */ +class TaskState { +public: + TaskState(Task *t, Renderer *r); + ~TaskState(); + + /** + * true if the assigned task has been rendered on screen. + */ + bool rendered; + +protected: + Task *const task; + Renderer *const renderer; +}; + + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/tests.cpp b/libopenage/renderer/tests.cpp new file mode 100644 index 0000000000..d5bf1e869e --- /dev/null +++ b/libopenage/renderer/tests.cpp @@ -0,0 +1,284 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include +#include +#include +#include + +#include "../log/log.h" +#include "opengl/shader.h" +#include "opengl/program.h" +#include "vertex_state.h" +#include "window.h" +#include "renderer.h" +#include "shader.h" +#include "shaders/simpletexture.h" +#include "texture.h" + +namespace openage { +namespace renderer { +namespace tests { + +/** + * render demo function collection. + */ +struct render_demo { + std::function setup; + std::function frame; + std::function resize; +}; + + +void render_test(Window &window, const render_demo *actions) { + SDL_Event event; + + actions->setup(&window); + + bool running = true; + while (running) { + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_RESIZED: { + coord::window new_size{event.window.data1, event.window.data2}; + log::log( + MSG(info) << "new window size: " + << new_size.x << " x " << new_size.y + ); + window.set_size(new_size); + actions->resize(new_size); + break; + }} + break; + + case SDL_QUIT: + running = false; + break; + + case SDL_KEYUP: { + SDL_Keycode sym = reinterpret_cast(&event)->keysym.sym; + switch (sym) { + case SDLK_ESCAPE: + running = false; + break; + default: + break; + } + break; + }} + } + + actions->frame(); + + window.swap(); + } +} + +void renderer_demo_0() { + Window window{"openage renderer testing"}; + Renderer renderer{window.get_context()}; + + ShaderSourceCode vshader_src( + shader_type::vertex, + "#version 330\n" + "layout(location = 0) in vec4 position;" + "smooth out vec4 fragpos;" + "void main() {" + "fragpos = position;" + "gl_Position = position;" + "}" + ); + + ShaderSourceCode fshader_src( + shader_type::fragment, + "#version 330\n" + "out vec4 color;\n" + "smooth in vec4 fragpos;" + "void main() {" + "color = vec4(1.0f, fragpos.y, fragpos.x, 1.0f);" + "}" + ); + + ProgramSource simplequad_src({&vshader_src, &fshader_src}); + + std::unique_ptr simplequad = renderer.add_program(simplequad_src); + + simplequad->dump_attributes(); + + float val = 0.9f; + const float vpos[] = { + -val, -val, .0f, 1.0f, + val, -val, .0f, 1.0f, + -val, val, .0f, 1.0f, + + val, -val, .0f, 1.0f, + -val, val, .0f, 1.0f, + val, val, .0f, 1.0f, + }; + + GLuint vpos_buf, posattr_id = 0; + + GLuint vao; + + render_demo test0{ + // init + [&](Window */*window*/) { + glEnable(GL_BLEND); + + glGenBuffers(1, &vpos_buf); + glBindBuffer(GL_ARRAY_BUFFER, vpos_buf); + // save vertex attributes to GPU: + glBufferData(GL_ARRAY_BUFFER, sizeof(vpos), vpos, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); // stores all the vertex attrib state. + }, + // frame + [&]() { + glClearColor(0.0, 0.0, 0.2, 1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + simplequad->use(); + + glBindBuffer(GL_ARRAY_BUFFER, vpos_buf); + glEnableVertexAttribArray(posattr_id); + glVertexAttribPointer(posattr_id, 4, GL_FLOAT, GL_FALSE, 0, (void *)0); + glDrawArrays(GL_TRIANGLES, 0, 6); + + glDisableVertexAttribArray(posattr_id); + + renderer.check_error(); + }, + // resize + [&](const coord::window &new_size) { + renderer.on_resize(new_size); + } + }; + + render_test(window, &test0); +} + + +void renderer_demo_1() { + Window window{"openage renderer testing"}; + Renderer renderer{window.get_context()}; + + ShaderSourceCode vshader_src( + shader_type::vertex, + "#version 330\n" + "layout(location = 0) in vec4 position;" + "layout(location = 1) in vec2 texcoord;" + "smooth out vec2 texpos;" + "void main() {" + "texpos = texcoord;" + "gl_Position = position;" + "}" + ); + + ShaderSourceCode fshader_src( + shader_type::fragment, + "#version 330\n" + "out vec4 color;" + "smooth in vec2 texpos;" + "uniform sampler2D tex;" + "void main() {" + "color = texture(tex, texpos.xy);" + "}" + ); + + ProgramSource simpletex_src({&vshader_src, &fshader_src}); + std::unique_ptr simpletex = renderer.add_program(simpletex_src); + simpletex->dump_attributes(); + + SimpleTextureMaterial tex_pipeline{simpletex.get()}; + + VertexBuffer vbo{window.get_context().get()}; + + FileTextureData gaben_data{"assets/gaben.png"}; + std::unique_ptr gaben = renderer.add_texture(gaben_data); + std::unique_ptr vao = window.get_context()->create_vertex_state(); + + render_demo test1{ + // init + [&](Window */*window*/) { + log::log(MSG(dbg) << "preparing test"); + + tex_pipeline.tex.set(gaben.get()); + tex_pipeline.position.set_layout(0); + tex_pipeline.texcoord.set_layout(1); + + float val = 0.9f; + tex_pipeline.set_positions({ + {-val, -val, .0f, 1.0f}, + {val, -val, .0f, 1.0f}, + {-val, val, .0f, 1.0f}, + + {val, -val, .0f, 1.0f}, + {-val, val, .0f, 1.0f}, + {val, val, .0f, 1.0f}, + }); + + tex_pipeline.texcoord.set({ + {0.0f, 1.0f}, + {1.0f, 1.0f}, + {0.0f, 0.0f}, + + {1.0f, 1.0f}, + {0.0f, 0.0f}, + {1.0f, 0.0f}, + }); + + // apply the pipeline properties + tex_pipeline.upload_uniforms(); + tex_pipeline.update_buffer(&vbo); + vao->attach_buffer(vbo); // upload buffer + + vao->bind(); + }, + // frame + [&]() { + glClearColor(0.0, 0.0, 0.2, 1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + + // combination of geometry and material with translations + // = draw task. + + // geometry: manager owns. + // material: manager owns? + + //Task t = material.draw(geometry); + glDrawArrays(GL_TRIANGLES, 0, 6); + + renderer.check_error(); + }, + // resize + [&](const coord::window &new_size) { + renderer.on_resize(new_size); + } + }; + + render_test(window, &test1); +} + + +void renderer_demo(int demo_id) { + switch (demo_id) { + case 0: + renderer_demo_0(); + break; + + case 1: + renderer_demo_1(); + break; + + default: + log::log(MSG(err) << "unknown renderer demo " << demo_id << " requested."); + break; + } +} + + +}}} // namespace openage::renderer::tests diff --git a/libopenage/renderer/tests.h b/libopenage/renderer/tests.h new file mode 100644 index 0000000000..327f3123dd --- /dev/null +++ b/libopenage/renderer/tests.h @@ -0,0 +1,16 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_TESTS_H_ +#define OPENAGE_RENDERER_TESTS_H_ + + +namespace openage { +namespace renderer { +namespace tests { + +// pxd: void renderer_demo(int demo_id) except + +void renderer_demo(int demo_id); + +}}} // openage::renderer::tests + +#endif diff --git a/libopenage/renderer/texture.cpp b/libopenage/renderer/texture.cpp new file mode 100644 index 0000000000..37a2aab4b8 --- /dev/null +++ b/libopenage/renderer/texture.cpp @@ -0,0 +1,151 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include + +#include "texture.h" + +#include "../error/error.h" +#include "../util/file.h" + +namespace openage { +namespace renderer { + +Texture::Texture(Context *ctx) + : + context{ctx} { +} + +Texture::~Texture() {} + +const std::tuple Texture::get_size() const { + return std::make_tuple(this->w, this->h); +} + + +const gamedata::subtexture *Texture::get_subtexture(size_t subid) const { + if (subid < this->subtextures.size()) { + return &this->subtextures[subid]; + } + else { + throw Error(MSG(err) << "Unknown subtexture requested: " << subid); + } +} + + +const std::tuple Texture::get_subtexture_coordinates(size_t subid) const { + auto tx = this->get_subtexture(subid); + return std::make_tuple( + ((float)tx->x) / this->w, + ((float)(tx->x + tx->w)) / this->w, + ((float)tx->y) / this->h, + ((float)(tx->y + tx->h)) / this->h + ); +} + + +int Texture::get_subtexture_count() const { + return this->subtextures.size(); +} + + +const std::tuple Texture::get_subtexture_size(size_t subid) const { + auto subtex = this->get_subtexture(subid); + return std::make_tuple(subtex->w, subtex->h); +} + + +TextureData::TextureData(int width, int height, char *data) + : + format{texture_format::rgba}, + w{width}, + h{height} { + + // rgba has 4 components + size_t pixel_size = 4 * this->w * this->h; + + // copy pixel data from surface + this->data = std::make_unique(pixel_size); + memcpy(this->data.get(), data, pixel_size); +} + +TextureData::TextureData(int width, int height, std::unique_ptr data) + : + format{texture_format::rgba}, + w{width}, + h{height} { + + this->data = std::move(data); +} + + +FileTextureData::FileTextureData(const std::string &filename, + bool use_metafile) + : + use_metafile{use_metafile}, + filename{filename} { + + SDL_Surface *surface = IMG_Load(this->filename.c_str()); + + if (!surface) { + throw Error(MSG(err) << + "Could not load texture from " << + filename << ": " << IMG_GetError()); + } else { + log::log(MSG(dbg) << "Texture has been loaded from " << filename); + } + + switch (surface->format->BytesPerPixel) { + case 3: // RGB 24 bit + this->format = texture_format::rgb; + break; + case 4: // RGBA 32 bit + this->format = texture_format::rgba; + break; + default: + throw Error( + MSG(err) << "Unknown texture bit depth for " + << filename << ": " << surface->format->BytesPerPixel + << " bytes per pixel"); + } + + this->w = surface->w; + this->h = surface->h; + + size_t pixel_size = surface->format->BytesPerPixel * surface->w * surface->h; + + // copy pixel data from surface + this->data = std::make_unique(pixel_size); + memcpy(this->data.get(), surface->pixels, pixel_size); + SDL_FreeSurface(surface); + + if (use_metafile) { + // change the suffix to .docx (lol) + std::string meta_filename = this->filename; + + size_t start_pos = meta_filename.find_last_of("."); + if (start_pos == std::string::npos) { + throw Error( + MSG(err) << "No filename extension found in: " + << meta_filename + ); + } + + // TODO: will probably bug if previous extension is longer than 5 + meta_filename.replace(start_pos, 5, ".docx"); + + log::log(MSG(info) << "Loading meta file: " << meta_filename); + + // get subtexture information by meta file exported by script + util::read_csv_file(meta_filename, this->subtextures); + } + else { + // we don't have a texture description file. + // use the whole image as one texture then. + gamedata::subtexture s{0, 0, this->w, this->h, this->w/2, this->h/2}; + + this->subtextures.push_back(s); + } +} + + +}} // namespace openage::renderer diff --git a/libopenage/renderer/texture.h b/libopenage/renderer/texture.h new file mode 100644 index 0000000000..a4c82e082c --- /dev/null +++ b/libopenage/renderer/texture.h @@ -0,0 +1,177 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_TEXTURE_H_ +#define OPENAGE_RENDERER_TEXTURE_H_ + +#include + +#include +#include +#include + +#include "../gamedata/texture.gen.h" + +namespace openage { +namespace renderer { + +class Context; + +/** + * Texture format, used for setting pixel data size. + */ +enum class texture_format { + rgb, + rgba, +}; + + +/** + * A texture for rendering graphically in 3d space. + * Obtained by registering some texture data to the renderer. + * + * The class supports subtextures, so that one big texture ("texture atlas") + * can contain several smaller images. These are the ones actually to be + * rendered. + */ +class Texture { +protected: + Texture(Context *ctx); + +public: + virtual ~Texture(); + + /** + * Return the dimensions of the whole texture bitmap + * @returns tuple(width, height) + */ + const std::tuple get_size() const; + + /** + * Get the subtexture coordinates by its idea. + */ + const gamedata::subtexture *get_subtexture(size_t subid) const; + + /** + * @return the number of available subtextures + */ + int get_subtexture_count() const; + + /** + * Fetch the size of the given subtexture. + * @param subid: index of the requested subtexture + */ + const std::tuple get_subtexture_size(size_t subid) const; + + /** + * get atlas subtexture coordinates. + * + * @returns left, right, top and bottom bounds as coordinates these pick + * the requested area out of the big texture. returned as floats in + * range 0.0 to 1.0, relative to the whole surface size. + */ + const std::tuple get_subtexture_coordinates(size_t subid) const; + + /** + * Bind the texture to the given texture unit slot id. + */ + virtual void bind_to(int slot) const = 0; + + /** + * The associated graphics context. + */ + Context *context; + +protected: + /** + * Atlas texture positions. + */ + std::vector subtextures; + + /** + * Width and height of this texture. + */ + size_t w, h; +}; + + +/** + * Data for textures. Just used for transmitting the data to the GPU. + */ +class TextureData { +protected: + TextureData() = default; + +public: + /** + * Create a texture from a rgba8 array. + * It will have w * h * 4byte storage. + * Each pixel has one byte for its r g b and alpha values, + * resulting in 32 bit per pixel. + * + * Copies the data to this texture. + */ + TextureData(int width, int height, char *data); + + /** + * Create a texture from an rgba8 array by moving the data. + * Each pixel consists of 4 chars, for r g b and alpha. + */ + TextureData(int width, int height, std::unique_ptr data); + + virtual ~TextureData() = default; + + /** + * The data format of the texture. + */ + texture_format format; + + /** + * Width and height of this texture. + */ + int w, h; + + /** + * Raw texture pixel data. + * r g b a values, each 8 bit. + */ + std::unique_ptr data; + + /** + * The atlas texture positions. + */ + std::vector subtextures; +}; + + +/** + * Create a texture from an image file. + * + * Uses SDL Image internally. + */ +class FileTextureData : public TextureData { +public: + /** + * Create a texture from a existing image file. + * + * For supported image file types, see the SDL_Image initialization in + * the engine. + */ + FileTextureData(const std::string &filename, bool use_metafile=false); + ~FileTextureData() = default; + +protected: + /** + * Use the meta information file providing info about + * texture atlas positions. + */ + bool use_metafile; + + /** + * File system path name of + */ + std::string filename; +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/vertex_buffer.cpp b/libopenage/renderer/vertex_buffer.cpp new file mode 100644 index 0000000000..bb57bf8268 --- /dev/null +++ b/libopenage/renderer/vertex_buffer.cpp @@ -0,0 +1,59 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "vertex_buffer.h" + +#include "context.h" + +namespace openage { +namespace renderer { + +VertexBuffer::VertexBuffer(Context *ctx, Buffer::usage usage) + : + usage{usage} { + + this->buffer = ctx->create_buffer(); +} + +VertexBuffer::VertexBuffer(VertexBuffer &&other) + : + sections{std::move(other.sections)}, + buffer{std::move(other.buffer)}, + usage{other.usage} {} + +const VertexBuffer &VertexBuffer::operator =(VertexBuffer &&other) { + sections = std::move(other.sections); + buffer = std::move(other.buffer); + usage = other.usage; + return *this; +} + +void VertexBuffer::upload() { + this->buffer->upload(Buffer::bind_target::vertex_attributes, this->usage); +} + +void VertexBuffer::alloc(size_t size) { + this->buffer->create(size); +} + +void VertexBuffer::reset() { + this->sections.clear(); +} + +void VertexBuffer::add_section(const vbo_section §ion) { + this->sections.push_back(section); +} + +char *VertexBuffer::get(bool will_modify) { + return this->buffer->get(will_modify); +} + +Buffer *VertexBuffer::get_buffer() { + return this->buffer.get(); +} + +const std::vector &VertexBuffer::get_sections() const { + return this->sections; +} + + +}} // openage::renderer diff --git a/libopenage/renderer/vertex_buffer.h b/libopenage/renderer/vertex_buffer.h new file mode 100644 index 0000000000..d811a543d1 --- /dev/null +++ b/libopenage/renderer/vertex_buffer.h @@ -0,0 +1,128 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_VERTEX_BUFFER_H_ +#define OPENAGE_RENDERER_VERTEX_BUFFER_H_ + +#include + +#include "buffer.h" +#include "../datastructure/constexpr_map.h" + +namespace openage { +namespace renderer { + +class Context; + + +/** + * The data type of a vertex attribute. + */ +enum class vertex_attribute_type { + integer_8, + integer_16, + integer_32, + uinteger_8, + uinteger_16, + uinteger_32, + float_32, + fixed_16_16, +}; + +constexpr auto vertex_attribute_size = datastructure::create_const_map( + std::make_pair(vertex_attribute_type::integer_8, 1), + std::make_pair(vertex_attribute_type::integer_16, 2), + std::make_pair(vertex_attribute_type::integer_32, 4), + std::make_pair(vertex_attribute_type::uinteger_8, 1), + std::make_pair(vertex_attribute_type::uinteger_16, 2), + std::make_pair(vertex_attribute_type::uinteger_32, 4), + std::make_pair(vertex_attribute_type::float_32, 4), + std::make_pair(vertex_attribute_type::fixed_16_16, 4) +); + +/** + * Represents a graphics vertex buffer. + * A a vertex buffer supplies potentially different information + * for each vertex, e.g. its position, texture coordinates, color, etc. + * + * This is uploaded to the GPU to provide vertex-specific variables + * in a shader. + */ +class VertexBuffer { +public: + + /** + * Describes a data section in a vertex buffer. + */ + struct vbo_section { + int attr_id; //!< attribute layout id. + vertex_attribute_type type; //!< attribute data type + size_t dimension; //!< attribute dimension + size_t offset; //!< start offset in the buffer + }; + + VertexBuffer(Context *ctx, + Buffer::usage usage=Buffer::usage::static_draw); + + VertexBuffer(VertexBuffer &&other); + const VertexBuffer &operator =(VertexBuffer &&other); + + virtual ~VertexBuffer() = default; + + /** + * Upload this buffer to the GPU and bind it to the + * required vertex attribute buffer slot. + */ + void upload(); + + /** + * Allocate a buffer of given size. + */ + void alloc(size_t size); + + /** + * Reset the metadata, leave data intact. + * To destroy the data buffer, call alloc again. + */ + void reset(); + + /** + * Add a single vertex buffer metadata entry. + */ + void add_section(const vbo_section §ion); + + /** + * Return the raw pointer to the allocated buffer. + */ + char *get(bool will_modify=false); + + /** + * Return the buffer metadata describing layout and position + * of buffer areas. + */ + const std::vector &get_sections() const; + + /** + * Return the underlying context-specific buffer object. + */ + Buffer *get_buffer(); + +protected: + /** + * List of vertex buffer sections describing the data width and types. + */ + std::vector sections; + + /** + * Associated raw gpu buffer. + */ + std::unique_ptr buffer; + + /** + * The predicted buffer access method. + */ + Buffer::usage usage; +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/vertex_state.cpp b/libopenage/renderer/vertex_state.cpp new file mode 100644 index 0000000000..59611c9f58 --- /dev/null +++ b/libopenage/renderer/vertex_state.cpp @@ -0,0 +1,12 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "vertex_state.h" + +namespace openage { +namespace renderer { + +VertexState::VertexState(Context *ctx) + : + context{ctx} {} + +}} // openage::renderer diff --git a/libopenage/renderer/vertex_state.h b/libopenage/renderer/vertex_state.h new file mode 100644 index 0000000000..48f9b964d3 --- /dev/null +++ b/libopenage/renderer/vertex_state.h @@ -0,0 +1,72 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_VERTEX_STATE_H_ +#define OPENAGE_RENDERER_VERTEX_STATE_H_ + +#include +#include + +namespace openage { +namespace renderer { + +class Buffer; +class Context; +class VertexBuffer; + + +/** + * Represents a vertex buffer state. + * You can enable this vertex state context so that the + * GPU pipeline uses the state defined in here for the vertex specification. + * + * In OpenGL, this represents a vertex array object (vao). + * This class is specialized by each graphics backend. + */ +class VertexState { +public: + VertexState(Context *ctx); + + const VertexState &operator =(VertexState &&other) = delete; + const VertexState &operator =(const VertexState &other) = delete; + VertexState(const VertexState &other) = delete; + VertexState(VertexState &&other) = delete; + + virtual ~VertexState() = default; + + /** + * Attach the given vertex buffer to this vertex state. + * Buffer contents are assigned to their corresponding + * attribute layout ids. + * + * This just enables the buffer to be used when this vertex + * state is active. + */ + virtual void attach_buffer(VertexBuffer &buf) = 0; + + /** + * Remove the attributes of the given vertex buffer from the + * active list. + */ + virtual void detach_buffer(VertexBuffer &buf) = 0; + + /** + * Make this vertex state the current one. + */ + virtual void bind() const = 0; + +protected: + /** + * Associated rendering context + */ + Context *const context; + + /** + * This vertex state has data for attributes with + * layout ids stored in here. + */ + std::unordered_set bound_attributes; +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/vulkan/context.h b/libopenage/renderer/vulkan/context.h new file mode 100644 index 0000000000..bf8c0cc4bd --- /dev/null +++ b/libopenage/renderer/vulkan/context.h @@ -0,0 +1,40 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_VULKAN_CONTEXT_H_ +#define OPENAGE_RENDERER_VULKAN_CONTEXT_H_ + +#include + +#include "../context.h" +#include "../../config.h" + + +namespace openage { +namespace renderer { + +/** + * Vulkan specific renderer code. + * Is selected if the requested backend is Vulkan. + */ +namespace vulkan { + +/** + * Vulkan render context. + * + * No API has been published yet, but it's likely to be very similar + * to OpenGL and Mantle. + */ +class Context : public renderer::Context { +public: + SDL_VulkanContext vkcontext; + + virtual void prepare() = 0; + virtual uint32_t get_window_flags() = 0; + virtual void create(SDL_Window *window) = 0; + virtual void setup() = 0; + virtual void destroy() = 0; +}; + +}} // namespace openage::renderer + +#endif diff --git a/libopenage/renderer/window.cpp b/libopenage/renderer/window.cpp new file mode 100644 index 0000000000..767dd16647 --- /dev/null +++ b/libopenage/renderer/window.cpp @@ -0,0 +1,97 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "window.h" + +#include +#include +#include + +#include "../log/log.h" +#include "../error/error.h" + + +namespace openage { +namespace renderer { + +Window::Window(const char *title) + : + size{800, 600} { + // TODO: ^ detect screen resolution and determine window size from it. + + // TODO: make the type user-selectable + this->context = Context::generate(context_type::autodetect); + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + throw Error{MSG(err) << "SDL video initialization: " << SDL_GetError()}; + } else { + log::log(MSG(info) << "Initialized SDL video subsystems."); + } + + this->context->prepare(); + + int32_t window_flags = this->context->get_window_flags() | SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED; + this->window = SDL_CreateWindow( + title, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + this->size.x, + this->size.y, + window_flags + ); + + if (this->window == nullptr) { + throw Error{MSG(err) << "Failed to create SDL window: " << SDL_GetError()}; + } else { + log::log(MSG(info) << "Created SDL window."); + } + + this->context->create(this->window); + + // load support for the PNG image formats, jpg bit: IMG_INIT_JPG + int wanted_image_formats = IMG_INIT_PNG; + int sdlimg_inited = IMG_Init(wanted_image_formats); + if ((sdlimg_inited & wanted_image_formats) != wanted_image_formats) { + throw Error{MSG(err) << "Failed to init PNG support: " << IMG_GetError()}; + } + + this->context->setup(); +} + +Window::~Window() { + log::log(MSG(info) << "Destroying render context..."); + this->context->destroy(); + + log::log(MSG(info) << "Destroying window..."); + SDL_DestroyWindow(this->window); + + log::log(MSG(info) << "Exiting SDL..."); + IMG_Quit(); + SDL_Quit(); +} + +void Window::swap() { + SDL_GL_SwapWindow(this->window); +} + +coord::window Window::get_size() { + return this->size; +} + +void Window::set_size(const coord::window &new_size, bool update) { + if (this->size.x != new_size.x or this->size.y != new_size.y) { + this->size = new_size; + if (update) { + SDL_SetWindowSize(this->window, this->size.x, this->size.y); + } + } +} + +std::shared_ptr Window::get_context() { + return this->context; +} + +SDL_Window* Window::get_raw_window() const { + return this->window; +} + +}} // namespace openage::renderer diff --git a/libopenage/renderer/window.h b/libopenage/renderer/window.h new file mode 100644 index 0000000000..63300c1b6d --- /dev/null +++ b/libopenage/renderer/window.h @@ -0,0 +1,70 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_WINDOW_H_ +#define OPENAGE_RENDERER_WINDOW_H_ + +#include "context.h" + +#include +#include + +#include "../coord/window.h" + +namespace openage { +namespace renderer { + + +class Window { +public: + Window(const Window &other) = delete; + Window(Window &&other) = delete; + Window &operator =(const Window &other) = delete; + Window &operator =(Window &&other) = delete; + + /** + * Create a shiny window with the given title. + */ + Window(const char *title); + ~Window(); + + /** + * @returns the window dimensions + */ + coord::window get_size(); + + /** + * Resize the drawing window. + */ + void set_size(const coord::window &new_size, bool update=false); + + /** + * Swaps the back and front framebuffers. + * Used to actually display the newly rendered frame. + */ + void swap(); + + /** + * Return the context created for this window. + */ + std::shared_ptr get_context(); + + // TODO: this shouldn't be exposed. + // It is only necessary because GuiBasic requires a raw SDL_Window handle, since the QtQuick + // gui needs the native context of our SDL window to work, so it (the QtQuick gui) is inherently + // coupled to our window. This probably means that the gui should be integrated either _into_ + // this Window class or _together_ with it in a conceptual unit that manages the GL context. + /** + * Return the raw SDL window handle. + */ + SDL_Window* get_raw_window() const; + +private: + coord::window size; + SDL_Window *window; + + std::shared_ptr context; +}; + +}} // namespace openage::renderer + +#endif diff --git a/libopenage/screenshot.cpp b/libopenage/screenshot.cpp index e24a229db0..207bcd9834 100644 --- a/libopenage/screenshot.cpp +++ b/libopenage/screenshot.cpp @@ -1,34 +1,29 @@ // Copyright 2014-2016 the openage authors. See copying.md for legal info. -#include "screenshot.h" -#include "util/strings.h" - -#include -#include #include -#include - -#include +#include #include "coord/window.h" #include "log/log.h" #include "log/message.h" #include +#include "screenshot.h" +#include "util/strings.h" +#include "renderer/renderer.h" namespace openage { -ScreenshotManager::ScreenshotManager() +ScreenshotManager::ScreenshotManager(renderer::Renderer *renderer) : - count{0} { -} + count{0}, + renderer{renderer} {} ScreenshotManager::~ScreenshotManager() {} std::string ScreenshotManager::gen_next_filename() { - std::time_t t = std::time(NULL); if (t == this->last_time) { @@ -38,9 +33,11 @@ std::string ScreenshotManager::gen_next_filename() { this->last_time = t; } - // these two values (32) *must* be the same for safety reasons - char timestamp[32]; - std::strftime(timestamp, 32, "%Y-%m-%d_%H-%M-%S", std::localtime(&t)); + constexpr const size_t timestamp_size = 32; + + char timestamp[timestamp_size]; + std::strftime(timestamp, timestamp_size, + "%Y-%m-%d_%H-%M-%S", std::localtime(&t)); return util::sformat("/tmp/openage_%s_%02d.png", timestamp, this->count); } @@ -49,45 +46,7 @@ std::string ScreenshotManager::gen_next_filename() { void ScreenshotManager::save_screenshot() { std::string filename = this->gen_next_filename(); - log::log(MSG(info) << "Saving screenshot to " << filename); - - int32_t rmask, gmask, bmask, amask; - rmask = 0x000000FF; - gmask = 0x0000FF00; - bmask = 0x00FF0000; - amask = 0xFF000000; - - SDL_Surface *screen = SDL_CreateRGBSurface( - SDL_SWSURFACE, - this->window_size.x, - this->window_size.y, - 32, - rmask, gmask, bmask, amask); - - size_t pxcount = screen->w * screen->h; - - auto pxdata = std::make_unique(pxcount); - - glReadPixels(0, 0, - this->window_size.x, this->window_size.y, - GL_RGBA, GL_UNSIGNED_BYTE, pxdata.get()); - - uint32_t *surface_pxls = (uint32_t *)screen->pixels; - - // we need to invert all pixel rows, but leave column order the same. - for (ssize_t row = 0; row < screen->h; row++) { - ssize_t irow = screen->h - 1 - row; - for (ssize_t col = 0; col < screen->w; col++) { - uint32_t pxl = pxdata[irow * screen->w + col]; - - // TODO: store the alpha channels in the screenshot, is buggy at the moment.. - surface_pxls[row * screen->w + col] = pxl | 0xFF000000; - } - } - - // call sdl_image for saving the screenshot to png - IMG_SavePNG(screen, filename.c_str()); - SDL_FreeSurface(screen); + this->renderer->screenshot(filename); } diff --git a/libopenage/screenshot.h b/libopenage/screenshot.h index 5fc1f6faf2..e177ef0731 100644 --- a/libopenage/screenshot.h +++ b/libopenage/screenshot.h @@ -5,34 +5,43 @@ #include #include -#include #include "coord/window.h" namespace openage { +namespace renderer { +class Renderer; +} class ScreenshotManager { public: - ScreenshotManager(); + ScreenshotManager(renderer::Renderer *renderer); ~ScreenshotManager(); - /** to be called to save a screenshot */ + /** + * To be called to save a screenshot. + */ void save_screenshot(); - /** size of the game window, in coord_sdl */ - coord::window window_size; - - private: - - /** to be called to get the next screenshot filename into the array */ + /** + * To be called to get the next screenshot filename into the array. + */ std::string gen_next_filename(); - /** contains the number to be in the next screenshot filename */ + /** + * Contains the number to be in the next screenshot filename. + */ unsigned count; - /** contains the last time when a screenshot was taken */ + /** + * Contains the last time when a screenshot was taken. + */ std::time_t last_time; + /** + * The renderer where to take screenshots from. + */ + class renderer::Renderer *renderer; }; } // openage diff --git a/libopenage/unit/unit_texture.cpp b/libopenage/unit/unit_texture.cpp index b03aa35af1..2fefa58ab9 100644 --- a/libopenage/unit/unit_texture.cpp +++ b/libopenage/unit/unit_texture.cpp @@ -5,9 +5,9 @@ #include "../coord/phys3.h" #include "../coord/window.h" -#include "../util/math_constants.h" #include "../gamestate/game_spec.h" #include "../texture.h" +#include "../util/math_constants.h" #include "unit_texture.h" namespace openage { diff --git a/libopenage/util/CMakeLists.txt b/libopenage/util/CMakeLists.txt index 0dd345c517..a2143c8e6b 100644 --- a/libopenage/util/CMakeLists.txt +++ b/libopenage/util/CMakeLists.txt @@ -17,9 +17,10 @@ add_sources(libopenage matrix.cpp matrix_test.cpp misc.cpp - opengl.cpp os.cpp profiler.cpp + quaternion.cpp + quaternion_test.cpp stringformatter.cpp strings.cpp subprocess.cpp diff --git a/libopenage/util/compiler.h b/libopenage/util/compiler.h index 957278cc36..6ef4bf8f44 100644 --- a/libopenage/util/compiler.h +++ b/libopenage/util/compiler.h @@ -2,7 +2,7 @@ #pragma once -/* +/** @file * Some general-purpose utilities related to the C++ compiler and standard * library implementations. * diff --git a/libopenage/util/constexpr.h b/libopenage/util/constexpr.h index bca775119d..e3da528989 100644 --- a/libopenage/util/constexpr.h +++ b/libopenage/util/constexpr.h @@ -8,6 +8,17 @@ namespace openage { namespace util { + +/** + * Evaluate `value` at compiletime and return it. + * This can force constexpr evaluation. + */ +template +constexpr inline T compiletime() { + return value; +} + + /** * this namespace contains constexpr functions, i.e. C++11 functions that are designed * to run at compile-time. @@ -106,7 +117,7 @@ constexpr truncated_string_literal create_truncated_string_literal(const char *s * * @param pos start checking at a certain position. for internal recursion usage only. */ -constexpr bool has_prefix(const char *str, const truncated_string_literal prefix, size_t pos = 0) { +constexpr bool has_prefix(const char *str, const truncated_string_literal prefix, size_t pos=0) { // if only c++14 had arrived a little bit earlier... return @@ -150,7 +161,4 @@ constexpr const char *strip_prefix(const char *str, const char *prefix) { return strip_prefix(str, create_truncated_string_literal(prefix)); } - -} // namespace constexpr_ -} // namespace util -} // namespace openage +}}} // openage::util::constexpr_ diff --git a/libopenage/util/enum.h b/libopenage/util/enum.h index 00f5a63e5a..e7ec64907f 100644 --- a/libopenage/util/enum.h +++ b/libopenage/util/enum.h @@ -65,13 +65,13 @@ class Enum { */ Enum() : - id{0} {}; + id{0} {} // regular low-cost copying. Enum(const Enum &other) : - id{other.id} {}; + id{other.id} {} Enum &operator =(const Enum other) { diff --git a/libopenage/util/file.cpp b/libopenage/util/file.cpp index 2e5f2a78bc..00f81dd6e7 100644 --- a/libopenage/util/file.cpp +++ b/libopenage/util/file.cpp @@ -2,12 +2,12 @@ #include "file.h" +#include +#include +#include #include #include -#include #include -#include -#include #include "../error/error.h" @@ -29,66 +29,26 @@ ssize_t file_size(Dir basedir, const std::string &fname) { } -ssize_t read_whole_file(char **result, const std::string &filename) { - return read_whole_file(result, filename.c_str()); -} - -ssize_t read_whole_file(char **result, const char *filename) { - - //get the file size - ssize_t content_length = file_size(filename); - - if (content_length < 0) { - throw Error(MSG(err) << "File nonexistant: " << filename); - } - - //open the file - FILE *filehandle = fopen(filename, "r"); - if (filehandle == NULL) { - throw Error(MSG(err) << "Failed to open file: " << filename); - } +std::string read_whole_file(const std::string &filename) { + std::ifstream file{filename}; - //allocate filesize + nullbyte - *result = new char[content_length + 1]; + std::string str{ + (std::istreambuf_iterator(file)), + std::istreambuf_iterator() + }; - //read the whole content - if (content_length != (ssize_t)fread(*result, 1, content_length, filehandle)) { - fclose(filehandle); - throw Error(MSG(err) << "Failed to read file: " << filename); - } else { - fclose(filehandle); - } - - //make sure 0-byte is at the end - (*result)[content_length] = '\0'; - - //return the file size - return content_length; + return str; } std::vector file_get_lines(const std::string &file_name) { - char *file_content; - ssize_t fsize = util::read_whole_file(&file_content, file_name); - - char *file_seeker = file_content; - char *current_line = file_content; - - auto result = std::vector{}; - - while ((size_t)file_seeker <= ((size_t)file_content + fsize) - && *file_seeker != '\0') { - - if (*file_seeker == '\n') { - *file_seeker = '\0'; - - result.push_back(std::string{current_line}); + std::string line; + std::vector result{}; + std::ifstream file{file_name}; - current_line = file_seeker + 1; - } - file_seeker += 1; + while (std::getline(file, line)) { + result.push_back(line); } - delete[] file_content; return result; } diff --git a/libopenage/util/file.h b/libopenage/util/file.h index e73927ab9d..2a1d10756f 100644 --- a/libopenage/util/file.h +++ b/libopenage/util/file.h @@ -16,11 +16,24 @@ namespace openage { namespace util { + +/** + * Return the size in bytes of a given file name. + */ ssize_t file_size(const std::string &filename); + + +/** + * Return the size in bytes of a filename relative to a directory. + */ ssize_t file_size(Dir basedir, const std::string &fname); -ssize_t read_whole_file(char **result, const char *filename); -ssize_t read_whole_file(char **result, const std::string &filename); + +/** + * Read the contents of a given filename. + */ +std::string read_whole_file(const std::string &filename); + /** * get the lines of a file. diff --git a/libopenage/util/math.h b/libopenage/util/math.h new file mode 100644 index 0000000000..759e939459 --- /dev/null +++ b/libopenage/util/math.h @@ -0,0 +1,29 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_UTIL_MATH_H_ +#define OPENAGE_UTIL_MATH_H_ + +#include + +namespace openage { +namespace math { + + +/** + * Approximate a float square root by magic. + */ +float reciprocal_sqrt(float x) { + long i; + float y, r; + + y = x * 0.5f; + i = *(long *)(&x); + i = 0x5f3759df - (i >> 1); // magic! + r = *(float *)(&i); + r = r * (1.5f - (r * r * y)); + return r; +} + +}} // openage::util + +#endif diff --git a/libopenage/util/math_constants.h b/libopenage/util/math_constants.h index e3a0f72107..32da424255 100644 --- a/libopenage/util/math_constants.h +++ b/libopenage/util/math_constants.h @@ -19,5 +19,4 @@ constexpr double INV2_SQRT_PI = 1.12837916709551257390; //!< 2/sqrt(pi) constexpr double SQRT_2 = 1.41421356237309504880; //!< sqrt(2) constexpr double INV_SQRT_2 = 0.707106781186547524401; //!< 1/sqrt(2) -} // namespace math -} // namespace openage +}} // openage::math diff --git a/libopenage/util/matrix.h b/libopenage/util/matrix.h index 7fa316d8e5..c90aa94802 100644 --- a/libopenage/util/matrix.h +++ b/libopenage/util/matrix.h @@ -2,9 +2,10 @@ #pragma once -#include -#include #include +#include +#include +#include #include #include "vector.h" @@ -20,6 +21,8 @@ class Matrix : public std::array, M> { public: static_assert(M > 0 and N > 0, "0-dimensional matrix not allowed"); + static constexpr float default_eps = 1e-5; + static constexpr size_t rows = M; static constexpr size_t cols = N; static constexpr bool is_square = (M == N); @@ -27,20 +30,23 @@ class Matrix : public std::array, M> { static constexpr bool is_column_vector = (N == 1); /** - * Default constructor + * Initialize the matrix to zeroes. */ - Matrix() = default; + Matrix() { + for (size_t i = 0; i < M; i++) { + for (size_t j = 0; j < N; j++) { + (*this)[i][j] = 0; + } + } + } - /** - * Default destructor - */ ~Matrix() = default; /** * Constructor from Vector */ - template::type> + template ::type> Matrix(const Vector &vec) { for (size_t i = 0; i < M; i++) { (*this)[i][0] = vec[i]; @@ -50,19 +56,50 @@ class Matrix : public std::array, M> { /** * Constructor with N*M values */ - template + template Matrix(T ... args) { + static_assert(sizeof...(args) == N*M, "not all values supplied"); + std::array temp{{static_cast(args)...}}; for (size_t i = 0; i < N*M; i++) { (*this)[i / (N*M)][i % (N*M)] = temp[i]; } } + /** + * Constructs the identity matrix for the current size. + */ + template ::type> + static Matrix identity() { + Matrix res; + + for (size_t i = 0; i < N; i++) { + res[i][i] = 1; + } + + return res; + } + + /** + * Test if both matrices contain the same values within epsilon. + */ + bool equals(const Matrix &other, float eps=default_eps) const { + for (size_t i = 0; i < N; i++) { + for (size_t j = 0; j < M; j++) { + if (std::abs((*this)[i][j] - other[i][j]) >= eps) { + return false; + } + } + } + return true; + } + /** * Matrix multiplication */ - template - Matrix operator*(const Matrix &other) const { + template + Matrix operator *(const Matrix &other) const { Matrix res; for (size_t i = 0; i < M; i++) { for (size_t j = 0; j < P; j++) { @@ -78,14 +115,14 @@ class Matrix : public std::array, M> { /** * Matrix-Vector multiplication */ - Matrix operator*(const Vector &vec) const { + Matrix operator *(const Vector &vec) const { return (*this) * static_cast>(vec); } /** * Matrix addition */ - Matrix operator+(const Matrix &other) const { + Matrix operator +(const Matrix &other) const { Matrix res; for (size_t i = 0; i < M; i++) { for (size_t j = 0; j < N; j++) { @@ -98,7 +135,7 @@ class Matrix : public std::array, M> { /** * Matrix subtraction */ - Matrix operator-(const Matrix &other) const { + Matrix operator -(const Matrix &other) const { Matrix res; for (size_t i = 0; i < M; i++) { for (size_t j = 0; j < N; j++) { @@ -111,7 +148,7 @@ class Matrix : public std::array, M> { /** * Scalar multiplication with assignment */ - void operator*=(float other) { + void operator *=(float other) { for (size_t i = 0; i < M; i++) { for (size_t j = 0; j < N; j++) { (*this)[i][j] *= other; @@ -122,7 +159,7 @@ class Matrix : public std::array, M> { /** * Scalar multiplication */ - Matrix operator*(float other) const { + Matrix operator *(float other) const { Matrix res(*this); res *= other; return res; @@ -131,7 +168,7 @@ class Matrix : public std::array, M> { /** * Scalar division with assignment */ - void operator/=(float other) { + void operator /=(float other) { for (size_t i = 0; i < M; i++) { for (size_t j = 0; j < N; j++) { (*this)[i][j] /= other; @@ -142,7 +179,7 @@ class Matrix : public std::array, M> { /** * Scalar division */ - Matrix operator/(float other) const { + Matrix operator /(float other) const { Matrix res(*this); res /= other; return res; @@ -165,7 +202,7 @@ class Matrix : public std::array, M> { * Conversion to Vector */ template::type> + typename=typename std::enable_if::type> Vector to_vector() const { auto res = Vector(); for (size_t i = 0; i < M; i++) { @@ -174,6 +211,42 @@ class Matrix : public std::array, M> { return res; } + /** + * Matrix trace: the sum of all diagonal entries + */ + template::type> + float trace() const { + float res = 0.0f; + + for (size_t i = 0; i < N; i++) { + res += (*this)[i][i]; + } + + return res; + } + + /** + * Print to output stream using '<<' + */ + friend std::ostream &operator <<(std::ostream &o, + const Matrix &mat) { + o << "("; + for (size_t j = 0; j < M-1; j++) { + o << "("; + for (size_t i = 0; i < N-1; i++) { + o << mat[j][i] << ",\t"; + } + o << mat[j][N-1] << ")"; + o << "," << std::endl << " "; + } + o << "("; + for (size_t i = 0; i < N-1; i++) { + o << mat[M-1][i] << ",\t"; + } + o << mat[M-1][N-1] << "))"; + return o; + } }; /** @@ -184,28 +257,6 @@ Matrix operator *(float a, const Matrix &mat) { return mat * a; } -/** - * Print to output stream using '<<' - */ -template -std::ostream &operator <<(std::ostream &o, const Matrix &mat) { - o << "("; - for (size_t j = 0; j < M-1; j++) { - o << "("; - for (size_t i = 0; i < N-1; i++) { - o << mat[j][i] << ",\t"; - } - o << mat[j][N-1] << ")"; - o << "," << std::endl << " "; - } - o << "("; - for (size_t i = 0; i < N-1; i++) { - o << mat[M-1][i] << ",\t"; - } - o << mat[M-1][N-1] << "))"; - return o; -} - using Matrix2 = Matrix<2, 2>; using Matrix3 = Matrix<3, 3>; using Matrix4 = Matrix<4, 4>; diff --git a/libopenage/util/opengl.cpp b/libopenage/util/opengl.cpp deleted file mode 100644 index 1cc8b31ee3..0000000000 --- a/libopenage/util/opengl.cpp +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. - -#include "opengl.h" - -#include - -#include "../error/error.h" - -namespace openage { -namespace util { - -void gl_check_error() { - int glerrorstate = 0; - - glerrorstate = glGetError(); - if (glerrorstate != GL_NO_ERROR) { - - const char *errormsg; - - //generate error message - switch (glerrorstate) { - case GL_INVALID_ENUM: - // An unacceptable value is specified for an enumerated argument. - // The offending command is ignored - // and has no other side effect than to set the error flag. - errormsg = "invalid enum passed to opengl call"; - break; - case GL_INVALID_VALUE: - // A numeric argument is out of range. - // The offending command is ignored - // and has no other side effect than to set the error flag. - errormsg = "invalid value passed to opengl call"; - break; - case GL_INVALID_OPERATION: - // The specified operation is not allowed in the current state. - // The offending command is ignored - // and has no other side effect than to set the error flag. - errormsg = "invalid operation performed during some state"; - break; - case GL_INVALID_FRAMEBUFFER_OPERATION: - // The framebuffer object is not complete. The offending command - // is ignored and has no other side effect than to set the error flag. - errormsg = "invalid framebuffer operation"; - break; - case GL_OUT_OF_MEMORY: - // There is not enough memory left to execute the command. - // The state of the GL is undefined, - // except for the state of the error flags, - // after this error is recorded. - errormsg = "out of memory, wtf?"; - break; - case GL_STACK_UNDERFLOW: - // An attempt has been made to perform an operation that would - // cause an internal stack to underflow. - errormsg = "stack underflow"; - break; - case GL_STACK_OVERFLOW: - // An attempt has been made to perform an operation that would - // cause an internal stack to overflow. - errormsg = "stack overflow"; - break; - default: - // unknown error state - errormsg = "unknown error"; - } - throw Error(MSG(err) << - "OpenGL error state after running draw method: " << glerrorstate << "\n" - "\t" << errormsg << "\n" - << "Run the game with --gl-debug to get more information: './run game --gl-debug'."); - } -} - -}} // openage::util diff --git a/libopenage/util/opengl.h b/libopenage/util/opengl.h deleted file mode 100644 index d1c3a2e300..0000000000 --- a/libopenage/util/opengl.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2014-2016 the openage authors. See copying.md for legal info. - -#pragma once - -namespace openage { -namespace util { - -/** - * query the current opengl context for any errors. - * - * raises exceptions on any error. - */ -void gl_check_error(); - -} -} diff --git a/libopenage/util/quaternion.cpp b/libopenage/util/quaternion.cpp new file mode 100644 index 0000000000..47098d2694 --- /dev/null +++ b/libopenage/util/quaternion.cpp @@ -0,0 +1,10 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "quaternion.h" + +namespace openage { +namespace util { + +// Quaternion is all templated, so there's nothing to implement. + +}} // openage::util diff --git a/libopenage/util/quaternion.h b/libopenage/util/quaternion.h new file mode 100644 index 0000000000..dd03e29732 --- /dev/null +++ b/libopenage/util/quaternion.h @@ -0,0 +1,438 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_UTIL_QUATERNION_H_ +#define OPENAGE_UTIL_QUATERNION_H_ + +#include + +#include "matrix.h" +#include "math_constants.h" +#include "vector.h" + +#include "../error/error.h" +#include "../log/log.h" + +namespace openage { +namespace util { + +/** + * Implements Quaternions to represent 3d rotations. + * + * The 4 components stores 3 components as rotation axis, + * and one component as the rotation amount. + * + * This is mainly Ken Shoemake stuff from: + * http://www.cs.ucr.edu/~vbz/resources/quatut.pdf + * + * Also: + * From Quaternion to Matrix and Back + * J.M.P. van Waveren, id Software, 2005 + */ +template +class Quaternion { +public: + using this_type = Quaternion; + using type = T; + + static constexpr T default_eps = 1e-4; + + Quaternion(T w, T x, T y, T z) + : + w{w}, x{x}, y{y}, z{z} {} + + /** + * Create a identity quaternion. + */ + Quaternion() + : + w{1}, x{0}, y{0}, z{0} {} + + /** + * Constructs a quaternion from a rotation matrix. + * mat is assumed to be a left-matrix: + * vec_transformed = mat * vec_orig. + * + * mat can be 3x3 or 4x4. + * + * This tries to avoid float-fuckups in near-zero divides + * by using larger components first: w, then x, y, or z. + * + * trace(mat) >= 0 => |w| > 1/2 + * => as small as a largest component can be. + * else: + * max diagonal entry <=> max (|x|, |y|, |z|) + * which is larger than |w| and >= 1/2 + */ + template + Quaternion(const Matrix &mat) { + + static_assert(N == 3 or N == 4, "only 3 and 4 dimensional matrices can be converted to a quaternion!"); + + T trace = mat.trace(); + T trace_cmp = trace; + + if (N == 4) { + trace_cmp -= mat[3][3]; + } else { + trace += 1.0; + } + + if (trace_cmp > 0) { + T trace_root = std::sqrt(trace); // = 2w + this->w = trace_root * 0.5; // = w + trace_root = 0.5 / trace_root; // = 1/4w + + this->x = (mat[2][1] - mat[1][2]) * trace_root; + this->y = (mat[0][2] - mat[2][0]) * trace_root; + this->z = (mat[1][0] - mat[0][1]) * trace_root; + } + else { + // determine highest diagonal component: + int n0 = 0; + + if (mat[1][1] > mat[0][0]) { + n0 = 1; + } + + if (mat[2][2] > mat[n0][n0]) { + n0 = 2; + } + + // following indices to 0, 1, 2 + int next_index[] = {1, 2, 0}; + + // indexable pointers + T *ptrs[] = {&this->x, &this->y, &this->z}; + + int n1 = next_index[n0]; + int n2 = next_index[n1]; + + // this avoids float fuckups: + T trace_ordered = (mat[n0][n0] - (mat[n1][n1] + mat[n2][n2])); + + if (N == 4) { + trace_ordered += mat[3][3]; + } else { + trace_ordered += 1.0; + } + + T trace_root = std::sqrt(trace_ordered); + + *ptrs[n0] = trace_root * 0.5; // = w + trace_root = 0.5 / trace_root; // = 1/4w + + *ptrs[n1] = (mat[n0][n1] + mat[n1][n0]) * trace_root; + *ptrs[n2] = (mat[n2][n0] + mat[n0][n2]) * trace_root; + + this->w = (mat[n2][n1] - mat[n1][n2]) * trace_root; + } + + if (N == 4) { + // normalize the quaternion by the matrix[3][3] entry + (*this) *= 1.0 / std::sqrt(mat[3][3]); + } + } + + Quaternion(const this_type &other) = default; + Quaternion(this_type &&other) = default; + Quaternion &operator =(const this_type &other) = default; + Quaternion &operator =(this_type &&other) = default; + + virtual ~Quaternion() = default; + + /** + * Create a quaternion from a rotation in radians around a given axis. + */ + static this_type from_rad(T rad, Vector3 axis) { + T rot = rad / 2.0; + this_type q{ + std::cos(rot), + axis[0] * std::sin(rot), + axis[1] * std::sin(rot), + axis[2] * std::sin(rot) + }; + return q; + } + + /** + * Create a quaternion from a rotation in degree around a given axis. + */ + static this_type from_deg(T deg, Vector3 axis) { + return this_type::from_rad((deg * math::PI) / 180.0, axis); + } + + /** + * Perform a dot product with another quaternion. + */ + T dot(const this_type &o) const { + return this->w * o.w + this->x * o.x + this->y * o.y + this->z * o.z; + } + + /** + * Calculate the length of the quaternion. + */ + T norm() const { + return std::sqrt(this->dot(*this)); + } + + /** + * Ensure that the quaternion's length equals 1. + */ + T normalize() { + T len = this->norm(); + *this /= len; + return len; + } + + /** + * Return the normalized version of this quaternion. + */ + this_type normalized() const { + this_type q{*this}; + q.normalize(); + return q; + } + + /** + * Inverted the quaternion: + * Flip the rotation axes and scale by + * inverse sum of all components squared. + * + * inv(q)= conj(q)/(w*w + x*x + y*y + z*z) + */ + void inverse() { + T dot = this->dot(*this); + if (dot > 0.0) { + *this = this->conjugated() / dot; + } + else { + throw Error(MSG(err) << "tried inverting " << *this); + } + } + + /** + * Return the inverted quaternion. + */ + this_type inversed() const { + this_type q{*this}; + q.inverse(); + return q; + } + + /** + * Conjugate the quaternion by additive inverting + * the x, y and z components. + */ + void conjugate() { + this->x = -this->x; + this->y = -this->y; + this->z = -this->z; + } + + /** + * Return the conjugated quaternion. + */ + this_type conjugated() const { + this_type q{*this}; + q.conjugate(); + return q; + } + + /** + * Test if the rotation of both quaternions is the same. + */ + bool equals(const this_type &other, T eps=default_eps) const { + T ori = this->dot(other); + return (1 - (ori*ori)) < eps; + } + + /** + * Test if both quaternion store the same numbers. + */ + bool equals_number(const this_type &other, T eps=default_eps) const { + bool result = true; + (this->w - other.w) < eps or (result = false); + (this->x - other.x) < eps or (result = false); + (this->y - other.y) < eps or (result = false); + (this->z - other.z) < eps or (result = false); + return result; + } + + /** + * Test rotation equality with another quaternion + * with given precision in radians. + */ + bool equals_rad(const this_type &other, T rad_eps=default_eps) const { + T ori = this->dot(other); + T angle = std::acos((2.0 * (ori * ori)) - 1.0); + + return std::abs(angle) < rad_eps; + } + + /** + * Test rotation equality with another quaternion + * with given precision in degree. + */ + bool equals_deg(const this_type &other, T deg_eps=default_eps) const { + return this->equals_rad(other, (deg_eps * 180.0) / math::PI); + } + + /** + * Generate the corresponding rotation matrix. + */ + Matrix3 to_matrix() const { + T x2 = this->x * 2; + T y2 = this->y * 2; + T z2 = this->z * 2; + + T x2w = x2 * this->w; + T y2w = y2 * this->w; + T z2w = z2 * this->w; + + T x3 = x2 * this->x; + T y2x = y2 * this->x; + T z2x = z2 * this->x; + + T y3 = y2 * this->y; + T z2y = z2 * this->y; + T z3 = z2 * this->z; + + Matrix3 m{ + 1.0 - (y3 + z3), y2x - z2w, z2x + y2w, + y2x + z2w, 1.0 - (x3 + z3), z2y - x2w, + z2x - y2w, z2y + x2w, 1.0 - (x3 + y3) + }; + + return m; + } + + /** + * Transforms a vector by this quaternion. + */ + Vector3 operator *(const Vector3 &vec) const { + Vector3 axis{this->x, this->y, this->z}; + + Vector3 axis_vec_normal = axis.cross_product(vec); + Vector3 axis_vec_inplane = axis.cross_product(axis_vec_normal); + + axis_vec_normal *= 2.0f * this->w; + axis_vec_inplane *= 2.0f; + + return vec + axis_vec_normal + axis_vec_inplane; + } + + const this_type &operator +=(const this_type &other) { + this->w += other.w; + this->x += other.x; + this->y += other.y; + this->z += other.z; + + return *this; + } + + this_type operator +(const this_type &other) const { + this_type q{*this}; + q += other; + return q; + } + + const this_type &operator -=(const this_type &other) { + this->w -= other.w; + this->x -= other.x; + this->y -= other.y; + this->z -= other.z; + + return *this; + } + + this_type operator -(const this_type &other) const { + this_type q{*this}; + q -= other; + return q; + } + + const this_type &operator *=(const T &fac) { + this->w *= fac; + this->x *= fac; + this->y *= fac; + this->z *= fac; + + return *this; + } + + this_type operator *(const T &fac) const { + this_type q{*this}; + q *= fac; + return q; + } + + const this_type &operator *=(const this_type &other) { + T w_new = (this->w * other.w - this->x * other.x - + this->y * other.y - this->z * other.z); + T x_new = (this->w * other.x + this->x * other.w + + this->y * other.z - this->z * other.y); + T y_new = (this->w * other.y - this->x * other.z + + this->y * other.w + this->z * other.x); + T z_new = (this->w * other.z + this->x * other.y - + this->y * other.x + this->z * other.w); + + this->w = w_new; + this->x = x_new; + this->y = y_new; + this->z = z_new; + + return *this; + } + + this_type operator *(const this_type &other) const { + this_type q{*this}; + q *= other; + return q; + } + + const this_type &operator /=(const T &fac) { + this->w /= fac; + this->x /= fac; + this->y /= fac; + this->z /= fac; + + return *this; + } + + this_type operator /(const T &fac) const { + this_type q{*this}; + q /= fac; + return q; + } + + const this_type operator -() const { + return this_type{-this->w, -this->x, -this->y, -this->z}; + } + + bool operator ==(const this_type &other) const { + return ((this->w == other.w) and + (this->x == other.x) and + (this->y == other.y) and + (this->z == other.z)); + } + + bool operator !=(const this_type &other) const { + return not (*this == other); + } + + friend std::ostream &operator <<(std::ostream &o, const this_type &q) { + o << "Quaternion(" << q.w << ", " << q.x; + o << ", " << q.y << ", " << q.z << ")"; + return o; + } + +protected: + /** + * Stored quaternion number components. + */ + T w, x, y, z; +}; + +}} // openage::util + +#endif diff --git a/libopenage/util/quaternion_test.cpp b/libopenage/util/quaternion_test.cpp new file mode 100644 index 0000000000..0ea067d7fa --- /dev/null +++ b/libopenage/util/quaternion_test.cpp @@ -0,0 +1,254 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "quaternion.h" + +#include + +#include "../log/log.h" +#include "../testing/testing.h" + +namespace openage { +namespace util { +namespace tests { + +void quaternion() { + { + // Quaternion construction tests + const Quaternion<> id{}; + Quaternion<> id_explicit{1.0, 0.0, 0.0, 0.0}; + + id.equals(id_explicit) or TESTFAIL; + id.equals_deg(id_explicit, 1e-5f) or TESTFAIL; + + Quaternion<> wrong{0.0, 0.0, 1.0, 0.0}; + id.equals(wrong) and TESTFAIL; + + Matrix3 id_mat3 = Matrix3::identity(); + Quaternion<> q_id_mat3{id_mat3}; + id.equals(q_id_mat3) or TESTFAIL; + + Matrix4 id_mat4 = Matrix4::identity(); + Quaternion<> q_id_mat4{id_mat4}; + id.equals(q_id_mat4) or TESTFAIL; + + Matrix4 neg_mat4{ + -1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, -1, 0, + 0, 0, 0, 1, + }; + + Quaternion<> q_neg{neg_mat4}; + Quaternion<> q_nex_exp{0, 0, 1, 0}; + + q_neg.equals(q_nex_exp) or TESTFAIL; + } + { + // member functions + Quaternion<> q0{1, 2, 3, 4}; + Quaternion<> q1{5, 6, 7, 8}; + + TESTEQUALS_FLOAT(q0.dot(q1), 5 + 12 + 21 + 32, 1e-5f); + TESTEQUALS_FLOAT(q0.norm(), 5.4772255 /*= sqrt(30) */, 1e-4f); + + Quaternion<> q2{2, 8, 4, 16}; + + Quaternion<> q2_normd = q2.normalized(); + q2_normd.equals(q2) or TESTFAIL; + + Quaternion<> q2conj_exp{2, -8, -4, -16}; + q2conj_exp.equals(q2.conjugated()) or TESTFAIL; + + Quaternion<> q2inv_exp{ + 0.0058823529411764705, + -0.023529411764705882, + -0.011764705882352941, + -0.047058823529411764 + }; + + Quaternion<> q2inv = q2.inversed(); + q2inv.normalized().equals(q2inv_exp.normalized()) or TESTFAIL; + + q2.normalize(); + Quaternion<> q2norm_exp{ + 0.10846522, 0.433860915, 0.216930457, 0.8677218312 + }; + q2norm_exp.equals(q2) or TESTFAIL; + } + { + // Operator tests. + Quaternion<> id{}; + Quaternion<> bla0{13, 37, 42, 235}; + bla0.equals(id * bla0) or TESTFAIL; + + Quaternion<> bla1{8, 16, 24, 32}; + Quaternion<> bla0_1_exp{21, 45, 50, 243}; + bla0_1_exp.equals(bla0 + bla1) or TESTFAIL; + + Quaternion<> bla1_2_exp{2, 8, 12, 16}; + bla1_2_exp.equals(bla1 / 4) or TESTFAIL; + + Quaternion<> bla1_3_exp{16, 64, 96, 128}; + bla1_3_exp.equals(bla1 * 8) or TESTFAIL; + + Quaternion<> bla1_4_exp{21, 53, 66, 267}; + bla1_4_exp.equals(bla0 + bla1) or TESTFAIL; + + Quaternion<> bla1_5_exp{5, 21, 18, 203}; + bla1_5_exp.equals(bla0 - bla1) or TESTFAIL; + + Quaternion<> bla2_exp{ + 13 * 21 - 37 * 45 - 42 * 50 - 235 * 243, + 13 * 45 + 37 * 21 + 42 * 243 - 235 * 50, + 13 * 50 - 37 * 243 + 42 * 21 + 235 * 45, + 13 * 243 + 37 * 50 - 42 * 45 + 235 * 21 + }; + bla2_exp.equals(bla0 * bla0_1_exp) or TESTFAIL; + + (bla2_exp == bla2_exp) or TESTFAIL; + (bla2_exp != bla0) or TESTFAIL; + + Quaternion<> bla0_neg_exp{-13, -37, -42, -235}; + bla0_neg_exp.equals(-bla0) or TESTFAIL; + } + { + enum class axis { + x, y, z + }; + + // Rotation tests + auto rot_mat = [&](axis a, float am, bool deg=true) -> Matrix3 { + if (deg) { + am = (am * math::PI) / 180.0; + } + + switch (a) { + + case axis::x: + return { + 1, 0, 0, + 0, std::cos(am), -std::sin(am), + 0, std::sin(am), std::cos(am) + }; + + case axis::y: + return { + std::cos(am), 0, std::sin(am), + 0, 1, 0, + -std::sin(am), 0, std::cos(am) + }; + + case axis::z: + return { + std::cos(am), -std::sin(am), 0, + std::sin(am), std::cos(am), 0, + 0, 0, 1, + }; + default: + return { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + }; + }; + }; + + // zero rotation = identity. + Matrix3 rot = rot_mat(axis::x, 0, false); + rot.equals(Matrix3::identity()) or TESTFAIL; + Quaternion<> q_rot{rot}; + Quaternion<> q_rot_deg = Quaternion<>::from_rad(0, {1, 0, 0}); + q_rot.equals_deg(q_rot_deg) or TESTFAIL; + + // 10 rad rotation + rot = rot_mat(axis::x, 10, false); + q_rot = Quaternion<>{rot}; + q_rot_deg = Quaternion<>::from_rad(10, {1, 0, 0}); + q_rot.equals_rad(q_rot_deg) or TESTFAIL; + + // wrong 90-rad-rotation: + q_rot_deg = Quaternion<>::from_rad(90, {1, 0, 0}); + q_rot.equals_rad(q_rot_deg) and TESTFAIL; + + // 10 deg rotation + rot = rot_mat(axis::x, 10); + q_rot = Quaternion<>{rot}; + q_rot_deg = Quaternion<>::from_deg(10, {1, 0, 0}); + q_rot.equals_deg(q_rot_deg) or TESTFAIL; + + // 90 deg rotation + rot = rot_mat(axis::x, 90); + q_rot = Quaternion<>{rot}; + q_rot_deg = Quaternion<>::from_deg(90, {1, 0, 0}); + q_rot.equals_deg(q_rot_deg) or TESTFAIL; + + rot = rot_mat(axis::y, 90); + q_rot = Quaternion<>{rot}; + q_rot_deg = Quaternion<>::from_deg(90, {0, 1, 0}); + q_rot.equals_deg(q_rot_deg) or TESTFAIL; + + rot = rot_mat(axis::z, 90); + q_rot = Quaternion<>{rot}; + q_rot_deg = Quaternion<>::from_deg(90, {0, 0, 1}); + q_rot.equals_deg(q_rot_deg) or TESTFAIL; + + // -90 deg rotation + rot = rot_mat(axis::y, -90); + q_rot = Quaternion<>{rot}; + q_rot_deg = Quaternion<>::from_deg(-90, {0, 1, 0}); + q_rot.equals_deg(q_rot_deg) or TESTFAIL; + + // 45 deg rotation + rot = rot_mat(axis::z, 45); + q_rot = Quaternion<>{rot}; + q_rot_deg = Quaternion<>::from_deg(45, {0, 0, 1}); + q_rot.equals_deg(q_rot_deg) or TESTFAIL; + + // rotation combination + rot = rot_mat(axis::z, 45) * rot_mat(axis::y, 60); + q_rot = Quaternion<>{rot}; + q_rot_deg = (Quaternion<>::from_deg(45, {0, 0, 1}) * + Quaternion<>::from_deg(60, {0, 1, 0})); + q_rot.equals_deg(q_rot_deg) or TESTFAIL; + + rot = rot_mat(axis::z, 45) * + rot_mat(axis::y, 60) * + rot_mat(axis::x, -200); + q_rot = Quaternion<>{rot}; + q_rot_deg = (Quaternion<>::from_deg(45, {0, 0, 1}) * + Quaternion<>::from_deg(60, {0, 1, 0}) * + Quaternion<>::from_deg(-200, {1, 0, 0})); + q_rot.equals_deg(q_rot_deg) or TESTFAIL; + + // to_matrix tests + rot = rot_mat(axis::x, 235); + q_rot = Quaternion<>::from_deg(235, {1, 0, 0}); + rot.equals(q_rot.to_matrix()) or TESTFAIL; + + rot = rot_mat(axis::y, -55); + q_rot = Quaternion<>::from_deg(-55, {0, 1, 0}); + rot.equals(q_rot.to_matrix()) or TESTFAIL; + + rot = rot_mat(axis::z, 64); + q_rot = Quaternion<>::from_deg(64, {0, 0, 1}); + rot.equals(q_rot.to_matrix()) or TESTFAIL; + } + { + Vector3 vec{5, 0, 0}; + Vector3 turned = Quaternion<>::from_deg(180, {0, 0, 1}) * vec; + Vector3 turned_exp{-5, 0, 0}; + turned.equals(turned_exp) or TESTFAIL; + + // intentional fail: + turned_exp = Vector3{-42, -42, -42}; + turned.equals(turned_exp) and TESTFAIL; + + // another turn + vec = Vector3{1337, 42, 235}; + turned = Quaternion<>::from_deg(90, {1, 0, 0}) * vec; + turned_exp = Vector3{1337, -235, 42}; + turned.equals(turned_exp) or TESTFAIL; + } +} + +}}} // openage::util::tests diff --git a/libopenage/util/vector.h b/libopenage/util/vector.h index c688bcc8e5..c7b4583f7e 100644 --- a/libopenage/util/vector.h +++ b/libopenage/util/vector.h @@ -2,11 +2,14 @@ #pragma once -#include -#include #include +#include +#include +#include #include +#include "../log/log.h" + namespace openage { namespace util { @@ -19,13 +22,19 @@ class Vector : public std::array { static_assert(N > 0, "0-dimensional vector not allowed"); /** - * Default constructor + * Default comparison epsilon. */ - Vector() = default; + static constexpr float default_eps = 1e-4; /** - * Default destructor + * Default, random-value constructor. */ + Vector() { + for (size_t i = 0; i < N; i++) { + (*this)[i] = 0; + } + } + ~Vector() = default; /** @@ -33,8 +42,24 @@ class Vector : public std::array { */ template Vector(T ... args) - : - std::array {{static_cast(args)...}} {} + : + std::array {{static_cast(args)...}} { + + static_assert(sizeof...(args) == N, "not all values supplied."); + } + + /** + * Equality test with given precision. + */ + bool equals(const Vector &other, float eps=default_eps) { + for (size_t i = 0; i < N; i++) { + float diff = std::abs((*this)[i] - other[i]); + if (diff >= eps) { + return false; + } + } + return true; + } /** * Vector addition with assignment @@ -115,7 +140,7 @@ class Vector : public std::array { /** * Dot product of two Vectors */ - float dot_product(const Vector &other) const { + float dot(const Vector &other) const { float res = 0; for (size_t i = 0; i < N; i++) { res += (*this)[i] * other[i]; @@ -127,7 +152,7 @@ class Vector : public std::array { * Euclidian norm aka length */ float norm() const { - return sqrtf((*this).dot_product(*this)); + return std::sqrt(this->dot(*this)); } /** @@ -151,28 +176,25 @@ class Vector : public std::array { ); } -}; - -/** - * Scalar multiplication with swapped arguments - */ -template -Vector operator *(float a, const Vector &v) { - return v * a; -} + /** + * Scalar multiplication with swapped arguments + */ + friend Vector operator *(float a, const Vector &v) { + return v * a; + } -/** - * Print to output stream using '<<' - */ -template -std::ostream &operator <<(std::ostream &o, const Vector &v) { - o << "("; - for (size_t i = 0; i < N-1; i++) { - o << v[i] << ", "; - } - o << v[N-1] << ")"; - return o; -} + /** + * Print to output stream using '<<' + */ + friend std::ostream &operator <<(std::ostream &o, const Vector &v) { + o << "("; + for (size_t i = 0; i < N-1; i++) { + o << v[i] << ", "; + } + o << v[N-1] << ")"; + return o; + } +}; using Vector2 = Vector<2>; diff --git a/libopenage/util/vector_test.cpp b/libopenage/util/vector_test.cpp index 971ca364c7..760509401a 100644 --- a/libopenage/util/vector_test.cpp +++ b/libopenage/util/vector_test.cpp @@ -9,6 +9,13 @@ namespace util { namespace tests { void vector() { + { + // zero-initialization test. + Vector<5> zero_explicit{0, 0, 0, 0, 0}; + Vector<5> zero; + + zero.equals(zero_explicit) or TESTFAIL; + } { // tests in 2 dimensions. // we want to be able to reuse the variable names later. @@ -16,27 +23,27 @@ void vector() { const Vector<2> b(3.0, 4.0); Vector<2> c; + // test basic operators. c = a + b; - TESTEQUALS(c[0], 4.0); - TESTEQUALS(c[1], 6.0); + c.equals({4.0, 6.0}) or TESTFAIL; c = a - b; - TESTEQUALS(c[0], -2.0); - TESTEQUALS(c[1], -2.0); + c.equals({-2.0, -2.0}) or TESTFAIL; c = 5 * a; - TESTEQUALS(c[0], 5.0); - TESTEQUALS(c[1], 10.0); + c.equals({5.0, 10.0}) or TESTFAIL; - // division by 8 should be precise c = a / 8; - TESTEQUALS(c[0], 0.125); - TESTEQUALS(c[1], 0.25); + c.equals({0.125, 0.25}) or TESTFAIL; - TESTEQUALS(a.dot_product(b), 11); + c.equals({13, 37}) and TESTFAIL; + + // test dot product, norm and normalization. + TESTEQUALS_FLOAT(a.dot(b), 11, 1e-7); c = b; - TESTEQUALS(c.norm(), 5); + TESTEQUALS_FLOAT(c.norm(), 5, 1e-7); + c.normalize(); TESTEQUALS_FLOAT(c.norm(), 1, 1e-7); } @@ -46,9 +53,8 @@ void vector() { const Vector<3> a(1.0, 2.0, 3.0); const Vector<3> b(4.0, 5.0, 6.0); Vector<3> c = a.cross_product(b); - TESTEQUALS(c[0], -3.0); - TESTEQUALS(c[1], 6.0); - TESTEQUALS(c[2], -3.0); + + c.equals({-3.0, 6.0, -3.0}) or TESTFAIL; } } diff --git a/libopenage/watch/CMakeLists.txt b/libopenage/watch/CMakeLists.txt new file mode 100644 index 0000000000..5dc8f99bf3 --- /dev/null +++ b/libopenage/watch/CMakeLists.txt @@ -0,0 +1,5 @@ +add_sources(libopenage + linux.cpp + tests.cpp + watch.cpp +) diff --git a/libopenage/watch/linux.cpp b/libopenage/watch/linux.cpp new file mode 100644 index 0000000000..7e2e9672f2 --- /dev/null +++ b/libopenage/watch/linux.cpp @@ -0,0 +1,123 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "linux.h" + +#if WITH_INOTIFY + +#include + +// inotify headers +#include +#include +#include + +#include "../util/file.h" + +namespace openage { +namespace watch { +namespace linux { + +size_t WatchManager::fd_count = 0; +size_t WatchManager::max_user_watches = 8192; +size_t WatchManager::max_user_instances = 128; + + +WatchManager::WatchManager() { + // initialize the first inotify instance + this->create_inotify_control(); +} + +int WatchManager::create_inotify_control() { + // TODO: figure out the actual number. + // other programs will be using inotify as well. + if (this->fd_count == this->max_user_instances) { + throw Error{MSG(err) << "number of inotify instances exhausted"}; + } + + int control_fd = inotify_init1(IN_NONBLOCK); + if (control_fd < 0) { + throw Error{MSG(err) << "Failed to initialize inotify!"}; + } + + this->watch_fds[control_fd]; + + this->fd_count += 1; + + return control_fd; +} + +// TODO: event type argument, don't hardcode CLOSE_WRITE. +void WatchManager::watch_file(const std::string &filename, const callback_t &callback) { + + // find the next free control fd slot. + int cfd = -1; + for (auto &it : this->watch_fds) { + if (this->watch_fds[it.first].size() < this->max_user_watches) { + cfd = it.first; + break; + } + } + + if (cfd < 0) { + cfd = this->create_inotify_control(); + } + + // create inotify update trigger for the requested file + int wd = inotify_add_watch(cfd, filename.c_str(), IN_CLOSE_WRITE); + if (wd < 0) { + throw Error{MSG(warn) << "Failed to add inotify watch for " << filename}; + } + this->watch_fds[cfd].emplace(std::make_pair(wd, file_watcher{callback, filename})); +} + + +void WatchManager::check_changes() { + char buf[event_queue_size * (sizeof(struct inotify_event) + NAME_MAX + 1)]; + ssize_t len; + + for (auto &wfd : this->watch_fds) { + int control_fd = wfd.first; + + while (true) { + // fetch all events, the kernel won't write "half" structs. + len = read(control_fd, buf, sizeof(buf)); + + if (len == -1 and errno == EAGAIN) { + // no events on this fd, nothing to do. + break; + } + else if (len == -1) { + throw Error{MSG(err) << "Failed to read inotify events!"}; + } + + // process fetched events. + // the kernel guarantees complete events in the buffer. + char *ptr = buf; + while (ptr < buf + len) { + auto *event = reinterpret_cast(ptr); + + // TODO: support more watch events + if (event->mask & IN_CLOSE_WRITE) { + // trigger the callback + file_watcher &w = this->watch_fds[control_fd][event->wd]; + w.callback(event_type::modify, w.filename); + } + + // move the buffer ptr to the next event. + ptr += sizeof(struct inotify_event) + event->len; + } + } + } +} + + +void WatchManager::fetch_limits() { + WatchManager::max_user_watches = std::stoi(util::read_whole_file("/proc/sys/fs/inotify/max_user_watches")); + + WatchManager::max_user_instances = std::stoi(util::read_whole_file("/proc/sys/fs/inotify/max_user_instances")); +} + + +}}} // openage::watch::linux + +#endif // WITH_INOTIFY diff --git a/libopenage/watch/linux.h b/libopenage/watch/linux.h new file mode 100644 index 0000000000..5d6bfbbce8 --- /dev/null +++ b/libopenage/watch/linux.h @@ -0,0 +1,89 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_WATCH_LINUX_H_ +#define OPENAGE_WATCH_LINUX_H_ + +#include "../config.h" +#if WITH_INOTIFY + +#include +#include + +#include "watch.h" + +// GCC stdlib defines linux as 1. Why would anybody use lowercase macros? +#undef linux + + +namespace openage { +namespace watch { +namespace linux { + +/** + * Number of events to fetch at once at maximum. + */ +constexpr size_t event_queue_size = 4; + +/** + * Linux file watcher powered by inotify. + */ +class WatchManager : public watch::WatchManager { +public: + WatchManager(); + virtual ~WatchManager() = default; + + /** + * Query the inotify fds for events. + */ + void check_changes() override; + + /** + * Create a new watch in this manager. + */ + void watch_file(const std::string &filename, const callback_t &callback) override; + + +protected: + /** + * Create an inotify control fd. + */ + int create_inotify_control(); + + /** + * inotify control fd => watch handle fd => file watcher. + * We may have multiple fds open. + * The kernel returns the handle fd when events are triggered. + */ + std::unordered_map> watch_fds; + + /** + * The number of currently opened inotify fds + * + * Global for the program! + */ + static size_t fd_count; + + /** + * Number of watches per control fd. + * "/proc/sys/fs/inotify/max_user_watches" + */ + static size_t max_user_watches; + + /** + * Number of opened inotify control fds. + * "/proc/sys/fs/inotify/max_user_instances" + */ + static size_t max_user_instances; + + /** + * Update the maximum inotify event counters from the /proc filesystem. + */ + static void fetch_limits(); +}; + + + +}}} // namespace openage::watch::linux + +#endif // WITH_INOTIFY +#endif diff --git a/libopenage/watch/tests.cpp b/libopenage/watch/tests.cpp new file mode 100644 index 0000000000..b5b8dfb7cd --- /dev/null +++ b/libopenage/watch/tests.cpp @@ -0,0 +1,19 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "watch.h" + +#include + +namespace openage { +namespace watch { +namespace tests { + +void run() { + std::unique_ptr manager = WatchManager::create(); + + manager->watch_file("/etc/passwd", [](event_type, const std::string&) { } ); + + manager->check_changes(); +} + +}}} // openage::watch::tests diff --git a/libopenage/watch/watch.cpp b/libopenage/watch/watch.cpp new file mode 100644 index 0000000000..21172dfab3 --- /dev/null +++ b/libopenage/watch/watch.cpp @@ -0,0 +1,24 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "watch.h" + +#include "linux.h" + +namespace openage { +namespace watch { + +std::unique_ptr WatchManager::create() { +#if WITH_INOTIFY + return std::make_unique(); +#else + return std::make_unique(); +#endif +} + +void DummyWatchManager::check_changes() {} + +void DummyWatchManager::watch_file(const std::string &, const callback_t &) {} + + + +}} // openage::watch diff --git a/libopenage/watch/watch.h b/libopenage/watch/watch.h new file mode 100644 index 0000000000..86fafe4350 --- /dev/null +++ b/libopenage/watch/watch.h @@ -0,0 +1,94 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_WATCH_WATCH_H_ +#define OPENAGE_WATCH_WATCH_H_ + +#include +#include +#include + +namespace openage { + +/** + * Filesystem watching functions. + * Provides functionality for event based file monitoring. + */ +namespace watch { + +/** + * File watching event types. + */ +enum class event_type { + create, //!< new creation + modify, //!< content modification + remove, //!< removal of entry +}; + + +/** + * Function type for event callbacks. + * has to be a function accepting (eventtype, filename) + */ +using callback_t = std::function; + + +/** + * Handle for watching a single file. + */ +struct file_watcher { + const callback_t callback; + const std::string filename; +}; + + +/** + * File change and creation monitoring. + */ +class WatchManager { +protected: + WatchManager() = default; + +public: + virtual ~WatchManager() = default; + + /** + * Check if any of the watched files/directories + * have any events queued and trigger the appropriate callback. + */ + virtual void check_changes() = 0; + + /** + * Create a new watch in this manager. + */ + virtual void watch_file(const std::string &filename, const callback_t &callback) = 0; + + /** + * Create a watch manager. Selects the plattform-compatible backend. + */ + static std::unique_ptr create(); +}; + +/** + * dummy watch manager that is used when no watch functionality + * has been compiled in. + */ +class DummyWatchManager : public WatchManager { +public: + DummyWatchManager() = default; + virtual ~DummyWatchManager() = default; + + /** + * Hehe, does just nothing. + */ + void check_changes() override; + + /** + * Huehue, does nothing as well. + */ + void watch_file(const std::string &, const callback_t &) override; +}; + + +}} // namespace openage::watch + +#endif diff --git a/openage/CMakeLists.txt b/openage/CMakeLists.txt index 939fa7c297..5a31ccc943 100644 --- a/openage/CMakeLists.txt +++ b/openage/CMakeLists.txt @@ -21,4 +21,5 @@ add_subdirectory(game) add_subdirectory(log) add_subdirectory(nyan) add_subdirectory(util) +add_subdirectory(renderer) add_subdirectory(testing) diff --git a/openage/game/main_cpp.pyx b/openage/game/main_cpp.pyx index 1eacc0d35f..129c4d75fe 100644 --- a/openage/game/main_cpp.pyx +++ b/openage/game/main_cpp.pyx @@ -10,7 +10,7 @@ def run_game(args, assets): del assets # unused for now. - cdef main_arguments args_cpp; + cdef main_arguments args_cpp args_cpp.data_directory = args.asset_dir.encode() if args.fps is not None: diff --git a/openage/renderer/CMakeLists.txt b/openage/renderer/CMakeLists.txt new file mode 100644 index 0000000000..1f32b350a4 --- /dev/null +++ b/openage/renderer/CMakeLists.txt @@ -0,0 +1,8 @@ +add_cython_modules( + renderer_cpp.pyx + tests.pyx +) + +add_py_modules( + __init__.py +) diff --git a/openage/renderer/__init__.py b/openage/renderer/__init__.py new file mode 100644 index 0000000000..ce8a6e3ce2 --- /dev/null +++ b/openage/renderer/__init__.py @@ -0,0 +1,5 @@ +# Copyright 2015-2015 the openage authors. See copying.md for legal info. + +""" +openage graphics renderer +""" diff --git a/openage/renderer/renderer_cpp.pyx b/openage/renderer/renderer_cpp.pyx new file mode 100644 index 0000000000..32ac9f55e3 --- /dev/null +++ b/openage/renderer/renderer_cpp.pyx @@ -0,0 +1 @@ +# Copyright 2015-2015 the openage authors. See copying.md for legal info. diff --git a/openage/renderer/tests.pyx b/openage/renderer/tests.pyx new file mode 100644 index 0000000000..532cf56585 --- /dev/null +++ b/openage/renderer/tests.pyx @@ -0,0 +1,26 @@ +# Copyright 2015-2015 the openage authors. See copying.md for legal info. + +""" +tests for the graphics renderer. +""" + +import argparse + +from libopenage.renderer.tests cimport renderer_demo as renderer_demo_c + +def renderer_demo(list argv): + """ + invokes the available render demos. + """ + + cmd = argparse.ArgumentParser( + prog='... renderer_demo', + description='Various renderer demos') + cmd.add_argument("test_id", type=int, help="id of the demo to run.") + + args = cmd.parse_args(argv) + + cdef int renderer_test_id = args.test_id + + with nogil: + renderer_demo_c(renderer_test_id) diff --git a/openage/testing/testlist.py b/openage/testing/testlist.py index d6bb2a3dd8..ac9abaca15 100644 --- a/openage/testing/testlist.py +++ b/openage/testing/testlist.py @@ -42,6 +42,8 @@ def demos_py(): "translates a C++ exception and its causes to python") yield ("openage.log.tests.demo", "demonstrates the translation of Python log messages") + yield ("openage.renderer.tests.renderer_demo", + "showcases the new renderer") def tests_cpp(): @@ -52,6 +54,7 @@ def tests_cpp(): """ yield "openage::coord::tests::coord" + yield "openage::datastructure::tests::constexpr_map" yield "openage::datastructure::tests::doubly_linked_list" yield "openage::datastructure::tests::pairing_heap" yield "openage::job::tests::test_job_manager" @@ -65,8 +68,10 @@ def tests_cpp(): yield "openage::util::tests::enum_" yield "openage::util::tests::init" yield "openage::util::tests::matrix" + yield "openage::util::tests::quaternion" yield "openage::util::tests::vector" yield "openage::input::tests::parse_event_string", "keybinds parsing" + yield "openage::watch::tests::run" def demos_cpp():