diff --git a/.mailmap b/.mailmap index d99a4bb399..042023e657 100644 --- a/.mailmap +++ b/.mailmap @@ -13,3 +13,4 @@ Henry Snoek coop shell (Michael Enßlin, Jonas Jelten, Andre Kupka) Franz-Niclas Muschter Niklas Fiekas +Wojciech Nawrocki diff --git a/CMakeLists.txt b/CMakeLists.txt index 23835f593e..35a497c22d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,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() diff --git a/README.md b/README.md index e16b5166dd..83a00de905 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Technology | Component **Qt5** | Graphical user interface **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 5736afb756..4de0db57ab 100755 --- a/configure +++ b/configure @@ -69,6 +69,8 @@ def getenv_bool(varname): OPTIONS = { "backtrace": "if_available", "inotify": "if_available", + "opengl": "if_available", + "vulkan": "if_available", "gperftools-tcmalloc": False, "gperftools-profiler": "if_available", } @@ -81,7 +83,6 @@ def features(args, parser): the defaults below will be used. """ - def sanitize_option_name(option): """ Check if the given feature exists """ if option not in OPTIONS: diff --git a/doc/renderer/doc.md b/doc/renderer/doc.md new file mode 100644 index 0000000000..73cc8b2150 --- /dev/null +++ b/doc/renderer/doc.md @@ -0,0 +1,143 @@ +# Openage graphics +The graphics subsystem is implemented in two levels. The first level is an abstraction over graphics APIs (OpenGL, Vulkan) and provides generic shader execution methods. The second level uses the first to draw openage-specific graphics, i.e. the actual world, units, etc. + +### Namespaces: +`openage::renderer` - the level 1 renderer +`openage::renderer::opengl` - the OpenGL implementation +`openage::renderer::vulkan` - the Vulkan implementation +`openage::renderer::resources` - management of graphics assets + +__TODO name__ +`openage::graphics` - the level 2 system + +Every namespace is an actual directory and all its classes are contained there. + +## Level 1: +### Overview +First things first, we might want to support multiple APIs. For now just OpenGL, but maybe Vulkan or some others. We want to abstract over these, but this can't unfortunately be done at the level of graphics primitives like textures, buffers, etc. Well, it can, but it introduces unnecessary complexity and possible overhead. That is because the next-gen (Vulkan, Metal, DX12) APIs are vastly different from the old ones - most importantly, they're bindless, so something like a Vulkan context (GL notion) doesn't even make sense. We therefore choose to abstract on the higher level of things-to-draw. + +It works similarly to the Unity engine. The user can submit resources to be uploaded to the GPU and receives a handle that identifies the uploaded resource. Resources can be added, updated and removed. Currently supported resource types: shader, texture. + +### Thread-safety +This level might or might not be threadsafe depending on the concrete implementation. The OpenGL version is, in typical GL fashion, so not-threadsafe it's almost anti-threadsafe. All code must be executed sequentially on a dedicated window thread, the same one on which the window and renderer were initially created. The plan for the Vulkan version is to make it at least independent of thread-local storage and hopefully completely threadsafe. + +### Shaders + + + +These are more involved than resources, since each shader also carries a set of uniforms. + +A shader program takes in uniform values and vertex inputs and produces a number of bitmaps written into its render targets. Every shader program is then a function of the form $S:P\times\prod\limits^{n_u} U_i\times\left(\prod\limits^{n_a}V_j\right)^n\rightarrow\prod\limits^{n_t}B_k$, where $P$ is the primitive type, $\{U_i\}$, $\{V_j\}$, $\{B_k\}$ are indexed families of uniform, vertex attribute and bitmap types, respectively and the shader takes in $n_u$ uniforms, $n$ vertices with $n_a$ attributes per vertex and outputs to $n_t$ render targets. + +All of these inputs are contained in the $Renderable$ type. There is a catch here, since shaders are stateful, arguments are preserved between calls. + +Keep in mind that the output bitmap is not necessarily what the final output in a render target will be, since multiple draw calls get combined into a single bitmap and the initial value of the target also has an effect. + +With this premise, we can create a renderer interface generic over low-level APIs and usage (although optimized for openage's purposes). The only renderables are sets of values to be passed to shader programs - they may or may not include geometry, textures, matrices, etc. + +Our $ShaderInput$ class conceptually has almost the same member types as the types in the domain of the corresponding shader program function, with some small differences. For example, OpenGL shaders have implicit vertex attributes like $gl\_VertexID$ which we do not valuate manually. + +For the most part, these types are not know statically, so $ShaderInput$ has to simulate them internally with dynamic dispatch while providing an external interface that acts as if its members are the same as the inputs to the shader. + +OTOH, when it is the case that they are know statically, we can generate the appropriate headers from the shader code for better performance (in the future). + +Figure out the interface for $ShaderInput$. + +Parametrize $ShaderInput$ over a $Shader$ type. Have one $DynamicShader$ for all shaders loaded at runtime and some $StaticShaderForSpecificThing$ classes for shaders known beforehand. Make $DynamicShader$ compile for all operations and $StaticShader$.. only have operations it supports implemented, making it check them at compile-time. + +```c++ +template +class ShaderInput { + template + void set_unif(T val) { + // always compiles for dynamic shader, + // only compiles for valid U and T for static shader + (instanceof S).set_unif(val); + } +}; + +template +void func() { + static_assert(N != N, "Invalid shader uniform name."); +} + +template<> +void func() { + cout << "val1" << endl; + return; +} + +template<> +void func() { + cout << "val2" << endl; + return; +} +``` + +How to deal with drawing the scene? +We want stateless drawing, but pure stateless incurs some overhead. We can probably do with some mix of sharing scene between renderer and user. Keep a Scene object containing renderables (just shader valuations) and pass it to the render method. +What shoud Scene contain? +```c++ +struct Scene { + std::vector; + OR + std::vector>; + SceneSettings... + alpha blending, ztest, all of that in Object + is there anything global? + From Unity3D: + far, near plane + default depth + projection type + occlusion culling method + HDR + AA styles + how to deal with unit picking? probably have to make it a separate render pass + that renders unit ids into a buffer. read it on CPU or GPU? GPU faster, but is it possible? + general render-pass related things; have objects for render passes? +}; +``` +alternatively call render_pass(..) for each pass rather than call render(OBJ) once where OBJ contains info about render passes. might be nicer since render pass info can be recovered. problem - have to sync CPU/GPU to wait for command buffer execution finish +QUESTION: + share ownership of scene data at all times or give views to the user that are only accessible when not rendering? + +### Usage +Sample usage: + +```c++ +Window window("title"); +auto renderer = opengl::GlRenderer(window.get_context()); + +resources::TextureData tex_data("/path.tex"); +std::unique_ptr tex = renderer->add_texture(tex_data); + +resources::ShaderSource vsrc = resources::ShaderSource::read_from_file("/path.vert", resources::shader_t::glsl_vertex); +resources::ShaderSource fsrc = resources::ShaderSource::read_from_file("/path.frag", resources::shader_t::glsl_fragment); + +std::unique_ptr prog = renderer->add_shader( { vsrc, fsrc } ); + +auto input = prog->new_uniform_input( + "color", { 0.0f, 1.0f, 0.0f }, + "time", 0.0f, + "num", 1337 +); + +RenderPass pass { + { { + input, + new Geometry(geometry_t::quad), + true, + true, + true, + true, + } }, // list of renderables + renderer->get_framebuffer_target(), + 1.0f, + 8, +}; + +renderer->render(pass); +``` + +## Level 2: +On top of that renderer, we build a level 2 graphics subsystem. It has an API that is actually specific to openage, and is threadsafe. The level-2 renderer calls the level 1 renderer and updates it to match the game state. diff --git a/libopenage/CMakeLists.txt b/libopenage/CMakeLists.txt index de5ad13240..21da12e840 100644 --- a/libopenage/CMakeLists.txt +++ b/libopenage/CMakeLists.txt @@ -37,8 +37,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") @@ -49,7 +49,6 @@ add_subdirectory("testing") add_subdirectory("unit") add_subdirectory("util") - # run codegen, add files to executable codegen_run() add_sources(libopenage GENERATED ${CODEGEN_TARGET_TUS}) @@ -150,6 +149,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/config.h.in b/libopenage/config.h.in index 403b3d97c5..5315990538 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/renderer/CMakeLists.txt b/libopenage/renderer/CMakeLists.txt index fbaa233231..b0be6529ae 100644 --- a/libopenage/renderer/CMakeLists.txt +++ b/libopenage/renderer/CMakeLists.txt @@ -1,6 +1,19 @@ add_sources(libopenage - color.cpp - text.cpp + color.cpp + geometry.cpp + renderer.h + shader_program.h + tests.cpp + text.cpp + texture.cpp + window.cpp ) -add_subdirectory(font) +pxdgen( + tests.h +) + +add_subdirectory(font/) +add_subdirectory(opengl/) +add_subdirectory(resources/) +#add_subdirectory(shaders/) diff --git a/libopenage/renderer/color.cpp b/libopenage/renderer/color.cpp index cac7f9a1ed..657631211f 100644 --- a/libopenage/renderer/color.cpp +++ b/libopenage/renderer/color.cpp @@ -10,11 +10,10 @@ 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}, diff --git a/libopenage/renderer/color.h b/libopenage/renderer/color.h index dfd44666b9..e1edb57589 100644 --- a/libopenage/renderer/color.h +++ b/libopenage/renderer/color.h @@ -8,20 +8,20 @@ namespace openage { namespace renderer { class Color { + using color_channel_t = uint8_t; + public: Color(); - - Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + Color(color_channel_t r, color_channel_t g, color_channel_t b, color_channel_t a); 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..27277ba147 --- /dev/null +++ b/libopenage/renderer/context.cpp @@ -0,0 +1,77 @@ +// 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!"}; + } + // NB: vulkan does not actually have contexts +#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..7306abee46 --- /dev/null +++ b/libopenage/renderer/context.h @@ -0,0 +1,158 @@ +// 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, +}; + + + +/** + * 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/geometry.cpp b/libopenage/renderer/geometry.cpp new file mode 100644 index 0000000000..70808b37e0 --- /dev/null +++ b/libopenage/renderer/geometry.cpp @@ -0,0 +1,16 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "geometry.h" + + +namespace openage { +namespace renderer { + +Geometry::Geometry(geometry_t type) + : type(type) {} + +geometry_t Geometry::get_type() const { + return this->type; +} + +}} //openage::renderer diff --git a/libopenage/renderer/geometry.h b/libopenage/renderer/geometry.h new file mode 100644 index 0000000000..c90e6102ca --- /dev/null +++ b/libopenage/renderer/geometry.h @@ -0,0 +1,34 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + + +namespace openage { +namespace renderer { + +/// The type of geometry. +enum class geometry_t { + /// This passes 4 vertices with undefined positions to the shader. + /// The shader has to set the positions itself (e.g. using gl_VertexID in OpenGL). + bufferless_quad, + /// This passes valid geometry defined by a mesh to the shader. + mesh, +}; + +/// A class representing geometry to be passed to a draw call. +class Geometry { +public: + virtual ~Geometry() = default; + + /// Returns the type of this geometry. + geometry_t get_type() const; + +protected: + /// Initialize the geometry to a given type. + explicit Geometry(geometry_t type); + +private: + geometry_t type; +}; + +}} // openage::renderer diff --git a/libopenage/renderer/opengl/CMakeLists.txt b/libopenage/renderer/opengl/CMakeLists.txt new file mode 100644 index 0000000000..e1294e7393 --- /dev/null +++ b/libopenage/renderer/opengl/CMakeLists.txt @@ -0,0 +1,15 @@ +add_sources(libopenage + buffer.cpp + context.cpp + framebuffer.cpp + geometry.cpp + render_target.cpp + renderer.cpp + scrapped.h + shader.cpp + shader_program.cpp + simple_object.cpp + texture.cpp + uniform_input.h + vertex_array.cpp +) diff --git a/libopenage/renderer/opengl/buffer.cpp b/libopenage/renderer/opengl/buffer.cpp new file mode 100644 index 0000000000..5259cece12 --- /dev/null +++ b/libopenage/renderer/opengl/buffer.cpp @@ -0,0 +1,53 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "buffer.h" + +#include "../../error/error.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +GlBuffer::GlBuffer(size_t size, GLenum usage) + : GlSimpleObject([] (GLuint handle) { glDeleteBuffers(1, &handle); } ) + , size(size) + , usage(usage) { + GLuint handle; + glGenBuffers(1, &handle); + this->handle = handle; + + this->bind(GL_ARRAY_BUFFER); + glBufferData(GL_ARRAY_BUFFER, size, 0, usage); +} + +GlBuffer::GlBuffer(const uint8_t *data, size_t size, GLenum usage) + : GlSimpleObject([] (GLuint handle) { glDeleteBuffers(1, &handle); } ) + , size(size) + , usage(usage) { + GLuint handle; + glGenBuffers(1, &handle); + this->handle = handle; + + this->bind(GL_ARRAY_BUFFER); + glBufferData(GL_ARRAY_BUFFER, size, data, usage); +} + +size_t GlBuffer::get_size() const { + return this->size; +} + +void GlBuffer::upload_data(const uint8_t *data, size_t offset, size_t size) { + if (unlikely(offset + size > this->size)) { + throw Error(MSG(err) << "Tried to upload more data to OpenGL buffer than can fit."); + } + + this->bind(GL_ARRAY_BUFFER); + glBufferSubData(GL_ARRAY_BUFFER, offset, size, data); +} + +void GlBuffer::bind(GLenum target) const { + glBindBuffer(target, *this->handle); +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/buffer.h b/libopenage/renderer/opengl/buffer.h new file mode 100644 index 0000000000..8e37ae50db --- /dev/null +++ b/libopenage/renderer/opengl/buffer.h @@ -0,0 +1,40 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// Represents an OpenGL buffer of memory +/// allocated on the GPU. +class GlBuffer final : public GlSimpleObject { +public: + /// Creates an empty buffer of the specified size. + GlBuffer(size_t size, GLenum usage = GL_STATIC_DRAW); + + /// Creates a buffer of the specified size and fills it with the given data. + GlBuffer(const uint8_t *data, size_t size, GLenum usage = GL_STATIC_DRAW); + + /// The size in bytes of this buffer. + size_t get_size() const; + + /// Uploads `size` bytes of new data starting at `offset`. + /// `offset + size` has to be less than or equal to `get_size()`. + void upload_data(const uint8_t *data, size_t offset, size_t size); + + /// Bind this buffer to the specified GL target. + void bind(GLenum target) const; + +private: + /// The size in bytes of this buffer. + size_t size; + + /// The GL usage hint for this buffer. + GLenum usage; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/context.cpp b/libopenage/renderer/opengl/context.cpp new file mode 100644 index 0000000000..214b924685 --- /dev/null +++ b/libopenage/renderer/opengl/context.cpp @@ -0,0 +1,221 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "context.h" + +#include + +#include "../../log/log.h" +#include "../../error/error.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// The first element is the lowest version we need, last is highest version we support. +static constexpr std::array, 1> gl_versions = {{ { 3, 3 } }}; // for now we don't need any higher versions + +/// Finds out the supported graphics functions and OpenGL version of the device. +static gl_context_capabilities find_capabilities() { + // This is really hacky. We try to create a context starting with + // the lowest GL version and retry until one version is not supported and fails. + // There is no other way to do this. (https://gamedev.stackexchange.com/a/28457) + + 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()); + } + + // 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); + + SDL_GLContext test_context; + 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 { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, gl_versions[i_ver - 1].first); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl_versions[i_ver - 1].second); + 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()); + } + SDL_GL_MakeCurrent(test_window, test_context); + + gl_context_capabilities caps; + + GLint temp; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &temp); + caps.max_texture_size = temp; + // TOOD maybe GL_MAX_TEXTURE_IMAGE_UNITS or maybe GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS + // lol opengl + glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &temp); + caps.max_texture_slots = temp; + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &temp); + caps.max_vertex_attributes = temp; + + glGetIntegerv(GL_MAJOR_VERSION, &caps.major_version); + glGetIntegerv(GL_MINOR_VERSION, &caps.minor_version); + + SDL_GL_DeleteContext(test_context); + SDL_DestroyWindow(test_window); + + return caps; +} + +GlContext::GlContext(SDL_Window *window) { + this->capabilities = find_capabilities(); + auto const &capabilities = this->capabilities; + + 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); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, capabilities.major_version); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, capabilities.minor_version); + + this->gl_context = SDL_GL_CreateContext(window); + + if (this->gl_context == nullptr) { + throw Error(MSG(err) << "OpenGL context creation failed. SDL error: " << SDL_GetError()); + } + + // We still have to verify that our version of libepoxy supports this version of OpenGL. + int epoxy_glv = capabilities.major_version * 10 + capabilities.minor_version; + if (!epoxy_is_desktop_gl() || epoxy_gl_version() < epoxy_glv) { + throw Error(MSG(err) << "The used version of libepoxy does not support OpenGL version " + << capabilities.major_version << "." << capabilities.minor_version); + } + + log::log(MSG(info) << "Created OpenGL context version " << capabilities.major_version << "." << capabilities.minor_version); + + // 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: " + << capabilities.max_texture_size); + if (capabilities.max_texture_size < 1024) { + throw Error(MSG(err) << "Maximum supported texture size is too small: " + << capabilities.max_texture_size); + } + + log::log(MSG(dbg) << "Maximum supported texture units: " + << capabilities.max_texture_slots); + if (capabilities.max_texture_slots < 2) { + throw Error(MSG(err) << "Your GPU doesn't have enough texture units: " + << capabilities.max_texture_slots); + } +} + +GlContext::~GlContext() { + if (this->gl_context != nullptr) { + SDL_GL_DeleteContext(this->gl_context); + } +} + +GlContext::GlContext(GlContext &&other) + : gl_context(other.gl_context) + , capabilities(other.capabilities) { + other.gl_context = nullptr; +} + +GlContext& GlContext::operator=(GlContext &&other) { + this->gl_context = other.gl_context; + this->capabilities = other.capabilities; + other.gl_context = nullptr; + + return *this; +} + +SDL_GLContext GlContext::get_raw_context() const { + return this->gl_context; +} + +gl_context_capabilities GlContext::get_capabilities() const { + return this->capabilities; +} + +void GlContext::check_error() { + GLenum error_state = glGetError(); + if (error_state != GL_NO_ERROR) { + const char *msg = [=] { + // generate error message + switch (error_state) { + 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. + return "GL_INVALID_ENUM"; + 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. + return "GL_INVALID_VALUE"; + 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. + return "GL_INVALID_OPERATION"; + 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. + return "GL_INVALID_FRAMEBUFFER_OPERATION"; + 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. + return "GL_OUT_OF_MEMORY"; + case GL_STACK_UNDERFLOW: + // An attempt has been made to perform an operation that would + // cause an internal stack to underflow. + return "GL_STACK_UNDERFLOW"; + case GL_STACK_OVERFLOW: + // An attempt has been made to perform an operation that would + // cause an internal stack to overflow. + return "GL_STACK_OVERFLOW"; + default: + // unknown error state + return "unknown error"; + } + }(); + + throw Error( + MSG(err) << "An OpenGL error has occured.\n\t" + << "(" << error_state << "): " << msg + ); + } +} + +void GlContext::set_vsync(bool on) { + if (on) { + // try to use swap control tearing (adaptive vsync) + if (SDL_GL_SetSwapInterval(-1) == -1) { + // otherwise fall back to standard vsync + SDL_GL_SetSwapInterval(1); + } + } + else { + SDL_GL_SetSwapInterval(0); + } +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/context.h b/libopenage/renderer/opengl/context.h new file mode 100644 index 0000000000..d03e65198c --- /dev/null +++ b/libopenage/renderer/opengl/context.h @@ -0,0 +1,61 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + + +namespace openage { +namespace renderer { +namespace opengl { + +/// Stores information about context capabilities and limitations. +struct gl_context_capabilities { + /// The maximum number of vertex attributes in a shader. + size_t max_vertex_attributes; + /// The amount of texture units (GL_TEXTUREi) available. + size_t max_texture_slots; + /// The maximum size of a single dimension of a texture. + size_t max_texture_size; + + int major_version; + int minor_version; +}; + +/// Manages an OpenGL context. +class GlContext { +public: + /// Create a GL context in the given SDL window. + explicit GlContext(SDL_Window*); + ~GlContext(); + + /// It doesn't make sense to have more than one instance of the same context. + GlContext(const GlContext&) = delete; + GlContext& operator=(const GlContext&) = delete; + + /// We have to support moving to avoid a mess somewhere else. + GlContext(GlContext&&); + GlContext& operator=(GlContext&&); + + /// Returns the underlying SDL context pointer. + SDL_GLContext get_raw_context() const; + + /// Returns the capabilities of this context. + gl_context_capabilities get_capabilities() const; + + /// Turns VSYNC on or off for this context. + void set_vsync(bool on); + + /// Checks whether the current GL context on this thread reported any errors + /// and throws an exception if it did. Note that it's static. + static void check_error(); + +private: + /// Pointer to SDL struct representing the GL context. + SDL_GLContext gl_context; + + /// Context capabilities. + gl_context_capabilities capabilities; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/framebuffer.cpp b/libopenage/renderer/opengl/framebuffer.cpp new file mode 100644 index 0000000000..f975c01cb1 --- /dev/null +++ b/libopenage/renderer/opengl/framebuffer.cpp @@ -0,0 +1,50 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "framebuffer.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +// TODO the validity of this object is contingent +// on its texture existing. use shared_ptr? +GlFramebuffer::GlFramebuffer(std::vector textures) + : GlSimpleObject([] (GLuint handle) { glDeleteFramebuffers(1, &handle); } ) +{ + GLuint handle; + glGenFramebuffers(1, &handle); + this->handle = handle; + + glBindFramebuffer(GL_FRAMEBUFFER, handle); + + std::vector drawBuffers; + + size_t colorTextureCount = 0; + for (size_t i = 0; i < textures.size(); i++) { + // TODO figure out attachment points from pixel formats + if (textures[i]->get_info().get_format() == resources::pixel_format::depth24) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textures[i]->get_handle(), 0); + } else { + auto attachmentPoint = GL_COLOR_ATTACHMENT0 + colorTextureCount++; + glFramebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint, GL_TEXTURE_2D, textures[i]->get_handle(), 0); + drawBuffers.push_back(attachmentPoint); + } + } + + glDrawBuffers(drawBuffers.size(), drawBuffers.data()); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + throw Error(MSG(err) << "Could not create OpenGL framebuffer."); + } +} + +void GlFramebuffer::bind_read() const { + glBindFramebuffer(GL_READ_FRAMEBUFFER, *this->handle); +} + +void GlFramebuffer::bind_write() const { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, *this->handle); +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/framebuffer.h b/libopenage/renderer/opengl/framebuffer.h new file mode 100644 index 0000000000..65ffc82710 --- /dev/null +++ b/libopenage/renderer/opengl/framebuffer.h @@ -0,0 +1,32 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "texture.h" +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// Represents an OpenGL Framebuffer Object. +/// It is a collection of bitmap targets that can be drawn into +/// and read from. +class GlFramebuffer final : public GlSimpleObject { +public: + /// Construct a framebuffer pointing at the given textures. + /// Texture are attached to points specific to their pixel format, + /// e.g. a depth texture will be set as the depth target. + GlFramebuffer(std::vector textures); + + /// Bind this framebuffer to GL_READ_FRAMEBUFFER. + void bind_read() const; + + /// Bind this framebuffer to GL_DRAW_FRAMEBUFFER. + void bind_write() const; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/geometry.cpp b/libopenage/renderer/opengl/geometry.cpp new file mode 100644 index 0000000000..a41039f933 --- /dev/null +++ b/libopenage/renderer/opengl/geometry.cpp @@ -0,0 +1,72 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "geometry.h" + +#include + +#include "../../error/error.h" +#include "../../datastructure/constexpr_map.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +static constexpr auto gl_prim = datastructure::create_const_map( + std::make_pair(resources::vertex_primitive_t::POINTS, GL_POINTS), + std::make_pair(resources::vertex_primitive_t::LINES, GL_LINES), + std::make_pair(resources::vertex_primitive_t::LINE_STRIP, GL_LINE_STRIP), + std::make_pair(resources::vertex_primitive_t::TRIANGLES, GL_TRIANGLES), + std::make_pair(resources::vertex_primitive_t::TRIANGLE_STRIP, GL_TRIANGLE_STRIP), + std::make_pair(resources::vertex_primitive_t::TRIANGLE_FAN, GL_TRIANGLE_FAN) +); + +static constexpr auto gl_idx_t = datastructure::create_const_map( + std::make_pair(resources::index_t::U8, GL_UNSIGNED_BYTE), + std::make_pair(resources::index_t::U16, GL_UNSIGNED_SHORT), + std::make_pair(resources::index_t::U32, GL_UNSIGNED_INT) +); + +GlGeometry::GlGeometry() + : Geometry(geometry_t::bufferless_quad) {} + +GlGeometry::GlGeometry(const resources::MeshData &mesh) + : Geometry(geometry_t::mesh) { + GlBuffer verts(mesh.get_data().data(), mesh.get_data().size()); + + this->mesh = GlMesh { + std::move(verts), + GlVertexArray (verts, mesh.get_info()), + {}, + {}, + mesh.get_data().size() / mesh.get_info().vert_size(), + gl_prim.get(mesh.get_info().get_primitive()), + }; + + if (mesh.get_ids()) { + this->mesh->indices = GlBuffer(mesh.get_ids()->data(), mesh.get_ids()->size()); + this->mesh->index_type = gl_idx_t.get(*mesh.get_info().get_index_type()); + this->mesh->vert_count = mesh.get_ids()->size() / sizeof(GLuint); + } +} + +void GlGeometry::draw() const { + if (this->get_type() == geometry_t::bufferless_quad) { + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + else if (this->get_type() == geometry_t::mesh) { + auto const& mesh = *this->mesh; + mesh.vao.bind(); + + if (mesh.indices) { + mesh.indices->bind(GL_ELEMENT_ARRAY_BUFFER); + + glDrawElements(mesh.primitive, mesh.vert_count, *mesh.index_type, 0); + } + else { + glDrawArrays(GL_TRIANGLE_STRIP, 0, mesh.vert_count); + } + } +} + +}}} //openage::renderer::opengl diff --git a/libopenage/renderer/opengl/geometry.h b/libopenage/renderer/opengl/geometry.h new file mode 100644 index 0000000000..b96fdfb3b3 --- /dev/null +++ b/libopenage/renderer/opengl/geometry.h @@ -0,0 +1,47 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "../geometry.h" +#include "../resources/mesh_data.h" + +#include "buffer.h" +#include "vertex_array.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// The OpenGL class representing geometry to be passed to a draw call. +class GlGeometry final : public Geometry { +public: + /// The default constructor makes a quad. + GlGeometry(); + + /// Initialize a meshed geometry. Relatively costly, has to initialize GL buffers and copy vertex data. + explicit GlGeometry(resources::MeshData const&); + + /// Executes a draw command for the geometry on the currently active context. + /// Assumes bound and valid shader program and all other necessary state. + void draw() const; + +private: + /// All the pieces of OpenGL state that represent a mesh. + struct GlMesh { + GlBuffer vertices; + GlVertexArray vao; + std::experimental::optional indices; + std::experimental::optional index_type; + size_t vert_count; + GLenum primitive; + }; + + /// Data managing GPU memory and interpretation of mesh data. + /// Only present if the type is a mesh. + std::experimental::optional mesh; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/render_target.cpp b/libopenage/renderer/opengl/render_target.cpp new file mode 100644 index 0000000000..ea35fb6644 --- /dev/null +++ b/libopenage/renderer/opengl/render_target.cpp @@ -0,0 +1,26 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "render_target.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +GlRenderTarget::GlRenderTarget() + : type(gl_render_target_t::display) {} + +GlRenderTarget::GlRenderTarget(std::vector textures) + : type(gl_render_target_t::textures) + , framebuffer(textures) {} + +void GlRenderTarget::bind_write() const { + if (this->type == gl_render_target_t::textures) { + this->framebuffer->bind_write(); + } + else { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + } +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/render_target.h b/libopenage/renderer/opengl/render_target.h new file mode 100644 index 0000000000..8346d52803 --- /dev/null +++ b/libopenage/renderer/opengl/render_target.h @@ -0,0 +1,47 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "../renderer.h" +#include "texture.h" +#include "framebuffer.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// The type of OpenGL render target +enum class gl_render_target_t { + /// The actual window. This is visible to the user after swapping front and back buffers + display, + /// A bunch of textures + textures, + // TODO renderbuffers mixed with textures +}; + +/// Represents an OpenGL target that can be drawn into. +/// It can be either a framebuffer or the display (the window). +class GlRenderTarget final : public RenderTarget { +public: + /// Construct a render target pointed at the default framebuffer - the window. + GlRenderTarget(); + + /// Construct a render target pointing at the given textures. + /// Texture are attached to points specific to their pixel format, + /// e.g. a depth texture will be set as the depth target. + GlRenderTarget(std::vector textures); + + /// Bind this render target to be drawn into. + void bind_write() const; + +private: + gl_render_target_t type; + + /// For textures target type, the framebuffer. + std::experimental::optional framebuffer; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp new file mode 100644 index 0000000000..9fdd61aa2f --- /dev/null +++ b/libopenage/renderer/opengl/renderer.cpp @@ -0,0 +1,85 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "renderer.h" + +#include "../../log/log.h" +#include "../../error/error.h" +#include "texture.h" +#include "shader_program.h" +#include "uniform_input.h" +#include "geometry.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +GlRenderer::GlRenderer(GlContext *ctx) + : gl_context(ctx) + , display() +{ + log::log(MSG(info) << "Created OpenGL renderer"); +} + +std::unique_ptr GlRenderer::add_texture(const resources::TextureData& data) { + return std::make_unique(data); +} + +std::unique_ptr GlRenderer::add_texture(const resources::TextureInfo& info) { + return std::make_unique(info); +} + +std::unique_ptr GlRenderer::add_shader(std::vector const& srcs) { + return std::make_unique(srcs, this->gl_context->get_capabilities()); +} + +std::unique_ptr GlRenderer::add_mesh_geometry(resources::MeshData const& mesh) { + return std::make_unique(mesh); +} + +std::unique_ptr GlRenderer::add_bufferless_quad() { + return std::make_unique(); +} + +std::unique_ptr GlRenderer::create_texture_target(std::vector textures) { + std::vector gl_textures; + for (auto tex : textures) { + gl_textures.push_back(static_cast(tex)); + } + + return std::make_unique(gl_textures); +} + +RenderTarget const* GlRenderer::get_display_target() { + return &this->display; +} + +void GlRenderer::render(RenderPass const& pass) { + auto gl_target = dynamic_cast(pass.target); + gl_target->bind_write(); + + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + for (auto obj : pass.renderables) { + if (obj.alpha_blending) { + glEnable(GL_BLEND); + } + else { + glDisable(GL_BLEND); + } + + if (obj.depth_test) { + glEnable(GL_DEPTH_TEST); + } + else { + glDisable(GL_DEPTH_TEST); + } + + auto in = dynamic_cast(obj.unif_in); + auto geom = dynamic_cast(obj.geometry); + in->program->execute_with(in, geom); + } +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/renderer.h b/libopenage/renderer/opengl/renderer.h new file mode 100644 index 0000000000..ae16f0adc8 --- /dev/null +++ b/libopenage/renderer/opengl/renderer.h @@ -0,0 +1,46 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include + +#include "context.h" +#include "../renderer.h" +#include "shader_program.h" +#include "render_target.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// The OpenGL specialization of the rendering interface. +class GlRenderer final : public Renderer { +public: + GlRenderer(GlContext*); + + std::unique_ptr add_texture(resources::TextureData const&) override; + std::unique_ptr add_texture(resources::TextureInfo const&) override; + + std::unique_ptr add_shader(std::vector const&) override; + + std::unique_ptr add_mesh_geometry(resources::MeshData const&) override; + std::unique_ptr add_bufferless_quad() override; + + std::unique_ptr create_texture_target(std::vector) override; + RenderTarget const* get_display_target() override; + + void render(RenderPass const&) override; + +private: + /// The GL context. + GlContext *gl_context; + + std::vector shaders; + + GlRenderTarget display; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/scrapped.h b/libopenage/renderer/opengl/scrapped.h new file mode 100644 index 0000000000..6a7c3ae1fd --- /dev/null +++ b/libopenage/renderer/opengl/scrapped.h @@ -0,0 +1,65 @@ +/* +THIS IS AN EXTENSION TO UNIFORM PARSING +ALSO PARSE VERTEX ATTRIBUTES AND PROVIDE INFORMATION ABOUT THEM + +GLint Program::get_uniformbuffer_id(const char *name) { + this->check_is_linked("Uniform buffer requested"); + return glGetUniformBlockIndex(this->id, name); +} + +/// Return the opengl layout id for a given vertex attribute 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; +} + +/// Set vertex attribute with given name to have a custom id. +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); + } +} + +/// Query OpenGL which of the vertex attributes are actually active +/// and haven't been optimized out by the compiler. +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); +} +*/ diff --git a/libopenage/renderer/opengl/shader.cpp b/libopenage/renderer/opengl/shader.cpp new file mode 100644 index 0000000000..556bba6d54 --- /dev/null +++ b/libopenage/renderer/opengl/shader.cpp @@ -0,0 +1,54 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "shader.h" + +#include "../../datastructure/constexpr_map.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +static constexpr auto gl_shdr_type = datastructure::create_const_map( + std::make_pair(resources::shader_source_t::glsl_vertex, GL_VERTEX_SHADER), + std::make_pair(resources::shader_source_t::glsl_geometry, GL_GEOMETRY_SHADER), + std::make_pair(resources::shader_source_t::glsl_tesselation_control, GL_TESS_CONTROL_SHADER), + std::make_pair(resources::shader_source_t::glsl_tesselation_evaluation, GL_TESS_EVALUATION_SHADER), + std::make_pair(resources::shader_source_t::glsl_fragment, GL_FRAGMENT_SHADER) +); + +GlShader::GlShader(const resources::ShaderSource &src) + : GlSimpleObject([] (GLuint handle) { glDeleteShader(handle); } ) + , type(gl_shdr_type.get(src.get_type())) { + + // allocate shader in opengl + GLuint handle = glCreateShader(this->type); + this->handle = handle; + + // load shader source + const char* data = src.get_source(); + glShaderSource(handle, 1, &data, 0); + + // compile shader source + glCompileShader(handle); + + // check compiliation result + GLint status; + glGetShaderiv(handle, GL_COMPILE_STATUS, &status); + + if (status != GL_TRUE) { + GLint loglen; + glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &loglen); + + std::vector infolog(loglen); + glGetShaderInfoLog(handle, loglen, 0, infolog.data()); + + throw Error(MSG(err) << "Failed to compiler shader:\n" << infolog.data() ); + } +} + +GLenum GlShader::get_type() const { + return this->type; +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/shader.h b/libopenage/renderer/opengl/shader.h new file mode 100644 index 0000000000..974eee6d3e --- /dev/null +++ b/libopenage/renderer/opengl/shader.h @@ -0,0 +1,27 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../resources/shader_source.h" +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// A single OpenGL shader stage. +class GlShader final : public GlSimpleObject { +public: + /// Compiles the shader from the given source. + explicit GlShader(const resources::ShaderSource&); + + /// Returns the stage of the rendering pipeline this shader defines. + GLenum get_type() const; + +private: + /// Which stage of the rendering pipeline this shader defines. + GLenum type; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/shader_program.cpp b/libopenage/renderer/opengl/shader_program.cpp new file mode 100644 index 0000000000..f15a9256a1 --- /dev/null +++ b/libopenage/renderer/opengl/shader_program.cpp @@ -0,0 +1,361 @@ +// Copyright 2013-2017 the openage authors. See copying.md for legal info. + +#include "shader_program.h" + +#include "../../error/error.h" +#include "../../log/log.h" +#include "../../datastructure/constexpr_map.h" + +#include "texture.h" +#include "shader.h" +#include "geometry.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +static constexpr auto glsl_to_gl_type = datastructure::create_const_map( + std::make_pair("int", GL_INT), + std::make_pair("uint", GL_UNSIGNED_INT), + std::make_pair("float", GL_FLOAT), + std::make_pair("double", GL_DOUBLE), + std::make_pair("vec2", GL_FLOAT_VEC2), + std::make_pair("vec3", GL_FLOAT_VEC3), + std::make_pair("mat3", GL_FLOAT_MAT3), + std::make_pair("mat4", GL_FLOAT_MAT4), + std::make_pair("ivec2", GL_INT_VEC2), + std::make_pair("ivec3", GL_INT_VEC3), + std::make_pair("sampler2D", GL_SAMPLER_2D) +); + +static void check_program_status(GLuint program, GLenum what_to_check) { + GLint status; + glGetProgramiv(program, what_to_check, &status); + + if (status != GL_TRUE) { + GLint loglen; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &loglen); + + std::vector infolog(loglen); + glGetProgramInfoLog(program, loglen, 0, infolog.data()); + + const char *what_str = [=] { + switch (what_to_check) { + case GL_LINK_STATUS: + return "linking"; + case GL_VALIDATE_STATUS: + return "validation"; + case GL_COMPILE_STATUS: + return "compilation"; + default: + return "unknown shader creation task"; + } + }(); + + throw Error(MSG(err) << "OpenGL shader program " << what_str << " failed:\n" << infolog.data(), true); + } +} + +static constexpr auto gl_type_size = datastructure::create_const_map( + std::make_pair(GL_FLOAT, 4), + std::make_pair(GL_FLOAT_VEC2, 8), + std::make_pair(GL_FLOAT_VEC3, 12), + std::make_pair(GL_FLOAT_VEC4, 16), + std::make_pair(GL_INT, 4), + std::make_pair(GL_INT_VEC2, 8), + std::make_pair(GL_INT_VEC3, 12), + std::make_pair(GL_INT_VEC4, 16), + std::make_pair(GL_UNSIGNED_INT, 4), + std::make_pair(GL_UNSIGNED_INT_VEC2, 8), + std::make_pair(GL_UNSIGNED_INT_VEC3, 12), + std::make_pair(GL_UNSIGNED_INT_VEC4, 16), + std::make_pair(GL_BOOL, 1), + std::make_pair(GL_BOOL_VEC2, 2), + std::make_pair(GL_BOOL_VEC3, 3), + std::make_pair(GL_BOOL_VEC4, 4), + std::make_pair(GL_FLOAT_MAT2, 16), + std::make_pair(GL_FLOAT_MAT3, 36), + std::make_pair(GL_FLOAT_MAT4, 64), + std::make_pair(GL_SAMPLER_1D, 4), + std::make_pair(GL_SAMPLER_2D, 4), + std::make_pair(GL_SAMPLER_3D, 4), + std::make_pair(GL_SAMPLER_CUBE, 4) +); + +GlShaderProgram::GlShaderProgram(const std::vector &srcs, const gl_context_capabilities &caps) + : GlSimpleObject([] (GLuint handle) { glDeleteProgram(handle); } ) { + GLuint handle = glCreateProgram(); + this->handle = handle; + + std::vector shaders; + for (auto src : srcs) { + GlShader shader(src); + glAttachShader(handle, shader.get_handle()); + shaders.push_back(std::move(shader)); + } + + glLinkProgram(handle); + check_program_status(handle, GL_LINK_STATUS); + + glValidateProgram(handle); + check_program_status(handle, GL_VALIDATE_STATUS); + + // after linking we can delete the shaders + for (auto const& shdr : shaders) { + glDetachShader(handle, shdr.get_handle()); + } + + // query program information + GLint val; + glGetProgramiv(handle, GL_ACTIVE_ATTRIBUTES, &val); + size_t attrib_count = val; + glGetProgramiv(handle, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &val); + size_t attrib_maxlen = val; + glGetProgramiv(handle, GL_ACTIVE_UNIFORMS, &val); + size_t unif_count = val; + glGetProgramiv(handle, GL_ACTIVE_UNIFORM_MAX_LENGTH, &val); + size_t unif_maxlen = val; + + std::vector name(std::max(unif_maxlen, attrib_maxlen)); + + GLuint tex_unit = 0; + for (GLuint i_unif = 0; i_unif < unif_count; ++i_unif) { + GLint count; + GLenum type; + glGetActiveUniform( + handle, + i_unif, + name.size(), + 0, + &count, + &type, + name.data() + ); + + this->uniforms.insert(std::make_pair( + name.data(), + GlUniform { + type, + GLint(i_unif), + size_t(count), + size_t(count) * gl_type_size.get(type), + } + )); + + if (count != 1) { + // TODO support them + log::log(MSG(warn) << "Found array uniform " << name.data() << " in shader. Arrays are unsupported."); + } + + if (type == GL_SAMPLER_2D) { + if (tex_unit >= caps.max_texture_slots) { + throw Error(MSG(err) + << "Tried to create shader that uses more texture sampler uniforms " + << "than there are texture unit slots available."); + } + this->texunits_per_unifs.insert(std::make_pair(name.data(), tex_unit)); + tex_unit += 1; + } + + // TODO optimized away detection + if (0 == -1) { + log::log(MSG(warn) + << "OpenGL shader uniform " << name.data() << " was present in the source, but isn't present in the program. Probably optimized away."); + continue; + } + } + + for (GLuint i_attrib = 0; i_attrib < attrib_count; ++i_attrib) { + GLint size; + GLenum type; + glGetActiveAttrib( + handle, + i_attrib, + name.size(), + 0, + &size, + &type, + name.data() + ); + + this->attribs.insert(std::make_pair( + name.data(), + GlVertexAttrib { + type, + GLint(i_attrib), + size, + } + )); + } + + log::log(MSG(info) << "Created OpenGL shader program"); + + log::log(MSG(dbg) << "Uniforms: "); + for (auto const &pair : this->uniforms) { + log::log(MSG(dbg) << "(" << pair.second.location << ") " << pair.first << ": " << pair.second.type); + } + log::log(MSG(dbg) << "Vertex attributes: "); + for (auto const &pair : this->attribs) { + log::log(MSG(dbg) << "(" << pair.second.location << ") " << pair.first << ": " << pair.second.type); + } +} + +void GlShaderProgram::use() const { + glUseProgram(*this->handle); + + for (auto const &pair : this->textures_per_texunits) { + // We have to bind the texture to their texture units here because + // the texture unit bindings are global to the context. Each time + // the shader switches, it is possible that some other shader overwrote + // these, and since we want the uniform values to persist across execute_with + // calls, we have to set them more often than just on execute_with. + glActiveTexture(GL_TEXTURE0 + pair.first); + glBindTexture(GL_TEXTURE_2D, pair.second); + } +} + +void GlShaderProgram::execute_with(const GlUniformInput *unif_in, const GlGeometry *geom) { + assert(unif_in->program == this); + + this->use(); + + uint8_t const* data = unif_in->update_data.data(); + for (auto const &pair : unif_in->update_offs) { + uint8_t const* ptr = data + pair.second; + auto loc = this->uniforms[pair.first].location; + + switch (this->uniforms[pair.first].type) { + case GL_INT: + glUniform1i(loc, *reinterpret_cast(ptr)); + break; + case GL_UNSIGNED_INT: + glUniform1ui(loc, *reinterpret_cast(ptr)); + break; + case GL_FLOAT: + glUniform1f(loc, *reinterpret_cast(ptr)); + break; + case GL_DOUBLE: + // TODO requires an extension + glUniform1d(loc, *reinterpret_cast(ptr)); + break; + case GL_FLOAT_VEC2: + glUniform2fv(loc, 1, reinterpret_cast(ptr)); + break; + case GL_FLOAT_VEC3: + glUniform3fv(loc, 1, reinterpret_cast(ptr)); + break; + case GL_FLOAT_VEC4: + glUniform4fv(loc, 1, reinterpret_cast(ptr)); + break; + case GL_FLOAT_MAT3: + glUniformMatrix3fv(loc, 1, false, reinterpret_cast(ptr)); + break; + case GL_FLOAT_MAT4: + glUniformMatrix4fv(loc, 1, false, reinterpret_cast(ptr)); + break; + case GL_INT_VEC2: + glUniform2iv(loc, 1, reinterpret_cast(ptr)); + break; + case GL_INT_VEC3: + glUniform3iv(loc, 1, reinterpret_cast(ptr)); + break; + case GL_SAMPLER_2D: { + GLuint tex_unit = this->texunits_per_unifs[pair.first]; + GLuint tex = *reinterpret_cast(ptr); + glActiveTexture(GL_TEXTURE0 + tex_unit); + glBindTexture(GL_TEXTURE_2D, tex); + //TODO: maybe call this at an more appropiate position + glUniform1i(loc, tex_unit); + this->textures_per_texunits[tex_unit] = tex; + break; + } + default: + throw Error(MSG(err) << "Tried to upload unknown uniform type to GL shader."); + } + } + + if (geom != nullptr) { + // TODO read obj.blend + family + geom->draw(); + } +} + +std::unique_ptr GlShaderProgram::new_unif_in() { + auto in = std::make_unique(); + in->program = this; + return in; +} + +bool GlShaderProgram::has_uniform(const char* name) { + return this->uniforms.count(name) == 1; +} + +void GlShaderProgram::set_unif(UniformInput *in, const char *unif, void const* val, GLenum type) { + GlUniformInput *unif_in = static_cast(in); + + if (this->uniforms.count(unif) == 0) { + throw Error(MSG(err) << "Tried to set uniform " << unif << " that does not exist in the shader program."); + } + + auto const& unif_data = this->uniforms.at(unif); + + if (unlikely(type != unif_data.type)) { + throw Error(MSG(err) << "Tried to set uniform " << unif << " to a value of the wrong type."); + } + + size_t size = gl_type_size.get(unif_data.type); + + if (unif_in->update_offs.count(unif) == 1) { + // already wrote to this uniform since last upload + size_t off = unif_in->update_offs[unif]; + memcpy(unif_in->update_data.data() + off, val, size); + } + else { + size_t prev_size = unif_in->update_data.size(); + unif_in->update_data.resize(prev_size + size); + memcpy(unif_in->update_data.data() + prev_size, val, size); + unif_in->update_offs.emplace(unif, prev_size); + } +} + +void GlShaderProgram::set_i32(UniformInput *in, const char *unif, int32_t val) { + this->set_unif(in, unif, &val, GL_INT); +} + +void GlShaderProgram::set_u32(UniformInput *in, const char *unif, uint32_t val) { + this->set_unif(in, unif, &val, GL_UNSIGNED_INT); +} + +void GlShaderProgram::set_f32(UniformInput *in, const char *unif, float val) { + this->set_unif(in, unif, &val, GL_FLOAT); +} + +void GlShaderProgram::set_f64(UniformInput *in, const char *unif, double val) { + // TODO requires extension + this->set_unif(in, unif, &val, GL_DOUBLE); +} + +void GlShaderProgram::set_v2f32(UniformInput *in, const char *unif, Eigen::Vector2f const& val) { + this->set_unif(in, unif, &val, GL_FLOAT_VEC2); +} + +void GlShaderProgram::set_v3f32(UniformInput *in, const char *unif, Eigen::Vector3f const& val) { + this->set_unif(in, unif, &val, GL_FLOAT_VEC3); +} + +void GlShaderProgram::set_v4f32(UniformInput *in, const char *unif, Eigen::Vector4f const& val) { + this->set_unif(in, unif, &val, GL_FLOAT_VEC4); +} + +void GlShaderProgram::set_m4f32(UniformInput *in, const char *unif, Eigen::Matrix4f const& val) { + this->set_unif(in, unif, val.data(), GL_FLOAT_MAT4); +} + +void GlShaderProgram::set_tex(UniformInput *in, const char *unif, Texture const* val) { + auto const& tex = *static_cast(val); + GLuint handle = tex.get_handle(); + this->set_unif(in, unif, &handle, GL_SAMPLER_2D); +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/shader_program.h b/libopenage/renderer/opengl/shader_program.h new file mode 100644 index 0000000000..70b75eb52c --- /dev/null +++ b/libopenage/renderer/opengl/shader_program.h @@ -0,0 +1,87 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "../shader_program.h" +#include "../resources/shader_source.h" +#include "../renderer.h" + +#include "uniform_input.h" +#include "context.h" +#include "geometry.h" +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// A handle to an OpenGL shader program. +class GlShaderProgram final : public ShaderProgram, public GlSimpleObject { +public: + /// Tries to create a shader program from the given sources. + /// Throws an exception on compile/link errors. + explicit GlShaderProgram(const std::vector&, const gl_context_capabilities&); + + /// Bind this program as the currently used one in the OpenGL context. + void use() const; + + /// Does what the description of Renderable specifies - updates the uniform values + /// and draws the Geometry if it's not nullptr. + void execute_with(const GlUniformInput*, const GlGeometry*); + + bool has_uniform(const char*) override; + +protected: + std::unique_ptr new_unif_in() override; + void set_i32(UniformInput*, const char*, int32_t) override; + void set_u32(UniformInput*, const char*, uint32_t) override; + void set_f32(UniformInput*, const char*, float) override; + void set_f64(UniformInput*, const char*, double) override; + void set_v2f32(UniformInput*, const char*, Eigen::Vector2f const&) override; + void set_v3f32(UniformInput*, const char*, Eigen::Vector3f const&) override; + void set_v4f32(UniformInput*, const char*, Eigen::Vector4f const&) override; + void set_m4f32(UniformInput*, const char*, Eigen::Matrix4f const&) override; + void set_tex(UniformInput*, const char*, Texture const*) override; + +private: + void set_unif(UniformInput*, const char*, void const*, GLenum); + + /// Represents a uniform location in the shader program. + struct GlUniform { + GLenum type; + GLint location; + /// For arrays, the number of elements. For scalars, 1. + size_t count; + /// The size in bytes of the whole uniform (whole array if it's one). + size_t size; + }; + + /// Represents a per-vertex input to the shader program. + struct GlVertexAttrib { + GLenum type; + GLint location; + // TODO what is this? + GLint size; + }; + + /// A map of uniform names to their descriptions. + std::unordered_map uniforms; + + /// A map of per-vertex attribute names to their descriptions. + std::unordered_map attribs; + + // TODO parse uniform buffer structure ugh + // std::unordered_map uniform_buffers; + // GlVertexInputInfo; + + /// A map from sampler uniform names to their assigned texture units. + std::unordered_map texunits_per_unifs; + /// A map from texture units to the texture handles that are currently bound to them. + std::unordered_map textures_per_texunits; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/simple_object.cpp b/libopenage/renderer/opengl/simple_object.cpp new file mode 100644 index 0000000000..8a68ee171b --- /dev/null +++ b/libopenage/renderer/opengl/simple_object.cpp @@ -0,0 +1,41 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +GlSimpleObject::GlSimpleObject(std::function delete_fun) + : delete_fun(delete_fun) {} + +GlSimpleObject::GlSimpleObject(GlSimpleObject&& other) + : handle(other.handle) + , delete_fun(std::move(other.delete_fun)) { + other.handle = {}; +} + +GlSimpleObject &GlSimpleObject::operator =(GlSimpleObject&& other) { + if (this->handle) { + this->delete_fun(*this->handle); + } + + this->handle = other.handle; + this->delete_fun = std::move(other.delete_fun); + other.handle = {}; + + return *this; +} + +GlSimpleObject::~GlSimpleObject() { + if (this->handle) { + this->delete_fun(*this->handle); + } +} + +GLuint GlSimpleObject::get_handle() const { + return *this->handle; +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/simple_object.h b/libopenage/renderer/opengl/simple_object.h new file mode 100644 index 0000000000..a4d4fb18eb --- /dev/null +++ b/libopenage/renderer/opengl/simple_object.h @@ -0,0 +1,47 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include + + +namespace openage { +namespace renderer { +namespace opengl { + +/// A base class for all classes representing OpenGL Objects to inherit from. +/// It allows moving the object, but not copying it through the copy constructor. +/// It has unique_ptr-like semantics. It is called 'simple', because in the future +/// we might want to add collections of objects and similar more advanced features. +class GlSimpleObject { +public: + // Moving the representation is okay. + GlSimpleObject(GlSimpleObject&&); + GlSimpleObject &operator =(GlSimpleObject&&); + + // Generally, copying GL objects is costly and if we want to allow it, + // we do so through an explicit copy() function. + GlSimpleObject(GlSimpleObject const&) = delete; + GlSimpleObject &operator =(GlSimpleObject const&) = delete; + + /// Uses delete_fun to destroy the underlying object, + /// but only if the handle is present (hasn't been moved out). + virtual ~GlSimpleObject(); + + /// Returns the handle to the underlying OpenGL Object. + GLuint get_handle() const; + +protected: + explicit GlSimpleObject(std::function delete_fun); + + /// The handle to the OpenGL Object that this class represents. + std::experimental::optional handle; + + /// The function that deletes the underlying OpenGL Object. + std::function delete_fun; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/texture.cpp b/libopenage/renderer/opengl/texture.cpp new file mode 100644 index 0000000000..85bd231947 --- /dev/null +++ b/libopenage/renderer/opengl/texture.cpp @@ -0,0 +1,96 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "texture.h" + +#include + +#include + +#include "../../error/error.h" +#include "../../datastructure/constexpr_map.h" + +#include "../resources/texture_data.h" +#include "render_target.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// The input and output formats for GL. +static constexpr auto gl_format = datastructure::create_const_map>( + // TODO check correctness of formats here + std::make_pair(resources::pixel_format::r16ui, std::make_tuple(GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_INT)), + std::make_pair(resources::pixel_format::r32ui, std::make_tuple(GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT)), + std::make_pair(resources::pixel_format::rgb8, std::make_tuple(GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE)), + std::make_pair(resources::pixel_format::depth24, std::make_tuple(GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE)), + std::make_pair(resources::pixel_format::rgba8, std::make_tuple(GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE)), + std::make_pair(resources::pixel_format::rgba8ui, std::make_tuple(GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE)) +); + +GlTexture::GlTexture(const resources::TextureData& data) + : Texture(data.get_info()) + , GlSimpleObject([] (GLuint handle) { glDeleteTextures(1, &handle); } ) +{ + // generate opengl texture handle + glGenTextures(1, &*this->handle); + glBindTexture(GL_TEXTURE_2D, *this->handle); + + // select pixel format + auto fmt_in_out = gl_format.get(this->info.get_format()); + + // store raw pixels to gpu + auto size = this->info.get_size(); + + glPixelStorei(GL_UNPACK_ALIGNMENT, this->info.get_row_alignment()); + + glTexImage2D( + GL_TEXTURE_2D, 0, + std::get<0>(fmt_in_out), size.first, size.second, 0, + std::get<1>(fmt_in_out), std::get<2>(fmt_in_out), data.get_data() + ); + + // drawing settings + // TODO these are outdated, use sampler settings + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +} + +GlTexture::GlTexture(const resources::TextureInfo &info) + : Texture(info) + , GlSimpleObject([] (GLuint handle) { glDeleteTextures(1, &handle); } ) +{ + // generate opengl texture handle + glGenTextures(1, &*this->handle); + glBindTexture(GL_TEXTURE_2D, *this->handle); + + auto fmt_in_out = gl_format.get(this->info.get_format()); + + auto dims = this->info.get_size(); + + glPixelStorei(GL_UNPACK_ALIGNMENT, this->info.get_row_alignment()); + + glTexImage2D( + GL_TEXTURE_2D, 0, + std::get<0>(fmt_in_out), dims.first, dims.second, 0, + std::get<1>(fmt_in_out), std::get<2>(fmt_in_out), nullptr + ); + + // TODO these are outdated, use sampler settings + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +} + +resources::TextureData GlTexture::into_data() { + auto fmt_in_out = gl_format.get(this->info.get_format()); + std::vector data(this->info.get_data_size()); + + glPixelStorei(GL_PACK_ALIGNMENT, this->info.get_row_alignment()); + glBindTexture(GL_TEXTURE_2D, *this->handle); + // TODO use a Pixel Buffer Object instead + glGetTexImage(GL_TEXTURE_2D, 0, std::get<1>(fmt_in_out), std::get<2>(fmt_in_out), data.data()); + + return resources::TextureData(resources::TextureInfo(this->info), std::move(data)); +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/texture.h b/libopenage/renderer/opengl/texture.h new file mode 100644 index 0000000000..a87342f49a --- /dev/null +++ b/libopenage/renderer/opengl/texture.h @@ -0,0 +1,27 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../resources/texture_data.h" +#include "../texture.h" +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// A handle to an OpenGL texture. +class GlTexture final : public Texture, public GlSimpleObject { +public: + /// Constructs a texture and fills it with the given data. + explicit GlTexture(const resources::TextureData&); + + /// Constructs an empty texture with the given parameters. + GlTexture(resources::TextureInfo const&); + + /// Downloads the texture from the GPU and returns it as CPU-accessible data. + resources::TextureData into_data() override; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/uniform_input.h b/libopenage/renderer/opengl/uniform_input.h new file mode 100644 index 0000000000..09cd385f5f --- /dev/null +++ b/libopenage/renderer/opengl/uniform_input.h @@ -0,0 +1,32 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "../renderer.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +class GlShaderProgram; + +/// Describes OpenGL-specific uniform valuations. +struct GlUniformInput final : public UniformInput { + /// The program that this was created for. + GlShaderProgram* program; + + /// We store uniform updates lazily. They are only actually uploaded to GPU + /// when a draw call is made. Update_offs maps the uniform names to where their + /// value is in update_data in terms of a byte-wise offset. This is only a partial + /// valuation, so not all uniforms have to be present here. + std::unordered_map update_offs; + + /// Buffer containing untyped uniform update data. + std::vector update_data; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/vertex_array.cpp b/libopenage/renderer/opengl/vertex_array.cpp new file mode 100644 index 0000000000..889ec46b49 --- /dev/null +++ b/libopenage/renderer/opengl/vertex_array.cpp @@ -0,0 +1,104 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "vertex_array.h" + +#include "../../error/error.h" +#include "../../datastructure/constexpr_map.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// The type of a single element in a per-vertex attribute. +static constexpr auto gl_types = datastructure::create_const_map( + std::make_pair(resources::vertex_input_t::V2F32, GL_FLOAT), + std::make_pair(resources::vertex_input_t::V3F32, GL_FLOAT) +); + +GlVertexArray::GlVertexArray(std::vector> buffers) + : GlSimpleObject([] (GLuint handle) { glDeleteVertexArrays(1, &handle); } ) +{ + GLuint handle; + glGenVertexArrays(1, &handle); + this->handle = handle; + + this->bind(); + + GLuint attrib = 0; + for (auto buf_info : buffers) { + auto const &buf = buf_info.first; + auto const &info = buf_info.second; + + buf.bind(GL_ARRAY_BUFFER); + + if (info.get_layout() == resources::vertex_layout_t::AOS) { + size_t offset = 0; + + for (auto in : info.get_inputs()) { + glEnableVertexAttribArray(attrib); + + glVertexAttribPointer( + attrib, + resources::vertex_input_count(in), + gl_types.get(in), + GL_FALSE, + info.vert_size(), + reinterpret_cast(offset) + ); + + offset += resources::vertex_input_size(in); + attrib += 1; + } + } + else if (info.get_layout() == resources::vertex_layout_t::SOA) { + size_t offset = 0; + size_t vert_count = buf.get_size() / info.vert_size(); + + for (auto in : info.get_inputs()) { + glEnableVertexAttribArray(attrib); + + glVertexAttribPointer( + attrib, + resources::vertex_input_count(in), + gl_types.get(in), + GL_FALSE, + 0, + reinterpret_cast(offset) + ); + + offset += resources::vertex_input_size(in) * vert_count; + attrib += 1; + } + + } + else { + throw Error(MSG(err) << "Unknown vertex layout."); + } + } +} + +GlVertexArray::GlVertexArray(GlBuffer const& buf, resources::VertexInputInfo const& info) + : GlVertexArray( { { buf, info } } ) {} + +GlVertexArray::GlVertexArray() + : GlSimpleObject([] (GLuint handle) { glDeleteVertexArrays(1, &handle); } ) +{ + GLuint handle; + glGenVertexArrays(1, &handle); + this->handle = handle; +} + +void GlVertexArray::bind() const { + glBindVertexArray(*this->handle); + + // TODO necessary? + /* + // then enable all contained attribute ids + for (auto &attr_id : this->bound_attributes) { + glEnableVertexAttribArray(attr_id); + } + */ +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/vertex_array.h b/libopenage/renderer/opengl/vertex_array.h new file mode 100644 index 0000000000..f23a9a1e92 --- /dev/null +++ b/libopenage/renderer/opengl/vertex_array.h @@ -0,0 +1,47 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../resources/mesh_data.h" +#include "buffer.h" +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// An OpenGL Vertex Array Object. It represents a mapping between a buffer +/// that contains vertex data and a way for the context to interpret it. +class GlVertexArray final : public GlSimpleObject { +public: + /// Initializes a vertex array from the given mesh data. + /// The inputs are parsed as follows - each subsequent (GlBuffer, VertexInputInfo) pair is added + /// to the VAO in the same order as they appear in the vector. Each buffer is bound according to its + /// input info and each subsequent vertex attribute is attached to an id in ascending order. + /// For example, the inputs: + /// in vec3 pos; + /// in vec2 uv; + /// in mat3 rot; + /// could be bound to two buffers like so: + /// GlBuffer buf1(data1, size1); + /// GlBuffer buf2(data2, size2); + /// GlVertexArray( { + /// std::make_pair(buf1, { { vertex_input_t::V3F32, vertex_input_t::V2F32 }, vertex_layout_t::SOA }, + /// std::make_pair(buf2, { { vertex_input_t::M3F32 }, vertex_input_t::AOS /*or ::SOA, doesn't matter*/ }, + /// } ); + /// Not that anyone would ever want to bind more than one buffer to a single mesh.. + GlVertexArray(std::vector> buffers); + + /// Executes the buffer list constructor with one element. + GlVertexArray(GlBuffer const&, resources::VertexInputInfo const&); + + /// The default constructor initializes an empty VAO with no attributes. + /// This is useful for bufferless drawing. + GlVertexArray(); + + /// Make this vertex array object the current one. + void bind() const; +}; + +}}} // openage::renderer::opengl 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/renderer.h b/libopenage/renderer/renderer.h new file mode 100644 index 0000000000..4660c7e035 --- /dev/null +++ b/libopenage/renderer/renderer.h @@ -0,0 +1,107 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + + +namespace openage { +namespace renderer { + +namespace resources { +class TextureData; +class TextureInfo; +class ShaderSource; +class MeshData; +} + +class ShaderProgram; +class Geometry; +class Texture; + +/// The abstract base for uniform input. Not terribly interesting. +class UniformInput { +protected: + UniformInput() = default; + +public: + virtual ~UniformInput() = default; +}; + +/// The abstract base for a render target. Not terribly interesting. +class RenderTarget { +protected: + RenderTarget() = default; + +public: + virtual ~RenderTarget() = default; +}; + +/// A renderable is a set of shader uniform inputs and a possible draw call. +/// When a renderable is parsed, the uniform inputs are first passed to the shader they were created with. +/// Then, if the geometry is not nullptr, the shader is executed with the geometry and the specified settings +/// and the results are written into the render target. If the geometry is nullptr, the uniform values are +/// uploaded to the shader but no draw call is performed. This can be used to, for example, first set the values +/// of uniforms that many objects have in common, and then only upload the uniforms that vary between them in +/// each draw call. This works because uniform values in any given shader are preserved across a render pass. +struct Renderable { + /// Uniform values to be set in the appropriate shader. + UniformInput const *unif_in; + /// The geometry. It can be a simple primitive or a complex mesh. + /// Can be nullptr to only set uniforms but do not perform draw call. + Geometry const *geometry; + /// Whether to perform alpha-based color blending with whatever was in the render target before. + bool alpha_blending; + /// Whether to perform depth testing and discard occluded fragments. + bool depth_test; +}; + +/// A render pass is a series of draw calls represented by renderables that output into the given render target. +struct RenderPass { + /// The renderables to parse and possibly execute. + std::vector renderables; + /// The render targe to write into. + RenderTarget const *target; +}; + +/// The renderer. This class is used for performing all graphics operations. It is abstract and has implementations +/// for various low-level graphics APIs like OpenGL. +class Renderer { +public: + virtual ~Renderer() = default; + + /// Uploads the given texture data (usually loaded from disk) to graphics hardware and makes it available + /// as a Texture object that can be used for various operations. + virtual std::unique_ptr add_texture(resources::TextureData const&) = 0; + + /// Creates a new empty texture with the given parameters on the graphics hardware. + virtual std::unique_ptr add_texture(resources::TextureInfo const&) = 0; + + /// Compiles the given shader source code into a shader program. A shader program is the main tool used + /// for graphics rendering. + virtual std::unique_ptr add_shader(std::vector const&) = 0; + + /// Creates a Geometry object from the given mesh data, uploading it to the GPU by creating appropriate buffer. + /// The vertex attributes will be passed to the shader as described in the mesh data. + virtual std::unique_ptr add_mesh_geometry(resources::MeshData const&) = 0; + + /// Adds a Geometry object that passes a simple 4-vertex drawing command with no vertex attributes to the shader. + /// Useful for generating positions in the vertex shader. + virtual std::unique_ptr add_bufferless_quad() = 0; + + /// Constructs a render target from the given textures. All subsequent drawing operations pointed at this + /// target will write to these textures. The textures are attached to the render target according to their + /// internal format, for example RGB will be attached as a color component and DEPTH will be attached as a + /// depth component. (TODO depth, not implemented yet) + virtual std::unique_ptr create_texture_target(std::vector) = 0; + + /// Returns the built-in display target that represents the window. Passes pointed at this target will have + /// their output visible on the screen. + virtual RenderTarget const* get_display_target() = 0; + + /// Executes a render pass. + virtual void render(RenderPass const&) = 0; +}; + +}} diff --git a/libopenage/renderer/resources/CMakeLists.txt b/libopenage/renderer/resources/CMakeLists.txt new file mode 100644 index 0000000000..4c58d2c733 --- /dev/null +++ b/libopenage/renderer/resources/CMakeLists.txt @@ -0,0 +1,6 @@ +add_sources(libopenage + mesh_data.cpp + shader_source.cpp + texture_data.cpp + texture_info.cpp +) diff --git a/libopenage/renderer/resources/mesh_data.cpp b/libopenage/renderer/resources/mesh_data.cpp new file mode 100644 index 0000000000..fdd95a3ca1 --- /dev/null +++ b/libopenage/renderer/resources/mesh_data.cpp @@ -0,0 +1,104 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "mesh_data.h" + +#include + +#include "../../error/error.h" +#include "../../datastructure/constexpr_map.h" + + +namespace openage { +namespace renderer { +namespace resources { + +static constexpr auto vin_size = datastructure::create_const_map( + std::make_pair(vertex_input_t::V2F32, 8), + std::make_pair(vertex_input_t::V3F32, 12), + std::make_pair(vertex_input_t::M3F32, 36) +); + +static constexpr auto vin_count = datastructure::create_const_map( + std::make_pair(vertex_input_t::V2F32, 2), + std::make_pair(vertex_input_t::V3F32, 3), + std::make_pair(vertex_input_t::M3F32, 9) +); + +size_t vertex_input_size(vertex_input_t in) { + return vin_size.get(in); +} + +size_t vertex_input_count(vertex_input_t in) { + return vin_count.get(in); +} + +VertexInputInfo::VertexInputInfo(std::vector inputs, vertex_layout_t layout, vertex_primitive_t primitive) + : inputs(inputs) + , layout(layout) + , primitive(primitive) {} + +VertexInputInfo::VertexInputInfo(std::vector inputs, vertex_layout_t layout, vertex_primitive_t primitive, index_t index_type) + : inputs(inputs) + , layout(layout) + , primitive(primitive) + , index_type(index_type) {} + +size_t VertexInputInfo::vert_size() const { + size_t size = 0; + for (auto in : this->inputs) { + size += vertex_input_size(in); + } + return size; +} + +const std::vector &VertexInputInfo::get_inputs() const { + return this->inputs; +} + +vertex_layout_t VertexInputInfo::get_layout() const { + return this->layout; +} + +vertex_primitive_t VertexInputInfo::get_primitive() const { + return this->primitive; +} + +std::experimental::optional VertexInputInfo::get_index_type() const { + return this->index_type; +} + +/// Vertices of a quadrilateral filling the whole screen. +/// Format: (pos, tex_coords) = (x, y, u, v) +static constexpr std::array quad_data = { { + -1.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f +} }; + +MeshData::MeshData(const util::Path &/*path*/) { + // asdf + throw "unimplemented lol"; +} + +MeshData::MeshData(init_quad_t) { + auto data_size = quad_data.size() * sizeof(decltype(quad_data)::value_type); + this->data = std::vector(data_size); + std::memcpy(data.data(), reinterpret_cast(quad_data.data()), data_size); + + this->info = VertexInputInfo { { vertex_input_t::V2F32, vertex_input_t::V2F32 }, vertex_layout_t::AOS, vertex_primitive_t::TRIANGLE_STRIP }; +} + +std::vector const &MeshData::get_data() const { + return this->data; +} + +std::experimental::optional> const &MeshData::get_ids() const { + return this->ids; +} + +VertexInputInfo MeshData::get_info() const { + return *this->info; +} + +}}} diff --git a/libopenage/renderer/resources/mesh_data.h b/libopenage/renderer/resources/mesh_data.h new file mode 100644 index 0000000000..85151dc41a --- /dev/null +++ b/libopenage/renderer/resources/mesh_data.h @@ -0,0 +1,131 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "../../util/path.h" + + +namespace openage { +namespace renderer { +namespace resources { + +/// The type of a single per-vertex input to the shader program. +enum class vertex_input_t { + V2F32, + V3F32, + M3F32, +}; + +/// The primitive type that the vertices in a mesh combine into. +enum class vertex_primitive_t { + POINTS, + LINES, + LINE_STRIP, + TRIANGLES, + TRIANGLE_STRIP, + TRIANGLE_FAN, +}; + +/// The type of indices used for indexed rendering. +enum class index_t { + U8, + U16, + U32, +}; + +/// Returns the size in bytes of a per-vertex input. +size_t vertex_input_size(vertex_input_t); + +/// Returns the number of elements of the underlying type in a given per-vertex input type. +/// For example, V3F32 has 3 float elements, so the value is 3. +size_t vertex_input_count(vertex_input_t); + +enum class vertex_layout_t { + /// Array of structs. XYZUV, XYZUV, XYZUV + AOS, + /// Struct of arrays. XYZXYZXYZ, UVUVUV + SOA, +}; + +/// Information about vertex input data - which components a vertex contains +/// and how vertices are laid out in memory. +class VertexInputInfo { +public: + /// Initialize an input info for array rendering. + VertexInputInfo(std::vector, vertex_layout_t, vertex_primitive_t); + + /// Initialize an input info for indexed rendering. + VertexInputInfo(std::vector, vertex_layout_t, vertex_primitive_t, index_t); + + /// Returns the list of per-vertex inputs. + const std::vector &get_inputs() const; + + /// Returns the layout of vertices in memory. + vertex_layout_t get_layout() const; + + /// Returns the size of a single vertex. + size_t vert_size() const; + + /// Returns the primitive interpretation mode. + vertex_primitive_t get_primitive() const; + + /// Returns the type of indices used for indexed drawing, + /// which may not be present if array drawing is used. + std::experimental::optional get_index_type() const; + +private: + /// What kind of data the vertices contain and how it is laid out in memory. + std::vector inputs; + + /// How the vertices are laid out in the data buffer. + vertex_layout_t layout; + + /// The primitive type. + vertex_primitive_t primitive; + + /// The type of indices if they exist. + std::experimental::optional index_type; +}; + +/// An empty struct used to initialize mesh data to a quad. +struct init_quad_t {}; + +class MeshData { +public: + /// Tries to load the mesh data from the specified file. + explicit MeshData(const util::Path&); + + /// Initializes the mesh data to a quad. + // TODO the empty struct is ugly + explicit MeshData(init_quad_t); + + /// Returns the raw vertex data. + std::vector const &get_data() const; + + /// Returns the indices used for indexed drawing if they exist. + std::experimental::optional> const &get_ids() const; + + /// Returns information describing the vertices in this mesh. + VertexInputInfo get_info() const; + +private: + /// The raw vertex data. The size is an integer multiple of the size of a single vertex. + std::vector data; + + /// The indices of vertices to be drawn if the mesh is indexed. + /// For array drawing, empty optional. + std::experimental::optional> ids; + + /// Information about how to interpret the data to make vertices. + /// This is optional because initialization is deferred until the constructor + /// obtains the mesh data e.g. from disk, but it should always be present after + /// construction. + std::experimental::optional info; +}; + +}}} diff --git a/libopenage/renderer/resources/shader_source.cpp b/libopenage/renderer/resources/shader_source.cpp new file mode 100644 index 0000000000..a80d7ce540 --- /dev/null +++ b/libopenage/renderer/resources/shader_source.cpp @@ -0,0 +1,44 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "shader_source.h" + +#include "../../util/file.h" + + +namespace openage { +namespace renderer { +namespace resources { +const char *shader_source_type_to_str(shader_source_t type) { + switch (type) { + case shader_source_t::glsl_vertex: + return "GLSL vertex shader"; + case shader_source_t::glsl_geometry: + return "GLSL geometry shader"; + case shader_source_t::glsl_tesselation_control: + return "GLSL tesselation control shader"; + case shader_source_t::glsl_tesselation_evaluation: + return "GLSL tesselation evaluation shader"; + case shader_source_t::glsl_fragment: + return "GLSL fragment shader"; + default: + return "unknown GLSL shader type"; + } +} + +ShaderSource::ShaderSource(shader_source_t type, std::string &&code) + : type(type) + , code(code) {} + +ShaderSource::ShaderSource(shader_source_t type, const util::Path &path) + : type(type) + , code(path.open().read()) {} + +shader_source_t ShaderSource::get_type() const { + return this->type; +} + +const char *ShaderSource::get_source() const { + return this->code.data(); +} + +}}} // openage::renderer::resources diff --git a/libopenage/renderer/resources/shader_source.h b/libopenage/renderer/resources/shader_source.h new file mode 100644 index 0000000000..52d39cbf38 --- /dev/null +++ b/libopenage/renderer/resources/shader_source.h @@ -0,0 +1,51 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "../../util/path.h" + + +namespace openage { +namespace renderer { +namespace resources { + +/// Available shader types present in modern graphics pipelines. +/// Contains information about the source language and the shader +/// stage that it describes. +enum class shader_source_t { + glsl_vertex, + glsl_geometry, + glsl_tesselation_control, + glsl_tesselation_evaluation, + glsl_fragment, +}; + +/// Returns a readable description of the given shader source type. +const char *shader_source_type_to_str(shader_source_t); + +/// Stores source code for a part of a shader program. +class ShaderSource { +public: + /// Obtain shader source code from a file. + ShaderSource(shader_source_t, const util::Path &path); + + /// Obtain shader source code from a string. + ShaderSource(shader_source_t, std::string &&code); + + /// Returns a view of the shader source code + const char *get_source() const; + + /// Returns the type of this shader source. + shader_source_t get_type() const; + +private: + /// The type of the source. + shader_source_t type; + + /// The shader source code. + std::string code; +}; + +}}} // openage::renderer::resources diff --git a/libopenage/renderer/resources/texture_data.cpp b/libopenage/renderer/resources/texture_data.cpp new file mode 100644 index 0000000000..8121e927e9 --- /dev/null +++ b/libopenage/renderer/resources/texture_data.cpp @@ -0,0 +1,136 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "texture_data.h" + +#include +#include + +#include "../../log/log.h" +#include "../../error/error.h" +#include "../../util/csv.h" + + +namespace openage { +namespace renderer { +namespace resources { + +TextureData::TextureData(const util::Path &path, bool use_metafile) { + std::string native_path = path.resolve_native_path(); + SDL_Surface *surface = IMG_Load(native_path.c_str()); + + if (!surface) { + throw Error(MSG(err) << + "Could not load texture from " << + native_path << ": " << IMG_GetError()); + } else { + log::log(MSG(dbg) << "Texture has been loaded from " << native_path); + } + + auto fmt = *surface->format; + + if (fmt.Rmask != 0xf000 || fmt.Gmask != 0x0f00 || fmt.Bmask != 0x00f0) { + throw Error(MSG(err) << "Texture " << native_path << " is not in RGB or RGBA format."); + } + + pixel_format format; + if (fmt.Amask == 0) { + if (fmt.BytesPerPixel != 3) { + throw Error(MSG(err) << "Texture " << native_path << " is in an unsupported RGB format."); + } + + format = pixel_format::rgb8; + } + else { + format = pixel_format::rgba8; + } + + auto w = surface->w; + auto h = surface->h; + + size_t data_size = surface->format->BytesPerPixel * surface->w * surface->h; + + // copy pixel data from surface + this->data = std::vector(data_size); + memcpy(this->data.data(), surface->pixels, data_size); + SDL_FreeSurface(surface); + + std::vector subtextures; + if (use_metafile) { + util::Path meta = path.get_parent() / path.get_stem() / ".docx"; + + log::log(MSG(info) << "Loading meta file: " << meta); + + // get subtexture information by meta file exported by script + subtextures = util::read_csv_file(meta); + } + else { + // we don't have a texture description file. + // use the whole image as one texture then. + gamedata::subtexture s{0, 0, w, h, w/2, h/2}; + + subtextures.push_back(s); + } + + this->info = TextureInfo(w, h, format, 1, std::move(subtextures)); +} + +TextureData::TextureData(TextureInfo &&info, std::vector &&data) + : info(std::move(info)) + , data(std::move(data)) {} + +const TextureInfo& TextureData::get_info() const { + return this->info; +} + +const uint8_t *TextureData::get_data() const { + return this->data.data(); +}; + +void TextureData::store(const util::Path& file) const { + log::log(MSG(info) << "Saving texture data to " << file); + + // TODO support row_alignment in copying below + throw "unimplemented"; + if (this->info.get_format() != pixel_format::rgba8) { + throw "unimplemented"; + } + + // color masks. + int32_t rmask, gmask, bmask, amask; + rmask = 0x000000FF; + gmask = 0x0000FF00; + bmask = 0x00FF0000; + amask = 0xFF000000; + + auto size = this->info.get_size(); + + // create output surface which will be stored later. + SDL_Surface *screen = SDL_CreateRGBSurface( + SDL_SWSURFACE, + size.first, size.second, + 32, rmask, gmask, bmask, amask + ); + + uint32_t *surf_data = reinterpret_cast(screen->pixels); + const uint32_t *data = reinterpret_cast(this->data.data()); + + // 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 pixel = data[irow * screen->w + col]; + + // TODO: store the alpha channels in the file, + // is buggy at the moment.. + surf_data[row * screen->w + col] = pixel | 0xFF000000; + } + } + + // call sdl_image for saving the screenshot to png + std::string path = file.resolve_native_path(); + IMG_SavePNG(screen, path.c_str()); + SDL_FreeSurface(screen); +} + +}}} diff --git a/libopenage/renderer/resources/texture_data.h b/libopenage/renderer/resources/texture_data.h new file mode 100644 index 0000000000..07f8177be9 --- /dev/null +++ b/libopenage/renderer/resources/texture_data.h @@ -0,0 +1,67 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include "texture_info.h" +#include "../../util/path.h" + + +namespace openage { +namespace renderer { +namespace resources { + +/// Stores CPU-accessible texture data in a byte buffer. Can be loaded and stored from/onto disk +/// and passed to or read from graphics hardware. In a sense it provides a mediator between +/// the filesystem, the CPU and graphics hardware. +class TextureData { +public: + /// Create a texture from an image file. + /// @param[in] use_metafile determines whether the loading should read an accompanying + /// metadata file to split the texture into subtextures + /// + /// Uses SDL Image internally. For supported image file types, + /// see the SDL_Image initialization in the engine. + TextureData(const util::Path &path, bool use_metafile = false); + + /// Construct by moving the information and raw texture data from somewhere else. + TextureData(TextureInfo &&info, std::vector &&data); + + /// Returns the information describing this texture data. + const TextureInfo& get_info() const; + + /// Returns a pointer to the raw texture data. + const uint8_t *get_data() const; + + /// Reads the pixel at the given position and casts it to the given type. + /// The texture is _not_ read as if it consisted of pixels of the given type, + /// but rather according to its original pixel format. + template + T read_pixel(size_t x, size_t y) const { + const uint8_t *data = this->data.data(); + auto dims = this->info.get_size(); + size_t off = (dims.second - y) * this->info.get_row_size(); + off += x * pixel_size(this->info.get_format()); + + if ((off + sizeof(T)) > this->info.get_data_size()) { + throw Error(MSG(err) << "Pixel position (" << x << ", " << y << ") is outside texture."); + } + + return *reinterpret_cast(data + off); + } + + /// Stores this texture data in the given file in the PNG format. + void store(const util::Path& file) const; + +private: + /// Information about this texture data. + TextureInfo info; + + /// The raw texture data. + std::vector data; +}; + +}}} // namespace openage::renderer::resources diff --git a/libopenage/renderer/resources/texture_info.cpp b/libopenage/renderer/resources/texture_info.cpp new file mode 100644 index 0000000000..dd791c973a --- /dev/null +++ b/libopenage/renderer/resources/texture_info.cpp @@ -0,0 +1,83 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "texture_info.h" + +#include "../../datastructure/constexpr_map.h" + + +namespace openage { +namespace renderer { +namespace resources { + +static constexpr auto pix_size = datastructure::create_const_map( + std::make_pair(pixel_format::r16ui, 2), + std::make_pair(pixel_format::r32ui, 4), + std::make_pair(pixel_format::rgb8, 3), + std::make_pair(pixel_format::depth24, 3), + std::make_pair(pixel_format::rgba8, 4), + std::make_pair(pixel_format::rgba8ui, 4) +); + +size_t pixel_size(pixel_format fmt) { + return pix_size.get(fmt); +} + +TextureInfo::TextureInfo(size_t width, size_t height, pixel_format fmt, size_t row_alignment, std::vector &&subs) + : w(width) + , h(height) + , format(fmt) + , row_alignment(row_alignment) + , subtextures(std::move(subs)) {} + +std::pair TextureInfo::get_size() const { + return std::make_pair(this->w, this->h); +} + +pixel_format TextureInfo::get_format() const { + return this->format; +} + +size_t TextureInfo::get_row_alignment() const { + return this->row_alignment; +} + +size_t TextureInfo::get_row_size() const { + size_t px_size = pixel_size(this->format); + size_t row_size = this->w * px_size; + row_size += row_size % this->row_alignment; // there might be padding at row ends to match the alignment + return row_size; +} + +size_t TextureInfo::get_data_size() const { + return this->get_row_size() * this->h; +} + +size_t TextureInfo::get_subtexture_count() const { + return this->subtextures.size(); +} + +const gamedata::subtexture& TextureInfo::get_subtexture(size_t subid) const { + if (subid < this->subtextures.size()) { + return this->subtextures[subid]; + } + else { + throw Error(MSG(err) << "Unknown subtexture requested: " << subid); + } +} + +std::pair TextureInfo::get_subtexture_size(size_t subid) const { + auto subtex = this->get_subtexture(subid); + return std::make_pair(subtex.w, subtex.h); +} + +std::tuple TextureInfo::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 + ); +} + +}}} diff --git a/libopenage/renderer/resources/texture_info.h b/libopenage/renderer/resources/texture_info.h new file mode 100644 index 0000000000..31f3a9f5d2 --- /dev/null +++ b/libopenage/renderer/resources/texture_info.h @@ -0,0 +1,97 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include "../../gamedata/texture.gen.h" + + +namespace openage { +namespace renderer { +namespace resources { + +/// How the pixels are represented in a texture. +enum class pixel_format { + /// 16 bits per pixel, unsigned integer, single channel + r16ui, + /// 32 bits per pixel, unsigned integer, single channel + r32ui, + /// 24 bits per pixel, float + rgb8, + /// 24 bits per pixel, depth texture + depth24, + /// 32 bits per pixel, float, alpha channel + rgba8, + /// 32 bits per pixel, unsigned integer, alpha channel + rgba8ui, +}; + +/// Returns the size in bytes of a single pixel of the specified format. +size_t pixel_size(pixel_format); + +/// Information for texture processing. +/// 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 TextureInfo { +public: + /// Constructs a TextureInfo with the given information. + TextureInfo(size_t width, size_t height, pixel_format, size_t row_alignment = 1, std::vector&& = std::vector()); + + TextureInfo() = default; + ~TextureInfo() = default; + + /// Returns the dimensions of the whole texture bitmap + /// @returns tuple(width, height) + std::pair get_size() const; + + /// @returns the format of pixels in this texture + pixel_format get_format() const; + + /// Returns the alignment of texture rows to byte boundaries. + size_t get_row_alignment() const; + + /// Returns the size in bytes of a single row, + /// including possible padding at its end. + size_t get_row_size() const; + + /// Returns the size in bytes of the raw pixel data. It is equal to + /// get_row_size() * get_size().second. + size_t get_data_size() const; + + /// Returns the number of available subtextures + size_t get_subtexture_count() const; + + /// Returns the coordinates of the subtexture with the given ID + const gamedata::subtexture& get_subtexture(size_t subid) const; + + /// Returns the size of the subtexture with the given ID + std::pair get_subtexture_size(size_t subid) const; + + /// Returns 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. + std::tuple get_subtexture_coordinates(size_t subid) const; + +private: + /// Width and height of this texture. + int32_t w, h; + + /// The pixel format of this texture. + pixel_format format; + + /// The alignment of texture rows to byte boundaries. Can be 1, 2, 4 or 8. + /// There is padding at the end of each row to match the alignment if the + /// row size is not a multiple of the alignment. + size_t row_alignment; + + /// Some textures are merged together into texture atlases, large images which contain + /// more than one individual texture. These are their positions in the atlas. + std::vector subtextures; +}; + +}}} diff --git a/libopenage/renderer/shader_program.h b/libopenage/renderer/shader_program.h new file mode 100644 index 0000000000..b25c240912 --- /dev/null +++ b/libopenage/renderer/shader_program.h @@ -0,0 +1,105 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include + +#include "../error/error.h" + + +namespace openage { +namespace renderer { + +class UniformInput; +class Texture; + +class ShaderProgram { +public: + // Template dispatches for uniform variable setting. + void update_uniform_input(UniformInput*) {} + + void update_uniform_input(UniformInput *input, const char *unif, int32_t val) { + this->set_i32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, uint32_t val) { + this->set_u32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, float val) { + this->set_f32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, double val) { + this->set_f64(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, Eigen::Vector2f const &val) { + this->set_v2f32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, Eigen::Vector3f const &val) { + this->set_v3f32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, Eigen::Vector4f const &val) { + this->set_v4f32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, Texture const *val) { + this->set_tex(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, Texture *val) { + this->set_tex(input, unif, val); + } + void update_uniform_input(UniformInput *input, const char *unif, Eigen::Matrix4f const &val) { + this->set_m4f32(input, unif, val); + } + template + void update_uniform_input(UniformInput*, const char *unif, T) { + throw Error(MSG(err) << "Tried to set uniform " << unif << " using unknown type."); + } + + /// Returns whether the shader program contains a uniform variable with the given name. + virtual bool has_uniform(const char *unif) = 0; + + /// Creates a new uniform input (a binding of uniform names to values) for this shader + /// and optionally sets some uniform values. To do that, just pass two arguments - + /// - a string literal and the value for that uniform for any uniform you want to set. + /// For example new_uniform_input("color", { 0.5, 0.5, 0.5, 1.0 }, "num", 5) will set + /// "color" to { 0.5, 0.5, 0.5, 0.5 } and "num" to 5. Types are important here and a type + /// mismatch between the uniform variable and the input might result in an error. + template + std::unique_ptr new_uniform_input(Ts... vals) { + auto input = this->new_unif_in(); + this->update_uniform_input(input.get(), vals...); + return input; + } + + /// Updates the given uniform input with new uniform values similarly to new_uniform_input. + /// For example, update_uniform_input(in, "awesome", true) will set the "awesome" uniform + /// in addition to whatever values were in the uniform input before. + template + void update_uniform_input(UniformInput *input, const char *unif, T val, Ts... vals) { + this->update_uniform_input(input, unif, val); + this->update_uniform_input(input, vals...); + } + +protected: + // Virtual dispatches to the actual shader program implementation. + virtual std::unique_ptr new_unif_in() = 0; + virtual void set_i32(UniformInput*, const char*, int32_t) = 0; + virtual void set_u32(UniformInput*, const char*, uint32_t) = 0; + virtual void set_f32(UniformInput*, const char*, float) = 0; + virtual void set_f64(UniformInput*, const char*, double) = 0; + virtual void set_v2f32(UniformInput*, const char*, Eigen::Vector2f const&) = 0; + virtual void set_v3f32(UniformInput*, const char*, Eigen::Vector3f const&) = 0; + virtual void set_v4f32(UniformInput*, const char*, Eigen::Vector4f const&) = 0; + virtual void set_m4f32(UniformInput*, const char*, Eigen::Matrix4f const&) = 0; + virtual void set_tex(UniformInput*, const char*, Texture const*) = 0; +}; + +}} 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/tests.cpp b/libopenage/renderer/tests.cpp new file mode 100644 index 0000000000..66d7a6c568 --- /dev/null +++ b/libopenage/renderer/tests.cpp @@ -0,0 +1,301 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include +#include +#include +#include +#include +#include + +#include "../log/log.h" +#include "../error/error.h" +#include "resources/shader_source.h" +#include "opengl/renderer.h" +#include "window.h" + + +namespace openage { +namespace renderer { +namespace tests { + +/** + * render demo function collection. + */ +struct render_demo { + std::function setup; + std::function frame; + std::function resize; + std::function click; +}; + +void render_test(Window &window, const render_demo *actions) { + SDL_Event event; + + actions->setup(&window); + + bool running = true; + while (running) { + while (SDL_PollEvent(&event)) { + if (event.type == SDL_WINDOWEVENT && event.window.event == 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 + ); + actions->resize(new_size); + } + else if (event.type == SDL_QUIT) { + running = false; + } + else if (event.type == SDL_KEYUP) { + SDL_Keycode sym = reinterpret_cast(&event)->keysym.sym; + if (sym == SDLK_ESCAPE) { + running = false; + } + } + else if (event.type == SDL_MOUSEBUTTONDOWN) { + actions->click(event.button.x, event.button.y); + } + } + + actions->frame(); + } +} + +void renderer_demo_0() { + Window window { "openage renderer testing" }; + window.make_context_current(); + auto renderer = std::make_unique(window.get_context()); + + auto vshader_src = resources::ShaderSource( + resources::shader_source_t::glsl_vertex, + R"s( +#version 330 + +layout(location=0) in vec2 position; +uniform mat4 mvp; + +void main() { +/* + gl_Position.x = 2.0 * float(gl_VertexID & 1) - 1.0; + gl_Position.y = 2.0 * float((gl_VertexID & 2) >> 1) - 1.0; + gl_Position.z = 0.0; + gl_Position.w = 1.0; + + gl_Position = mvp * gl_Position; +*/ +gl_Position = mvp * vec4(position, 0.0, 1.0); +} +)s"); + + auto fshader_src = resources::ShaderSource( + resources::shader_source_t::glsl_fragment, + R"s( +#version 330 + +uniform vec4 color; +uniform uint u_id; + +layout(location=0) out vec4 col; +layout(location=1) out uint id; + +void main() { + if (color.a == 0.0) { + discard; + } + col = color; + id = u_id + 1u; +} +)s"); + + auto vshader_display_src = resources::ShaderSource( + resources::shader_source_t::glsl_vertex, + R"s( +#version 330 + + +layout(location=0) in vec2 position; +layout(location=1) in vec2 uv; +out vec2 v_uv; + +void main() { +/* + gl_Position.x = 2.0 * float(gl_VertexID & 1) - 1.0; + gl_Position.y = 2.0 * float((gl_VertexID & 2) >> 1) - 1.0; + gl_Position.z = 0.0; + gl_Position.w = 1.0; + + v_uv = gl_Position.xy * 0.5 + 0.5; +*/ +gl_Position = vec4(position, 0.0, 1.0); +v_uv = uv; +} +)s"); + + + auto fshader_display_src = resources::ShaderSource( + resources::shader_source_t::glsl_fragment, + R"s( +#version 330 + +uniform sampler2D color_texture; + +in vec2 v_uv; +out vec4 col; + +void main() { + col = texture(color_texture, v_uv); +} +)s"); + + auto shader = renderer->add_shader( { vshader_src, fshader_src } ); + auto shader_display = renderer->add_shader( { vshader_display_src, fshader_display_src } ); + + auto transform1 = Eigen::Affine3f::Identity(); + transform1.prescale(Eigen::Vector3f(0.4f, 0.2f, 1.0f)); + transform1.prerotate(Eigen::AngleAxisf(30.0f * 3.14159f / 180.0f, Eigen::Vector3f::UnitX())); + transform1.pretranslate(Eigen::Vector3f(-0.4f, -0.6f, 0.0f)); + + auto unif_in1 = shader->new_uniform_input( + "mvp", transform1.matrix(), + "color", Eigen::Vector4f(1.0f, 0.0f, 0.0f, 1.0f), + "u_id", 1u + ); + + auto transform2 = Eigen::Affine3f::Identity(); + transform2.prescale(Eigen::Vector3f(0.3f, 0.1f, 1.0f)); + transform2.prerotate(Eigen::AngleAxisf(50.0f * 3.14159f / 180.0f, Eigen::Vector3f::UnitZ())); + + auto transform3 = transform2; + + transform2.pretranslate(Eigen::Vector3f(0.3f, 0.1f, 0.3f)); + + auto unif_in2 = shader->new_uniform_input( + "mvp", transform2.matrix(), + "color", Eigen::Vector4f(0.0f, 1.0f, 0.0f, 1.0f), + "u_id", 2u + ); + + transform3.prerotate(Eigen::AngleAxisf(90.0f * 3.14159f / 180.0f, Eigen::Vector3f::UnitZ())); + transform3.pretranslate(Eigen::Vector3f(0.3f, 0.1f, 0.5f)); + + auto unif_in3 = shader->new_uniform_input( + "mvp", transform3.matrix(), + "color", Eigen::Vector4f(0.0f, 0.0f, 1.0f, 1.0f), + "u_id", 3u + ); + + auto quad = renderer->add_mesh_geometry( resources::MeshData { resources::init_quad_t {} } ); + Renderable obj1 { + unif_in1.get(), + quad.get(), + true, + true, + }; + + Renderable obj2{ + unif_in2.get(), + quad.get(), + true, + true, + }; + + Renderable obj3 { + unif_in3.get(), + quad.get(), + true, + true, + }; + + auto size = window.get_size(); + auto color_texture = renderer->add_texture(resources::TextureInfo(size.x, size.y, resources::pixel_format::rgba8)); + auto id_texture = renderer->add_texture(resources::TextureInfo(size.x, size.y, resources::pixel_format::r32ui)); + auto depth_texture = renderer->add_texture(resources::TextureInfo(size.x, size.y, resources::pixel_format::depth24)); + auto fbo = renderer->create_texture_target( { color_texture.get(), id_texture.get(), depth_texture.get() } ); + + auto color_texture_uniform = shader_display->new_uniform_input("color_texture", color_texture.get()); + Renderable display_obj { + color_texture_uniform.get(), + quad.get(), + false, + false, + }; + + RenderPass pass { + { obj1, obj2, obj3 }, + fbo.get() + }; + + RenderPass display_pass { + { display_obj }, + renderer->get_display_target(), + }; + + resources::TextureData id_texture_data = id_texture->into_data(); + bool texture_data_valid = false; + + render_demo test0{ + // init + [&](Window */*window*/) { + glDepthFunc(GL_LEQUAL); + glDepthRange(0.0, 1.0); + // what is this + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + }, + // frame + [&]() { + texture_data_valid = false; + renderer->render(pass); + renderer->render(display_pass); + window.swap(); + window.get_context()->check_error(); + }, + // resize + [&](const coord::window &new_size) { + // handle in renderer.. + glViewport(0, 0, new_size.x, new_size.y); + + // resize fbo + color_texture = renderer->add_texture(resources::TextureInfo(new_size.x, new_size.y, resources::pixel_format::rgba8)); + id_texture = renderer->add_texture(resources::TextureInfo(new_size.x, new_size.y, resources::pixel_format::r32ui)); + depth_texture = renderer->add_texture(resources::TextureInfo(new_size.x, new_size.y, resources::pixel_format::depth24)); + fbo = renderer->create_texture_target( { color_texture.get(), id_texture.get(), depth_texture.get() } ); + + shader_display->update_uniform_input(color_texture_uniform.get(), "color_texture", color_texture.get()); + pass.target = fbo.get(); + }, + //click + [&](const uint16_t x, const uint16_t y) { + log::log(INFO << "Clicked at location (" << x << ", " << y << ")"); + if (!texture_data_valid) { + id_texture_data = id_texture->into_data(); + texture_data_valid = true; + } + auto id = id_texture_data.read_pixel(x, y); + log::log(INFO << "Id-texture-value at location: " << id); + if (id == 0) { + //no renderable at given location + log::log(INFO << "Clicked at non existent object"); + return; + } + id--; //real id is id-1 + log::log(INFO << "Object number " << id << " clicked."); + } + }; + + render_test(window, &test0); +} + +void renderer_demo(int demo_id) { + switch (demo_id) { + case 0: + renderer_demo_0(); + 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..b0de0dfebb --- /dev/null +++ b/libopenage/renderer/tests.h @@ -0,0 +1,13 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + + +namespace openage { +namespace renderer { +namespace tests { + +// pxd: void renderer_demo(int demo_id) except + +void renderer_demo(int demo_id); + +}}} // openage::renderer::tests diff --git a/libopenage/renderer/texture.cpp b/libopenage/renderer/texture.cpp new file mode 100644 index 0000000000..8368a96b7d --- /dev/null +++ b/libopenage/renderer/texture.cpp @@ -0,0 +1,21 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include + +#include "texture.h" +#include "../error/error.h" + + +namespace openage { +namespace renderer { + +Texture::Texture(resources::TextureInfo info) + : info(info) {} + +Texture::~Texture() {} + +const resources::TextureInfo& Texture::get_info() const { + return this->info; +} + +}} // namespace openage::renderer diff --git a/libopenage/renderer/texture.h b/libopenage/renderer/texture.h new file mode 100644 index 0000000000..8de75030bc --- /dev/null +++ b/libopenage/renderer/texture.h @@ -0,0 +1,32 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "resources/texture_data.h" + + +namespace openage { +namespace renderer { + +/// An abstract base for a handle to a texture buffer allocated in graphics hardware. +/// Can be obtained by passing texture data to the renderer. +class Texture { +public: + virtual ~Texture(); + + /// Returns the texture information. + const resources::TextureInfo& get_info() const; + + /// Copies this texture's data from graphics hardware into a CPU-accessible + /// TextureData buffer. + virtual resources::TextureData into_data() = 0; + +protected: + /// Constructs the base with the given information. + Texture(resources::TextureInfo); + + /// Information about the size, format, etc. of this texture. + resources::TextureInfo info; +}; + +}} diff --git a/libopenage/renderer/vulkan/context.h b/libopenage/renderer/vulkan/context.h new file mode 100644 index 0000000000..9fce119c20 --- /dev/null +++ b/libopenage/renderer/vulkan/context.h @@ -0,0 +1,18 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + + +namespace openage { +namespace renderer { +namespace vulkan { + +/** + * A Vulkan graphics device, usually a GPU. + */ +class VkDevice { +public: + VkDevice() {} +}; + +}}} // namespace openage::renderer::vulkan diff --git a/libopenage/renderer/window.cpp b/libopenage/renderer/window.cpp new file mode 100644 index 0000000000..76652c09bc --- /dev/null +++ b/libopenage/renderer/window.cpp @@ -0,0 +1,141 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "window.h" + +#include +#include +#include + +#include "opengl/context.h" +#include "../log/log.h" +#include "../error/error.h" + + +namespace openage { +namespace renderer { + +/// A static SDL singleton manager. Used to lazily initialize SDL. +class SDL { +public: + static void make_available() { + if (!inited) { + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + throw Error{MSG(err) << "SDL video initialization failed: " << SDL_GetError()}; + } + + // Initialize 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 initialize PNG support: " << IMG_GetError()}; + } + } + + sdl = SDL(); + + SDL_version version; + SDL_GetVersion(&version); + + log::log(MSG(info) << "Initialized SDL " << uint32_t(version.major) << "." << uint32_t(version.minor) << "." << uint32_t(version.patch)); + } + + ~SDL() { + if (inited) { + IMG_Quit(); + SDL_Quit(); + + log::log(MSG(info) << "Exited SDL"); + } + } + +private: + static SDL sdl; + static bool inited; +}; + +bool SDL::inited = false; + +Window::Window(const char *title) + : size{800, 600} { + // TODO: ^ detect screen resolution and determine window size from it. + + SDL::make_available(); + + // Even with Vulkan rendering, we still need GL for QtQuick. + // TODO Qt might support Vulkan natively in the future + // We need HIGHDPI for eventual support of GUI scaling. + // TODO HIGHDPI fails (requires newer SDL2?) + int32_t window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED; //| SDL_WINDOW_HIGHDPI; + 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()}; + } + + log::log(MSG(info) << "Created SDL window"); + + this->context = opengl::GlContext(this->window); + + // TODO catch window events such as resizes +} + +Window::Window(Window &&other) + : window(other.window) + , context(std::move(other.context)) { + other.window = nullptr; +} + +Window &Window::operator=(Window &&other) { + if (this->window != nullptr) { + SDL_DestroyWindow(this->window); + log::log(MSG(info) << "Destroyed SDL window"); + } + + this->window = other.window; + this->context = std::move(other.context); + other.window = nullptr; + return *this; +} + +Window::~Window() { + if (this->window != nullptr) { + SDL_DestroyWindow(this->window); + log::log(MSG(info) << "Destroyed SDL window"); + } +} + +coord::window Window::get_size() { + return this->size; +} + +void Window::set_size(const coord::window &new_size) { + if (this->size.x != new_size.x || this->size.y != new_size.y) { + SDL_SetWindowSize(this->window, this->size.x, this->size.y); + this->size = new_size; + } +} + +void Window::swap() { + SDL_GL_SwapWindow(this->window); +} + +void Window::make_context_current() { + SDL_GL_MakeCurrent(this->window, this->context->get_raw_context()); +} + +SDL_Window *Window::get_raw_window() const { + return this->window; +} + +opengl::GlContext *Window::get_context() { + return &this->context.value(); +} + +}} //openage::renderer diff --git a/libopenage/renderer/window.h b/libopenage/renderer/window.h new file mode 100644 index 0000000000..f922a00413 --- /dev/null +++ b/libopenage/renderer/window.h @@ -0,0 +1,68 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include + +#include "opengl/context.h" +#include "../coord/window.h" + + +namespace openage { +namespace renderer { + +class Window { +public: + /// Create a shiny window with the given title. + Window(const char *title); + ~Window(); + + /// A window cannot be copied. + Window(const Window &other) = delete; + Window &operator =(const Window &other) = delete; + + /// But it can be moved. + Window(Window &&other); + Window &operator =(Window &&other); + + /// Returns the dimensions of this window. + coord::window get_size(); + + /// Force the window to the given size. It's generally not a good idea to use this, + /// as it makes the window jump around wierdly. + void set_size(const coord::window &new_size); + + /// Swap the front and back framebuffers. This has to be after drawing every frame to actually + /// display it. + void swap(); + + /// Make this window's context the current rendering context of the current thread. + /// Only use this and most other GL functions on a dedicated window thread. + void make_context_current(); + + // 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. + /// Returns the raw SDL window handle. + SDL_Window *get_raw_window() const; + + /// Return a pointer to this window's GL context. + opengl::GlContext *get_context(); + +private: + /// The current size of the framebuffer. + coord::window size; + /// The SDL struct representing this window. + SDL_Window *window; + + /// This window's OpenGL context. It's optional because it can't be constructed immediately, + /// but after the constructor runs it's guaranteed to be available. + std::experimental::optional context; +}; + +}} // namespace openage::renderer diff --git a/libopenage/util/constexpr.h b/libopenage/util/constexpr.h index bca775119d..930e68e9f4 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. @@ -150,7 +161,6 @@ 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 diff --git a/openage/CMakeLists.txt b/openage/CMakeLists.txt index 12d46f6f5c..9464b77826 100644 --- a/openage/CMakeLists.txt +++ b/openage/CMakeLists.txt @@ -21,4 +21,5 @@ add_subdirectory(cvar) add_subdirectory(game) add_subdirectory(log) add_subdirectory(util) +add_subdirectory(renderer) add_subdirectory(testing) 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 2f230b38ff..3241cbce8d 100644 --- a/openage/testing/testlist.py +++ b/openage/testing/testlist.py @@ -46,6 +46,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 benchmark_py():