From e54cf15c3348aba9556d8387c255b11db1556382 Mon Sep 17 00:00:00 2001 From: Wojciech Nawrocki Date: Fri, 15 Jun 2018 00:06:59 +0200 Subject: [PATCH 1/7] renderer: level 1 implementation Co-authored-by: Erik Griese Co-authored-by: surajhanchinal Co-authored-by: Jonas Jelten --- .gitignore | 4 + .mailmap | 1 + CMakeLists.txt | 8 + README.md | 2 +- buildsystem/modules/FindInotify.cmake | 4 +- configure | 3 +- libopenage/CMakeLists.txt | 35 +- libopenage/config.h.in | 2 + libopenage/renderer/CMakeLists.txt | 19 +- libopenage/renderer/README.md | 149 ++++++++ libopenage/renderer/geometry.cpp | 20 ++ libopenage/renderer/geometry.h | 49 +++ libopenage/renderer/opengl/CMakeLists.txt | 15 + libopenage/renderer/opengl/buffer.cpp | 53 +++ libopenage/renderer/opengl/buffer.h | 43 +++ libopenage/renderer/opengl/context.cpp | 221 ++++++++++++ libopenage/renderer/opengl/context.h | 61 ++++ libopenage/renderer/opengl/framebuffer.cpp | 50 +++ libopenage/renderer/opengl/framebuffer.h | 32 ++ libopenage/renderer/opengl/geometry.cpp | 72 ++++ libopenage/renderer/opengl/geometry.h | 49 +++ libopenage/renderer/opengl/lookup.h | 103 ++++++ libopenage/renderer/opengl/render_target.cpp | 35 ++ libopenage/renderer/opengl/render_target.h | 50 +++ libopenage/renderer/opengl/renderer.cpp | 103 ++++++ libopenage/renderer/opengl/renderer.h | 46 +++ libopenage/renderer/opengl/shader.cpp | 58 +++ libopenage/renderer/opengl/shader.h | 27 ++ libopenage/renderer/opengl/shader_program.cpp | 338 ++++++++++++++++++ libopenage/renderer/opengl/shader_program.h | 90 +++++ libopenage/renderer/opengl/simple_object.cpp | 41 +++ libopenage/renderer/opengl/simple_object.h | 47 +++ libopenage/renderer/opengl/texture.cpp | 112 ++++++ libopenage/renderer/opengl/texture.h | 28 ++ libopenage/renderer/opengl/texture_array.cpp | 100 ++++++ libopenage/renderer/opengl/texture_array.h | 37 ++ libopenage/renderer/opengl/uniform_input.h | 32 ++ libopenage/renderer/opengl/vertex_array.cpp | 166 +++++++++ libopenage/renderer/opengl/vertex_array.h | 50 +++ libopenage/renderer/opengl/window.cpp | 105 ++++++ libopenage/renderer/opengl/window.h | 44 +++ libopenage/renderer/renderer.h | 112 ++++++ libopenage/renderer/resources/CMakeLists.txt | 6 + libopenage/renderer/resources/mesh_data.cpp | 131 +++++++ libopenage/renderer/resources/mesh_data.h | 153 ++++++++ .../renderer/resources/shader_source.cpp | 34 ++ libopenage/renderer/resources/shader_source.h | 59 +++ .../renderer/resources/texture_data.cpp | 194 ++++++++++ libopenage/renderer/resources/texture_data.h | 71 ++++ .../renderer/resources/texture_info.cpp | 101 ++++++ libopenage/renderer/resources/texture_info.h | 107 ++++++ libopenage/renderer/sdl_global.cpp | 59 +++ libopenage/renderer/sdl_global.h | 11 + libopenage/renderer/shader_program.h | 118 ++++++ libopenage/renderer/tests.cpp | 267 ++++++++++++++ libopenage/renderer/tests.h | 16 + libopenage/renderer/texture.cpp | 21 ++ libopenage/renderer/texture.h | 36 ++ libopenage/renderer/texture_array.cpp | 18 + libopenage/renderer/texture_array.h | 34 ++ libopenage/renderer/vulkan/CMakeLists.txt | 6 + libopenage/renderer/vulkan/README.md | 3 + .../renderer/vulkan/graphics_device.cpp | 168 +++++++++ libopenage/renderer/vulkan/graphics_device.h | 69 ++++ libopenage/renderer/vulkan/loader.cpp | 55 +++ libopenage/renderer/vulkan/loader.h | 44 +++ libopenage/renderer/vulkan/render_target.h | 162 +++++++++ libopenage/renderer/vulkan/renderer.cpp | 312 ++++++++++++++++ libopenage/renderer/vulkan/renderer.h | 30 ++ libopenage/renderer/vulkan/shader_program.h | 61 ++++ libopenage/renderer/vulkan/util.h | 56 +++ libopenage/renderer/vulkan/windowvk.cpp | 170 +++++++++ libopenage/renderer/vulkan/windowvk.h | 46 +++ libopenage/renderer/window.cpp | 36 ++ libopenage/renderer/window.h | 62 ++++ openage/CMakeLists.txt | 1 + openage/cvar/config_file.py | 1 + openage/renderer/CMakeLists.txt | 8 + openage/renderer/__init__.py | 5 + openage/renderer/renderer_cpp.pyx | 1 + openage/renderer/tests.pyx | 50 +++ openage/testing/testlist.py | 2 + 82 files changed, 5381 insertions(+), 19 deletions(-) create mode 100644 libopenage/renderer/README.md create mode 100644 libopenage/renderer/geometry.cpp create mode 100644 libopenage/renderer/geometry.h create mode 100644 libopenage/renderer/opengl/CMakeLists.txt create mode 100644 libopenage/renderer/opengl/buffer.cpp create mode 100644 libopenage/renderer/opengl/buffer.h create mode 100644 libopenage/renderer/opengl/context.cpp create mode 100644 libopenage/renderer/opengl/context.h create mode 100644 libopenage/renderer/opengl/framebuffer.cpp create mode 100644 libopenage/renderer/opengl/framebuffer.h create mode 100644 libopenage/renderer/opengl/geometry.cpp create mode 100644 libopenage/renderer/opengl/geometry.h create mode 100644 libopenage/renderer/opengl/lookup.h create mode 100644 libopenage/renderer/opengl/render_target.cpp create mode 100644 libopenage/renderer/opengl/render_target.h create mode 100644 libopenage/renderer/opengl/renderer.cpp create mode 100644 libopenage/renderer/opengl/renderer.h create mode 100644 libopenage/renderer/opengl/shader.cpp create mode 100644 libopenage/renderer/opengl/shader.h create mode 100644 libopenage/renderer/opengl/shader_program.cpp create mode 100644 libopenage/renderer/opengl/shader_program.h create mode 100644 libopenage/renderer/opengl/simple_object.cpp create mode 100644 libopenage/renderer/opengl/simple_object.h create mode 100644 libopenage/renderer/opengl/texture.cpp create mode 100644 libopenage/renderer/opengl/texture.h create mode 100644 libopenage/renderer/opengl/texture_array.cpp create mode 100644 libopenage/renderer/opengl/texture_array.h create mode 100644 libopenage/renderer/opengl/uniform_input.h create mode 100644 libopenage/renderer/opengl/vertex_array.cpp create mode 100644 libopenage/renderer/opengl/vertex_array.h create mode 100644 libopenage/renderer/opengl/window.cpp create mode 100644 libopenage/renderer/opengl/window.h create mode 100644 libopenage/renderer/renderer.h create mode 100644 libopenage/renderer/resources/CMakeLists.txt create mode 100644 libopenage/renderer/resources/mesh_data.cpp create mode 100644 libopenage/renderer/resources/mesh_data.h create mode 100644 libopenage/renderer/resources/shader_source.cpp create mode 100644 libopenage/renderer/resources/shader_source.h create mode 100644 libopenage/renderer/resources/texture_data.cpp create mode 100644 libopenage/renderer/resources/texture_data.h create mode 100644 libopenage/renderer/resources/texture_info.cpp create mode 100644 libopenage/renderer/resources/texture_info.h create mode 100644 libopenage/renderer/sdl_global.cpp create mode 100644 libopenage/renderer/sdl_global.h create mode 100644 libopenage/renderer/shader_program.h create mode 100644 libopenage/renderer/tests.cpp create mode 100644 libopenage/renderer/tests.h create mode 100644 libopenage/renderer/texture.cpp create mode 100644 libopenage/renderer/texture.h create mode 100644 libopenage/renderer/texture_array.cpp create mode 100644 libopenage/renderer/texture_array.h create mode 100644 libopenage/renderer/vulkan/CMakeLists.txt create mode 100644 libopenage/renderer/vulkan/README.md create mode 100644 libopenage/renderer/vulkan/graphics_device.cpp create mode 100644 libopenage/renderer/vulkan/graphics_device.h create mode 100644 libopenage/renderer/vulkan/loader.cpp create mode 100644 libopenage/renderer/vulkan/loader.h create mode 100644 libopenage/renderer/vulkan/render_target.h create mode 100644 libopenage/renderer/vulkan/renderer.cpp create mode 100644 libopenage/renderer/vulkan/renderer.h create mode 100644 libopenage/renderer/vulkan/shader_program.h create mode 100644 libopenage/renderer/vulkan/util.h create mode 100644 libopenage/renderer/vulkan/windowvk.cpp create mode 100644 libopenage/renderer/vulkan/windowvk.h create mode 100644 libopenage/renderer/window.cpp create mode 100644 libopenage/renderer/window.h create mode 100644 openage/renderer/CMakeLists.txt create mode 100644 openage/renderer/__init__.py create mode 100644 openage/renderer/renderer_cpp.pyx create mode 100644 openage/renderer/tests.pyx diff --git a/.gitignore b/.gitignore index e9289c82bd..c729940558 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,7 @@ perf.data* # macOS .DS_Store + +# copyrighted assets +/assets/converted/ +/assets/terrain/ 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 7531d8a5d7..940d385b6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,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 865bad2c36..f7710db876 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,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 [**nyan**](https://github.com/SFTtech/nyan) | Content Configuration and Modding 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/configure b/configure index b0f8dafea8..e8054b3706 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", "ncurses": "if_available", @@ -82,7 +84,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/libopenage/CMakeLists.txt b/libopenage/CMakeLists.txt index 08b481d84b..018cae7742 100644 --- a/libopenage/CMakeLists.txt +++ b/libopenage/CMakeLists.txt @@ -190,25 +190,34 @@ else() have_config_option(inotify INOTIFY false) endif() -# ncurses support -if(WANT_NCURSES) - set(CURSES_NEED_NCURSES TRUE) - find_package(Curses) +# opengl support +if(WANT_OPENGL) + find_package(OpenGL) endif() -if(WANT_NCURSES AND CURSES_FOUND) - have_config_option(ncurses NCURSES true) - target_include_directories(libopenage PRIVATE ${Curses_INCLUDE_DIRS}) - target_link_libraries(libopenage PRIVATE ${CURSES_LIBRARIES}) +# 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(ncurses NCURSES false) + have_config_option(opengl OPENGL false) endif() -# remember to add more options to config.{cpp,h}.in! +if(WANT_VULKAN AND VULKAN_FOUND) + have_config_option(vulkan VULKAN true) + include_directories(${Vulkan_INCLUDE_DIRS}) +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() -################################################## -# build configuration generation get_config_option_string() configure_file(config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/config.h) @@ -243,6 +252,7 @@ target_include_directories(libopenage # to the public api of libopenage target_link_libraries(libopenage PUBLIC + pthread nyan::nyan ${PNG_LIBRARIES} ${OPUS_LIBRARIES} @@ -254,6 +264,7 @@ target_link_libraries(libopenage ${EPOXY_LIBRARIES} ${MATH_LIB} ${OPENGL_LIBRARY} + ${Vulkan_LIBRARIES} ${SDL2IMAGE_LIBRARIES} ${SDL2_LIBRARY} ${UTIL_LIB} diff --git a/libopenage/config.h.in b/libopenage/config.h.in index f826e690bf..cb8653177b 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} #define WITH_NCURSES ${WITH_NCURSES} diff --git a/libopenage/renderer/CMakeLists.txt b/libopenage/renderer/CMakeLists.txt index fbaa233231..0ccde1345b 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 + sdl_global.cpp + tests.cpp + text.cpp + texture.cpp + texture_array.cpp + window.cpp ) -add_subdirectory(font) +pxdgen( + tests.h +) + +add_subdirectory(font/) +add_subdirectory(opengl/) +add_subdirectory(resources/) +#add_subdirectory(vulkan/) diff --git a/libopenage/renderer/README.md b/libopenage/renderer/README.md new file mode 100644 index 0000000000..e6f804266c --- /dev/null +++ b/libopenage/renderer/README.md @@ -0,0 +1,149 @@ +# 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. + +### Usage +#### Renderer +All interaction with the renderer is done through the abstract `Renderer` class, initialized with a concrete implementation. For an OpenGL implementation, first create a window and then make a renderer for it: +```c++ +opengl::GlWindow window("title", 1024, 768); +std::unique_ptr renderer = window.make_renderer(); +``` + +The `opengl` namespace or any other implementation-specific namespace like `vulkan`` should not ever be used after initializing the window. + +#### Resources +The `resources` namespace provides classes for initializing and loading meshes, textures, shaders, etc. +These objects can then be passed to the renderer to make them usable with graphics hardware, e.g.: +```c++ +auto vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + "#version 330\nvoid main() {}" +); + +auto fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + "#version 330\nvoid main() {}" +); + +auto shader = renderer->add_shader( { vshader_src, fshader_src } ); + +auto tex = resources::Texture2dData(game_path / "/assets/gaben.png"); +auto gpu_tex = renderer->add_texture(tex); +``` + +#### RenderPass and Renderable +Graphics operations are executed through submitting `RenderPass`es and `Renderable`s to the `Renderer` object. For details, +see `renderer.h`. + +#### Sample usage: +Sample usage: + +```c++ +opengl::GlWindow window("title", 1024, 768'); +std::unique_ptr renderer = window.make_renderer(); + +resources::TextureData tex_data(game_path / "/path.tex"); +std::unique_ptr tex = renderer->add_texture(tex_data); + +resources::ShaderSource vsrc = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + game_path / "path.vert" +); +resources::ShaderSource fsrc = resources::ShaderSource(resources::shader_t::glsl_fragment); + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + game_path / "path.frag" +); + +std::unique_ptr prog = renderer->add_shader( { vsrc, fsrc } ); + +std::unique_ptr input = prog->new_uniform_input( + "color", { 0.0f, 1.0f, 0.0f }, + "time", 0.0f, + "num", 1337 +); + +std::unique_ptr geom = renderer->add_bufferless_quad(); + +RenderPass pass { + { { + input.get(), + geom.get(), + true, + true, + } }, + renderer->get_framebuffer_target(), +}; + +renderer->render(pass); +``` + +## Level 2: +On top of the level 1 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. In some documentation this is also called the "presenter". + +## TODO: +- [x] Abstraction of render backend + - [x] OpenGL 3.3 or higher + - [ ] Use 4.x when available (optional) + - [ ] Support ancient 2.x legacy (optional) + - [ ] Vulkan (optional, #242) +- [ ] Render pipeline abstraction + - [x] Textures + - [x] Shaders + - [x] Uniforms + - [ ] Uniform buffers + - [x] Vertex attributes + - [x] Geometries + - [x] Quad primitives + - [ ] Circles + - [ ] Smooth paths + - [ ] Mesh importing (optional) + - [x] Render targets + - [x] Framebuffers + - [ ] Renderbuffers (optional) +- [ ] Functionality + - [x] Screenshot support + - [ ] [PBO optimization](http://www.songho.ca/opengl/gl_pbo.html) for texture downloading (optional) + - [x] Pixel-perfect unit hitbox for unit selection and damage areas (#368, #671 ) + - [ ] Outline rendering + - [ ] Investigate why tree textures render incorrectly (#359, [maybe this?](http://www.adriancourreges.com/blog/2017/05/09/beware-of-transparent-pixels/)) + - [ ] Fix #374 +- [ ] Terrain rendering + - [ ] Merge terrain texture into a single bitmap + - [ ] Cache blending results (#154, #158) + - [ ] Do as much as possible in shaders (#149) + - [ ] Clip tiles properly (#141) +- [ ] Optimizations + - [ ] [Occlusion queries](https://vertostudio.com/gamedev/?p=177) + - [ ] Minimize OpenGL state changes (batch by shader, then by buffer) + - [ ] Texture binpacking into atlas + - [ ] Smooth zooming +- [ ] Integration + - [ ] Rewrite all of drawing functionality to be expressed in terms of `Renderer` + - [ ] Get rid of GL code everywhere except the rendering backend and the GUI + - [ ] Write a `GameRenderer` that takes evaluations of curves at the current instant as input + - [ ] GUI integration + - [ ] TBD (#624) diff --git a/libopenage/renderer/geometry.cpp b/libopenage/renderer/geometry.cpp new file mode 100644 index 0000000000..078cf96a4c --- /dev/null +++ b/libopenage/renderer/geometry.cpp @@ -0,0 +1,20 @@ +// 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; +} + +void Geometry::update_verts(const std::vector& verts) { + this->update_verts_offset(verts, 0); +} + +}} //openage::renderer diff --git a/libopenage/renderer/geometry.h b/libopenage/renderer/geometry.h new file mode 100644 index 0000000000..072f682727 --- /dev/null +++ b/libopenage/renderer/geometry.h @@ -0,0 +1,49 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + + +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; + + /// In a meshed geometry, updates the vertex data. The size and type of the vertex data has to be the same as before. + /// If the mesh is indexed, indices will stay the same. + /// @throws if there is a size mismatch between the new and old vertex data + void update_verts(std::vector const& verts); + + /// In a meshed geometry, updates the vertex data starting from the offset-th vertex. The type of the vertex + /// data has to be the same as it was on initializing the geometry. The size plus the offset cannot exceed the + /// previous size of the vertex data. If the mesh is indexed, indices will stay the same. + /// @throws if there is a size mismatch between the new and old vertex data + virtual void update_verts_offset(std::vector const& verts, size_t offset) = 0; + +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..99283cbb2b --- /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 + shader.cpp + shader_program.cpp + simple_object.cpp + texture.cpp + texture_array.cpp + vertex_array.cpp + window.cpp +) diff --git a/libopenage/renderer/opengl/buffer.cpp b/libopenage/renderer/opengl/buffer.cpp new file mode 100644 index 0000000000..a91842f74e --- /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_COPY_WRITE_BUFFER); + glBufferData(GL_COPY_WRITE_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_COPY_WRITE_BUFFER); + glBufferData(GL_COPY_WRITE_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_COPY_WRITE_BUFFER); + glBufferSubData(GL_COPY_WRITE_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..adb6e2ab2d --- /dev/null +++ b/libopenage/renderer/opengl/buffer.h @@ -0,0 +1,43 @@ +// 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. + /// Binds the GL_COPY_WRITE_BUFFER target. + GlBuffer(size_t size, GLenum usage = GL_STATIC_DRAW); + + /// Creates a buffer of the specified size and fills it with the given data. + /// Binds the GL_COPY_WRITE_BUFFER target. + 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()`. + /// Binds the GL_COPY_WRITE_BUFFER target. + 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..8b20cc589e --- /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; + // TODO 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..844de8082c --- /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..fd1602ad2c --- /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..68f266855a --- /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" + +#include "lookup.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +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_PRIMITIVE.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_INDEX_TYPE.get(*mesh.get_info().get_index_type()); + this->mesh->vert_count = mesh.get_ids()->size() / sizeof(GLuint); + } +} + +void GlGeometry::update_verts_offset(std::vector const &verts, size_t offset) { + if (this->get_type() != geometry_t::mesh) { + throw Error(MSG(err) << "Cannot update vertex data for non-mesh GlGeometry."); + } + + if (verts.size() != this->mesh->vertices.get_size()) { + throw Error(MSG(err) << "Size mismatch between old and new vertex data for GlGeometry."); + } + + // TODO support offset updating + this->mesh->vertices.upload_data(verts.data(), offset, verts.size()); +} + +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..75f225b067 --- /dev/null +++ b/libopenage/renderer/opengl/geometry.h @@ -0,0 +1,49 @@ +// 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; + + void update_verts_offset(std::vector const&, size_t) override; + +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/lookup.h b/libopenage/renderer/opengl/lookup.h new file mode 100644 index 0000000000..5b88254741 --- /dev/null +++ b/libopenage/renderer/opengl/lookup.h @@ -0,0 +1,103 @@ +// Copyright 2018 the openage authors. See copying.md for legal info. +// Lookup tables for translating various things in OpenGL. + +#include + +#include "../resources/texture_info.h" +#include "../resources/mesh_data.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// Input and output pixel formats from pixel_format. +static constexpr auto GL_PIXEL_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::bgr8, std::make_tuple(GL_RGB8, GL_BGR, 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)), + std::make_pair(resources::pixel_format::depth24, std::make_tuple(GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE)) +); + +/// Sizes of various uniform/vertex input types in shaders. +static constexpr auto GL_SHADER_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_2D_ARRAY, 4), + std::make_pair(GL_SAMPLER_3D, 4), + std::make_pair(GL_SAMPLER_CUBE, 4) +); + +/// GLSL type strings with corresponding GL types. +static constexpr auto GLSL_TYPE_NAME = 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), + std::make_pair("sampler2DArray", GL_SAMPLER_2D_ARRAY) +); + +/// Generic vertex input types from GL types. +static constexpr auto GL_VERT_IN_TYPE = datastructure::create_const_map( + std::make_pair(GL_FLOAT, resources::vertex_input_t::F32), + std::make_pair(GL_FLOAT_VEC2, resources::vertex_input_t::V2F32), + std::make_pair(GL_FLOAT_VEC3, resources::vertex_input_t::V3F32), + std::make_pair(GL_FLOAT_MAT3, resources::vertex_input_t::M3F32) +); + +/// The type of a single element in a per-vertex attribute. +static constexpr auto GL_VERT_IN_ELEM_TYPE = datastructure::create_const_map( + std::make_pair(resources::vertex_input_t::F32, GL_FLOAT), + std::make_pair(resources::vertex_input_t::V2F32, GL_FLOAT), + std::make_pair(resources::vertex_input_t::V3F32, GL_FLOAT), + std::make_pair(resources::vertex_input_t::M3F32, GL_FLOAT) +); + +/// Mapping from generic primitive types to GL types. +static constexpr auto GL_PRIMITIVE = 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) +); + +/// Mapping from generic index types to GL types. +static constexpr auto GL_INDEX_TYPE = 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) +); + +}}} // 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..88f4ae224a --- /dev/null +++ b/libopenage/renderer/opengl/render_target.cpp @@ -0,0 +1,35 @@ +// 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); + } +} + +void GlRenderTarget::bind_read() const { + if (this->type == gl_render_target_t::textures) { + this->framebuffer->bind_read(); + } + else { + glBindFramebuffer(GL_READ_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..8ee2882aac --- /dev/null +++ b/libopenage/renderer/opengl/render_target.h @@ -0,0 +1,50 @@ +// 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; + + /// Bind this render target to be read from. + void bind_read() 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..d1a408882c --- /dev/null +++ b/libopenage/renderer/opengl/renderer.cpp @@ -0,0 +1,103 @@ +// 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::Texture2dData& data) { + return std::make_unique(data); +} + +std::unique_ptr GlRenderer::add_texture(const resources::Texture2dInfo& 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; +} + +resources::Texture2dData GlRenderer::display_into_data() { + GLint params[4]; + glGetIntegerv(GL_VIEWPORT, params); + + GLint width = params[2]; + GLint height = params[3]; + + resources::Texture2dInfo tex_info(width, height, resources::pixel_format::rgba8, 4); + std::vector data(tex_info.get_data_size()); + + static_cast(this->get_display_target())->bind_read(); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glReadnPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, tex_info.get_data_size(), data.data()); + + resources::Texture2dData img(std::move(tex_info), std::move(data)); + return img.flip_y(); +} + +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..6ebddfe765 --- /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::Texture2dData const&) override; + std::unique_ptr add_texture(resources::Texture2dInfo 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; + + resources::Texture2dData display_into_data() override; + + void render(RenderPass const&) override; + +private: + /// The GL context. + GlContext *gl_context; + + GlRenderTarget display; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/shader.cpp b/libopenage/renderer/opengl/shader.cpp new file mode 100644 index 0000000000..419f4eab2a --- /dev/null +++ b/libopenage/renderer/opengl/shader.cpp @@ -0,0 +1,58 @@ +// 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_stage_t::vertex, GL_VERTEX_SHADER), + std::make_pair(resources::shader_stage_t::geometry, GL_GEOMETRY_SHADER), + std::make_pair(resources::shader_stage_t::tesselation_control, GL_TESS_CONTROL_SHADER), + std::make_pair(resources::shader_stage_t::tesselation_evaluation, GL_TESS_EVALUATION_SHADER), + std::make_pair(resources::shader_stage_t::fragment, GL_FRAGMENT_SHADER) +); + +GlShader::GlShader(const resources::ShaderSource &src) + : GlSimpleObject([] (GLuint handle) { glDeleteShader(handle); } ) + , type(gl_shdr_type.get(src.get_stage())) +{ + if (src.get_lang() != resources::shader_lang_t::glsl) { + throw Error(MSG(err) << "Unsupported shader language passed to OpenGL renderer."); + } + + // allocate shader in opengl + GLuint handle = glCreateShader(this->type); + this->handle = handle; + + // load shader source + const char* data = src.get_source().c_str(); + 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..94df9c980f --- /dev/null +++ b/libopenage/renderer/opengl/shader_program.cpp @@ -0,0 +1,338 @@ +// 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" +#include "lookup.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +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); + } +} + +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_SHADER_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); + + // By the time we call bind, the texture may have been deleted, but if it's fixed + // afterwards using execute_with, the render state will still be fine, so we can ignore + // this error. + // TODO this will swallow actual errors elsewhere, and should be avoided. how? + glGetError(); + } +} + +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 a more appropriate 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::map GlShaderProgram::vertex_attributes() const { + std::map attrib_map; + + for (auto const& attr : this->attribs) { + attrib_map[attr.second.location] = GL_VERT_IN_TYPE.get(attr.second.type); + } + + return attrib_map; +} + +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 (unlikely(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_SHADER_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 { + // first time writing to this uniform since last upload + 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, Texture2d 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..08ee8f0eca --- /dev/null +++ b/libopenage/renderer/opengl/shader_program.h @@ -0,0 +1,90 @@ +// 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. If geometry is null, only the + /// uniform values are updated. + void execute_with(const GlUniformInput*, const GlGeometry*); + + bool has_uniform(const char*) override; + + std::map vertex_attributes() const 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*, Texture2d 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..80d8b7635e --- /dev/null +++ b/libopenage/renderer/opengl/texture.cpp @@ -0,0 +1,112 @@ +// 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 "../../log/log.h" + +#include "../resources/texture_data.h" +#include "render_target.h" +#include "lookup.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +GlTexture2d::GlTexture2d(const resources::Texture2dData& data) + : Texture2d(data.get_info()) + , GlSimpleObject([] (GLuint handle) { glDeleteTextures(1, &handle); } ) +{ + GLuint handle; + glGenTextures(1, &handle); + this->handle = handle; + + glBindTexture(GL_TEXTURE_2D, *this->handle); + + // select pixel format + auto fmt_in_out = GL_PIXEL_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); + + log::log(MSG(dbg) << "Created OpenGL texture from data"); +} + +GlTexture2d::GlTexture2d(const resources::Texture2dInfo &info) + : Texture2d(info) + , GlSimpleObject([] (GLuint handle) { glDeleteTextures(1, &handle); } ) +{ + GLuint handle; + glGenTextures(1, &handle); + this->handle = handle; + + glBindTexture(GL_TEXTURE_2D, *this->handle); + + auto fmt_in_out = GL_PIXEL_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); + + log::log(MSG(dbg) << "Created OpenGL texture from parameters"); +} + +resources::Texture2dData GlTexture2d::into_data() { + auto fmt_in_out = GL_PIXEL_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::Texture2dData(resources::Texture2dInfo(this->info), std::move(data)); +} + +void GlTexture2d::upload(resources::Texture2dData const& data) { + if (this->info != data.get_info()) { + throw Error(MSG(err) << "Tried to upload texture data of different format into an existing GPU texture."); + } + + glBindTexture(GL_TEXTURE_2D, *this->handle); + + auto size = this->info.get_size(); + auto fmt_in_out = GL_PIXEL_FORMAT.get(this->info.get_format()); + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + size.first, size.second, + std::get<1>(fmt_in_out), std::get<2>(fmt_in_out), + data.get_data() + ); +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/texture.h b/libopenage/renderer/opengl/texture.h new file mode 100644 index 0000000000..e9c81054a4 --- /dev/null +++ b/libopenage/renderer/opengl/texture.h @@ -0,0 +1,28 @@ +// 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 GlTexture2d final : public Texture2d, public GlSimpleObject { +public: + /// Constructs a texture and fills it with the given data. + explicit GlTexture2d(const resources::Texture2dData&); + + /// Constructs an empty texture with the given parameters. + GlTexture2d(resources::Texture2dInfo const&); + + resources::Texture2dData into_data() override; + + void upload(resources::Texture2dData const&) override; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/texture_array.cpp b/libopenage/renderer/opengl/texture_array.cpp new file mode 100644 index 0000000000..d4ec576241 --- /dev/null +++ b/libopenage/renderer/opengl/texture_array.cpp @@ -0,0 +1,100 @@ +// Copyright 2018 the openage authors. See copying.md for legal info. + +#include "texture_array.h" + +#include + +#include + +#include "../../error/error.h" +#include "../../datastructure/constexpr_map.h" +#include "../../log/log.h" + +#include "../resources/texture_data.h" +#include "render_target.h" +#include "lookup.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +GlTexture2dArray::GlTexture2dArray(const std::vector& data) + // Call the data-less constructor first. + : GlTexture2dArray(data.size(), data[0].get_info()) +{ + auto fmt_in_out = GL_PIXEL_FORMAT.get(this->layer_info.get_format()); + auto size = this->layer_info.get_size(); + + // Fill the array with given data + size_t i = 0; + for (auto const& tex : data) { + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, + 0, // mipmap number + 0, 0, i, // xoffset, yoffset, zoffset + size.first, size.second, 1, // width, height, depth + std::get<1>(fmt_in_out), // format + std::get<2>(fmt_in_out), // type + tex.get_data() // data + ); + + i += 1; + } +} + +GlTexture2dArray::GlTexture2dArray(size_t n_layers, resources::Texture2dInfo layer_info) + : Texture2dArray(layer_info) + , GlSimpleObject([] (GLuint handle) { glDeleteTextures(1, &handle); } ) + , n_layers(n_layers) +{ + GLuint handle; + glGenTextures(1, &handle); + this->handle = handle; + + glBindTexture(GL_TEXTURE_2D_ARRAY, *this->handle); + + auto fmt_in_out = GL_PIXEL_FORMAT.get(this->layer_info.get_format()); + auto size = this->layer_info.get_size(); + + // Create empty image + glTexImage3D(GL_TEXTURE_2D_ARRAY, + 0, // mipmap level + std::get<0>(fmt_in_out), // gpu texel format + size.first, // width + size.second, // height + n_layers, // depth + 0, // border + std::get<1>(fmt_in_out), // cpu pixel format + std::get<2>(fmt_in_out), // cpu pixel type + nullptr // data + ); + + // TODO these are outdated, use sampler settings + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + log::log(MSG(dbg) << "Created an OpenGL texture array."); +} + +void GlTexture2dArray::upload(size_t layer, resources::Texture2dData const& data) { + if (layer >= this->n_layers) { + throw Error(MSG(err) << "Cannot upload to layer " << layer << " in texture array with " + << this->n_layers << " layers."); + } + + glBindTexture(GL_TEXTURE_2D_ARRAY, *this->handle); + + auto fmt_in_out = GL_PIXEL_FORMAT.get(this->layer_info.get_format()); + auto size = this->layer_info.get_size(); + + glTexSubImage3D( GL_TEXTURE_2D_ARRAY, + 0, // mipmap number + 0,0,layer, // xoffset, yoffset, zoffset + size.first, size.second, 1, // width, height, depth + std::get<1>(fmt_in_out), // format + std::get<2>(fmt_in_out), // type + data.get_data() // data + ); +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/texture_array.h b/libopenage/renderer/opengl/texture_array.h new file mode 100644 index 0000000000..e4a39c54c3 --- /dev/null +++ b/libopenage/renderer/opengl/texture_array.h @@ -0,0 +1,37 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "../texture_array.h" + +#include "simple_object.h" +#include "../resources/texture_data.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// An OpenGL array of 2D textures. +class GlTexture2dArray final : public Texture2dArray, public GlSimpleObject { +public: + /// Constructs an array of the same number of layers as the size of the given + /// vector, and fills the layers with the corresponding vector element. The + /// texture formats in all vector elements must be the same as defined by + /// Textur2dInfo::operator==. + GlTexture2dArray(const std::vector&); + + /// Constructs an array of ln_layers empty layers, with the per-layer texture + /// format specified in layer_info. + GlTexture2dArray(size_t n_layers, resources::Texture2dInfo layer_info); + + void upload(size_t layer, resources::Texture2dData const&) override; + +private: + /// The number of layers (elements) in the array, AKA the depth. + size_t n_layers; +}; + +}}} // 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..3cc0b53bf4 --- /dev/null +++ b/libopenage/renderer/opengl/vertex_array.cpp @@ -0,0 +1,166 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "vertex_array.h" + +#include + +#include "../../error/error.h" +#include "../../datastructure/constexpr_map.h" + +#include "lookup.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +GlVertexArray::GlVertexArray(std::vector> buffers) + : GlSimpleObject([] (GLuint handle) { glDeleteVertexArrays(1, &handle); } ) +{ + GLuint handle; + glGenVertexArrays(1, &handle); + this->handle = handle; + + this->bind(); + + if (buffers.size() == 1 and buffers[0].second.get_shader_input_map()) { // Map inputs according to specified pairing + auto const& info = buffers[0].second; + auto const& in_map = *info.get_shader_input_map(); + auto const& in = info.get_inputs(); + + auto& buf = buffers[0].first; + buf.bind(GL_ARRAY_BUFFER); + + std::map in_map_sorted(in_map.begin(), in_map.end()); + + size_t offset = 0; + size_t next_idx = 0; + + if (info.get_layout() == resources::vertex_layout_t::AOS) { + for (auto mapping : in_map_sorted) { + if (mapping.first != next_idx) { + // Some attributes were skipped, so add them to the offset without enabling in VAO + for (size_t i = next_idx; i < mapping.first; i++) { + offset += resources::vertex_input_size(in[i]); + } + } + + glVertexAttribPointer( + mapping.second, + resources::vertex_input_count(in[mapping.first]), + GL_VERT_IN_ELEM_TYPE.get(in[mapping.first]), + GL_FALSE, + info.vert_size(), + reinterpret_cast(offset) + ); + + offset += resources::vertex_input_size(in[mapping.first]); + next_idx = mapping.first + 1; + } + } else if (info.get_layout() == resources::vertex_layout_t::SOA) { + size_t vert_count = buf.get_size() / info.vert_size(); + + for (auto mapping : in_map_sorted) { + if (mapping.first != next_idx) { + // Some attributes were skipped, so add them to the offset without enabling in VAO + for (size_t i = next_idx; i < mapping.first; i++) { + offset += resources::vertex_input_size(in[i]) * vert_count; + } + } + + glVertexAttribPointer( + mapping.second, + resources::vertex_input_count(in[mapping.first]), + GL_VERT_IN_ELEM_TYPE.get(in[mapping.first]), + GL_FALSE, + 0, + reinterpret_cast(offset) + ); + + offset += resources::vertex_input_size(in[mapping.first]) * vert_count; + next_idx = mapping.first + 1; + } + } else { + throw Error(MSG(err) << "Unknown vertex format."); + } + } else { // Map inputs in ascending order + GLuint attrib = 0; + + for (auto buf_info : buffers) { + auto const &buf = buf_info.first; + auto const &info = buf_info.second; + + if (unlikely(info.get_shader_input_map())) { + throw Error(MSG(err) << "Shader input mapping is unsupported when constructing a VAO from multiple buffers."); + } + + 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_VERT_IN_ELEM_TYPE.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_VERT_IN_ELEM_TYPE.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..3dc5a028fc --- /dev/null +++ b/libopenage/renderer/opengl/vertex_array.h @@ -0,0 +1,50 @@ +// 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. + /// If no shader input mapping is present, 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*/ }, + /// } ); + /// + /// A shader input mapping is only allowed when there is a single element in `buffers`. In such a case, + /// the vertex inputs are paired with VAO attributes according to the mapping instead of in ascending order. + 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/opengl/window.cpp b/libopenage/renderer/opengl/window.cpp new file mode 100644 index 0000000000..d4e2a88508 --- /dev/null +++ b/libopenage/renderer/opengl/window.cpp @@ -0,0 +1,105 @@ +// Copyright 2018-2018 the openage authors. See copying.md for legal info. + +#include "window.h" + +#include "../../log/log.h" +#include "../../error/error.h" +#include "../sdl_global.h" + +#include "renderer.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +GlWindow::GlWindow(const char *title, size_t width, size_t height) + : Window(width, height) +{ + make_sdl_available(); + + // 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; + this->window = SDL_CreateWindow( + title, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + this->size.first, + this->size.second, + window_flags + ); + + if (this->window == nullptr) { + throw Error{MSG(err) << "Failed to create SDL window: " << SDL_GetError()}; + } + + this->context = opengl::GlContext(this->window); + this->add_resize_callback([] (size_t w, size_t h) { glViewport(0, 0, w, h); } ); + + log::log(MSG(info) << "Created SDL window with OpenGL context."); +} + +GlWindow::~GlWindow() { + SDL_DestroyWindow(this->window); +} + +void GlWindow::set_size(size_t width, size_t height) { + if (this->size.first != width || this->size.second != height) { + SDL_SetWindowSize(this->window, width, height); + this->size = std::make_pair(width, height); + } + + for (auto& cb : this->on_resize) { + cb(width, height); + } +} + +void GlWindow::update() { + SDL_Event event; + + while (SDL_PollEvent(&event)) { + if (event.type == SDL_WINDOWEVENT) { + if (event.window.event == SDL_WINDOWEVENT_RESIZED) { + size_t width = event.window.data1; + size_t height = event.window.data2; + log::log(MSG(dbg) << "Window resized to: " << width << "x" << height); + this->size = std::make_pair(width, height); + for (auto& cb : this->on_resize) { + cb(width, height); + } + } + } else if (event.type == SDL_QUIT) { + this->should_be_closed = true; + // TODO call on_destroy + } else if (event.type == SDL_KEYUP) { + auto const ev = *reinterpret_cast(&event); + for (auto& cb : this->on_key) { + cb(ev); + } + // TODO handle keydown + } else if (event.type == SDL_MOUSEBUTTONUP) { + auto const ev = *reinterpret_cast(&event); + for (auto& cb : this->on_mouse_button) { + cb(ev); + } + // TODO handle mousedown + } + } + + SDL_GL_SwapWindow(this->window); +} + +std::unique_ptr GlWindow::make_renderer() { + return std::make_unique(this->get_context()); +} + +void GlWindow::make_context_current() { + SDL_GL_MakeCurrent(this->window, this->context->get_raw_context()); +} + +opengl::GlContext *GlWindow::get_context() { + return &this->context.value(); +} + +}}} // namespace openage::renderer::opengl diff --git a/libopenage/renderer/opengl/window.h b/libopenage/renderer/opengl/window.h new file mode 100644 index 0000000000..a29acf1165 --- /dev/null +++ b/libopenage/renderer/opengl/window.h @@ -0,0 +1,44 @@ +// Copyright 2018-2018 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../window.h" + +#include + +#include "context.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +class GlWindow final : public Window { +public: + /// Create a shiny window with the given title. + GlWindow(const char *title, size_t width, size_t height); + ~GlWindow(); + + void set_size(size_t width, size_t height) override; + + void update() override; + + std::unique_ptr make_renderer() override; + + /// 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(); + + /// Return a pointer to this window's GL context. + opengl::GlContext *get_context(); + +private: + /// The SDL struct representing this window. + SDL_Window *window; + + /// The 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::opengl diff --git a/libopenage/renderer/renderer.h b/libopenage/renderer/renderer.h new file mode 100644 index 0000000000..c77d5ba2d0 --- /dev/null +++ b/libopenage/renderer/renderer.h @@ -0,0 +1,112 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + + +namespace openage { +namespace renderer { + +namespace resources { +class Texture2dData; +class Texture2dInfo; +class ShaderSource; +class MeshData; +} + +class ShaderProgram; +class Geometry; +class Texture2d; + +/// The abstract base for uniform input. Besides the uniform values, it stores information about +/// which shader program the input was created for. +class UniformInput { +protected: + UniformInput() = default; + +public: + virtual ~UniformInput() = default; +}; + +/// The abstract base for a render target. +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. Contains a reference to the correct shader, and this + /// is the shader that will be used for drawing if geometry is present. + 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 target 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::Texture2dData const&) = 0; + + /// Creates a new empty texture with the given parameters on the graphics hardware. + virtual std::unique_ptr add_texture(resources::Texture2dInfo 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. Textures are attached to the target in the order that they are + /// present in within the vector. Depth textures are attached as depth components. Textures of every other + /// type are attached as color components. + 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; + + /// Stores the display framebuffer into a CPU-accessible data object. Essentially, this takes a screenshot. + virtual resources::Texture2dData display_into_data() = 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..c6a46bba92 --- /dev/null +++ b/libopenage/renderer/resources/mesh_data.cpp @@ -0,0 +1,131 @@ +// 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::F32, 4), + 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::F32, 1), + 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) {} + +void VertexInputInfo::add_shader_input_map(std::unordered_map&& in_map) { + for (auto mapping : in_map) { + if (unlikely(mapping.first >= this->inputs.size())) { + throw Error(MSG(err) << "A shader input mapping is out-of-range, exceeding the available number of attributes."); + } + } + + this->shader_input_map = std::move(in_map); +} + +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; +} + +std::experimental::optional> const& VertexInputInfo::get_shader_input_map() const { + return this->shader_input_map; +} + +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; +} + +MeshData::MeshData(const util::Path &/*path*/) { + // asdf + throw "unimplemented lol"; +} + +MeshData::MeshData(std::vector&& verts, VertexInputInfo info) + : data(std::move(verts)) + , info(info) {} + +MeshData::MeshData(std::vector &&verts, std::vector &&ids, VertexInputInfo info) + : data(std::move(verts)) + , ids(std::move(ids)) + , info(info) {} + +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; +} + +/// Vertices of a quadrilateral filling the whole screen. +/// Format: (pos, tex_coords) = (x, y, u, v) +static constexpr const 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::make_quad() { + auto const data_size = QUAD_DATA.size() * sizeof(decltype(QUAD_DATA)::value_type); + std::vector verts(data_size); + std::memcpy(verts.data(), reinterpret_cast(QUAD_DATA.data()), data_size); + + VertexInputInfo info { { vertex_input_t::V2F32, vertex_input_t::V2F32 }, vertex_layout_t::AOS, vertex_primitive_t::TRIANGLE_STRIP }; + + return MeshData(std::move(verts), info); +} + +}}} diff --git a/libopenage/renderer/resources/mesh_data.h b/libopenage/renderer/resources/mesh_data.h new file mode 100644 index 0000000000..4545daa627 --- /dev/null +++ b/libopenage/renderer/resources/mesh_data.h @@ -0,0 +1,153 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#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 { + F32, + 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); + + /// Adds an mesh->shader input mapping to the info. By default, attributes are mapped + /// one-to-one according to their order in the input vector, e.g. (vec2 pos, vec2 uv) + /// maps into (0: vec2 pos, 1: vec2 uv) in the shader. However, if a shader skips indices + /// in the layout or takes its inputs in a different order, this can be specified using the + /// map. The map entries must have the format (index_in_vector, index_in_shader) and will + /// overwrite the default mapping. If an entry for a given index in the vector is missing, + /// that attribute and its data will be skipped. + void add_shader_input_map(std::unordered_map&&); + + /// Returns the list of per-vertex inputs. + const std::vector &get_inputs() const; + + /// Returns the shader input map or an empty optional if it's not present. + std::experimental::optional> const& get_shader_input_map() 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; + + /// An optional attribute specifying how vertex inputs in the mesh map to vertex inputs + /// in a given shader, for example to reorder inputs of the form (pos, uv) into a shader + /// that takes in (uv, pos) inputs. + std::experimental::optional> shader_input_map; + + /// 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; +}; + +class MeshData { +public: + /// Tries to load the mesh data from the specified file. + explicit MeshData(const util::Path&); + + /// Initializes the mesh data to a custom unindexed vertex vector described by the given info. + MeshData(std::vector &&verts, VertexInputInfo); + + /// Initializes the mesh data to a custom indexed vertex vector described by the given info. + MeshData(std::vector &&verts, std::vector &&ids, VertexInputInfo); + + /// 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; + + /// Initializes the mesh data with a simple quadrilateral spanning the whole window space, + /// with (vec2 pos, vec2 uv) vertex format. + static MeshData make_quad(); + +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..f5bd1ea2bc --- /dev/null +++ b/libopenage/renderer/resources/shader_source.cpp @@ -0,0 +1,34 @@ +// 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 { + +ShaderSource::ShaderSource(shader_lang_t lang, shader_stage_t stage, std::string &&code) + : lang(lang) + , stage(stage) + , code(std::move(code)) {} + +ShaderSource::ShaderSource(shader_lang_t lang, shader_stage_t stage, const util::Path& path) + : lang(lang) + , stage(stage) + , code(path.open().read()) {} + +std::string const& ShaderSource::get_source() const { + return this->code; +} + +shader_lang_t ShaderSource::get_lang() const { + return this->lang; +} + +shader_stage_t ShaderSource::get_stage() const { + return this->stage; +} + +}}} // 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..6ac36c758c --- /dev/null +++ b/libopenage/renderer/resources/shader_source.h @@ -0,0 +1,59 @@ +// 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 { + +/// The source language of a shader. +enum class shader_lang_t { + glsl, + spirv, +}; + +/// Shader stages present in modern graphics pipelines. +enum class shader_stage_t { + vertex, + geometry, + tesselation_control, + tesselation_evaluation, + fragment, +}; + +/// Stores source code for a part of a shader program. +class ShaderSource { +public: + /// Obtain shader source code from a file. + ShaderSource(shader_lang_t, shader_stage_t, const util::Path &path); + + /// Obtain shader source code from a string. + ShaderSource(shader_lang_t, shader_stage_t, std::string &&code); + + /// Returns a view of the shader source code. This might be text + /// or binary data. + std::string const& get_source() const; + + /// Returns the language of this shader source. + shader_lang_t get_lang() const; + + /// Returns the stage which this shader source implements. + shader_stage_t get_stage() const; + +private: + /// The source language. + shader_lang_t lang; + + /// The implemented stage. + shader_stage_t stage; + + /// 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..1e89f292d2 --- /dev/null +++ b/libopenage/renderer/resources/texture_data.cpp @@ -0,0 +1,194 @@ +// 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 { + +/// Tries to guess the alignment of image rows based on image parameters. Kinda +/// black magic and might not actually work. +/// @param width in pixels of the image +/// @param fmt of pixels in the image +/// @param row_size the actual size in bytes of an image row, including padding +static size_t guess_row_alignment(size_t width, pixel_format fmt, size_t row_size) { + // Use the highest possible alignment for even-width images. + if (width % 8 == 0) { + return 8; + } else if (width % 4 == 0) { + return 4; + } else if (width % 2 == 0) { + return 2; + } + + // The size of meaningful data in each row. + size_t pix_bytes = width * pixel_size(fmt); + // The size of padding. + size_t padding = row_size - pix_bytes; + + if (padding == 0) { + return 1; + } else if (padding <= 1) { + return 2; + } else if (padding <= 3) { + return 4; + } else if (padding <= 7) { + return 8; + } + + // Bail with a sane value. + return 4; +} + +Texture2dData::Texture2dData(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 surf_fmt = *surface->format; + + pixel_format pix_fmt; + switch (surf_fmt.format) { + case SDL_PIXELFORMAT_RGB24: + pix_fmt = pixel_format::rgb8; + break; + case SDL_PIXELFORMAT_BGR24: + pix_fmt = pixel_format::bgr8; + break; +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + case SDL_PIXELFORMAT_RGBA8888: +#else + case SDL_PIXELFORMAT_ABGR8888: +#endif + pix_fmt = pixel_format::rgba8; + break; + default: + throw Error(MSG(err) << "Texture " << native_path << " uses an unsupported format."); + } + + auto w = surface->w; + auto h = surface->h; + + size_t data_size = surf_fmt.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()).with_suffix(".slp.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); + } + + size_t align = guess_row_alignment(w, pix_fmt, surface->pitch); + this->info = Texture2dInfo(w, h, pix_fmt, align, std::move(subtextures)); +} + +Texture2dData::Texture2dData(Texture2dInfo &&info, std::vector &&data) + : info(std::move(info)) + , data(std::move(data)) {} + +Texture2dData Texture2dData::flip_y() { + size_t row_size = this->info.get_row_size(); + size_t height = this->info.get_size().second; + + std::vector new_data(this->data.size()); + + for (size_t y = 0; y < height; ++y) { + std::copy(this->data.data() + row_size * y, this->data.data() + row_size * (y+1), new_data.end() - row_size * (y+1)); + } + + this->data = new_data; + + Texture2dInfo new_info(this->info); + + return Texture2dData(std::move(new_info), std::move(new_data)); +} + +const Texture2dInfo& Texture2dData::get_info() const { + return this->info; +} + +const uint8_t *Texture2dData::get_data() const { + return this->data.data(); +} + +void Texture2dData::store(const util::Path& file) const { + log::log(MSG(info) << "Saving texture data to " << file); + + if (this->info.get_format() != pixel_format::rgba8) { + throw "unimplemented"; + } + + auto size = this->info.get_size(); + +// If an older SDL2 is used, we have to specify the format manually. +#ifndef SDL_PIXELFORMAT_RGBA32 + uint32_t rmask, gmask, bmask, amask; +#if SDL_BYTEORDER == SDL_BIG_ENDIAN + rmask = 0xff000000; + gmask = 0x00ff0000; + bmask = 0x0000ff00; + amask = 0x000000ff; +#else // little endian, like x86 + rmask = 0x000000ff; + gmask = 0x0000ff00; + bmask = 0x00ff0000; + amask = 0xff000000; +#endif + + SDL_Surface *surf = SDL_CreateRGBSurfaceFrom( + // const_cast is okay, because the surface doesn't modify data + const_cast(static_cast(this->data.data())), + size.first, + size.second, + 32, + this->info.get_row_size(), + rmask, gmask, bmask, amask + ); +#else + SDL_Surface *surf = SDL_CreateRGBSurfaceWithFormatFrom( + // const_cast is okay, because the surface doesn't modify data + const_cast(static_cast(this->data.data())), + size.first, + size.second, + 32, + this->info.get_row_size(), + SDL_PIXELFORMAT_RGBA32 + ); +#endif + + // Call sdl_image for saving the screenshot to PNG + std::string path = file.resolve_native_path_w(); + IMG_SavePNG(surf, path.c_str()); + SDL_FreeSurface(surf); +} + +}}} diff --git a/libopenage/renderer/resources/texture_data.h b/libopenage/renderer/resources/texture_data.h new file mode 100644 index 0000000000..5e9da11813 --- /dev/null +++ b/libopenage/renderer/resources/texture_data.h @@ -0,0 +1,71 @@ +// 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 2D texture data in a CPU-accessible byte buffer. Provides methods for loading from +/// and storing onto disk, as well as sending to and receiving from graphics hardware. +class Texture2dData { +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. + Texture2dData(const util::Path &path, bool use_metafile = false); + + /// Construct by moving the information and raw texture data from somewhere else. + Texture2dData(Texture2dInfo &&info, std::vector &&data); + + /// Flips the texture along the Y-axis and returns the flipped data with the same info. + /// Sometimes necessary when converting between storage modes. + Texture2dData flip_y(); + + /// Returns the information describing this texture data. + const Texture2dInfo& get_info() const; + + /// Returns a pointer to the raw texture data, in row-major order. + 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, so the coordinates + /// have to be specified according to that. + 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. + Texture2dInfo 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..5816efe9bf --- /dev/null +++ b/libopenage/renderer/resources/texture_info.cpp @@ -0,0 +1,101 @@ +// 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::bgr8, 3), + std::make_pair(pixel_format::rgba8, 4), + std::make_pair(pixel_format::rgba8ui, 4), + std::make_pair(pixel_format::depth24, 3) +); + +size_t pixel_size(pixel_format fmt) { + return pix_size.get(fmt); +} + +Texture2dInfo::Texture2dInfo(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)) {} + +bool Texture2dInfo::operator==(Texture2dInfo const& other) { + return other.w == this->w + and other.h == this->h + and other.format == this->format + and other.row_alignment == this->row_alignment; +} + +bool Texture2dInfo::operator!=(Texture2dInfo const& other) { + return not (*this == other); +} + +std::pair Texture2dInfo::get_size() const { + return std::make_pair(this->w, this->h); +} + +pixel_format Texture2dInfo::get_format() const { + return this->format; +} + +size_t Texture2dInfo::get_row_alignment() const { + return this->row_alignment; +} + +size_t Texture2dInfo::get_row_size() const { + size_t px_size = pixel_size(this->format); + size_t row_size = this->w * px_size; + + if (row_size % this->row_alignment != 0) { + // Unaligned rows, have to add padding. + size_t padding = this->row_alignment - (row_size % this->row_alignment); + row_size += padding; + } + + return row_size; +} + +size_t Texture2dInfo::get_data_size() const { + return this->get_row_size() * this->h; +} + +size_t Texture2dInfo::get_subtexture_count() const { + return this->subtextures.size(); +} + +const gamedata::subtexture& Texture2dInfo::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 Texture2dInfo::get_subtexture_size(size_t subid) const { + auto subtex = this->get_subtexture(subid); + return std::make_pair(subtex.w, subtex.h); +} + +std::tuple Texture2dInfo::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..d89dc16bb2 --- /dev/null +++ b/libopenage/renderer/resources/texture_info.h @@ -0,0 +1,107 @@ +// 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, RGB order + rgb8, + /// 24 bits per pixel, float, BGR order + bgr8, + /// 24 bits per pixel, depth texture + depth24, + /// 32 bits per pixel, float, alpha channel, RGBA order + rgba8, + /// 32 bits per pixel, unsigned integer, alpha channel, RGBA order + rgba8ui, +}; + +/// Returns the size in bytes of a single pixel of the specified format. +size_t pixel_size(pixel_format); + +/// Contains information about a 2D texture surface, without actual texture data. +/// The class supports subtextures, so that one big texture ("texture atlas") +/// can contain several smaller images, which can be extracted for rendering +/// one-by-one. +class Texture2dInfo { +public: + /// Constructs a Texture2dInfo with the given information. + Texture2dInfo(size_t width, size_t height, pixel_format, size_t row_alignment = 1, std::vector&& = std::vector()); + + Texture2dInfo() = default; + Texture2dInfo(Texture2dInfo const&) = default; + ~Texture2dInfo() = default; + + /// Compares the texture parameters _excluding_ the subtexture information + /// and returns true if they're equal, false otherwise. + bool operator==(Texture2dInfo const&); + + /// See operator==. + bool operator!=(Texture2dInfo const&); + + /// 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/sdl_global.cpp b/libopenage/renderer/sdl_global.cpp new file mode 100644 index 0000000000..1a1acead96 --- /dev/null +++ b/libopenage/renderer/sdl_global.cpp @@ -0,0 +1,59 @@ +// Copyright 2018-2018 the openage authors. See copying.md for legal info. + +#include +#include +#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)); + + inited = true; + } + + ~SDL() { + if (inited) { + IMG_Quit(); + SDL_Quit(); + + log::log(MSG(info) << "Exited SDL"); + } + } + +private: + static SDL sdl; + static bool inited; +}; + +bool SDL::inited = false; + +void make_sdl_available() { + SDL::make_available(); +} + +}} // namespace openage::renderer diff --git a/libopenage/renderer/sdl_global.h b/libopenage/renderer/sdl_global.h new file mode 100644 index 0000000000..2a96752eb6 --- /dev/null +++ b/libopenage/renderer/sdl_global.h @@ -0,0 +1,11 @@ +// Copyright 2018-2018 the openage authors. See copying.md for legal info. + + +namespace openage { +namespace renderer { + +/// Lazily initializes the global SDL singleton if it isn't yet available, +/// otherwise does nothing. +void make_sdl_available(); + +}} // namespace openage::renderer diff --git a/libopenage/renderer/shader_program.h b/libopenage/renderer/shader_program.h new file mode 100644 index 0000000000..c73adb8cc3 --- /dev/null +++ b/libopenage/renderer/shader_program.h @@ -0,0 +1,118 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include + +#include "resources/mesh_data.h" +#include "../error/error.h" + + +namespace openage { +namespace renderer { + +class UniformInput; +class Texture2d; + +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, Texture2d const* val) { + this->set_tex(input, unif, val); + } + + void update_uniform_input(UniformInput* input, const char* unif, Texture2d* 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...); + } + + /// Returns a list of _active_ vertex attributes in the shader program. Active attributes + /// are those which have an effect on the shader output, meaning they are included in the + /// output calculation and are not unused. Inactive attributes may or may not be present + /// in the list - in particular, in the OpenGL implementation they will most likely be missing. + /// The returned value is a map from the attribute location to its type. Locations need not + /// be consecutive. + virtual std::map vertex_attributes() const = 0; + +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*, Texture2d const*) = 0; +}; + +}} diff --git a/libopenage/renderer/tests.cpp b/libopenage/renderer/tests.cpp new file mode 100644 index 0000000000..cee23c8a0f --- /dev/null +++ b/libopenage/renderer/tests.cpp @@ -0,0 +1,267 @@ +// 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 "resources/texture_data.h" +#include "resources/mesh_data.h" +#include "texture.h" +#include "shader_program.h" +#include "geometry.h" +#include "opengl/window.h" + + +namespace openage { +namespace renderer { +namespace tests { + +// Macro for debugging OpenGL state. +#define TEST_DBG(txt) \ + printf("before %s\n", txt); \ + window.get_context()->check_error(); \ + printf("after %s\n", txt); + +void renderer_demo_0(util::Path path) { + opengl::GlWindow window("openage renderer test", 800, 600); + + auto renderer = window.make_renderer(); + + auto vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + R"s( +#version 330 + +layout(location=0) in vec2 position; +layout(location=1) in vec2 uv; +uniform mat4 mvp; +out vec2 v_uv; + +void main() { + gl_Position = mvp * vec4(position, 0.0, 1.0); + v_uv = vec2(uv.x, 1.0 - uv.y); +} +)s"); + + auto fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + R"s( +#version 330 + +in vec2 v_uv; +uniform sampler2D tex; +uniform uint u_id; + +layout(location=0) out vec4 col; +layout(location=1) out uint id; + +void main() { + vec4 tex_val = texture(tex, v_uv); + if (tex_val.a == 0) { + discard; + } + col = tex_val; + id = u_id + 1u; +} +)s"); + + auto vshader_display_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + R"s( +#version 330 + +layout(location=0) in vec2 position; +layout(location=1) in vec2 uv; +uniform mat4 proj; +out vec2 v_uv; + +void main() { + gl_Position = proj * vec4(position, 0.0, 1.0); + v_uv = uv; +} +)s"); + + auto fshader_display_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::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 tex = resources::Texture2dData(path / "/assets/gaben.png"); + auto gltex = renderer->add_texture(tex); + auto unif_in2 = shader->new_uniform_input( + "mvp", transform2.matrix(), + //"color", Eigen::Vector4f(0.0f, 1.0f, 0.0f, 1.0f), + "u_id", 2u, + "tex", gltex.get() + ); + + 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::make_quad()); + 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::Texture2dInfo(size.first, size.second, resources::pixel_format::rgba8)); + auto id_texture = renderer->add_texture(resources::Texture2dInfo(size.first, size.second, resources::pixel_format::r32ui)); + auto depth_texture = renderer->add_texture(resources::Texture2dInfo(size.first, size.second, 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::Texture2dData id_texture_data = id_texture->into_data(); + bool texture_data_valid = false; + + glDepthFunc(GL_LEQUAL); + glDepthRange(0.0, 1.0); + // what is this + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + window.add_mouse_button_callback([&] (SDL_MouseButtonEvent const& ev) { + auto x = ev.x; + auto y = ev.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."); + renderer->display_into_data().store(path / "/assets/screen.png"); + } ); + + window.add_resize_callback([&] (size_t w, size_t h) { + // Calculate projection matrix + float aspectRatio = float(w)/float(h); + float xScale = 1.0/aspectRatio; + + Eigen::Matrix4f pmat; + pmat << xScale, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1; + + // resize fbo + color_texture = renderer->add_texture(resources::Texture2dInfo(w, h, resources::pixel_format::rgba8)); + id_texture = renderer->add_texture(resources::Texture2dInfo(w, h, resources::pixel_format::r32ui)); + depth_texture = renderer->add_texture(resources::Texture2dInfo(w, h, resources::pixel_format::depth24)); + fbo = renderer->create_texture_target( { color_texture.get(), id_texture.get(), depth_texture.get() } ); + texture_data_valid = false; + + shader_display->update_uniform_input(color_texture_uniform.get(), "color_texture", color_texture.get(), "proj", pmat); + pass.target = fbo.get(); + } ); + + while (!window.should_close()) { + renderer->render(pass); + renderer->render(display_pass); + window.update(); + window.get_context()->check_error(); + } +} + +void renderer_demo(int demo_id, util::Path path) { + switch (demo_id) { + case 0: + renderer_demo_0(path); + 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..467ec256f3 --- /dev/null +++ b/libopenage/renderer/tests.h @@ -0,0 +1,16 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +// pxd: from libopenage.util.path cimport Path +#include "../util/path.h" + + +namespace openage { +namespace renderer { +namespace tests { + +// pxd: void renderer_demo(int demo_id, Path path) except + +void renderer_demo(int demo_id, util::Path path); + +}}} // openage::renderer::tests diff --git a/libopenage/renderer/texture.cpp b/libopenage/renderer/texture.cpp new file mode 100644 index 0000000000..e2bfdc1302 --- /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 { + +Texture2d::Texture2d(resources::Texture2dInfo info) + : info(info) {} + +Texture2d::~Texture2d() {} + +const resources::Texture2dInfo& Texture2d::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..e00dc7bc5b --- /dev/null +++ b/libopenage/renderer/texture.h @@ -0,0 +1,36 @@ +// 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 Texture2d { +public: + virtual ~Texture2d(); + + /// Returns the texture information. + const resources::Texture2dInfo& get_info() const; + + /// Copies this texture's data from graphics hardware into a CPU-accessible + /// Texture2dData buffer. + virtual resources::Texture2dData into_data() = 0; + + /// Uploads the provided data into the GPU texture storage. The format has + /// to match the format this Texture was originally created with. + virtual void upload(resources::Texture2dData const&) = 0; + +protected: + /// Constructs the base with the given information. + Texture2d(resources::Texture2dInfo); + + /// Information about the size, format, etc. of this texture. + resources::Texture2dInfo info; +}; + +}} diff --git a/libopenage/renderer/texture_array.cpp b/libopenage/renderer/texture_array.cpp new file mode 100644 index 0000000000..d951450531 --- /dev/null +++ b/libopenage/renderer/texture_array.cpp @@ -0,0 +1,18 @@ +// Copyright 2018 the openage authors. See copying.md for legal info. + +#include "texture_array.h" + + +namespace openage { +namespace renderer { + +Texture2dArray::Texture2dArray(resources::Texture2dInfo info) + : layer_info(info) {} + +Texture2dArray::~Texture2dArray() {} + +resources::Texture2dInfo const& Texture2dArray::get_info() const { + return this->layer_info; +} + +}} // namespace openage::renderer diff --git a/libopenage/renderer/texture_array.h b/libopenage/renderer/texture_array.h new file mode 100644 index 0000000000..a01da738eb --- /dev/null +++ b/libopenage/renderer/texture_array.h @@ -0,0 +1,34 @@ +// Copyright 2018 the openage authors. See copying.md for legal info. + +#pragma once + +#include "resources/texture_data.h" + + +namespace openage { +namespace renderer { + +/// A texture array, where individual elements are 2D textures. The array elements +/// are referred to as "layers", and every layer must have the same format +/// (size, pixel format, etc). +class Texture2dArray { +public: + virtual ~Texture2dArray(); + + /// Returns information about the layer format. + resources::Texture2dInfo const& get_info() const; + + /// Uploads the given texture data into the specified layer. `layer` must + /// be strictly less than the size of the array and the data format must + /// match the format this array was originally created with. + virtual void upload(size_t layer, resources::Texture2dData const&) = 0; + +protected: + /// Constructs the base class. + Texture2dArray(resources::Texture2dInfo); + + /// Information about the size, format, etc. of every layer in this array. + resources::Texture2dInfo layer_info; +}; + +}} // openage::renderer diff --git a/libopenage/renderer/vulkan/CMakeLists.txt b/libopenage/renderer/vulkan/CMakeLists.txt new file mode 100644 index 0000000000..fa77887b8c --- /dev/null +++ b/libopenage/renderer/vulkan/CMakeLists.txt @@ -0,0 +1,6 @@ +add_sources(libopenage + graphics_device.cpp + loader.cpp + renderer.cpp + windowvk.cpp +) diff --git a/libopenage/renderer/vulkan/README.md b/libopenage/renderer/vulkan/README.md new file mode 100644 index 0000000000..0982ebf6d7 --- /dev/null +++ b/libopenage/renderer/vulkan/README.md @@ -0,0 +1,3 @@ +# Vulkan renderer + +An experimental, mostly unimplemented Vulkan renderer. Can draw one triangle at best. diff --git a/libopenage/renderer/vulkan/graphics_device.cpp b/libopenage/renderer/vulkan/graphics_device.cpp new file mode 100644 index 0000000000..a7abc97ce4 --- /dev/null +++ b/libopenage/renderer/vulkan/graphics_device.cpp @@ -0,0 +1,168 @@ +#include "graphics_device.h" + +#include + +#include "../../error/error.h" +#include "../../log/log.h" + +#include "util.h" + + +namespace openage { +namespace renderer { +namespace vulkan { + +std::experimental::optional VlkGraphicsDevice::find_device_surface_support(VkPhysicalDevice dev, VkSurfaceKHR surf) { + // Search for queue families in the device + auto q_fams = vk_do_ritual(vkGetPhysicalDeviceQueueFamilyProperties, dev); + + std::experimental::optional maybe_graphics_fam = {}; + std::experimental::optional maybe_present_fam = {}; + + // Figure out if any of the families supports graphics + for (size_t i = 0; i < q_fams.size(); i++) { + auto const& q_fam = q_fams[i]; + + if (q_fam.queueCount > 0) { + if (q_fam.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + maybe_graphics_fam = i; + + // See if it also supports present + VkBool32 support = false; + vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surf, &support); + if (support) { + // This family support both, we're done + maybe_present_fam = i; + break; + } + } + } + } + + if (!maybe_graphics_fam) { + // This device has no graphics queue family that works with the surface + return {}; + } + + SurfaceSupportDetails details = {}; + details.phys_device = dev; + details.surface = surf; + + // If we have found a family that support both graphics and present + if (maybe_present_fam) { + details.graphics_fam = *maybe_graphics_fam; + details.maybe_present_fam = {}; + } else { + // Otherwise look for a present-only queue + for (size_t i = 0; i < q_fams.size(); i++) { + auto const& q_fam = q_fams[i]; + if (q_fam.queueCount > 0) { + VkBool32 support = false; + vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surf, &support); + if (support) { + maybe_present_fam = i; + break; + } + } + } + + if (!maybe_present_fam) { + // This device has no present queue family that works with the surface + return {}; + } + + details.graphics_fam = *maybe_graphics_fam; + details.maybe_present_fam = maybe_present_fam; + } + + // Obtain other information + details.surface_formats = vk_do_ritual(vkGetPhysicalDeviceSurfaceFormatsKHR, dev, surf); + details.present_modes = vk_do_ritual(vkGetPhysicalDeviceSurfacePresentModesKHR, dev, surf); + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev, surf, &details.surface_caps); + + // Finally, check that we have at least one format and present mode + if (details.surface_formats.empty() || details.present_modes.empty()) { + return {}; + } + + return details; +} + +VlkGraphicsDevice::VlkGraphicsDevice(VkPhysicalDevice dev, std::vector const& q_fams) + : phys_device(dev) +{ + // Prepare queue creation info for each family requested + std::vector q_infos(q_fams.size()); + const float p = 1.0f; + + for (size_t i = 0; i < q_fams.size(); i++) { + q_infos[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + q_infos[i].queueFamilyIndex = q_fams[i]; + q_infos[i].queueCount = 1; + q_infos[i].pQueuePriorities = &p; + } + + // Request these extensions + std::vector ext_names = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; + + // Check if extensions are available + auto exts = vk_do_ritual(vkEnumerateDeviceExtensionProperties, dev, nullptr); + for (auto ext : ext_names) { + if (std::count_if(exts.begin(), exts.end(), [=] (VkExtensionProperties const& p) { + return std::strcmp(p.extensionName, ext) == 0; + } ) == 0) + { + throw Error(MSG(err) << "Tried to instantiate device, but it's missing this extension: " << ext); + } + } + +#ifndef NDEBUG + { + VkPhysicalDeviceProperties dev_props; + vkGetPhysicalDeviceProperties(this->phys_device, &dev_props); + log::log(MSG(dbg) << "Chosen Vulkan graphics device: " << dev_props.deviceName); + log::log(MSG(dbg) << "Device extensions:"); + for (auto const& ext : exts) { + log::log(MSG(dbg) << "\t" << ext.extensionName); + } + } +#endif + + // Prepare device creation + VkDeviceCreateInfo create_dev {}; + create_dev.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + create_dev.queueCreateInfoCount = q_infos.size(); + create_dev.pQueueCreateInfos = q_infos.data(); + create_dev.enabledExtensionCount = ext_names.size(); + create_dev.ppEnabledExtensionNames = ext_names.data(); + + VkPhysicalDeviceFeatures features {}; + // TODO request features + create_dev.pEnabledFeatures = &features; + + VK_CALL_CHECKED(vkCreateDevice, this->phys_device, &create_dev, nullptr, &this->device); + + // Obtain handles for the created queues + this->queues.resize(q_fams.size()); + for (size_t i = 0; i < q_fams.size(); i++) { + vkGetDeviceQueue(this->device, q_fams[i], 0, &this->queues[i]); + } +} + +VkPhysicalDevice VlkGraphicsDevice::get_physical_device() const { + return this->phys_device; +} + +VkDevice VlkGraphicsDevice::get_device() const { + return this->device; +} + +VkQueue VlkGraphicsDevice::get_queue(size_t idx) const { + return this->queues[idx]; +} + +VlkGraphicsDevice::~VlkGraphicsDevice() { + vkDestroyDevice(this->device, nullptr); +} + +}}} // openage::renderer::vulkan diff --git a/libopenage/renderer/vulkan/graphics_device.h b/libopenage/renderer/vulkan/graphics_device.h new file mode 100644 index 0000000000..836fb1c49b --- /dev/null +++ b/libopenage/renderer/vulkan/graphics_device.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +#include + + +namespace openage { +namespace renderer { +namespace vulkan { + +/// Contains information about the support of a given physical device for a given surface, +/// for example the formats using which it can present onto the surface. +struct SurfaceSupportDetails { + /// The physical device. + VkPhysicalDevice phys_device; + + /// The surface to which it can draw. + VkSurfaceKHR surface; + + /// Available present modes. + std::vector present_modes; + + /// Available surface formats. + std::vector surface_formats; + + /// Various capabilities of presentation to the surface. + VkSurfaceCapabilitiesKHR surface_caps; + + /// Index of the device queue family with graphics support. + uint32_t graphics_fam; + + /// Index of the queue family with presentation support. This might be the same as the graphics + /// family, in which case this optional is empty. + std::experimental::optional maybe_present_fam; +}; + +/// Owns a device capable of graphics operations and surface presentation using WSI. +class VlkGraphicsDevice { + /// The underlying physical device. + VkPhysicalDevice phys_device; + + /// Logical device, owned by this object. + VkDevice device; + + /// The queues instantiated for this device. + std::vector queues; + +public: + /// Given a physical device and a surface, checks whether the device is capable of presenting to the surface. + /// If it is, returns information about its presentation capabilities, otherwise returns an empty optional. + static std::experimental::optional find_device_surface_support(VkPhysicalDevice, VkSurfaceKHR); + + /// Given a physical device and a list of queue family indices in that device, instantiates + /// a logical device with a queue per each of the families. The device has to support the + /// swapchain extension. + VlkGraphicsDevice(VkPhysicalDevice dev, std::vector const& q_fams); + + VkPhysicalDevice get_physical_device() const; + VkDevice get_device() const; + VkQueue get_queue(size_t idx) const; + + // TODO structure isn't ideal, maybe store SurfaceSupportDetails in here? + + ~VlkGraphicsDevice(); +}; + +}}} // openage::renderer::vulkan diff --git a/libopenage/renderer/vulkan/loader.cpp b/libopenage/renderer/vulkan/loader.cpp new file mode 100644 index 0000000000..36d499072b --- /dev/null +++ b/libopenage/renderer/vulkan/loader.cpp @@ -0,0 +1,55 @@ +#include "loader.h" + +#include "../../error/error.h" + + +namespace openage { +namespace renderer { +namespace vulkan { + +VlkLoader::VlkLoader() + : inited(false) {} + +void VlkLoader::init(VkInstance instance) { + #ifndef NDEBUG + this->pCreateDebugReportCallbackEXT = PFN_vkCreateDebugReportCallbackEXT(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); + this->pDestroyDebugReportCallbackEXT = PFN_vkDestroyDebugReportCallbackEXT(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")); + #endif + + this->inited = true; +} + +#ifndef NDEBUG +VkResult VlkLoader::vkCreateDebugReportCallbackEXT( + VkInstance instance, + const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugReportCallbackEXT* pCallback +) { + if (!this->inited) { + throw Error(MSG(err) << "Tried to request function from Vulkan extension loader before initializing it."); + } + + if (this->pCreateDebugReportCallbackEXT != nullptr) { + return this->pCreateDebugReportCallbackEXT(instance, pCreateInfo, pAllocator, pCallback); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void VlkLoader::vkDestroyDebugReportCallbackEXT( + VkInstance instance, + VkDebugReportCallbackEXT callback, + const VkAllocationCallbacks* pAllocator +) { + if (!this->inited) { + throw Error(MSG(err) << "Tried to request function from Vulkan extension loader before initializing it."); + } + + if (this->pDestroyDebugReportCallbackEXT != nullptr) { + this->pDestroyDebugReportCallbackEXT(instance, callback, pAllocator); + } +} +#endif + +}}} // openage::renderer::vulkan diff --git a/libopenage/renderer/vulkan/loader.h b/libopenage/renderer/vulkan/loader.h new file mode 100644 index 0000000000..f08742f30a --- /dev/null +++ b/libopenage/renderer/vulkan/loader.h @@ -0,0 +1,44 @@ +#pragma once + +#include + + +namespace openage { +namespace renderer { +namespace vulkan { + +/// A class for dynamically loading Vulkan extension functions. +class VlkLoader { +#ifndef NDEBUG + PFN_vkCreateDebugReportCallbackEXT pCreateDebugReportCallbackEXT; + PFN_vkDestroyDebugReportCallbackEXT pDestroyDebugReportCallbackEXT; +#endif + + bool inited; + +public: + VlkLoader(); + + /// Initialize this loader for the given Vulkan instance. + void init(VkInstance); + +#ifndef NDEBUG + /// Part of VK_EXT_debug_report, allows setting a callback for debug events. + VkResult vkCreateDebugReportCallbackEXT( + VkInstance instance, + const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugReportCallbackEXT* pCallback + ); + + + /// Part of VK_EXT_debug_report, destroys the debug callback object. + void vkDestroyDebugReportCallbackEXT( + VkInstance instance, + VkDebugReportCallbackEXT callback, + const VkAllocationCallbacks* pAllocator + ); +#endif +}; + +}}} // openage::renderer::vulkan diff --git a/libopenage/renderer/vulkan/render_target.h b/libopenage/renderer/vulkan/render_target.h new file mode 100644 index 0000000000..600326c37a --- /dev/null +++ b/libopenage/renderer/vulkan/render_target.h @@ -0,0 +1,162 @@ +#pragma once + +#include "../renderer.h" + +#include "graphics_device.h" + + +namespace openage { +namespace renderer { +namespace vulkan { + +/// A Vulkan representation of a display target that can be drawn onto directly, +/// that is _not_ by copying data from another object. +class VlkDrawableDisplay { +public: + // use shared_ptr? + VkDevice device; + VkSwapchainKHR swapchain; + VkFormat format; + VkExtent2D extent; + std::vector images; + std::vector image_views; + std::vector framebuffers; + + VkSurfaceFormatKHR choose_best_surface_format(std::vector const& formats) { + // If the implementation doesn't have preferred formats, choose our own + if (formats.size() == 1 + && formats[0].format == VK_FORMAT_UNDEFINED) + { + return { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }; + } + + // Otherwise if one of the preferred formats is good, choose that + for (const auto& fmt : formats) { + if (fmt.format == VK_FORMAT_B8G8R8_UNORM + && fmt.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + { + return fmt; + } + } + + // Otherwise select any format + return formats[0]; + } + + VkPresentModeKHR choose_best_present_mode(std::vector const& modes) { + VkPresentModeKHR ret = VK_PRESENT_MODE_MAILBOX_KHR; + + for (const auto& mode : modes) { + if (mode == VK_PRESENT_MODE_MAILBOX_KHR) { + return mode; + } else if (mode == VK_PRESENT_MODE_FIFO_KHR) { + ret = mode; + } else if (mode == VK_PRESENT_MODE_FIFO_RELAXED_KHR + && ret != VK_PRESENT_MODE_FIFO_KHR) + { + ret = mode; + } else if (mode == VK_PRESENT_MODE_IMMEDIATE_KHR + && ret != VK_PRESENT_MODE_FIFO_KHR + && ret != VK_PRESENT_MODE_FIFO_RELAXED_KHR) + { + ret = mode; + } + } + + return ret; + } + + VlkDrawableDisplay(VkDevice device, SurfaceSupportDetails details) + : device(device) { + VkSurfaceFormatKHR format = choose_best_surface_format(details.surface_formats); + this->format = format.format; + + VkPresentModeKHR mode = choose_best_present_mode(details.present_modes); + + if (details.surface_caps.currentExtent.width != std::numeric_limits::max()) { + this->extent = details.surface_caps.currentExtent; + } else { + // TODO choose a size from this->size in this case + throw Error(MSG(err) << "Window manager does not provide a window size."); + } + + uint32_t img_count = details.surface_caps.minImageCount + 1; + if (details.surface_caps.maxImageCount != 0) { + img_count = std::max(img_count, details.surface_caps.maxImageCount); + } + + VkSwapchainCreateInfoKHR cr_swap = {}; + cr_swap.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + cr_swap.surface = details.surface; + cr_swap.minImageCount = img_count; + cr_swap.imageFormat = this->format; + cr_swap.imageColorSpace = format.colorSpace; + cr_swap.presentMode = mode; + // TODO why doesn't validation work? + cr_swap.imageExtent = this->extent; + cr_swap.imageArrayLayers = 1; + // or VK_IMAGE_USAGE_TRANSFER_DST_BIT if not drawing directly (postprocess) + cr_swap.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + if (details.maybe_present_fam) { + // We have to share the swapchain between different queues in this case + cr_swap.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + cr_swap.queueFamilyIndexCount = 2; + std::array q_fams = {{ details.graphics_fam, *details.maybe_present_fam }}; + cr_swap.pQueueFamilyIndices = q_fams.data(); + } else { + // Otherwise only one queue can access it + cr_swap.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + cr_swap.preTransform = details.surface_caps.currentTransform; + cr_swap.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + // Discard pixels which are not visible + cr_swap.clipped = VK_TRUE; + cr_swap.oldSwapchain = VK_NULL_HANDLE; + + VK_CALL_CHECKED(vkCreateSwapchainKHR, this->device, &cr_swap, nullptr, &this->swapchain); + + this->images = vk_do_ritual(vkGetSwapchainImagesKHR, this->device, this->swapchain); + + // TODO move out? + for (auto img : this->images) { + VkImageViewCreateInfo cr_view = {}; + cr_view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + cr_view.image = img; + cr_view.viewType = VK_IMAGE_VIEW_TYPE_2D; + cr_view.format = this->format; + cr_view.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + cr_view.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + cr_view.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + cr_view.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + cr_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + cr_view.subresourceRange.baseMipLevel = 0; + cr_view.subresourceRange.levelCount = 1; + cr_view.subresourceRange.baseArrayLayer = 0; + cr_view.subresourceRange.layerCount = 1; + + VkImageView view; + VK_CALL_CHECKED(vkCreateImageView, this->device, &cr_view, nullptr, &view); + + this->image_views.push_back(view); + } + } + + ~VlkDrawableDisplay() { + vkDestroySwapchainKHR(this->device, this->swapchain, nullptr); + } +}; + +class VlkFramebuffer final : public RenderTarget { + std::vector attachments; + VkFramebuffer framebuffer; + VkViewport viewport; + +public: + VlkFramebuffer(VkRenderPass pass, std::vector const& attachments) { + + } +}; + +}}} // openage::renderer::vulkan diff --git a/libopenage/renderer/vulkan/renderer.cpp b/libopenage/renderer/vulkan/renderer.cpp new file mode 100644 index 0000000000..a0dfbe659b --- /dev/null +++ b/libopenage/renderer/vulkan/renderer.cpp @@ -0,0 +1,312 @@ +#include "renderer.h" + +#include "../../error/error.h" +#include "../../util/path.h" +#include "../../util/fslike/directory.h" + +#include "../resources/shader_source.h" + +#include "util.h" +#include "graphics_device.h" +#include "render_target.h" +#include "shader_program.h" + + +namespace openage { +namespace renderer { +namespace vulkan { + +void VlkRenderer::do_the_thing() { + // Enumerate available physical devices + auto devices = vk_do_ritual(vkEnumeratePhysicalDevices, this->instance); + if (devices.size() == 0) { + throw Error(MSG(err) << "No Vulkan devices available."); + } + + std::vector> support_per_dev; + for (auto dev : devices) { + auto support = VlkGraphicsDevice::find_device_surface_support(dev, surface); + if (support) { + support_per_dev.emplace_back(dev, *support); + } + } + + if (support_per_dev.empty()) { + throw Error(MSG(err) << "No valid Vulkan device available."); + } + + // TODO rate devices based on capabilities + // Given an instance and surface, selects the best (fastest, most capable, etc.) graphics device + // which supports rendering onto that particular surface and constructs the object. + + auto const& info = support_per_dev[0]; + + // Create a logical device with a single queue for both graphics and present + if (info.second.maybe_present_fam) { + throw 0; + } + + VlkGraphicsDevice dev(info.first, { info.second.graphics_fam } ); + + VlkDrawableDisplay display(dev.get_device(), info.second); + + // TODO reinit swapchain on window resize + + auto dir = std::make_shared("/home/wojtek/Programming/C++/openage/"); + + auto vert = resources::ShaderSource( + resources::shader_lang_t::spirv, + resources::shader_stage_t::vertex, + util::Path(dir) / "assets/shaders/vert.spv" + ); + + auto frag = resources::ShaderSource( + resources::shader_lang_t::spirv, + resources::shader_stage_t::fragment, + util::Path(dir) / "assets/shaders/frag.spv" + ); + + VlkShaderProgram prog(dev.get_device(), { vert, frag } ); + + VkPipelineVertexInputStateCreateInfo cr_vert_in = {}; + cr_vert_in.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + // all init'd to 0 + + VkPipelineInputAssemblyStateCreateInfo cr_in_asm = {}; + cr_in_asm.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + cr_in_asm.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + cr_in_asm.primitiveRestartEnable = VK_FALSE; + + VkViewport viewport = {}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = display.extent.width; + viewport.height = display.extent.height; + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = { 0, 0 }; + scissor.extent = display.extent; + + VkPipelineViewportStateCreateInfo cr_view = {}; + cr_view.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + cr_view.viewportCount = 1; + cr_view.pViewports = &viewport; + cr_view.scissorCount = 1; + cr_view.pScissors = &scissor; + + VkPipelineRasterizationStateCreateInfo cr_raster = {}; + cr_raster.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + cr_raster.depthClampEnable = VK_FALSE; + cr_raster.rasterizerDiscardEnable = VK_FALSE; + cr_raster.polygonMode = VK_POLYGON_MODE_FILL; + cr_raster.lineWidth = 1.0f; + cr_raster.cullMode = VK_CULL_MODE_BACK_BIT; + cr_raster.frontFace = VK_FRONT_FACE_CLOCKWISE; + cr_raster.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo cr_msaa = {}; + cr_msaa.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + cr_msaa.sampleShadingEnable = VK_FALSE; + cr_msaa.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState blend_state = {}; + blend_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT + | VK_COLOR_COMPONENT_G_BIT + | VK_COLOR_COMPONENT_B_BIT + | VK_COLOR_COMPONENT_A_BIT; + blend_state.blendEnable = VK_TRUE; + blend_state.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + blend_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + blend_state.colorBlendOp = VK_BLEND_OP_ADD; + blend_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + blend_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + blend_state.alphaBlendOp = VK_BLEND_OP_ADD; + + VkPipelineColorBlendStateCreateInfo cr_blend = {}; + cr_blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + cr_blend.logicOpEnable = VK_FALSE; + cr_blend.attachmentCount = 1; + cr_blend.pAttachments = &blend_state; + + VkPipelineLayoutCreateInfo cr_layout = {}; + cr_layout.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + // empty object + + VkPipelineLayout layout; + VK_CALL_CHECKED(vkCreatePipelineLayout, dev.get_device(), &cr_layout, nullptr, &layout); + + /// RENDERPASS + VkAttachmentDescription color_attachment = {}; + color_attachment.format = display.format; + color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference color_attachment_ref = {}; + color_attachment_ref.attachment = 0; + color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &color_attachment_ref; + + VkSubpassDependency dep = {}; + dep.srcSubpass = VK_SUBPASS_EXTERNAL; + dep.dstSubpass = 0; + dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dep.srcAccessMask = 0; + dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + VkRenderPassCreateInfo cr_render_pass = {}; + cr_render_pass.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + cr_render_pass.attachmentCount = 1; + cr_render_pass.pAttachments = &color_attachment; + cr_render_pass.subpassCount = 1; + cr_render_pass.pSubpasses = &subpass; + cr_render_pass.dependencyCount = 1; + cr_render_pass.pDependencies = &dep; + + VkRenderPass render_pass; + VK_CALL_CHECKED(vkCreateRenderPass, dev.get_device(), &cr_render_pass, nullptr, &render_pass); + /// RENDERPASS + + VkGraphicsPipelineCreateInfo cr_pipeline = {}; + cr_pipeline.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + cr_pipeline.stageCount = 2; + cr_pipeline.pStages = prog.pipeline_stage_infos.data(); + cr_pipeline.pVertexInputState = &cr_vert_in; + cr_pipeline.pInputAssemblyState = &cr_in_asm; + cr_pipeline.pViewportState = &cr_view; + cr_pipeline.pRasterizationState = &cr_raster; + cr_pipeline.pMultisampleState = &cr_msaa; + cr_pipeline.pDepthStencilState = nullptr; + cr_pipeline.pColorBlendState = &cr_blend; + cr_pipeline.pDynamicState = nullptr; + cr_pipeline.layout = layout; + cr_pipeline.renderPass = render_pass; + cr_pipeline.subpass = 0; + cr_pipeline.basePipelineHandle = VK_NULL_HANDLE; + cr_pipeline.basePipelineIndex = -1; + + VkPipeline pipeline; + VK_CALL_CHECKED(vkCreateGraphicsPipelines, dev.get_device(), VK_NULL_HANDLE, 1, &cr_pipeline, nullptr, &pipeline); + + std::vector fbufs; + for (auto view : display.image_views) { + VkFramebufferCreateInfo cr_fbuf = {}; + cr_fbuf.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + cr_fbuf.renderPass = render_pass; + cr_fbuf.attachmentCount = 1; + cr_fbuf.pAttachments = &view; + cr_fbuf.width = display.extent.width; + cr_fbuf.height = display.extent.height; + cr_fbuf.layers = 1; + + VkFramebuffer fbuf; + VK_CALL_CHECKED(vkCreateFramebuffer, dev.get_device(), &cr_fbuf, nullptr, &fbuf); + + fbufs.push_back(fbuf); + } + + VkCommandPoolCreateInfo cr_pool = {}; + cr_pool.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + cr_pool.queueFamilyIndex = info.second.graphics_fam; + cr_pool.flags = 0; + + VkCommandPool pool; + VK_CALL_CHECKED(vkCreateCommandPool, dev.get_device(), &cr_pool, nullptr, &pool); + + std::vector cmd_bufs(fbufs.size()); + VkCommandBufferAllocateInfo cr_cmd_bufs = {}; + cr_cmd_bufs.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + cr_cmd_bufs.commandPool = pool; + cr_cmd_bufs.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + cr_cmd_bufs.commandBufferCount = static_cast(cmd_bufs.size()); + + VK_CALL_CHECKED(vkAllocateCommandBuffers, dev.get_device(), &cr_cmd_bufs, cmd_bufs.data()); + + for (size_t i = 0; i < cmd_bufs.size(); i++) { + auto cmd_buf = cmd_bufs[i]; + auto fbuf = fbufs[i]; + + VkCommandBufferBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + begin_info.pInheritanceInfo = nullptr; + + vkBeginCommandBuffer(cmd_buf, &begin_info); + + VkRenderPassBeginInfo cmd_render = {}; + cmd_render.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + cmd_render.renderPass = render_pass; + cmd_render.framebuffer = fbuf; + cmd_render.renderArea.offset = { 0, 0 }; + cmd_render.renderArea.extent = display.extent; + + VkClearValue clear_color = {{{ 0.0f, 0.0f, 0.0f, 1.0f }}}; + cmd_render.clearValueCount = 1; + cmd_render.pClearValues = &clear_color; + + vkCmdBeginRenderPass(cmd_buf, &cmd_render, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + + vkCmdDraw(cmd_buf, 3, 1, 0, 0); + + vkCmdEndRenderPass(cmd_buf); + + VK_CALL_CHECKED(vkEndCommandBuffer, cmd_buf); + + VkSemaphoreCreateInfo cr_sem = {}; + cr_sem.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkSemaphore sem_image_ready; + VkSemaphore sem_render_done; + VK_CALL_CHECKED(vkCreateSemaphore, dev.get_device(), &cr_sem, nullptr, &sem_image_ready); + VK_CALL_CHECKED(vkCreateSemaphore, dev.get_device(), &cr_sem, nullptr, &sem_render_done); + + uint32_t img_idx = 0; + VK_CALL_CHECKED(vkAcquireNextImageKHR, dev.get_device(), display.swapchain, std::numeric_limits::max(), sem_image_ready, VK_NULL_HANDLE, &img_idx); + + VkSubmitInfo submit = {}; + submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit.waitSemaphoreCount = 1; + submit.pWaitSemaphores = &sem_image_ready; + VkPipelineStageFlags mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + submit.pWaitDstStageMask = &mask; + + submit.commandBufferCount = 1; + submit.pCommandBuffers = &cmd_bufs[img_idx]; + + submit.signalSemaphoreCount = 1; + submit.pSignalSemaphores = &sem_render_done; + + VK_CALL_CHECKED(vkQueueSubmit, dev.get_queue(0), 1, &submit, VK_NULL_HANDLE); + + VkPresentInfoKHR present_info = {}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = &sem_render_done; + + present_info.swapchainCount = 1; + present_info.pSwapchains = &display.swapchain; + present_info.pImageIndices = &img_idx; + present_info.pResults = nullptr; + + vkQueuePresentKHR(dev.get_queue(0), &present_info); + + vkQueueWaitIdle(dev.get_queue(0)); + + vkDeviceWaitIdle(dev.get_device()); + } +} + +}}} // openage::renderer::vulkan diff --git a/libopenage/renderer/vulkan/renderer.h b/libopenage/renderer/vulkan/renderer.h new file mode 100644 index 0000000000..45b271e186 --- /dev/null +++ b/libopenage/renderer/vulkan/renderer.h @@ -0,0 +1,30 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "../renderer.h" + +#include "graphics_device.h" + + +namespace openage { +namespace renderer { +namespace vulkan { + +/// A renderer using the Vulkan API. +class VlkRenderer { + VkInstance instance; + + VkSurfaceKHR surface; + +public: + VlkRenderer(VkInstance instance, VkSurfaceKHR surface) + : instance(instance) + , surface(surface) {} + + void do_the_thing(); +}; + +}}} // openage::renderer::vulkan diff --git a/libopenage/renderer/vulkan/shader_program.h b/libopenage/renderer/vulkan/shader_program.h new file mode 100644 index 0000000000..f525a13baf --- /dev/null +++ b/libopenage/renderer/vulkan/shader_program.h @@ -0,0 +1,61 @@ +#pragma once + +#include "../../error/error.h" +#include "../../log/log.h" + +#include "../resources/shader_source.h" +#include "../shader_program.h" + + +namespace openage { +namespace renderer { +namespace vulkan { + +static VkShaderStageFlagBits vk_shader_stage(resources::shader_stage_t stage) { + switch (stage) { + case resources::shader_stage_t::vertex: return VK_SHADER_STAGE_VERTEX_BIT; + case resources::shader_stage_t::geometry: return VK_SHADER_STAGE_GEOMETRY_BIT; + case resources::shader_stage_t::tesselation_control: return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + case resources::shader_stage_t::tesselation_evaluation: return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; + case resources::shader_stage_t::fragment: return VK_SHADER_STAGE_FRAGMENT_BIT; + } +} + +class VlkShaderProgram /* final : public ShaderProgram */ { +public: + std::vector modules; + std::vector pipeline_stage_infos; + + explicit VlkShaderProgram(VkDevice dev, std::vector const& srcs) { + // TODO reflect with spirv-cross + // TODO if glsl, compile to spirv with libshaderc + + for (auto const& src : srcs) { + if (src.get_lang() != resources::shader_lang_t::spirv) { + throw Error(MSG(err) << "Unsupported shader language in Vulkan shader."); + } + + VkShaderModuleCreateInfo cr_shdr = {}; + cr_shdr.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + cr_shdr.codeSize = src.get_source().size(); + cr_shdr.pCode = reinterpret_cast(src.get_source().data()); + + VkShaderModule mod; + VK_CALL_CHECKED(vkCreateShaderModule, dev, &cr_shdr, nullptr, &mod); + + this->modules.push_back(mod); + + VkPipelineShaderStageCreateInfo cr_stage = {}; + cr_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + cr_stage.stage = vk_shader_stage(src.get_stage()); + cr_stage.module = mod; + cr_stage.pName = "main"; + + this->pipeline_stage_infos.push_back(cr_stage); + } + + log::log(MSG(dbg) << "Created modules for Vulkan shader"); + } +}; + +}}} // openage::renderer::vulkan diff --git a/libopenage/renderer/vulkan/util.h b/libopenage/renderer/vulkan/util.h new file mode 100644 index 0000000000..459235c41e --- /dev/null +++ b/libopenage/renderer/vulkan/util.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +#include "../../error/error.h" + + +template +std::vector vk_do_ritual(R2 (*func)(uint32_t*, R*)) { + uint32_t count = 0; + func(&count, nullptr); + std::vector ret(count); + func(&count, ret.data()); + + return ret; +} + +template +std::vector vk_do_ritual(R2 (*func)(T, uint32_t*, R*), T2&& a) { + uint32_t count = 0; + func(std::forward(a), &count, nullptr); + std::vector ret(count); + func(std::forward(a), &count, ret.data()); + + return ret; +} + +template +std::vector vk_do_ritual(R2 (*func)(T, U, uint32_t*, R*), T2&& a, U2&& b) { + uint32_t count = 0; + func(std::forward(a), std::forward(b), &count, nullptr); + std::vector ret(count); + func(std::forward(a), std::forward(b), &count, ret.data()); + + return ret; +} + +template +std::vector vk_do_ritual(R2 (*func)(T, U, V, uint32_t*, R*), T2&& a, U2&& b, V2&& c) { + uint32_t count = 0; + func(std::forward(a), std::forward(b), std::forward(c), &count, nullptr); + std::vector ret(count); + func(std::forward(a), std::forward(b), std::forward(c), &count, ret.data()); + + return ret; +} + +#define VK_CALL_CHECKED(fun, ...) \ + { \ + VkResult res = fun(__VA_ARGS__); \ + if (res != VK_SUCCESS) { \ + } \ + } + +//throw Error(MSG(err) << "Call to Vulkan function " << #fun << " failed with " << vk::to_string(vk::Result(res)) << "."); \ +// diff --git a/libopenage/renderer/vulkan/windowvk.cpp b/libopenage/renderer/vulkan/windowvk.cpp new file mode 100644 index 0000000000..4d4b6a1aca --- /dev/null +++ b/libopenage/renderer/vulkan/windowvk.cpp @@ -0,0 +1,170 @@ +#include "windowvk.h" + +#include + +#include + +#include "../../error/error.h" +#include "../../log/log.h" +#include "graphics_device.h" +#include "util.h" + + + +namespace openage { +namespace renderer { +namespace vulkan { + +#ifndef NDEBUG +static VKAPI_ATTR VkBool32 VKAPI_CALL vlk_debug_cb( + VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT objType, + uint64_t obj, + size_t location, + int32_t code, + const char* layerPrefix, + const char* msg, + void* userData) +{ + log::log(MSG(dbg) << layerPrefix << " " << msg); + + return VK_FALSE; +} +#endif + +/// Queries the Vulkan implementation for available extensions and layers. +static vlk_capabilities find_capabilities() { + vlk_capabilities caps; + + // Find which layers are available. + auto layers = vk_do_ritual(vkEnumerateInstanceLayerProperties); + + log::log(MSG(dbg) << "Available Vulkan layers:"); + for (auto const& lr : layers) { + caps.layers.insert(lr.layerName); + log::log(MSG(dbg) << "\t" << lr.layerName); + } + + // Find which extensions are available. + // This is annoying, since an enumeration call without a filter-by-layer parameter + // won't return all extensions. We thus have to enumerate extensions for each layer + // and then remove duplicates. + + // First retrieve non-layer extensions. + auto props = vk_do_ritual(vkEnumerateInstanceExtensionProperties, nullptr); + + for (auto const& p : props) { + caps.extensions.emplace(p.extensionName); + } + + // Then retrieve extensions from layers. + for (auto const& lr : layers) { + auto lr_props = vk_do_ritual(vkEnumerateInstanceExtensionProperties, lr.layerName); + + for (auto const& p : lr_props) { + caps.extensions.emplace(p.extensionName); + } + } + + log::log(MSG(dbg) << "Available Vulkan extensions:"); + for (const auto& ext : caps.extensions) { + log::log(MSG(dbg) << "\t" << ext); + } + + return caps; +} + +VlkWindow::VlkWindow(const char* title) + : capabilities(find_capabilities()) +{ + // Find which extensions the SDL window requires. + auto extension_names = vk_do_ritual(SDL_Vulkan_GetInstanceExtensions, this->window); + /* + if (succ != SDL_TRUE) { + throw Error(MSG(err) << "Failed to obtain required Vulkan extension names from SDL."); + } + */ + +#ifndef NDEBUG + extension_names.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); +#endif + extension_names.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + + // Check if all extensions are available. + for (auto ext : extension_names) { + if (this->capabilities.extensions.count(ext) == 0) { + throw Error(MSG(err) << "Vulkan driver is missing required extension: " << ext); + } + } + + // The names of Vulkan layers which we need. + std::vector layer_names; +#ifndef NDEBUG + layer_names.push_back("VK_LAYER_LUNARG_standard_validation"); +#endif + + // Check if all layers are available + for (auto lr : layer_names) { + if (this->capabilities.layers.count(lr) == 0) { + throw Error(MSG(err) << "Vulkan driver is missing required layer: " << lr); + } + } + + // Setup application description. + VkApplicationInfo app_info = {}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pApplicationName = title; + app_info.apiVersion = VK_MAKE_VERSION(1, 0, 57); + + VkInstanceCreateInfo inst_info = {}; + inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + inst_info.pApplicationInfo = &app_info; + inst_info.enabledExtensionCount = extension_names.size(); + inst_info.ppEnabledExtensionNames = extension_names.data(); + inst_info.enabledLayerCount = layer_names.size(); + inst_info.ppEnabledLayerNames = layer_names.data(); + + // A Vulkan instance is a proxy for all usage of Vulkan from our application, + // kind of like a GL context. Initialize it. + VK_CALL_CHECKED(vkCreateInstance, &inst_info, nullptr, &this->instance); + + this->loader.init(this->instance); + +#ifndef NDEBUG + VkDebugReportCallbackCreateInfoEXT cb_info = {}; + cb_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; + cb_info.flags = + VK_DEBUG_REPORT_ERROR_BIT_EXT + | VK_DEBUG_REPORT_WARNING_BIT_EXT + | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT + | VK_DEBUG_REPORT_INFORMATION_BIT_EXT + | VK_DEBUG_REPORT_DEBUG_BIT_EXT; + cb_info.pfnCallback = vlk_debug_cb; + + VK_CALL_CHECKED(this->loader.vkCreateDebugReportCallbackEXT, this->instance, &cb_info, nullptr, &this->debug_callback); +#endif + + // Surface is an object that we draw into, corresponding to the window area. + auto succ = SDL_Vulkan_CreateSurface(this->window, this->instance, &this->surface); + if (succ != SDL_TRUE) { + throw Error(MSG(err) << "Failed to create Vulkan surface on SDL window."); + } +} + +VlkWindow::~VlkWindow() { +#ifndef NDEBUG + this->loader.vkDestroyDebugReportCallbackEXT(this->instance, this->debug_callback, nullptr); +#endif + vkDestroySurfaceKHR(this->instance, this->surface, nullptr); + vkDestroyInstance(this->instance, nullptr); +} + +VkInstance VlkWindow::get_instance() const { + return this->instance; +} + +VkSurfaceKHR VlkWindow::get_surface() const { + return this->surface; +} + +}}} // openage::renderer::vulkan diff --git a/libopenage/renderer/vulkan/windowvk.h b/libopenage/renderer/vulkan/windowvk.h new file mode 100644 index 0000000000..bf1b6bd694 --- /dev/null +++ b/libopenage/renderer/vulkan/windowvk.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +#include + +#include "../window.h" + +#include "loader.h" + + +namespace openage { +namespace renderer { +namespace vulkan { + +struct vlk_capabilities { + /// Names of available layers. + std::set layers; + /// Names of available extensions. + // TODO are these only available when the corresponding layer is active? + std::set extensions; +}; + +// TODO dirty hack to graft vk functionality onto window. +// needs better structure (not inheritance! (?)) for proper support +class VlkWindow : public openage::renderer::Window { + vlk_capabilities capabilities; + + VkInstance instance; + VkSurfaceKHR surface; +#ifndef NDEBUG + VkDebugReportCallbackEXT debug_callback; +#endif + VlkLoader loader; + +public: + VlkWindow(const char* title); + ~VlkWindow(); + + VkInstance get_instance() const; + VkSurfaceKHR get_surface() const; +}; + +}}} // openage::renderer::vulkan diff --git a/libopenage/renderer/window.cpp b/libopenage/renderer/window.cpp new file mode 100644 index 0000000000..50ef624ee3 --- /dev/null +++ b/libopenage/renderer/window.cpp @@ -0,0 +1,36 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "window.h" + + +namespace openage { +namespace renderer { + +Window::Window(size_t width, size_t height) + : size(std::make_pair(width, height)) {} + +std::pair Window::get_size() const { + return this->size; +} + +bool Window::should_close() const { + return this->should_be_closed; +} + +void Window::add_mouse_button_callback(mouse_button_cb_t cb) { + this->on_mouse_button.push_back(cb); +} + +void Window::add_mouse_wheel_callback(mouse_wheel_cb_t cb) { + this->on_mouse_wheel.push_back(cb); +} + +void Window::add_key_callback(key_cb_t cb) { + this->on_key.push_back(cb); +} + +void Window::add_resize_callback(resize_cb_t cb) { + this->on_resize.push_back(cb); +} + +}} //openage::renderer diff --git a/libopenage/renderer/window.h b/libopenage/renderer/window.h new file mode 100644 index 0000000000..5f124c3518 --- /dev/null +++ b/libopenage/renderer/window.h @@ -0,0 +1,62 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include + +#include "renderer.h" + + +namespace openage { +namespace renderer { + +class Window { +public: + virtual ~Window() = default; + + /// Returns the dimensions of this window. + std::pair get_size() const; + + /// Returns true if this window should be closed. + bool should_close() const; + + using key_cb_t = std::function; + using mouse_button_cb_t = std::function; + using mouse_wheel_cb_t = std::function; + using resize_cb_t = std::function; + + void add_key_callback(key_cb_t); + void add_mouse_button_callback(mouse_button_cb_t); + void add_mouse_wheel_callback(mouse_wheel_cb_t); + void add_resize_callback(resize_cb_t); + + /// 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. + virtual void set_size(size_t width, size_t height) = 0; + + /// Polls for window events, calls callbacks for these events, swaps front and back framebuffers + /// to present graphics onto screen. This has to be called at the end of every graphics frame. + virtual void update() = 0; + + /// Creates a renderer which uses the window's graphics API and targets the window. + virtual std::unique_ptr make_renderer() = 0; + +protected: + Window(size_t width, size_t height); + + bool should_be_closed = false; + + /// The current size of the framebuffer. + std::pair size; + + std::vector on_key; + std::vector on_mouse_button; + std::vector on_mouse_wheel; + std::vector on_resize; +}; + +}} // namespace openage::renderer 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/cvar/config_file.py b/openage/cvar/config_file.py index 6991177e1a..d1c988ae1a 100644 --- a/openage/cvar/config_file.py +++ b/openage/cvar/config_file.py @@ -34,6 +34,7 @@ def load_config_file(path, set_cvar_func, loaded_files=None): with path.open() as config: for line in config: + print("Reading config line: %s" % line) lstrip = line.lstrip() if not lstrip or lstrip.startswith("#"): continue 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..5e3cee1655 --- /dev/null +++ b/openage/renderer/tests.pyx @@ -0,0 +1,50 @@ +# Copyright 2015-2015 the openage authors. See copying.md for legal info. + +""" +tests for the graphics renderer. +""" + +import argparse + +from libopenage.util.path cimport Path as Path_cpp +from libopenage.pyinterface.pyobject cimport PyObj +from cpython.ref cimport PyObject +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='Demo of the new renderer') + cmd.add_argument("test_id", type=int, help="id of the demo to run.") + cmd.add_argument("--asset-dir", + help="Use this as an additional asset directory.") + cmd.add_argument("--cfg-dir", + help="Use this as an additional config directory.") + + args = cmd.parse_args(argv) + + + from ..cvar.location import get_config_path + from ..assets import get_asset_path + from ..util.fslike.union import Union + + # create virtual file system for data paths + root = Union().root + + # mount the assets folder union at "assets/" + root["assets"].mount(get_asset_path(args.asset_dir)) + + # mount the config folder at "cfg/" + root["cfg"].mount(get_config_path(args.cfg_dir)) + + cdef int renderer_test_id = args.test_id + + cdef Path_cpp root_cpp = Path_cpp(PyObj(root.fsobj), + root.parts) + + with nogil: + renderer_demo_c(renderer_test_id, root_cpp) diff --git a/openage/testing/testlist.py b/openage/testing/testlist.py index 2264a7fc52..ad25302f6d 100644 --- a/openage/testing/testlist.py +++ b/openage/testing/testlist.py @@ -48,6 +48,8 @@ def demos_py(): "demonstrates the translation of Python log messages") yield ("openage.convert.opus.demo.convert", "encodes an opus file from a wave file") + yield ("openage.renderer.tests.renderer_demo", + "showcases the new renderer") def benchmark_py(): From 7fba02911dc23981aaf558a617c84ebe375dc160 Mon Sep 17 00:00:00 2001 From: Wojciech Nawrocki Date: Fri, 22 Jun 2018 00:25:24 +0200 Subject: [PATCH 2/7] renderer: code compliance --- buildsystem/modules/FindInotify.cmake | 2 +- libopenage/renderer/geometry.cpp | 2 +- libopenage/renderer/geometry.h | 2 +- libopenage/renderer/opengl/buffer.cpp | 2 +- libopenage/renderer/opengl/buffer.h | 2 +- libopenage/renderer/opengl/context.cpp | 2 +- libopenage/renderer/opengl/context.h | 2 +- libopenage/renderer/opengl/framebuffer.cpp | 2 +- libopenage/renderer/opengl/framebuffer.h | 2 +- libopenage/renderer/opengl/geometry.cpp | 2 +- libopenage/renderer/opengl/geometry.h | 2 +- libopenage/renderer/opengl/lookup.h | 8 ++++++-- libopenage/renderer/opengl/render_target.cpp | 2 +- libopenage/renderer/opengl/render_target.h | 2 +- libopenage/renderer/opengl/renderer.cpp | 2 +- libopenage/renderer/opengl/renderer.h | 2 +- libopenage/renderer/opengl/shader.cpp | 2 +- libopenage/renderer/opengl/shader.h | 2 +- libopenage/renderer/opengl/shader_program.cpp | 2 +- libopenage/renderer/opengl/shader_program.h | 2 +- libopenage/renderer/opengl/simple_object.cpp | 2 +- libopenage/renderer/opengl/simple_object.h | 2 +- libopenage/renderer/opengl/texture.cpp | 2 +- libopenage/renderer/opengl/texture.h | 2 +- libopenage/renderer/opengl/texture_array.cpp | 2 +- libopenage/renderer/opengl/texture_array.h | 2 +- libopenage/renderer/opengl/uniform_input.h | 2 +- libopenage/renderer/opengl/vertex_array.cpp | 2 +- libopenage/renderer/opengl/vertex_array.h | 2 +- libopenage/renderer/renderer.h | 2 +- libopenage/renderer/resources/mesh_data.cpp | 4 ++-- libopenage/renderer/resources/mesh_data.h | 2 +- libopenage/renderer/resources/shader_source.cpp | 2 +- libopenage/renderer/resources/shader_source.h | 2 +- libopenage/renderer/resources/texture_data.cpp | 2 +- libopenage/renderer/resources/texture_data.h | 2 +- libopenage/renderer/resources/texture_info.cpp | 2 +- libopenage/renderer/resources/texture_info.h | 2 +- libopenage/renderer/sdl_global.h | 2 ++ libopenage/renderer/shader_program.h | 2 +- libopenage/renderer/tests.cpp | 2 +- libopenage/renderer/tests.h | 2 +- libopenage/renderer/texture.cpp | 2 +- libopenage/renderer/texture.h | 2 +- libopenage/renderer/texture_array.cpp | 2 +- libopenage/renderer/texture_array.h | 2 +- libopenage/renderer/vulkan/graphics_device.cpp | 2 ++ libopenage/renderer/vulkan/graphics_device.h | 2 ++ libopenage/renderer/vulkan/loader.cpp | 2 ++ libopenage/renderer/vulkan/loader.h | 2 ++ libopenage/renderer/vulkan/render_target.h | 2 ++ libopenage/renderer/vulkan/renderer.cpp | 2 ++ libopenage/renderer/vulkan/renderer.h | 2 +- libopenage/renderer/vulkan/shader_program.h | 2 ++ libopenage/renderer/vulkan/util.h | 2 ++ libopenage/renderer/vulkan/windowvk.cpp | 2 ++ libopenage/renderer/vulkan/windowvk.h | 2 ++ libopenage/renderer/window.cpp | 2 +- libopenage/renderer/window.h | 2 +- openage/renderer/__init__.py | 2 +- openage/renderer/renderer_cpp.pyx | 2 +- openage/renderer/tests.pyx | 2 +- 62 files changed, 79 insertions(+), 53 deletions(-) diff --git a/buildsystem/modules/FindInotify.cmake b/buildsystem/modules/FindInotify.cmake index 4b0cd367e3..0362028536 100644 --- a/buildsystem/modules/FindInotify.cmake +++ b/buildsystem/modules/FindInotify.cmake @@ -1,4 +1,4 @@ -# Copyright 2014-2015 the openage authors. See copying.md for legal info. +# Copyright 2014-2018 the openage authors. See copying.md for legal info. # This module defines # diff --git a/libopenage/renderer/geometry.cpp b/libopenage/renderer/geometry.cpp index 078cf96a4c..c2f60734ab 100644 --- a/libopenage/renderer/geometry.cpp +++ b/libopenage/renderer/geometry.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include "geometry.h" diff --git a/libopenage/renderer/geometry.h b/libopenage/renderer/geometry.h index 072f682727..90ce58b8cd 100644 --- a/libopenage/renderer/geometry.h +++ b/libopenage/renderer/geometry.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/buffer.cpp b/libopenage/renderer/opengl/buffer.cpp index a91842f74e..ac1a2ed430 100644 --- a/libopenage/renderer/opengl/buffer.cpp +++ b/libopenage/renderer/opengl/buffer.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include "buffer.h" diff --git a/libopenage/renderer/opengl/buffer.h b/libopenage/renderer/opengl/buffer.h index adb6e2ab2d..78c2d9f40b 100644 --- a/libopenage/renderer/opengl/buffer.h +++ b/libopenage/renderer/opengl/buffer.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/context.cpp b/libopenage/renderer/opengl/context.cpp index 8b20cc589e..3e00363300 100644 --- a/libopenage/renderer/opengl/context.cpp +++ b/libopenage/renderer/opengl/context.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include "context.h" diff --git a/libopenage/renderer/opengl/context.h b/libopenage/renderer/opengl/context.h index d03e65198c..71f2cd9200 100644 --- a/libopenage/renderer/opengl/context.h +++ b/libopenage/renderer/opengl/context.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/framebuffer.cpp b/libopenage/renderer/opengl/framebuffer.cpp index 844de8082c..ec7ed75df4 100644 --- a/libopenage/renderer/opengl/framebuffer.cpp +++ b/libopenage/renderer/opengl/framebuffer.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #include "framebuffer.h" diff --git a/libopenage/renderer/opengl/framebuffer.h b/libopenage/renderer/opengl/framebuffer.h index fd1602ad2c..6c40da603d 100644 --- a/libopenage/renderer/opengl/framebuffer.h +++ b/libopenage/renderer/opengl/framebuffer.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/geometry.cpp b/libopenage/renderer/opengl/geometry.cpp index 68f266855a..87df193800 100644 --- a/libopenage/renderer/opengl/geometry.cpp +++ b/libopenage/renderer/opengl/geometry.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include "geometry.h" diff --git a/libopenage/renderer/opengl/geometry.h b/libopenage/renderer/opengl/geometry.h index 75f225b067..a7f59280d5 100644 --- a/libopenage/renderer/opengl/geometry.h +++ b/libopenage/renderer/opengl/geometry.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/lookup.h b/libopenage/renderer/opengl/lookup.h index 5b88254741..982b644c0f 100644 --- a/libopenage/renderer/opengl/lookup.h +++ b/libopenage/renderer/opengl/lookup.h @@ -1,5 +1,9 @@ -// Copyright 2018 the openage authors. See copying.md for legal info. -// Lookup tables for translating various things in OpenGL. +// Copyright 2018-2018 the openage authors. See copying.md for legal info. + +// Lookup tables for translating between OpenGL-specific values and generic renderer values, +// as well as mapping things like type sizes within OpenGL. + +#pragma once #include diff --git a/libopenage/renderer/opengl/render_target.cpp b/libopenage/renderer/opengl/render_target.cpp index 88f4ae224a..e61627fd8b 100644 --- a/libopenage/renderer/opengl/render_target.cpp +++ b/libopenage/renderer/opengl/render_target.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #include "render_target.h" diff --git a/libopenage/renderer/opengl/render_target.h b/libopenage/renderer/opengl/render_target.h index 8ee2882aac..8aafdec6cd 100644 --- a/libopenage/renderer/opengl/render_target.h +++ b/libopenage/renderer/opengl/render_target.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp index d1a408882c..73ef862859 100644 --- a/libopenage/renderer/opengl/renderer.cpp +++ b/libopenage/renderer/opengl/renderer.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #include "renderer.h" diff --git a/libopenage/renderer/opengl/renderer.h b/libopenage/renderer/opengl/renderer.h index 6ebddfe765..464336fe89 100644 --- a/libopenage/renderer/opengl/renderer.h +++ b/libopenage/renderer/opengl/renderer.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/shader.cpp b/libopenage/renderer/opengl/shader.cpp index 419f4eab2a..d6310c628f 100644 --- a/libopenage/renderer/opengl/shader.cpp +++ b/libopenage/renderer/opengl/shader.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #include "shader.h" diff --git a/libopenage/renderer/opengl/shader.h b/libopenage/renderer/opengl/shader.h index 974eee6d3e..de4bdef142 100644 --- a/libopenage/renderer/opengl/shader.h +++ b/libopenage/renderer/opengl/shader.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/shader_program.cpp b/libopenage/renderer/opengl/shader_program.cpp index 94df9c980f..3b0a6e1d20 100644 --- a/libopenage/renderer/opengl/shader_program.cpp +++ b/libopenage/renderer/opengl/shader_program.cpp @@ -1,4 +1,4 @@ -// Copyright 2013-2017 the openage authors. See copying.md for legal info. +// Copyright 2013-2018 the openage authors. See copying.md for legal info. #include "shader_program.h" diff --git a/libopenage/renderer/opengl/shader_program.h b/libopenage/renderer/opengl/shader_program.h index 08ee8f0eca..4bf56e62c2 100644 --- a/libopenage/renderer/opengl/shader_program.h +++ b/libopenage/renderer/opengl/shader_program.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/simple_object.cpp b/libopenage/renderer/opengl/simple_object.cpp index 8a68ee171b..148f4cfc25 100644 --- a/libopenage/renderer/opengl/simple_object.cpp +++ b/libopenage/renderer/opengl/simple_object.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include "simple_object.h" diff --git a/libopenage/renderer/opengl/simple_object.h b/libopenage/renderer/opengl/simple_object.h index a4d4fb18eb..6ec43d8901 100644 --- a/libopenage/renderer/opengl/simple_object.h +++ b/libopenage/renderer/opengl/simple_object.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/texture.cpp b/libopenage/renderer/opengl/texture.cpp index 80d8b7635e..dacc9cd8ca 100644 --- a/libopenage/renderer/opengl/texture.cpp +++ b/libopenage/renderer/opengl/texture.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include "texture.h" diff --git a/libopenage/renderer/opengl/texture.h b/libopenage/renderer/opengl/texture.h index e9c81054a4..f7b4bdd9f8 100644 --- a/libopenage/renderer/opengl/texture.h +++ b/libopenage/renderer/opengl/texture.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/texture_array.cpp b/libopenage/renderer/opengl/texture_array.cpp index d4ec576241..f678e36cab 100644 --- a/libopenage/renderer/opengl/texture_array.cpp +++ b/libopenage/renderer/opengl/texture_array.cpp @@ -1,4 +1,4 @@ -// Copyright 2018 the openage authors. See copying.md for legal info. +// Copyright 2018-2018 the openage authors. See copying.md for legal info. #include "texture_array.h" diff --git a/libopenage/renderer/opengl/texture_array.h b/libopenage/renderer/opengl/texture_array.h index e4a39c54c3..ae3064cb5f 100644 --- a/libopenage/renderer/opengl/texture_array.h +++ b/libopenage/renderer/opengl/texture_array.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/uniform_input.h b/libopenage/renderer/opengl/uniform_input.h index 09cd385f5f..a93e436e97 100644 --- a/libopenage/renderer/opengl/uniform_input.h +++ b/libopenage/renderer/opengl/uniform_input.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/opengl/vertex_array.cpp b/libopenage/renderer/opengl/vertex_array.cpp index 3cc0b53bf4..bf11589ab0 100644 --- a/libopenage/renderer/opengl/vertex_array.cpp +++ b/libopenage/renderer/opengl/vertex_array.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include "vertex_array.h" diff --git a/libopenage/renderer/opengl/vertex_array.h b/libopenage/renderer/opengl/vertex_array.h index 3dc5a028fc..14e2fa7cd4 100644 --- a/libopenage/renderer/opengl/vertex_array.h +++ b/libopenage/renderer/opengl/vertex_array.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/renderer.h b/libopenage/renderer/renderer.h index c77d5ba2d0..c02bbd4704 100644 --- a/libopenage/renderer/renderer.h +++ b/libopenage/renderer/renderer.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/resources/mesh_data.cpp b/libopenage/renderer/resources/mesh_data.cpp index c6a46bba92..04ff98e88a 100644 --- a/libopenage/renderer/resources/mesh_data.cpp +++ b/libopenage/renderer/resources/mesh_data.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #include "mesh_data.h" @@ -84,7 +84,7 @@ std::experimental::optional VertexInputInfo::get_index_type() const { } MeshData::MeshData(const util::Path &/*path*/) { - // asdf + // TODO implement mesh loaders throw "unimplemented lol"; } diff --git a/libopenage/renderer/resources/mesh_data.h b/libopenage/renderer/resources/mesh_data.h index 4545daa627..cd884d7d88 100644 --- a/libopenage/renderer/resources/mesh_data.h +++ b/libopenage/renderer/resources/mesh_data.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/resources/shader_source.cpp b/libopenage/renderer/resources/shader_source.cpp index f5bd1ea2bc..0dcf38a39b 100644 --- a/libopenage/renderer/resources/shader_source.cpp +++ b/libopenage/renderer/resources/shader_source.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include "shader_source.h" diff --git a/libopenage/renderer/resources/shader_source.h b/libopenage/renderer/resources/shader_source.h index 6ac36c758c..1008569d9e 100644 --- a/libopenage/renderer/resources/shader_source.h +++ b/libopenage/renderer/resources/shader_source.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/resources/texture_data.cpp b/libopenage/renderer/resources/texture_data.cpp index 1e89f292d2..9236c71672 100644 --- a/libopenage/renderer/resources/texture_data.cpp +++ b/libopenage/renderer/resources/texture_data.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include "texture_data.h" diff --git a/libopenage/renderer/resources/texture_data.h b/libopenage/renderer/resources/texture_data.h index 5e9da11813..4c6f0edddf 100644 --- a/libopenage/renderer/resources/texture_data.h +++ b/libopenage/renderer/resources/texture_data.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/resources/texture_info.cpp b/libopenage/renderer/resources/texture_info.cpp index 5816efe9bf..c6cf9c6461 100644 --- a/libopenage/renderer/resources/texture_info.cpp +++ b/libopenage/renderer/resources/texture_info.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #include "texture_info.h" diff --git a/libopenage/renderer/resources/texture_info.h b/libopenage/renderer/resources/texture_info.h index d89dc16bb2..cefc9491df 100644 --- a/libopenage/renderer/resources/texture_info.h +++ b/libopenage/renderer/resources/texture_info.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/sdl_global.h b/libopenage/renderer/sdl_global.h index 2a96752eb6..a1e8170c3a 100644 --- a/libopenage/renderer/sdl_global.h +++ b/libopenage/renderer/sdl_global.h @@ -1,5 +1,7 @@ // Copyright 2018-2018 the openage authors. See copying.md for legal info. +#pragma once + namespace openage { namespace renderer { diff --git a/libopenage/renderer/shader_program.h b/libopenage/renderer/shader_program.h index c73adb8cc3..1875653fc2 100644 --- a/libopenage/renderer/shader_program.h +++ b/libopenage/renderer/shader_program.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/tests.cpp b/libopenage/renderer/tests.cpp index cee23c8a0f..d7420f60ad 100644 --- a/libopenage/renderer/tests.cpp +++ b/libopenage/renderer/tests.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include #include diff --git a/libopenage/renderer/tests.h b/libopenage/renderer/tests.h index 467ec256f3..3c838bc9f1 100644 --- a/libopenage/renderer/tests.h +++ b/libopenage/renderer/tests.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/texture.cpp b/libopenage/renderer/texture.cpp index e2bfdc1302..c96400ec77 100644 --- a/libopenage/renderer/texture.cpp +++ b/libopenage/renderer/texture.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include diff --git a/libopenage/renderer/texture.h b/libopenage/renderer/texture.h index e00dc7bc5b..ee214b946a 100644 --- a/libopenage/renderer/texture.h +++ b/libopenage/renderer/texture.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/texture_array.cpp b/libopenage/renderer/texture_array.cpp index d951450531..25f8fb173b 100644 --- a/libopenage/renderer/texture_array.cpp +++ b/libopenage/renderer/texture_array.cpp @@ -1,4 +1,4 @@ -// Copyright 2018 the openage authors. See copying.md for legal info. +// Copyright 2018-2018 the openage authors. See copying.md for legal info. #include "texture_array.h" diff --git a/libopenage/renderer/texture_array.h b/libopenage/renderer/texture_array.h index a01da738eb..485019ca48 100644 --- a/libopenage/renderer/texture_array.h +++ b/libopenage/renderer/texture_array.h @@ -1,4 +1,4 @@ -// Copyright 2018 the openage authors. See copying.md for legal info. +// Copyright 2018-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/vulkan/graphics_device.cpp b/libopenage/renderer/vulkan/graphics_device.cpp index a7abc97ce4..9556b888a7 100644 --- a/libopenage/renderer/vulkan/graphics_device.cpp +++ b/libopenage/renderer/vulkan/graphics_device.cpp @@ -1,3 +1,5 @@ +// Copyright 2017-2018 the openage authors. See copying.md for legal info. + #include "graphics_device.h" #include diff --git a/libopenage/renderer/vulkan/graphics_device.h b/libopenage/renderer/vulkan/graphics_device.h index 836fb1c49b..c0750ebd28 100644 --- a/libopenage/renderer/vulkan/graphics_device.h +++ b/libopenage/renderer/vulkan/graphics_device.h @@ -1,3 +1,5 @@ +// Copyright 2017-2018 the openage authors. See copying.md for legal info. + #pragma once #include diff --git a/libopenage/renderer/vulkan/loader.cpp b/libopenage/renderer/vulkan/loader.cpp index 36d499072b..c4e7c89078 100644 --- a/libopenage/renderer/vulkan/loader.cpp +++ b/libopenage/renderer/vulkan/loader.cpp @@ -1,3 +1,5 @@ +// Copyright 2017-2018 the openage authors. See copying.md for legal info. + #include "loader.h" #include "../../error/error.h" diff --git a/libopenage/renderer/vulkan/loader.h b/libopenage/renderer/vulkan/loader.h index f08742f30a..66375790c0 100644 --- a/libopenage/renderer/vulkan/loader.h +++ b/libopenage/renderer/vulkan/loader.h @@ -1,3 +1,5 @@ +// Copyright 2017-2018 the openage authors. See copying.md for legal info. + #pragma once #include diff --git a/libopenage/renderer/vulkan/render_target.h b/libopenage/renderer/vulkan/render_target.h index 600326c37a..25d1a2678d 100644 --- a/libopenage/renderer/vulkan/render_target.h +++ b/libopenage/renderer/vulkan/render_target.h @@ -1,3 +1,5 @@ +// Copyright 2017-2018 the openage authors. See copying.md for legal info. + #pragma once #include "../renderer.h" diff --git a/libopenage/renderer/vulkan/renderer.cpp b/libopenage/renderer/vulkan/renderer.cpp index a0dfbe659b..9d69198bc4 100644 --- a/libopenage/renderer/vulkan/renderer.cpp +++ b/libopenage/renderer/vulkan/renderer.cpp @@ -1,3 +1,5 @@ +// Copyright 2017-2018 the openage authors. See copying.md for legal info. + #include "renderer.h" #include "../../error/error.h" diff --git a/libopenage/renderer/vulkan/renderer.h b/libopenage/renderer/vulkan/renderer.h index 45b271e186..e36b549cbf 100644 --- a/libopenage/renderer/vulkan/renderer.h +++ b/libopenage/renderer/vulkan/renderer.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the openage authors. See copying.md for legal info. +// Copyright 2017-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/vulkan/shader_program.h b/libopenage/renderer/vulkan/shader_program.h index f525a13baf..c8374790d4 100644 --- a/libopenage/renderer/vulkan/shader_program.h +++ b/libopenage/renderer/vulkan/shader_program.h @@ -1,3 +1,5 @@ +// Copyright 2017-2018 the openage authors. See copying.md for legal info. + #pragma once #include "../../error/error.h" diff --git a/libopenage/renderer/vulkan/util.h b/libopenage/renderer/vulkan/util.h index 459235c41e..229e676b8e 100644 --- a/libopenage/renderer/vulkan/util.h +++ b/libopenage/renderer/vulkan/util.h @@ -1,3 +1,5 @@ +// Copyright 2017-2018 the openage authors. See copying.md for legal info. + #pragma once #include diff --git a/libopenage/renderer/vulkan/windowvk.cpp b/libopenage/renderer/vulkan/windowvk.cpp index 4d4b6a1aca..3ecb4a1b7b 100644 --- a/libopenage/renderer/vulkan/windowvk.cpp +++ b/libopenage/renderer/vulkan/windowvk.cpp @@ -1,3 +1,5 @@ +// Copyright 2017-2018 the openage authors. See copying.md for legal info. + #include "windowvk.h" #include diff --git a/libopenage/renderer/vulkan/windowvk.h b/libopenage/renderer/vulkan/windowvk.h index bf1b6bd694..289929859d 100644 --- a/libopenage/renderer/vulkan/windowvk.h +++ b/libopenage/renderer/vulkan/windowvk.h @@ -1,3 +1,5 @@ +// Copyright 2017-2018 the openage authors. See copying.md for legal info. + #pragma once #include diff --git a/libopenage/renderer/window.cpp b/libopenage/renderer/window.cpp index 50ef624ee3..dfec393f12 100644 --- a/libopenage/renderer/window.cpp +++ b/libopenage/renderer/window.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #include "window.h" diff --git a/libopenage/renderer/window.h b/libopenage/renderer/window.h index 5f124c3518..bc2ad38f5c 100644 --- a/libopenage/renderer/window.h +++ b/libopenage/renderer/window.h @@ -1,4 +1,4 @@ -// Copyright 2015-2017 the openage authors. See copying.md for legal info. +// Copyright 2015-2018 the openage authors. See copying.md for legal info. #pragma once diff --git a/openage/renderer/__init__.py b/openage/renderer/__init__.py index ce8a6e3ce2..bcc1afbece 100644 --- a/openage/renderer/__init__.py +++ b/openage/renderer/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2015-2015 the openage authors. See copying.md for legal info. +# Copyright 2015-2018 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 index 32ac9f55e3..f99673f57f 100644 --- a/openage/renderer/renderer_cpp.pyx +++ b/openage/renderer/renderer_cpp.pyx @@ -1 +1 @@ -# Copyright 2015-2015 the openage authors. See copying.md for legal info. +# Copyright 2015-2018 the openage authors. See copying.md for legal info. diff --git a/openage/renderer/tests.pyx b/openage/renderer/tests.pyx index 5e3cee1655..857b404d19 100644 --- a/openage/renderer/tests.pyx +++ b/openage/renderer/tests.pyx @@ -1,4 +1,4 @@ -# Copyright 2015-2015 the openage authors. See copying.md for legal info. +# Copyright 2015-2018 the openage authors. See copying.md for legal info. """ tests for the graphics renderer. From 49d46c2b78cc2e445d1dc0e1cd2b459302ddee75 Mon Sep 17 00:00:00 2001 From: Wojciech Nawrocki Date: Sat, 30 Jun 2018 15:07:59 +0100 Subject: [PATCH 3/7] renderer: remove experimental namespace, misc fixes --- libopenage/renderer/opengl/CMakeLists.txt | 26 ++--- libopenage/renderer/opengl/context.cpp | 4 +- libopenage/renderer/opengl/geometry.cpp | 16 ++- libopenage/renderer/opengl/geometry.h | 8 +- libopenage/renderer/opengl/lookup.h | 110 +++++++++--------- libopenage/renderer/opengl/render_target.h | 4 +- libopenage/renderer/opengl/shader_program.cpp | 2 +- libopenage/renderer/opengl/simple_object.h | 4 +- libopenage/renderer/opengl/texture_array.h | 10 +- libopenage/renderer/opengl/vertex_array.cpp | 9 +- libopenage/renderer/opengl/window.h | 4 +- libopenage/renderer/resources/CMakeLists.txt | 8 +- libopenage/renderer/resources/mesh_data.cpp | 6 +- libopenage/renderer/resources/mesh_data.h | 16 +-- .../renderer/vulkan/graphics_device.cpp | 6 +- libopenage/renderer/vulkan/graphics_device.h | 6 +- libopenage/renderer/vulkan/windowvk.h | 2 +- 17 files changed, 121 insertions(+), 120 deletions(-) diff --git a/libopenage/renderer/opengl/CMakeLists.txt b/libopenage/renderer/opengl/CMakeLists.txt index 99283cbb2b..849c339c6f 100644 --- a/libopenage/renderer/opengl/CMakeLists.txt +++ b/libopenage/renderer/opengl/CMakeLists.txt @@ -1,15 +1,15 @@ add_sources(libopenage - buffer.cpp - context.cpp - framebuffer.cpp - geometry.cpp - render_target.cpp - renderer.cpp - shader.cpp - shader_program.cpp - simple_object.cpp - texture.cpp - texture_array.cpp - vertex_array.cpp - window.cpp + buffer.cpp + context.cpp + framebuffer.cpp + geometry.cpp + render_target.cpp + renderer.cpp + shader.cpp + shader_program.cpp + simple_object.cpp + texture.cpp + texture_array.cpp + vertex_array.cpp + window.cpp ) diff --git a/libopenage/renderer/opengl/context.cpp b/libopenage/renderer/opengl/context.cpp index 3e00363300..a19f55b30f 100644 --- a/libopenage/renderer/opengl/context.cpp +++ b/libopenage/renderer/opengl/context.cpp @@ -133,13 +133,13 @@ GlContext::~GlContext() { GlContext::GlContext(GlContext &&other) : gl_context(other.gl_context) - , capabilities(other.capabilities) { + , capabilities(std::move(other.capabilities)) { other.gl_context = nullptr; } GlContext& GlContext::operator=(GlContext &&other) { this->gl_context = other.gl_context; - this->capabilities = other.capabilities; + this->capabilities = std::move(other.capabilities); other.gl_context = nullptr; return *this; diff --git a/libopenage/renderer/opengl/geometry.cpp b/libopenage/renderer/opengl/geometry.cpp index 87df193800..bffbfa72a4 100644 --- a/libopenage/renderer/opengl/geometry.cpp +++ b/libopenage/renderer/opengl/geometry.cpp @@ -51,10 +51,12 @@ void GlGeometry::update_verts_offset(std::vector const &verts, size_t o } void GlGeometry::draw() const { - if (this->get_type() == geometry_t::bufferless_quad) { + switch (this->get_type()) { + case geometry_t::bufferless_quad: glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - } - else if (this->get_type() == geometry_t::mesh) { + break; + + case geometry_t::mesh: { auto const& mesh = *this->mesh; mesh.vao.bind(); @@ -62,10 +64,14 @@ void GlGeometry::draw() const { mesh.indices->bind(GL_ELEMENT_ARRAY_BUFFER); glDrawElements(mesh.primitive, mesh.vert_count, *mesh.index_type, 0); - } - else { + } else { glDrawArrays(GL_TRIANGLE_STRIP, 0, mesh.vert_count); } + + break; + } + default: + throw Error(MSG(err) << "Unknown geometry type in GlGeometry."); } } diff --git a/libopenage/renderer/opengl/geometry.h b/libopenage/renderer/opengl/geometry.h index a7f59280d5..70dd061117 100644 --- a/libopenage/renderer/opengl/geometry.h +++ b/libopenage/renderer/opengl/geometry.h @@ -2,7 +2,7 @@ #pragma once -#include +#include #include "../geometry.h" #include "../resources/mesh_data.h" @@ -35,15 +35,15 @@ class GlGeometry final : public Geometry { struct GlMesh { GlBuffer vertices; GlVertexArray vao; - std::experimental::optional indices; - std::experimental::optional index_type; + std::optional indices; + std::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; + std::optional mesh; }; }}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/lookup.h b/libopenage/renderer/opengl/lookup.h index 982b644c0f..3ba86fed38 100644 --- a/libopenage/renderer/opengl/lookup.h +++ b/libopenage/renderer/opengl/lookup.h @@ -17,74 +17,74 @@ namespace opengl { /// Input and output pixel formats from pixel_format. static constexpr auto GL_PIXEL_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::bgr8, std::make_tuple(GL_RGB8, GL_BGR, 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)), - std::make_pair(resources::pixel_format::depth24, std::make_tuple(GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE)) + // 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::bgr8, std::make_tuple(GL_RGB8, GL_BGR, 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)), + std::make_pair(resources::pixel_format::depth24, std::make_tuple(GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE)) ); /// Sizes of various uniform/vertex input types in shaders. static constexpr auto GL_SHADER_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_2D_ARRAY, 4), - std::make_pair(GL_SAMPLER_3D, 4), - std::make_pair(GL_SAMPLER_CUBE, 4) + 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_2D_ARRAY, 4), + std::make_pair(GL_SAMPLER_3D, 4), + std::make_pair(GL_SAMPLER_CUBE, 4) ); /// GLSL type strings with corresponding GL types. static constexpr auto GLSL_TYPE_NAME = 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), - std::make_pair("sampler2DArray", GL_SAMPLER_2D_ARRAY) + 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), + std::make_pair("sampler2DArray", GL_SAMPLER_2D_ARRAY) ); /// Generic vertex input types from GL types. static constexpr auto GL_VERT_IN_TYPE = datastructure::create_const_map( - std::make_pair(GL_FLOAT, resources::vertex_input_t::F32), - std::make_pair(GL_FLOAT_VEC2, resources::vertex_input_t::V2F32), - std::make_pair(GL_FLOAT_VEC3, resources::vertex_input_t::V3F32), - std::make_pair(GL_FLOAT_MAT3, resources::vertex_input_t::M3F32) + std::make_pair(GL_FLOAT, resources::vertex_input_t::F32), + std::make_pair(GL_FLOAT_VEC2, resources::vertex_input_t::V2F32), + std::make_pair(GL_FLOAT_VEC3, resources::vertex_input_t::V3F32), + std::make_pair(GL_FLOAT_MAT3, resources::vertex_input_t::M3F32) ); /// The type of a single element in a per-vertex attribute. static constexpr auto GL_VERT_IN_ELEM_TYPE = datastructure::create_const_map( - std::make_pair(resources::vertex_input_t::F32, GL_FLOAT), - std::make_pair(resources::vertex_input_t::V2F32, GL_FLOAT), - std::make_pair(resources::vertex_input_t::V3F32, GL_FLOAT), - std::make_pair(resources::vertex_input_t::M3F32, GL_FLOAT) + std::make_pair(resources::vertex_input_t::F32, GL_FLOAT), + std::make_pair(resources::vertex_input_t::V2F32, GL_FLOAT), + std::make_pair(resources::vertex_input_t::V3F32, GL_FLOAT), + std::make_pair(resources::vertex_input_t::M3F32, GL_FLOAT) ); /// Mapping from generic primitive types to GL types. @@ -99,9 +99,9 @@ static constexpr auto GL_PRIMITIVE = 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) + 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) ); }}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/render_target.h b/libopenage/renderer/opengl/render_target.h index 8aafdec6cd..592602d85e 100644 --- a/libopenage/renderer/opengl/render_target.h +++ b/libopenage/renderer/opengl/render_target.h @@ -2,7 +2,7 @@ #pragma once -#include +#include #include "../renderer.h" #include "texture.h" @@ -44,7 +44,7 @@ class GlRenderTarget final : public RenderTarget { gl_render_target_t type; /// For textures target type, the framebuffer. - std::experimental::optional framebuffer; + std::optional framebuffer; }; }}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/shader_program.cpp b/libopenage/renderer/opengl/shader_program.cpp index 3b0a6e1d20..a2631c746c 100644 --- a/libopenage/renderer/opengl/shader_program.cpp +++ b/libopenage/renderer/opengl/shader_program.cpp @@ -183,7 +183,7 @@ void GlShaderProgram::use() const { } void GlShaderProgram::execute_with(const GlUniformInput *unif_in, const GlGeometry *geom) { - assert(unif_in->program == this); + ENSURE(unif_in->program == this); this->use(); diff --git a/libopenage/renderer/opengl/simple_object.h b/libopenage/renderer/opengl/simple_object.h index 6ec43d8901..ade15bb3b7 100644 --- a/libopenage/renderer/opengl/simple_object.h +++ b/libopenage/renderer/opengl/simple_object.h @@ -2,7 +2,7 @@ #pragma once -#include +#include #include #include @@ -38,7 +38,7 @@ class GlSimpleObject { explicit GlSimpleObject(std::function delete_fun); /// The handle to the OpenGL Object that this class represents. - std::experimental::optional handle; + std::optional handle; /// The function that deletes the underlying OpenGL Object. std::function delete_fun; diff --git a/libopenage/renderer/opengl/texture_array.h b/libopenage/renderer/opengl/texture_array.h index ae3064cb5f..6fdfe90777 100644 --- a/libopenage/renderer/opengl/texture_array.h +++ b/libopenage/renderer/opengl/texture_array.h @@ -17,7 +17,7 @@ namespace opengl { /// An OpenGL array of 2D textures. class GlTexture2dArray final : public Texture2dArray, public GlSimpleObject { public: - /// Constructs an array of the same number of layers as the size of the given + /// Constructs an array of the same number of layers as the size of the given /// vector, and fills the layers with the corresponding vector element. The /// texture formats in all vector elements must be the same as defined by /// Textur2dInfo::operator==. @@ -25,13 +25,13 @@ class GlTexture2dArray final : public Texture2dArray, public GlSimpleObject { /// Constructs an array of ln_layers empty layers, with the per-layer texture /// format specified in layer_info. - GlTexture2dArray(size_t n_layers, resources::Texture2dInfo layer_info); + GlTexture2dArray(size_t n_layers, resources::Texture2dInfo layer_info); - void upload(size_t layer, resources::Texture2dData const&) override; + void upload(size_t layer, resources::Texture2dData const&) override; private: - /// The number of layers (elements) in the array, AKA the depth. - size_t n_layers; + /// The number of layers (elements) in the array, AKA the depth. + size_t n_layers; }; }}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/vertex_array.cpp b/libopenage/renderer/opengl/vertex_array.cpp index bf11589ab0..8be1918083 100644 --- a/libopenage/renderer/opengl/vertex_array.cpp +++ b/libopenage/renderer/opengl/vertex_array.cpp @@ -154,13 +154,8 @@ GlVertexArray::GlVertexArray() 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); - } - */ + // Enabling the vertexAttribArrays here again is unnecessary, + // because the VAO bind does it anyway. } }}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/window.h b/libopenage/renderer/opengl/window.h index a29acf1165..d656fb67c2 100644 --- a/libopenage/renderer/opengl/window.h +++ b/libopenage/renderer/opengl/window.h @@ -4,7 +4,7 @@ #include "../window.h" -#include +#include #include "context.h" @@ -38,7 +38,7 @@ class GlWindow final : public Window { /// The 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; + std::optional context; }; }}} // namespace openage::renderer::opengl diff --git a/libopenage/renderer/resources/CMakeLists.txt b/libopenage/renderer/resources/CMakeLists.txt index 4c58d2c733..75c138b1ab 100644 --- a/libopenage/renderer/resources/CMakeLists.txt +++ b/libopenage/renderer/resources/CMakeLists.txt @@ -1,6 +1,6 @@ add_sources(libopenage - mesh_data.cpp - shader_source.cpp - texture_data.cpp - texture_info.cpp + 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 index 04ff98e88a..346af389af 100644 --- a/libopenage/renderer/resources/mesh_data.cpp +++ b/libopenage/renderer/resources/mesh_data.cpp @@ -67,7 +67,7 @@ const std::vector &VertexInputInfo::get_inputs() const { return this->inputs; } -std::experimental::optional> const& VertexInputInfo::get_shader_input_map() const { +std::optional> const& VertexInputInfo::get_shader_input_map() const { return this->shader_input_map; } @@ -79,7 +79,7 @@ vertex_primitive_t VertexInputInfo::get_primitive() const { return this->primitive; } -std::experimental::optional VertexInputInfo::get_index_type() const { +std::optional VertexInputInfo::get_index_type() const { return this->index_type; } @@ -101,7 +101,7 @@ std::vector const& MeshData::get_data() const { return this->data; } -std::experimental::optional> const &MeshData::get_ids() const { +std::optional> const &MeshData::get_ids() const { return this->ids; } diff --git a/libopenage/renderer/resources/mesh_data.h b/libopenage/renderer/resources/mesh_data.h index cd884d7d88..9cb7bec1d2 100644 --- a/libopenage/renderer/resources/mesh_data.h +++ b/libopenage/renderer/resources/mesh_data.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include "../../util/path.h" @@ -77,7 +77,7 @@ class VertexInputInfo { const std::vector &get_inputs() const; /// Returns the shader input map or an empty optional if it's not present. - std::experimental::optional> const& get_shader_input_map() const; + std::optional> const& get_shader_input_map() const; /// Returns the layout of vertices in memory. vertex_layout_t get_layout() const; @@ -90,7 +90,7 @@ class VertexInputInfo { /// 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; + std::optional get_index_type() const; private: /// What kind of data the vertices contain and how it is laid out in memory. @@ -99,7 +99,7 @@ class VertexInputInfo { /// An optional attribute specifying how vertex inputs in the mesh map to vertex inputs /// in a given shader, for example to reorder inputs of the form (pos, uv) into a shader /// that takes in (uv, pos) inputs. - std::experimental::optional> shader_input_map; + std::optional> shader_input_map; /// How the vertices are laid out in the data buffer. vertex_layout_t layout; @@ -108,7 +108,7 @@ class VertexInputInfo { vertex_primitive_t primitive; /// The type of indices if they exist. - std::experimental::optional index_type; + std::optional index_type; }; class MeshData { @@ -126,7 +126,7 @@ class MeshData { std::vector const &get_data() const; /// Returns the indices used for indexed drawing if they exist. - std::experimental::optional> const &get_ids() const; + std::optional> const &get_ids() const; /// Returns information describing the vertices in this mesh. VertexInputInfo get_info() const; @@ -141,13 +141,13 @@ class MeshData { /// The indices of vertices to be drawn if the mesh is indexed. /// For array drawing, empty optional. - std::experimental::optional> ids; + std::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; + std::optional info; }; }}} diff --git a/libopenage/renderer/vulkan/graphics_device.cpp b/libopenage/renderer/vulkan/graphics_device.cpp index 9556b888a7..b84f5fe53a 100644 --- a/libopenage/renderer/vulkan/graphics_device.cpp +++ b/libopenage/renderer/vulkan/graphics_device.cpp @@ -14,12 +14,12 @@ namespace openage { namespace renderer { namespace vulkan { -std::experimental::optional VlkGraphicsDevice::find_device_surface_support(VkPhysicalDevice dev, VkSurfaceKHR surf) { +std::optional VlkGraphicsDevice::find_device_surface_support(VkPhysicalDevice dev, VkSurfaceKHR surf) { // Search for queue families in the device auto q_fams = vk_do_ritual(vkGetPhysicalDeviceQueueFamilyProperties, dev); - std::experimental::optional maybe_graphics_fam = {}; - std::experimental::optional maybe_present_fam = {}; + std::optional maybe_graphics_fam = {}; + std::optional maybe_present_fam = {}; // Figure out if any of the families supports graphics for (size_t i = 0; i < q_fams.size(); i++) { diff --git a/libopenage/renderer/vulkan/graphics_device.h b/libopenage/renderer/vulkan/graphics_device.h index c0750ebd28..e29297461c 100644 --- a/libopenage/renderer/vulkan/graphics_device.h +++ b/libopenage/renderer/vulkan/graphics_device.h @@ -2,7 +2,7 @@ #pragma once -#include +#include #include #include @@ -35,7 +35,7 @@ struct SurfaceSupportDetails { /// Index of the queue family with presentation support. This might be the same as the graphics /// family, in which case this optional is empty. - std::experimental::optional maybe_present_fam; + std::optional maybe_present_fam; }; /// Owns a device capable of graphics operations and surface presentation using WSI. @@ -52,7 +52,7 @@ class VlkGraphicsDevice { public: /// Given a physical device and a surface, checks whether the device is capable of presenting to the surface. /// If it is, returns information about its presentation capabilities, otherwise returns an empty optional. - static std::experimental::optional find_device_surface_support(VkPhysicalDevice, VkSurfaceKHR); + static std::optional find_device_surface_support(VkPhysicalDevice, VkSurfaceKHR); /// Given a physical device and a list of queue family indices in that device, instantiates /// a logical device with a queue per each of the families. The device has to support the diff --git a/libopenage/renderer/vulkan/windowvk.h b/libopenage/renderer/vulkan/windowvk.h index 289929859d..6b5db4ea1b 100644 --- a/libopenage/renderer/vulkan/windowvk.h +++ b/libopenage/renderer/vulkan/windowvk.h @@ -4,7 +4,7 @@ #include #include -#include +#include #include From 802558b4082417890679f474ea08f00e852466f2 Mon Sep 17 00:00:00 2001 From: Wojciech Nawrocki Date: Sat, 30 Jun 2018 15:26:02 +0100 Subject: [PATCH 4/7] renderer: move doc, misc parameter fix --- {libopenage => doc/code}/renderer/README.md | 44 -------------------- libopenage/renderer/opengl/texture_array.cpp | 2 +- libopenage/renderer/opengl/texture_array.h | 2 +- 3 files changed, 2 insertions(+), 46 deletions(-) rename {libopenage => doc/code}/renderer/README.md (73%) diff --git a/libopenage/renderer/README.md b/doc/code/renderer/README.md similarity index 73% rename from libopenage/renderer/README.md rename to doc/code/renderer/README.md index e6f804266c..6cfefd134f 100644 --- a/libopenage/renderer/README.md +++ b/doc/code/renderer/README.md @@ -103,47 +103,3 @@ renderer->render(pass); ## Level 2: On top of the level 1 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. In some documentation this is also called the "presenter". - -## TODO: -- [x] Abstraction of render backend - - [x] OpenGL 3.3 or higher - - [ ] Use 4.x when available (optional) - - [ ] Support ancient 2.x legacy (optional) - - [ ] Vulkan (optional, #242) -- [ ] Render pipeline abstraction - - [x] Textures - - [x] Shaders - - [x] Uniforms - - [ ] Uniform buffers - - [x] Vertex attributes - - [x] Geometries - - [x] Quad primitives - - [ ] Circles - - [ ] Smooth paths - - [ ] Mesh importing (optional) - - [x] Render targets - - [x] Framebuffers - - [ ] Renderbuffers (optional) -- [ ] Functionality - - [x] Screenshot support - - [ ] [PBO optimization](http://www.songho.ca/opengl/gl_pbo.html) for texture downloading (optional) - - [x] Pixel-perfect unit hitbox for unit selection and damage areas (#368, #671 ) - - [ ] Outline rendering - - [ ] Investigate why tree textures render incorrectly (#359, [maybe this?](http://www.adriancourreges.com/blog/2017/05/09/beware-of-transparent-pixels/)) - - [ ] Fix #374 -- [ ] Terrain rendering - - [ ] Merge terrain texture into a single bitmap - - [ ] Cache blending results (#154, #158) - - [ ] Do as much as possible in shaders (#149) - - [ ] Clip tiles properly (#141) -- [ ] Optimizations - - [ ] [Occlusion queries](https://vertostudio.com/gamedev/?p=177) - - [ ] Minimize OpenGL state changes (batch by shader, then by buffer) - - [ ] Texture binpacking into atlas - - [ ] Smooth zooming -- [ ] Integration - - [ ] Rewrite all of drawing functionality to be expressed in terms of `Renderer` - - [ ] Get rid of GL code everywhere except the rendering backend and the GUI - - [ ] Write a `GameRenderer` that takes evaluations of curves at the current instant as input - - [ ] GUI integration - - [ ] TBD (#624) diff --git a/libopenage/renderer/opengl/texture_array.cpp b/libopenage/renderer/opengl/texture_array.cpp index f678e36cab..b53676d0c7 100644 --- a/libopenage/renderer/opengl/texture_array.cpp +++ b/libopenage/renderer/opengl/texture_array.cpp @@ -42,7 +42,7 @@ GlTexture2dArray::GlTexture2dArray(const std::vector& } } -GlTexture2dArray::GlTexture2dArray(size_t n_layers, resources::Texture2dInfo layer_info) +GlTexture2dArray::GlTexture2dArray(size_t n_layers, resources::Texture2dInfo const& layer_info) : Texture2dArray(layer_info) , GlSimpleObject([] (GLuint handle) { glDeleteTextures(1, &handle); } ) , n_layers(n_layers) diff --git a/libopenage/renderer/opengl/texture_array.h b/libopenage/renderer/opengl/texture_array.h index 6fdfe90777..dc89705d18 100644 --- a/libopenage/renderer/opengl/texture_array.h +++ b/libopenage/renderer/opengl/texture_array.h @@ -25,7 +25,7 @@ class GlTexture2dArray final : public Texture2dArray, public GlSimpleObject { /// Constructs an array of ln_layers empty layers, with the per-layer texture /// format specified in layer_info. - GlTexture2dArray(size_t n_layers, resources::Texture2dInfo layer_info); + GlTexture2dArray(size_t n_layers, resources::Texture2dInfo const& layer_info); void upload(size_t layer, resources::Texture2dData const&) override; From b72316b0907f102da9e6135d57bc4d626a9ad901 Mon Sep 17 00:00:00 2001 From: Wojciech Nawrocki Date: Sat, 14 Jul 2018 22:24:55 +0200 Subject: [PATCH 5/7] renderer: factor out 2D subtexture struct, misc fixes --- libopenage/renderer/resources/CMakeLists.txt | 1 + .../renderer/resources/texture_data.cpp | 57 ++++---- .../renderer/resources/texture_info.cpp | 20 +-- libopenage/renderer/resources/texture_info.h | 24 +++- .../renderer/resources/texture_subinfo.cpp | 51 ++++++++ .../renderer/resources/texture_subinfo.h | 40 ++++++ libopenage/renderer/sdl_global.cpp | 8 +- libopenage/renderer/tests.cpp | 7 +- libopenage/renderer/texture_array.h | 22 ++-- libopenage/renderer/vulkan/CMakeLists.txt | 8 +- .../renderer/vulkan/graphics_device.cpp | 122 +++++++++--------- libopenage/renderer/vulkan/renderer.cpp | 6 +- libopenage/renderer/vulkan/renderer.h | 4 +- 13 files changed, 234 insertions(+), 136 deletions(-) create mode 100644 libopenage/renderer/resources/texture_subinfo.cpp create mode 100644 libopenage/renderer/resources/texture_subinfo.h diff --git a/libopenage/renderer/resources/CMakeLists.txt b/libopenage/renderer/resources/CMakeLists.txt index 75c138b1ab..250509a305 100644 --- a/libopenage/renderer/resources/CMakeLists.txt +++ b/libopenage/renderer/resources/CMakeLists.txt @@ -3,4 +3,5 @@ add_sources(libopenage shader_source.cpp texture_data.cpp texture_info.cpp + texture_subinfo.cpp ) diff --git a/libopenage/renderer/resources/texture_data.cpp b/libopenage/renderer/resources/texture_data.cpp index 9236c71672..de534dcacc 100644 --- a/libopenage/renderer/resources/texture_data.cpp +++ b/libopenage/renderer/resources/texture_data.cpp @@ -19,7 +19,7 @@ namespace resources { /// @param width in pixels of the image /// @param fmt of pixels in the image /// @param row_size the actual size in bytes of an image row, including padding -static size_t guess_row_alignment(size_t width, pixel_format fmt, size_t row_size) { +static constexpr size_t guess_row_alignment(size_t width, pixel_format fmt, size_t row_size) { // Use the highest possible alignment for even-width images. if (width % 8 == 0) { return 8; @@ -50,7 +50,10 @@ static size_t guess_row_alignment(size_t width, pixel_format fmt, size_t row_siz Texture2dData::Texture2dData(const util::Path &path, bool use_metafile) { std::string native_path = path.resolve_native_path(); - SDL_Surface *surface = IMG_Load(native_path.c_str()); + std::unique_ptr surface( + IMG_Load(native_path.c_str()), + &SDL_FreeSurface + ); if (!surface) { throw Error(MSG(err) << @@ -89,20 +92,19 @@ Texture2dData::Texture2dData(const util::Path &path, bool use_metafile) { // 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; + std::vector subtextures; if (use_metafile) { util::Path meta = (path.get_parent() / path.get_stem()).with_suffix(".slp.docx"); log::log(MSG(info) << "Loading meta file: " << meta); // get subtexture information by meta file exported by script - subtextures = util::read_csv_file(meta); + 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}; + Texture2dSubInfo s{0, 0, w, h, w/2, h/2}; subtextures.push_back(s); } @@ -144,7 +146,7 @@ void Texture2dData::store(const util::Path& file) const { log::log(MSG(info) << "Saving texture data to " << file); if (this->info.get_format() != pixel_format::rgba8) { - throw "unimplemented"; + throw Error(MSG(err) << "Storing 2D textures into files is unimplemented. PRs welcome :D"); } auto size = this->info.get_size(); @@ -164,31 +166,36 @@ void Texture2dData::store(const util::Path& file) const { amask = 0xff000000; #endif - SDL_Surface *surf = SDL_CreateRGBSurfaceFrom( - // const_cast is okay, because the surface doesn't modify data - const_cast(static_cast(this->data.data())), - size.first, - size.second, - 32, - this->info.get_row_size(), - rmask, gmask, bmask, amask + std::unique_ptr surf( + SDL_CreateRGBSurfaceFrom( + // const_cast is okay, because the surface doesn't modify data + const_cast(static_cast(this->data.data())), + size.first, + size.second, + 32, + this->info.get_row_size(), + rmask, gmask, bmask, amask + ), + &SDL_FreeSurface ); #else - SDL_Surface *surf = SDL_CreateRGBSurfaceWithFormatFrom( - // const_cast is okay, because the surface doesn't modify data - const_cast(static_cast(this->data.data())), - size.first, - size.second, - 32, - this->info.get_row_size(), - SDL_PIXELFORMAT_RGBA32 + std::unique_ptr surf( + SDL_CreateRGBSurfaceWithFormatFrom( + // const_cast is okay, because the surface doesn't modify data + const_cast(static_cast(this->data.data())), + size.first, + size.second, + 32, + this->info.get_row_size(), + SDL_PIXELFORMAT_RGBA32 + ), + &SDL_FreeSurface ); #endif // Call sdl_image for saving the screenshot to PNG std::string path = file.resolve_native_path_w(); - IMG_SavePNG(surf, path.c_str()); - SDL_FreeSurface(surf); + IMG_SavePNG(surf.get(), path.c_str()); } }}} diff --git a/libopenage/renderer/resources/texture_info.cpp b/libopenage/renderer/resources/texture_info.cpp index c6cf9c6461..a0cd106348 100644 --- a/libopenage/renderer/resources/texture_info.cpp +++ b/libopenage/renderer/resources/texture_info.cpp @@ -2,28 +2,12 @@ #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::bgr8, 3), - std::make_pair(pixel_format::rgba8, 4), - std::make_pair(pixel_format::rgba8ui, 4), - std::make_pair(pixel_format::depth24, 3) -); - -size_t pixel_size(pixel_format fmt) { - return pix_size.get(fmt); -} - -Texture2dInfo::Texture2dInfo(size_t width, size_t height, pixel_format fmt, size_t row_alignment, std::vector &&subs) +Texture2dInfo::Texture2dInfo(size_t width, size_t height, pixel_format fmt, size_t row_alignment, std::vector&& subs) : w(width) , h(height) , format(fmt) @@ -74,7 +58,7 @@ size_t Texture2dInfo::get_subtexture_count() const { return this->subtextures.size(); } -const gamedata::subtexture& Texture2dInfo::get_subtexture(size_t subid) const { +const Texture2dSubInfo& Texture2dInfo::get_subtexture(size_t subid) const { if (subid < this->subtextures.size()) { return this->subtextures[subid]; } diff --git a/libopenage/renderer/resources/texture_info.h b/libopenage/renderer/resources/texture_info.h index cefc9491df..797152933f 100644 --- a/libopenage/renderer/resources/texture_info.h +++ b/libopenage/renderer/resources/texture_info.h @@ -5,7 +5,9 @@ #include #include -#include "../../gamedata/texture.gen.h" +#include "../../datastructure/constexpr_map.h" + +#include "texture_subinfo.h" namespace openage { @@ -31,7 +33,19 @@ enum class pixel_format { }; /// Returns the size in bytes of a single pixel of the specified format. -size_t pixel_size(pixel_format); +constexpr size_t pixel_size(pixel_format fmt) { + 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::bgr8, 3), + std::make_pair(pixel_format::rgba8, 4), + std::make_pair(pixel_format::rgba8ui, 4), + std::make_pair(pixel_format::depth24, 3) + ); + + return pix_size.get(fmt); +} /// Contains information about a 2D texture surface, without actual texture data. /// The class supports subtextures, so that one big texture ("texture atlas") @@ -40,7 +54,7 @@ size_t pixel_size(pixel_format); class Texture2dInfo { public: /// Constructs a Texture2dInfo with the given information. - Texture2dInfo(size_t width, size_t height, pixel_format, size_t row_alignment = 1, std::vector&& = std::vector()); + Texture2dInfo(size_t width, size_t height, pixel_format, size_t row_alignment = 1, std::vector&& = std::vector()); Texture2dInfo() = default; Texture2dInfo(Texture2dInfo const&) = default; @@ -75,7 +89,7 @@ class Texture2dInfo { 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; + const Texture2dSubInfo& 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; @@ -101,7 +115,7 @@ class Texture2dInfo { /// 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; + std::vector subtextures; }; }}} diff --git a/libopenage/renderer/resources/texture_subinfo.cpp b/libopenage/renderer/resources/texture_subinfo.cpp new file mode 100644 index 0000000000..96771e2ddb --- /dev/null +++ b/libopenage/renderer/resources/texture_subinfo.cpp @@ -0,0 +1,51 @@ +// Copyright 2013-2018 the openage authors. See copying.md for legal info. + +#include "texture_subinfo.h" + + +namespace openage { +namespace renderer { +namespace resources { + +Texture2dSubInfo::Texture2dSubInfo(const std::string& line) { + constexpr size_t member_count = 6; + + std::vector buf = openage::util::split_escape( + line, ',', member_count + ); + + if (buf.size() != member_count) { + throw Error( + MSG(err) << "Tokenizing subtexture led to " + << buf.size() << " columns (expected " + << member_count << ")!" + ); + } + + size_t elems_read = 0; + elems_read += sscanf(buf[0].c_str(), "%u", &this->x); + elems_read += sscanf(buf[1].c_str(), "%u", &this->y); + elems_read += sscanf(buf[2].c_str(), "%u", &this->w); + elems_read += sscanf(buf[3].c_str(), "%u", &this->h); + elems_read += sscanf(buf[4].c_str(), "%u", &this->cx); + elems_read += sscanf(buf[5].c_str(), "%u", &this->cy); + + if (elems_read != member_count) { + throw Error(MSG(err) << "Failed to read subtexture members from CSV line."); + } +} + +Texture2dSubInfo::Texture2dSubInfo(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t cx, uint32_t cy) + : x(x) + , y(y) + , w(w) + , h(h) + , cx(cx) + , cy(cy) {} + +int Texture2dSubInfo::fill(const std::string& line) { + *this = Texture2dSubInfo(line); + return -1; +} + +}}} diff --git a/libopenage/renderer/resources/texture_subinfo.h b/libopenage/renderer/resources/texture_subinfo.h new file mode 100644 index 0000000000..33a44ddf62 --- /dev/null +++ b/libopenage/renderer/resources/texture_subinfo.h @@ -0,0 +1,40 @@ +// Copyright 2013-2018 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "../../util/csv.h" + + +namespace openage { +namespace renderer { +namespace resources { + +/** + * Describes a position of a (sub)texture within a larger texture. + * Used for texture atlases and animations where all frames + * are within one texture file. */ +struct Texture2dSubInfo { + /// The subtexture position within the atlas + uint32_t x, y; + + /// The subtexture size + uint32_t w, h; + + /// Position of the subtexture's center within the atlas + uint32_t cx, cy; + + /// Initializes the info from a CSV line containing its members. + Texture2dSubInfo(const std::string& line); + + /// Initializes the info from data. + Texture2dSubInfo(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t cx, uint32_t cy); + + Texture2dSubInfo() = default; + + /// Needed for compatibility reasons, don't use this directly. + int fill(const std::string& line); +}; + +}}} diff --git a/libopenage/renderer/sdl_global.cpp b/libopenage/renderer/sdl_global.cpp index 1a1acead96..6298602d26 100644 --- a/libopenage/renderer/sdl_global.cpp +++ b/libopenage/renderer/sdl_global.cpp @@ -26,22 +26,22 @@ class SDL { } } - sdl = SDL(); + SDL::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)); - inited = true; + SDL::inited = true; } ~SDL() { - if (inited) { + if (SDL::inited) { IMG_Quit(); SDL_Quit(); - log::log(MSG(info) << "Exited SDL"); + log::log(MSG(info) << "Destroyed SDL global context"); } } diff --git a/libopenage/renderer/tests.cpp b/libopenage/renderer/tests.cpp index d7420f60ad..6f6ecf076a 100644 --- a/libopenage/renderer/tests.cpp +++ b/libopenage/renderer/tests.cpp @@ -219,7 +219,6 @@ void main() { } id--; //real id is id-1 log::log(INFO << "Object number " << id << " clicked."); - renderer->display_into_data().store(path / "/assets/screen.png"); } ); window.add_resize_callback([&] (size_t w, size_t h) { @@ -229,9 +228,9 @@ void main() { Eigen::Matrix4f pmat; pmat << xScale, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1; + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1; // resize fbo color_texture = renderer->add_texture(resources::Texture2dInfo(w, h, resources::pixel_format::rgba8)); diff --git a/libopenage/renderer/texture_array.h b/libopenage/renderer/texture_array.h index 485019ca48..ff53ced1b8 100644 --- a/libopenage/renderer/texture_array.h +++ b/libopenage/renderer/texture_array.h @@ -13,22 +13,22 @@ namespace renderer { /// (size, pixel format, etc). class Texture2dArray { public: - virtual ~Texture2dArray(); + virtual ~Texture2dArray(); - /// Returns information about the layer format. - resources::Texture2dInfo const& get_info() const; + /// Returns information about the layer format. + resources::Texture2dInfo const& get_info() const; - /// Uploads the given texture data into the specified layer. `layer` must - /// be strictly less than the size of the array and the data format must - /// match the format this array was originally created with. - virtual void upload(size_t layer, resources::Texture2dData const&) = 0; + /// Uploads the given texture data into the specified layer. `layer` must + /// be strictly less than the size of the array and the data format must + /// match the format this array was originally created with. + virtual void upload(size_t layer, resources::Texture2dData const&) = 0; protected: - /// Constructs the base class. - Texture2dArray(resources::Texture2dInfo); + /// Constructs the base class. + Texture2dArray(resources::Texture2dInfo); - /// Information about the size, format, etc. of every layer in this array. - resources::Texture2dInfo layer_info; + /// Information about the size, format, etc. of every layer in this array. + resources::Texture2dInfo layer_info; }; }} // openage::renderer diff --git a/libopenage/renderer/vulkan/CMakeLists.txt b/libopenage/renderer/vulkan/CMakeLists.txt index fa77887b8c..569d36c672 100644 --- a/libopenage/renderer/vulkan/CMakeLists.txt +++ b/libopenage/renderer/vulkan/CMakeLists.txt @@ -1,6 +1,6 @@ add_sources(libopenage - graphics_device.cpp - loader.cpp - renderer.cpp - windowvk.cpp + graphics_device.cpp + loader.cpp + renderer.cpp + windowvk.cpp ) diff --git a/libopenage/renderer/vulkan/graphics_device.cpp b/libopenage/renderer/vulkan/graphics_device.cpp index b84f5fe53a..e9c8e08133 100644 --- a/libopenage/renderer/vulkan/graphics_device.cpp +++ b/libopenage/renderer/vulkan/graphics_device.cpp @@ -15,79 +15,79 @@ namespace renderer { namespace vulkan { std::optional VlkGraphicsDevice::find_device_surface_support(VkPhysicalDevice dev, VkSurfaceKHR surf) { - // Search for queue families in the device - auto q_fams = vk_do_ritual(vkGetPhysicalDeviceQueueFamilyProperties, dev); + // Search for queue families in the device + auto q_fams = vk_do_ritual(vkGetPhysicalDeviceQueueFamilyProperties, dev); - std::optional maybe_graphics_fam = {}; - std::optional maybe_present_fam = {}; + std::optional maybe_graphics_fam = {}; + std::optional maybe_present_fam = {}; - // Figure out if any of the families supports graphics + // Figure out if any of the families supports graphics + for (size_t i = 0; i < q_fams.size(); i++) { + auto const& q_fam = q_fams[i]; + + if (q_fam.queueCount > 0) { + if (q_fam.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + maybe_graphics_fam = i; + + // See if it also supports present + VkBool32 support = false; + vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surf, &support); + if (support) { + // This family support both, we're done + maybe_present_fam = i; + break; + } + } + } + } + + if (!maybe_graphics_fam) { + // This device has no graphics queue family that works with the surface + return {}; + } + + SurfaceSupportDetails details = {}; + details.phys_device = dev; + details.surface = surf; + + // If we have found a family that support both graphics and present + if (maybe_present_fam) { + details.graphics_fam = *maybe_graphics_fam; + details.maybe_present_fam = {}; + } else { + // Otherwise look for a present-only queue for (size_t i = 0; i < q_fams.size(); i++) { auto const& q_fam = q_fams[i]; - if (q_fam.queueCount > 0) { - if (q_fam.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - maybe_graphics_fam = i; - - // See if it also supports present - VkBool32 support = false; - vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surf, &support); - if (support) { - // This family support both, we're done - maybe_present_fam = i; - break; - } + VkBool32 support = false; + vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surf, &support); + if (support) { + maybe_present_fam = i; + break; } } } - if (!maybe_graphics_fam) { - // This device has no graphics queue family that works with the surface + if (!maybe_present_fam) { + // This device has no present queue family that works with the surface return {}; } - SurfaceSupportDetails details = {}; - details.phys_device = dev; - details.surface = surf; - - // If we have found a family that support both graphics and present - if (maybe_present_fam) { - details.graphics_fam = *maybe_graphics_fam; - details.maybe_present_fam = {}; - } else { - // Otherwise look for a present-only queue - for (size_t i = 0; i < q_fams.size(); i++) { - auto const& q_fam = q_fams[i]; - if (q_fam.queueCount > 0) { - VkBool32 support = false; - vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surf, &support); - if (support) { - maybe_present_fam = i; - break; - } - } - } - - if (!maybe_present_fam) { - // This device has no present queue family that works with the surface - return {}; - } - - details.graphics_fam = *maybe_graphics_fam; - details.maybe_present_fam = maybe_present_fam; - } + details.graphics_fam = *maybe_graphics_fam; + details.maybe_present_fam = maybe_present_fam; + } - // Obtain other information - details.surface_formats = vk_do_ritual(vkGetPhysicalDeviceSurfaceFormatsKHR, dev, surf); - details.present_modes = vk_do_ritual(vkGetPhysicalDeviceSurfacePresentModesKHR, dev, surf); - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev, surf, &details.surface_caps); + // Obtain other information + details.surface_formats = vk_do_ritual(vkGetPhysicalDeviceSurfaceFormatsKHR, dev, surf); + details.present_modes = vk_do_ritual(vkGetPhysicalDeviceSurfacePresentModesKHR, dev, surf); + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev, surf, &details.surface_caps); - // Finally, check that we have at least one format and present mode - if (details.surface_formats.empty() || details.present_modes.empty()) { - return {}; - } + // Finally, check that we have at least one format and present mode + if (details.surface_formats.empty() || details.present_modes.empty()) { + return {}; + } - return details; + return details; } VlkGraphicsDevice::VlkGraphicsDevice(VkPhysicalDevice dev, std::vector const& q_fams) @@ -111,9 +111,9 @@ VlkGraphicsDevice::VlkGraphicsDevice(VkPhysicalDevice dev, std::vector auto exts = vk_do_ritual(vkEnumerateDeviceExtensionProperties, dev, nullptr); for (auto ext : ext_names) { if (std::count_if(exts.begin(), exts.end(), [=] (VkExtensionProperties const& p) { - return std::strcmp(p.extensionName, ext) == 0; - } ) == 0) - { + return std::strcmp(p.extensionName, ext) == 0; + } ) == 0 + ) { throw Error(MSG(err) << "Tried to instantiate device, but it's missing this extension: " << ext); } } diff --git a/libopenage/renderer/vulkan/renderer.cpp b/libopenage/renderer/vulkan/renderer.cpp index 9d69198bc4..c955f88164 100644 --- a/libopenage/renderer/vulkan/renderer.cpp +++ b/libopenage/renderer/vulkan/renderer.cpp @@ -18,7 +18,9 @@ namespace openage { namespace renderer { namespace vulkan { -void VlkRenderer::do_the_thing() { +void VlkRenderer::do_the_thing(fslike::Path& dir) { + // Please keep in mind that this function is only for testing and so might be messy. + // Enumerate available physical devices auto devices = vk_do_ritual(vkEnumeratePhysicalDevices, this->instance); if (devices.size() == 0) { @@ -54,8 +56,6 @@ void VlkRenderer::do_the_thing() { // TODO reinit swapchain on window resize - auto dir = std::make_shared("/home/wojtek/Programming/C++/openage/"); - auto vert = resources::ShaderSource( resources::shader_lang_t::spirv, resources::shader_stage_t::vertex, diff --git a/libopenage/renderer/vulkan/renderer.h b/libopenage/renderer/vulkan/renderer.h index e36b549cbf..cd4444e5c1 100644 --- a/libopenage/renderer/vulkan/renderer.h +++ b/libopenage/renderer/vulkan/renderer.h @@ -5,6 +5,7 @@ #include #include "../renderer.h" +#include "../../util/path.h" #include "graphics_device.h" @@ -24,7 +25,8 @@ class VlkRenderer { : instance(instance) , surface(surface) {} - void do_the_thing(); + /// Testing function that draws a triangle. Not part of the final renderer implementation. + void do_the_thing(fslike::Path& root); }; }}} // openage::renderer::vulkan From 5d280f72c3b74f948751a2610afc8d43542ae37d Mon Sep 17 00:00:00 2001 From: Wojciech Nawrocki Date: Sun, 15 Jul 2018 00:06:41 +0200 Subject: [PATCH 6/7] renderer: clang-tidy --- libopenage/renderer/opengl/context.cpp | 9 +++--- libopenage/renderer/opengl/renderer.cpp | 1 - libopenage/renderer/opengl/shader_program.cpp | 4 +-- libopenage/renderer/opengl/window.cpp | 2 +- .../renderer/resources/texture_data.cpp | 29 +++++++++++-------- libopenage/renderer/tests.cpp | 6 ++-- 6 files changed, 27 insertions(+), 24 deletions(-) diff --git a/libopenage/renderer/opengl/context.cpp b/libopenage/renderer/opengl/context.cpp index a19f55b30f..9d9e5150e7 100644 --- a/libopenage/renderer/opengl/context.cpp +++ b/libopenage/renderer/opengl/context.cpp @@ -44,11 +44,10 @@ static gl_context_capabilities find_capabilities() { << 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_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); diff --git a/libopenage/renderer/opengl/renderer.cpp b/libopenage/renderer/opengl/renderer.cpp index 73ef862859..836b604386 100644 --- a/libopenage/renderer/opengl/renderer.cpp +++ b/libopenage/renderer/opengl/renderer.cpp @@ -16,7 +16,6 @@ namespace opengl { GlRenderer::GlRenderer(GlContext *ctx) : gl_context(ctx) - , display() { log::log(MSG(info) << "Created OpenGL renderer"); } diff --git a/libopenage/renderer/opengl/shader_program.cpp b/libopenage/renderer/opengl/shader_program.cpp index a2631c746c..eb86708fe7 100644 --- a/libopenage/renderer/opengl/shader_program.cpp +++ b/libopenage/renderer/opengl/shader_program.cpp @@ -216,10 +216,10 @@ void GlShaderProgram::execute_with(const GlUniformInput *unif_in, const GlGeomet glUniform4fv(loc, 1, reinterpret_cast(ptr)); break; case GL_FLOAT_MAT3: - glUniformMatrix3fv(loc, 1, false, reinterpret_cast(ptr)); + glUniformMatrix3fv(loc, 1, GLboolean(false), reinterpret_cast(ptr)); break; case GL_FLOAT_MAT4: - glUniformMatrix4fv(loc, 1, false, reinterpret_cast(ptr)); + glUniformMatrix4fv(loc, 1, GLboolean(false), reinterpret_cast(ptr)); break; case GL_INT_VEC2: glUniform2iv(loc, 1, reinterpret_cast(ptr)); diff --git a/libopenage/renderer/opengl/window.cpp b/libopenage/renderer/opengl/window.cpp index d4e2a88508..672279f195 100644 --- a/libopenage/renderer/opengl/window.cpp +++ b/libopenage/renderer/opengl/window.cpp @@ -58,7 +58,7 @@ void GlWindow::set_size(size_t width, size_t height) { void GlWindow::update() { SDL_Event event; - while (SDL_PollEvent(&event)) { + while (SDL_PollEvent(&event) != 0) { if (event.type == SDL_WINDOWEVENT) { if (event.window.event == SDL_WINDOWEVENT_RESIZED) { size_t width = event.window.data1; diff --git a/libopenage/renderer/resources/texture_data.cpp b/libopenage/renderer/resources/texture_data.cpp index de534dcacc..51a530e514 100644 --- a/libopenage/renderer/resources/texture_data.cpp +++ b/libopenage/renderer/resources/texture_data.cpp @@ -23,9 +23,11 @@ static constexpr size_t guess_row_alignment(size_t width, pixel_format fmt, size // Use the highest possible alignment for even-width images. if (width % 8 == 0) { return 8; - } else if (width % 4 == 0) { + } + if (width % 4 == 0) { return 4; - } else if (width % 2 == 0) { + } + if (width % 2 == 0) { return 2; } @@ -36,11 +38,14 @@ static constexpr size_t guess_row_alignment(size_t width, pixel_format fmt, size if (padding == 0) { return 1; - } else if (padding <= 1) { + } + if (padding <= 1) { return 2; - } else if (padding <= 3) { + } + if (padding <= 3) { return 4; - } else if (padding <= 7) { + } + if (padding <= 7) { return 8; } @@ -56,13 +61,13 @@ Texture2dData::Texture2dData(const util::Path &path, bool use_metafile) { ); 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); + throw Error(MSG(err) + << "Could not load texture from " << native_path + << ": " << IMG_GetError()); } + log::log(MSG(dbg) << "Texture has been loaded from " << native_path); + auto surf_fmt = *surface->format; pixel_format pix_fmt; @@ -84,8 +89,8 @@ Texture2dData::Texture2dData(const util::Path &path, bool use_metafile) { throw Error(MSG(err) << "Texture " << native_path << " uses an unsupported format."); } - auto w = surface->w; - auto h = surface->h; + uint32_t w = uint32_t(surface->w); + uint32_t h = uint32_t(surface->h); size_t data_size = surf_fmt.BytesPerPixel * surface->w * surface->h; diff --git a/libopenage/renderer/tests.cpp b/libopenage/renderer/tests.cpp index 6f6ecf076a..640019b0d2 100644 --- a/libopenage/renderer/tests.cpp +++ b/libopenage/renderer/tests.cpp @@ -23,9 +23,9 @@ namespace renderer { namespace tests { // Macro for debugging OpenGL state. -#define TEST_DBG(txt) \ +#define DBG_GL(txt) \ printf("before %s\n", txt); \ - window.get_context()->check_error(); \ + opengl::GlContext::check_error(); \ printf("after %s\n", txt); void renderer_demo_0(util::Path path) { @@ -247,7 +247,7 @@ void main() { renderer->render(pass); renderer->render(display_pass); window.update(); - window.get_context()->check_error(); + opengl::GlContext::check_error(); } } From f0f6993132c19d0ba5b0eab45b7e07e9b111415e Mon Sep 17 00:00:00 2001 From: Wojciech Nawrocki Date: Sun, 15 Jul 2018 00:16:28 +0200 Subject: [PATCH 7/7] cmake: fix renderer Vulkan-less build, check for Eigen --- libopenage/CMakeLists.txt | 24 +++++++++++--- libopenage/renderer/CMakeLists.txt | 10 ++++-- libopenage/renderer/opengl/shader_program.cpp | 2 +- .../renderer/vulkan/graphics_device.cpp | 10 +++--- libopenage/renderer/vulkan/loader.cpp | 4 +-- libopenage/renderer/vulkan/render_target.h | 2 +- libopenage/renderer/vulkan/renderer.cpp | 5 ++- libopenage/renderer/vulkan/renderer.h | 2 +- libopenage/renderer/vulkan/shader_program.h | 1 + libopenage/renderer/vulkan/util.h | 4 +-- libopenage/renderer/vulkan/windowvk.cpp | 33 ++++++++++++++----- libopenage/renderer/vulkan/windowvk.h | 2 ++ 12 files changed, 69 insertions(+), 30 deletions(-) diff --git a/libopenage/CMakeLists.txt b/libopenage/CMakeLists.txt index 018cae7742..1caf5e874e 100644 --- a/libopenage/CMakeLists.txt +++ b/libopenage/CMakeLists.txt @@ -54,13 +54,13 @@ endif() find_library(FONTCONFIG_LIB fontconfig) find_package(Freetype REQUIRED) -find_package(OpenGL REQUIRED) find_package(PNG REQUIRED) find_package(SDL2 REQUIRED) find_package(SDL2Image REQUIRED) find_package(Opusfile REQUIRED) find_package(Epoxy REQUIRED) find_package(HarfBuzz 1.0.0 REQUIRED) +find_package(Eigen3 3.3 REQUIRED NO_MODULE) set(QT_VERSION_REQ "5.5") find_package(Qt5Core ${QT_VERSION_REQ} REQUIRED) @@ -190,6 +190,20 @@ else() have_config_option(inotify INOTIFY false) endif() +# ncurses support +if(WANT_NCURSES) + set(CURSES_NEED_NCURSES TRUE) + find_package(Curses) +endif() + +if(WANT_NCURSES AND CURSES_FOUND) + have_config_option(ncurses NCURSES true) + target_include_directories(libopenage PRIVATE ${Curses_INCLUDE_DIRS}) + target_link_libraries(libopenage PRIVATE ${CURSES_LIBRARIES}) +else() + have_config_option(ncurses NCURSES false) +endif() + # opengl support if(WANT_OPENGL) find_package(OpenGL) @@ -203,6 +217,7 @@ endif() if(WANT_OPENGL AND OPENGL_FOUND) have_config_option(opengl OPENGL true) include_directories(${OPENGL_INCLUDE_DIR}) + target_link_libraries(libopenage PRIVATE ${OPENGL_LIBRARY}) else() have_config_option(opengl OPENGL false) endif() @@ -210,6 +225,7 @@ endif() if(WANT_VULKAN AND VULKAN_FOUND) have_config_option(vulkan VULKAN true) include_directories(${Vulkan_INCLUDE_DIRS}) + target_link_libraries(libopenage PRIVATE ${Vulkan_LIBRARIES}) else() have_config_option(vulkan VULKAN false) endif() @@ -218,6 +234,8 @@ if(NOT (OPENGL_FOUND OR VULKAN_FOUND)) message(FATAL_ERROR "One of OpenGL or Vulkan is required!") endif() +################################################## +# build configuration generation get_config_option_string() configure_file(config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/config.h) @@ -234,7 +252,6 @@ endif() # directories for header inclusion target_include_directories(libopenage PRIVATE - ${OPENGL_INCLUDE_DIR} ${FREETYPE_INCLUDE_DIRS} ${EPOXY_INCLUDE_DIRS} ${OPUS_INCLUDE_DIRS} @@ -254,6 +271,7 @@ target_link_libraries(libopenage PUBLIC pthread nyan::nyan + Eigen3::Eigen ${PNG_LIBRARIES} ${OPUS_LIBRARIES} ${OGG_LIB} @@ -263,8 +281,6 @@ target_link_libraries(libopenage ${FREETYPE_LIBRARIES} ${EPOXY_LIBRARIES} ${MATH_LIB} - ${OPENGL_LIBRARY} - ${Vulkan_LIBRARIES} ${SDL2IMAGE_LIBRARIES} ${SDL2_LIBRARY} ${UTIL_LIB} diff --git a/libopenage/renderer/CMakeLists.txt b/libopenage/renderer/CMakeLists.txt index 0ccde1345b..366ebde28b 100644 --- a/libopenage/renderer/CMakeLists.txt +++ b/libopenage/renderer/CMakeLists.txt @@ -14,6 +14,12 @@ pxdgen( ) add_subdirectory(font/) -add_subdirectory(opengl/) add_subdirectory(resources/) -#add_subdirectory(vulkan/) + +if(OPENGL_FOUND) + add_subdirectory(opengl/) +endif() + +if(VULKAN_FOUND) + add_subdirectory(vulkan/) +endif() diff --git a/libopenage/renderer/opengl/shader_program.cpp b/libopenage/renderer/opengl/shader_program.cpp index eb86708fe7..9f1627e2b3 100644 --- a/libopenage/renderer/opengl/shader_program.cpp +++ b/libopenage/renderer/opengl/shader_program.cpp @@ -183,7 +183,7 @@ void GlShaderProgram::use() const { } void GlShaderProgram::execute_with(const GlUniformInput *unif_in, const GlGeometry *geom) { - ENSURE(unif_in->program == this); + ENSURE(unif_in->program == this, "Uniform input passed to different shader than it was created with."); this->use(); diff --git a/libopenage/renderer/vulkan/graphics_device.cpp b/libopenage/renderer/vulkan/graphics_device.cpp index e9c8e08133..69f1577278 100644 --- a/libopenage/renderer/vulkan/graphics_device.cpp +++ b/libopenage/renderer/vulkan/graphics_device.cpp @@ -26,13 +26,13 @@ std::optional VlkGraphicsDevice::find_device_surface_supp auto const& q_fam = q_fams[i]; if (q_fam.queueCount > 0) { - if (q_fam.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + if ((q_fam.queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0u) { maybe_graphics_fam = i; // See if it also supports present - VkBool32 support = false; + VkBool32 support = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surf, &support); - if (support) { + if (support != VK_FALSE) { // This family support both, we're done maybe_present_fam = i; break; @@ -59,9 +59,9 @@ std::optional VlkGraphicsDevice::find_device_surface_supp for (size_t i = 0; i < q_fams.size(); i++) { auto const& q_fam = q_fams[i]; if (q_fam.queueCount > 0) { - VkBool32 support = false; + VkBool32 support = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surf, &support); - if (support) { + if (support != VK_FALSE) { maybe_present_fam = i; break; } diff --git a/libopenage/renderer/vulkan/loader.cpp b/libopenage/renderer/vulkan/loader.cpp index c4e7c89078..fdeb6f8c94 100644 --- a/libopenage/renderer/vulkan/loader.cpp +++ b/libopenage/renderer/vulkan/loader.cpp @@ -34,9 +34,9 @@ VkResult VlkLoader::vkCreateDebugReportCallbackEXT( if (this->pCreateDebugReportCallbackEXT != nullptr) { return this->pCreateDebugReportCallbackEXT(instance, pCreateInfo, pAllocator, pCallback); - } else { - return VK_ERROR_EXTENSION_NOT_PRESENT; } + + return VK_ERROR_EXTENSION_NOT_PRESENT; } void VlkLoader::vkDestroyDebugReportCallbackEXT( diff --git a/libopenage/renderer/vulkan/render_target.h b/libopenage/renderer/vulkan/render_target.h index 25d1a2678d..1951e0f8bb 100644 --- a/libopenage/renderer/vulkan/render_target.h +++ b/libopenage/renderer/vulkan/render_target.h @@ -156,7 +156,7 @@ class VlkFramebuffer final : public RenderTarget { VkViewport viewport; public: - VlkFramebuffer(VkRenderPass pass, std::vector const& attachments) { + VlkFramebuffer(VkRenderPass /*pass*/, std::vector const& /*attachments*/) { } }; diff --git a/libopenage/renderer/vulkan/renderer.cpp b/libopenage/renderer/vulkan/renderer.cpp index c955f88164..a380fd78f0 100644 --- a/libopenage/renderer/vulkan/renderer.cpp +++ b/libopenage/renderer/vulkan/renderer.cpp @@ -3,7 +3,6 @@ #include "renderer.h" #include "../../error/error.h" -#include "../../util/path.h" #include "../../util/fslike/directory.h" #include "../resources/shader_source.h" @@ -18,12 +17,12 @@ namespace openage { namespace renderer { namespace vulkan { -void VlkRenderer::do_the_thing(fslike::Path& dir) { +void VlkRenderer::do_the_thing(util::Path& dir) { // Please keep in mind that this function is only for testing and so might be messy. // Enumerate available physical devices auto devices = vk_do_ritual(vkEnumeratePhysicalDevices, this->instance); - if (devices.size() == 0) { + if (devices.empty()) { throw Error(MSG(err) << "No Vulkan devices available."); } diff --git a/libopenage/renderer/vulkan/renderer.h b/libopenage/renderer/vulkan/renderer.h index cd4444e5c1..7bb13cd6ce 100644 --- a/libopenage/renderer/vulkan/renderer.h +++ b/libopenage/renderer/vulkan/renderer.h @@ -26,7 +26,7 @@ class VlkRenderer { , surface(surface) {} /// Testing function that draws a triangle. Not part of the final renderer implementation. - void do_the_thing(fslike::Path& root); + void do_the_thing(util::Path& dir); }; }}} // openage::renderer::vulkan diff --git a/libopenage/renderer/vulkan/shader_program.h b/libopenage/renderer/vulkan/shader_program.h index c8374790d4..e46030dd13 100644 --- a/libopenage/renderer/vulkan/shader_program.h +++ b/libopenage/renderer/vulkan/shader_program.h @@ -20,6 +20,7 @@ static VkShaderStageFlagBits vk_shader_stage(resources::shader_stage_t stage) { case resources::shader_stage_t::tesselation_control: return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; case resources::shader_stage_t::tesselation_evaluation: return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; case resources::shader_stage_t::fragment: return VK_SHADER_STAGE_FRAGMENT_BIT; + default: throw Error(MSG(err) << "Unknown shader stage."); } } diff --git a/libopenage/renderer/vulkan/util.h b/libopenage/renderer/vulkan/util.h index 229e676b8e..04ab3c872c 100644 --- a/libopenage/renderer/vulkan/util.h +++ b/libopenage/renderer/vulkan/util.h @@ -51,8 +51,6 @@ std::vector vk_do_ritual(R2 (*func)(T, U, V, uint32_t*, R*), T2&& a, U2&& b, { \ VkResult res = fun(__VA_ARGS__); \ if (res != VK_SUCCESS) { \ + throw Error(MSG(err) << "Call to Vulkan function " << #fun << " failed with " << res << "."); \ } \ } - -//throw Error(MSG(err) << "Call to Vulkan function " << #fun << " failed with " << vk::to_string(vk::Result(res)) << "."); \ -// diff --git a/libopenage/renderer/vulkan/windowvk.cpp b/libopenage/renderer/vulkan/windowvk.cpp index 3ecb4a1b7b..a2fccbf6a0 100644 --- a/libopenage/renderer/vulkan/windowvk.cpp +++ b/libopenage/renderer/vulkan/windowvk.cpp @@ -8,25 +8,26 @@ #include "../../error/error.h" #include "../../log/log.h" +#include "../sdl_global.h" + #include "graphics_device.h" #include "util.h" - namespace openage { namespace renderer { namespace vulkan { #ifndef NDEBUG static VKAPI_ATTR VkBool32 VKAPI_CALL vlk_debug_cb( - VkDebugReportFlagsEXT flags, - VkDebugReportObjectTypeEXT objType, - uint64_t obj, - size_t location, - int32_t code, + VkDebugReportFlagsEXT /*flags*/, + VkDebugReportObjectTypeEXT /*objType*/, + uint64_t /*obj*/, + size_t /*location*/, + int32_t /*code*/, const char* layerPrefix, const char* msg, - void* userData) + void* /*userData*/) { log::log(MSG(dbg) << layerPrefix << " " << msg); @@ -77,8 +78,24 @@ static vlk_capabilities find_capabilities() { } VlkWindow::VlkWindow(const char* title) - : capabilities(find_capabilities()) + : Window(800, 600) + , capabilities(find_capabilities()) { + make_sdl_available(); + + this->window = SDL_CreateWindow( + title, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + this->size.first, + this->size.second, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED + ); + + if (this->window == nullptr) { + throw Error{MSG(err) << "Failed to create SDL window: " << SDL_GetError()}; + } + // Find which extensions the SDL window requires. auto extension_names = vk_do_ritual(SDL_Vulkan_GetInstanceExtensions, this->window); /* diff --git a/libopenage/renderer/vulkan/windowvk.h b/libopenage/renderer/vulkan/windowvk.h index 6b5db4ea1b..14d9d37c0e 100644 --- a/libopenage/renderer/vulkan/windowvk.h +++ b/libopenage/renderer/vulkan/windowvk.h @@ -37,6 +37,8 @@ class VlkWindow : public openage::renderer::Window { #endif VlkLoader loader; + SDL_Window* window; + public: VlkWindow(const char* title); ~VlkWindow();