diff --git a/.gitignore b/.gitignore index 9a40979cd9..ff8ce3cbbf 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,7 @@ perf.data* /GPATH /GRTAGS /GTAGS +#copyrighted assets +/assets/converted/ +/assets/terrain/ +.vscode/settings.json diff --git a/.mailmap b/.mailmap index d99a4bb399..042023e657 100644 --- a/.mailmap +++ b/.mailmap @@ -13,3 +13,4 @@ Henry Snoek coop shell (Michael Enßlin, Jonas Jelten, Andre Kupka) Franz-Niclas Muschter Niklas Fiekas +Wojciech Nawrocki diff --git a/CMakeLists.txt b/CMakeLists.txt index 23835f593e..35a497c22d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,14 @@ if(NOT DEFINED WANT_INOTIFY) set(WANT_INOTIFY if_available) endif() +if(NOT DEFINED WANT_OPENGL) + set(WANT_OPENGL if_available) +endif() + +if(NOT DEFINED WANT_VULKAN) + set(WANT_VULKAN if_available) +endif() + if(NOT DEFINED WANT_GPERFTOOLS_PROFILER) set(WANT_GPERFTOOLS_PROFILER if_available) endif() diff --git a/README.md b/README.md index e16b5166dd..83a00de905 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Technology | Component **Qt5** | Graphical user interface **Cython** | Glue code **CMake** | Build system -**OpenGL2.1** | Rendering, shaders +**OpenGL3.3** | Rendering, shaders **SDL2** | Cross-platform Audio/Input/Window handling **Opus** | Audio codec **Humans** | Mixing together all of the above diff --git a/assets/screen.png b/assets/screen.png new file mode 100644 index 0000000000..e8a53efbba Binary files /dev/null and b/assets/screen.png differ diff --git a/assets/test_shaders/alphamask.frag.glsl b/assets/test_shaders/alphamask.frag.glsl new file mode 100644 index 0000000000..fc44a8982f --- /dev/null +++ b/assets/test_shaders/alphamask.frag.glsl @@ -0,0 +1,34 @@ +// alpha masking shader +// +// applies an alpha mask texture to a base texture, +// then draws the masked texture. +#version 330 + +// the base and mask texture, base is the plain terrain tile +uniform sampler2D base_texture; +uniform sampler2D mask_texture; + +// disable blending and show the mask instead +//uniform bool show_mask; + +// get those interpolated texture position from vertexshader +in vec2 base_tex_position; +in vec2 mask_tex_position; + +layout(location=0) out vec4 blended_pixel; + +void main() { + // get the texel from the uniform texture. + vec4 base_pixel = texture2D(base_texture, base_tex_position); + vec4 mask_pixel = texture2D(mask_texture, mask_tex_position); + + //float factor = 1.0 - mask_pixel.x; + float factor = mask_pixel.x; + vec4 blended_pixel = vec4(base_pixel.r, base_pixel.g, base_pixel.b, base_pixel.a - factor); + + //if (0) { + // gl_FragColor = mask_pixel; + //} else { + gl_FragColor = blended_pixel; + //} +} diff --git a/assets/test_shaders/alphamask.vert.glsl b/assets/test_shaders/alphamask.vert.glsl new file mode 100644 index 0000000000..25d62a917e --- /dev/null +++ b/assets/test_shaders/alphamask.vert.glsl @@ -0,0 +1,25 @@ +// vertex shader for applying an alpha mask to a texture +#version 330 + +// modelview*projection matrix +uniform mat4 mvp_matrix; + +// the position of this vertex +layout(location=0) in vec2 vertex_position; + +// send the texture coordinates to the fragmentshader +layout(location=1) in vec2 base_tex_coordinates; +layout(location=2) in vec2 mask_tex_coordinates; + +// send the texture coordinates to the fragmentshader +out vec2 base_tex_position; +out vec2 mask_tex_position; + +void main(void) { + // transform the position with the mvp matrix + gl_Position = mvp_matrix * vec4(vertex_position,0.0,1.0); + + // set the fixpoints for the tex coordinates at this vertex + mask_tex_position = mask_tex_coordinates; + base_tex_position = base_tex_coordinates; +} diff --git a/assets/test_shaders/batch.frag.glsl b/assets/test_shaders/batch.frag.glsl new file mode 100644 index 0000000000..bf5c73df70 --- /dev/null +++ b/assets/test_shaders/batch.frag.glsl @@ -0,0 +1,157 @@ +#version 330 core +out vec4 FragColor; + +in vec4 ourColor; +in vec2 pos; +in vec2 uv; +in float a_id; +uniform vec2 mouse_pos = vec2(0.0,0.0); + +uniform sampler2D texture_0; +uniform sampler2D texture_1; +uniform sampler2D texture_2; +uniform sampler2D texture_3; +uniform sampler2D texture_4; +uniform sampler2D texture_5; +uniform sampler2D texture_6; +uniform sampler2D texture_7; +uniform sampler2D texture_8; +uniform sampler2D texture_9; +uniform sampler2D texture_10; +uniform sampler2D texture_11; +uniform sampler2D texture_12; +uniform sampler2D texture_13; +uniform sampler2D texture_14; +uniform sampler2D texture_15; +uniform sampler2D texture_16; +uniform sampler2D texture_17; +uniform sampler2D texture_18; +uniform sampler2D texture_19; +uniform sampler2D texture_20; +uniform sampler2D texture_21; +uniform sampler2D texture_22; +uniform sampler2D texture_23; +uniform sampler2D texture_24; +uniform sampler2D texture_25; +uniform sampler2D texture_26; +uniform sampler2D texture_27; +uniform sampler2D texture_28; +uniform sampler2D texture_29; +uniform sampler2D texture_30; +uniform sampler2D texture_31; + +void switcher(in int index,out vec4 color){ + +switch(index){ +case 0: + color = texture(texture_0, uv); + break; +case 1: + color = texture(texture_1, uv); + break; +case 2: + color = texture(texture_2, uv); + break; +case 3: + color = texture(texture_3, uv); + break; +case 4: + color = texture(texture_4, uv); + break; +case 5: + color = texture(texture_5, uv); + break; +case 6: + color = texture(texture_6, uv); + break; +case 7: + color = texture(texture_7, uv); + break; +case 8: + color = texture(texture_8, uv); + break; +case 9: + color = texture(texture_9, uv); + break; +case 10: + color = texture(texture_10, uv); + break; +case 11: + color = texture(texture_11, uv); + break; +case 12: + color = texture(texture_12, uv); + break; +case 13: + color = texture(texture_13, uv); + break; +case 14: + color = texture(texture_14, uv); + break; +case 15: + color = texture(texture_15, uv); + break; +case 16: + color = texture(texture_16, uv); + break; +case 17: + color = texture(texture_17, uv); + break; +case 18: + color = texture(texture_18, uv); + break; +case 19: + color = texture(texture_19, uv); + break; +case 20: + color = texture(texture_20, uv); + break; +case 21: + color = texture(texture_21, uv); + break; +case 22: + color = texture(texture_22, uv); + break; +case 23: + color = texture(texture_23, uv); + break; +case 24: + color = texture(texture_24, uv); + break; +case 25: + color = texture(texture_25, uv); + break; +case 26: + color = texture(texture_26, uv); + break; +case 27: + color = texture(texture_27, uv); + break; +case 28: + color = texture(texture_28, uv); + break; + +case 29: + color = texture(texture_29, uv); + break; +case 30: + color = texture(texture_30, uv); + break; + +case 31: + color = texture(texture_31, uv); + break; +} +} +void main() +{ + float intensity = 500.0/(length(pos - mouse_pos)); + FragColor = ourColor*intensity; + if(a_id != -1.0){ + int index = int(a_id + 0.5); + vec4 tex_color; + switcher(index,tex_color); + FragColor = tex_color;//*intensity; + } + +} diff --git a/assets/test_shaders/batch.vert.glsl b/assets/test_shaders/batch.vert.glsl new file mode 100644 index 0000000000..42c3335b29 --- /dev/null +++ b/assets/test_shaders/batch.vert.glsl @@ -0,0 +1,27 @@ +#version 330 core +layout (location = 0) in vec2 aPos; +layout (location = 1) in vec4 aColor; +layout (location = 2) in vec2 auv; +layout (location = 3) in float active_id; +layout (location = 4) in float is_terrain; +uniform mat4 ortho = mat4(1.0); +uniform mat4 dimet = mat4(1.0); +out vec4 ourColor; +out vec2 pos; +out vec2 uv; +out float a_id; + + + +void main() +{ +if(is_terrain == 1.0){ + gl_Position = ortho*dimet*vec4(aPos,0.0, 1.0); +} +else + gl_Position = ortho*vec4(aPos,0.0, 1.0); + ourColor = aColor; + pos =aPos; + uv = auv; + a_id = active_id; +} diff --git a/assets/test_shaders/fshader_display_src.frag.glsl b/assets/test_shaders/fshader_display_src.frag.glsl new file mode 100644 index 0000000000..a6732d0c7f --- /dev/null +++ b/assets/test_shaders/fshader_display_src.frag.glsl @@ -0,0 +1,10 @@ +#version 330 + +uniform sampler2D color_texture; + +in vec2 v_uv; +out vec4 col; + +void main() { + col = texture(color_texture, v_uv); +} diff --git a/assets/test_shaders/fshader_src.frag.glsl b/assets/test_shaders/fshader_src.frag.glsl new file mode 100644 index 0000000000..bdaf26de1e --- /dev/null +++ b/assets/test_shaders/fshader_src.frag.glsl @@ -0,0 +1,17 @@ +#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; +} diff --git a/assets/test_shaders/not_batch.frag.glsl b/assets/test_shaders/not_batch.frag.glsl new file mode 100644 index 0000000000..ae209bcf22 --- /dev/null +++ b/assets/test_shaders/not_batch.frag.glsl @@ -0,0 +1,15 @@ +#version 330 core +out vec4 FragColor; + +in vec4 ourColor; +in vec2 pos; +in vec2 auv; +uniform sampler2DArray texture_array; +uniform int layer = 0; +//uniform sampler2D texture_array; +void main() +{ + //float intensity = 100.0/(length(pos - mouse_pos)); + //FragColor = ourColor;//*intensity; + FragColor = texture(texture_array,vec3(auv,layer)); +} diff --git a/assets/test_shaders/not_batch.vert.glsl b/assets/test_shaders/not_batch.vert.glsl new file mode 100644 index 0000000000..4e81a37a82 --- /dev/null +++ b/assets/test_shaders/not_batch.vert.glsl @@ -0,0 +1,21 @@ +#version 330 core +layout (location = 0) in vec2 aPos; +layout (location = 1) in vec4 aColor; +layout (location = 2) in vec2 uv; +uniform mat4 ortho = mat4(1.0); +uniform mat4 dimet = mat4(1.0); +/*mat4 ortho = mat4(2.0/1920.0,0.0,0.0,-1.0, + 0.0,-2.0/1080.0,0.0,1.0, + 0.0,0.0,1.0,0.0, + 0.0,0.0,1.0,1.0); +*/ +out vec4 ourColor; +out vec2 pos; +out vec2 auv; +void main() +{ + gl_Position = ortho*dimet*vec4(aPos,0.0, 1.0); + ourColor = aColor; + pos =aPos; + auv = uv; +} diff --git a/assets/test_shaders/terrain.frag.glsl b/assets/test_shaders/terrain.frag.glsl new file mode 100644 index 0000000000..acd158f0df --- /dev/null +++ b/assets/test_shaders/terrain.frag.glsl @@ -0,0 +1,11 @@ +#version 330 core +out vec4 FragColor; + +in vec2 pos; +in vec2 auv; +in float tex_ind; +uniform sampler2DArray texture_array; +void main() +{ + FragColor = texture(texture_array,vec3(auv,tex_ind)); +} diff --git a/assets/test_shaders/terrain.vert.glsl b/assets/test_shaders/terrain.vert.glsl new file mode 100644 index 0000000000..2ee5497f66 --- /dev/null +++ b/assets/test_shaders/terrain.vert.glsl @@ -0,0 +1,23 @@ +#version 330 core +layout (location = 0) in vec2 aPos; +layout (location = 1) in vec2 uv; +layout (location = 2) in float terrain_index; +layout (location = 3) in float alpha_mask; + +uniform mat4 ortho = mat4(1.0); +uniform mat4 dimet = mat4(1.0); +/*mat4 ortho = mat4(2.0/1920.0,0.0,0.0,-1.0, + 0.0,-2.0/1080.0,0.0,1.0, + 0.0,0.0,1.0,0.0, + 0.0,0.0,1.0,1.0); +*/ +out vec2 pos; +out vec2 auv; +out float tex_ind; +void main() +{ + gl_Position = ortho*dimet*vec4(aPos,0.0, 1.0); + pos =aPos; + auv = uv; + tex_ind = terrain_index; +} diff --git a/assets/test_shaders/vshader_display_src.vert.glsl b/assets/test_shaders/vshader_display_src.vert.glsl new file mode 100644 index 0000000000..09f5f46978 --- /dev/null +++ b/assets/test_shaders/vshader_display_src.vert.glsl @@ -0,0 +1,11 @@ +#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; +} diff --git a/assets/test_shaders/vshader_src.vert.glsl b/assets/test_shaders/vshader_src.vert.glsl new file mode 100644 index 0000000000..08d6120347 --- /dev/null +++ b/assets/test_shaders/vshader_src.vert.glsl @@ -0,0 +1,15 @@ + +#version 330 + +layout(location=0) in vec2 position; +layout(location=1) in vec2 uv; +uniform mat4 mvp; +uniform vec2 pos = vec2(0.0,0.0); +out vec2 v_uv; + +void main() { + gl_Position = mvp * vec4(position + pos, 0.0, 1.0); + + v_uv = vec2(uv.x, uv.y); +} + 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/cfg/keybinds.oac b/cfg/keybinds.oac index 7dc176e4ec..0532af4126 100644 --- a/cfg/keybinds.oac +++ b/cfg/keybinds.oac @@ -10,7 +10,8 @@ set TOGGLE_DEBUG_GRID F4 set QUICK_SAVE F5 set QUICK_LOAD F9 set TOGGLE_PROFILER F12 -set TOGGLE_BLENDING Space +set TOGGLE_BLENDING F11 +set TOGGLE_FILLMODE Space set TOGGLE_CONSTRUCT_MODE m set TOGGLE_UNIT_DEBUG p set TRAIN_OBJECT t diff --git a/configure b/configure index 5736afb756..4de0db57ab 100755 --- a/configure +++ b/configure @@ -69,6 +69,8 @@ def getenv_bool(varname): OPTIONS = { "backtrace": "if_available", "inotify": "if_available", + "opengl": "if_available", + "vulkan": "if_available", "gperftools-tcmalloc": False, "gperftools-profiler": "if_available", } @@ -81,7 +83,6 @@ def features(args, parser): the defaults below will be used. """ - def sanitize_option_name(option): """ Check if the given feature exists """ if option not in OPTIONS: diff --git a/doc/renderer/doc.md b/doc/renderer/doc.md new file mode 100644 index 0000000000..73cc8b2150 --- /dev/null +++ b/doc/renderer/doc.md @@ -0,0 +1,143 @@ +# Openage graphics +The graphics subsystem is implemented in two levels. The first level is an abstraction over graphics APIs (OpenGL, Vulkan) and provides generic shader execution methods. The second level uses the first to draw openage-specific graphics, i.e. the actual world, units, etc. + +### Namespaces: +`openage::renderer` - the level 1 renderer +`openage::renderer::opengl` - the OpenGL implementation +`openage::renderer::vulkan` - the Vulkan implementation +`openage::renderer::resources` - management of graphics assets + +__TODO name__ +`openage::graphics` - the level 2 system + +Every namespace is an actual directory and all its classes are contained there. + +## Level 1: +### Overview +First things first, we might want to support multiple APIs. For now just OpenGL, but maybe Vulkan or some others. We want to abstract over these, but this can't unfortunately be done at the level of graphics primitives like textures, buffers, etc. Well, it can, but it introduces unnecessary complexity and possible overhead. That is because the next-gen (Vulkan, Metal, DX12) APIs are vastly different from the old ones - most importantly, they're bindless, so something like a Vulkan context (GL notion) doesn't even make sense. We therefore choose to abstract on the higher level of things-to-draw. + +It works similarly to the Unity engine. The user can submit resources to be uploaded to the GPU and receives a handle that identifies the uploaded resource. Resources can be added, updated and removed. Currently supported resource types: shader, texture. + +### Thread-safety +This level might or might not be threadsafe depending on the concrete implementation. The OpenGL version is, in typical GL fashion, so not-threadsafe it's almost anti-threadsafe. All code must be executed sequentially on a dedicated window thread, the same one on which the window and renderer were initially created. The plan for the Vulkan version is to make it at least independent of thread-local storage and hopefully completely threadsafe. + +### Shaders + + + +These are more involved than resources, since each shader also carries a set of uniforms. + +A shader program takes in uniform values and vertex inputs and produces a number of bitmaps written into its render targets. Every shader program is then a function of the form $S:P\times\prod\limits^{n_u} U_i\times\left(\prod\limits^{n_a}V_j\right)^n\rightarrow\prod\limits^{n_t}B_k$, where $P$ is the primitive type, $\{U_i\}$, $\{V_j\}$, $\{B_k\}$ are indexed families of uniform, vertex attribute and bitmap types, respectively and the shader takes in $n_u$ uniforms, $n$ vertices with $n_a$ attributes per vertex and outputs to $n_t$ render targets. + +All of these inputs are contained in the $Renderable$ type. There is a catch here, since shaders are stateful, arguments are preserved between calls. + +Keep in mind that the output bitmap is not necessarily what the final output in a render target will be, since multiple draw calls get combined into a single bitmap and the initial value of the target also has an effect. + +With this premise, we can create a renderer interface generic over low-level APIs and usage (although optimized for openage's purposes). The only renderables are sets of values to be passed to shader programs - they may or may not include geometry, textures, matrices, etc. + +Our $ShaderInput$ class conceptually has almost the same member types as the types in the domain of the corresponding shader program function, with some small differences. For example, OpenGL shaders have implicit vertex attributes like $gl\_VertexID$ which we do not valuate manually. + +For the most part, these types are not know statically, so $ShaderInput$ has to simulate them internally with dynamic dispatch while providing an external interface that acts as if its members are the same as the inputs to the shader. + +OTOH, when it is the case that they are know statically, we can generate the appropriate headers from the shader code for better performance (in the future). + +Figure out the interface for $ShaderInput$. + +Parametrize $ShaderInput$ over a $Shader$ type. Have one $DynamicShader$ for all shaders loaded at runtime and some $StaticShaderForSpecificThing$ classes for shaders known beforehand. Make $DynamicShader$ compile for all operations and $StaticShader$.. only have operations it supports implemented, making it check them at compile-time. + +```c++ +template +class ShaderInput { + template + void set_unif(T val) { + // always compiles for dynamic shader, + // only compiles for valid U and T for static shader + (instanceof S).set_unif(val); + } +}; + +template +void func() { + static_assert(N != N, "Invalid shader uniform name."); +} + +template<> +void func() { + cout << "val1" << endl; + return; +} + +template<> +void func() { + cout << "val2" << endl; + return; +} +``` + +How to deal with drawing the scene? +We want stateless drawing, but pure stateless incurs some overhead. We can probably do with some mix of sharing scene between renderer and user. Keep a Scene object containing renderables (just shader valuations) and pass it to the render method. +What shoud Scene contain? +```c++ +struct Scene { + std::vector; + OR + std::vector>; + SceneSettings... + alpha blending, ztest, all of that in Object + is there anything global? + From Unity3D: + far, near plane + default depth + projection type + occlusion culling method + HDR + AA styles + how to deal with unit picking? probably have to make it a separate render pass + that renders unit ids into a buffer. read it on CPU or GPU? GPU faster, but is it possible? + general render-pass related things; have objects for render passes? +}; +``` +alternatively call render_pass(..) for each pass rather than call render(OBJ) once where OBJ contains info about render passes. might be nicer since render pass info can be recovered. problem - have to sync CPU/GPU to wait for command buffer execution finish +QUESTION: + share ownership of scene data at all times or give views to the user that are only accessible when not rendering? + +### Usage +Sample usage: + +```c++ +Window window("title"); +auto renderer = opengl::GlRenderer(window.get_context()); + +resources::TextureData tex_data("/path.tex"); +std::unique_ptr tex = renderer->add_texture(tex_data); + +resources::ShaderSource vsrc = resources::ShaderSource::read_from_file("/path.vert", resources::shader_t::glsl_vertex); +resources::ShaderSource fsrc = resources::ShaderSource::read_from_file("/path.frag", resources::shader_t::glsl_fragment); + +std::unique_ptr prog = renderer->add_shader( { vsrc, fsrc } ); + +auto input = prog->new_uniform_input( + "color", { 0.0f, 1.0f, 0.0f }, + "time", 0.0f, + "num", 1337 +); + +RenderPass pass { + { { + input, + new Geometry(geometry_t::quad), + true, + true, + true, + true, + } }, // list of renderables + renderer->get_framebuffer_target(), + 1.0f, + 8, +}; + +renderer->render(pass); +``` + +## Level 2: +On top of that renderer, we build a level 2 graphics subsystem. It has an API that is actually specific to openage, and is threadsafe. The level-2 renderer calls the level 1 renderer and updates it to match the game state. diff --git a/libopenage/CMakeLists.txt b/libopenage/CMakeLists.txt index de5ad13240..196cd264f4 100644 --- a/libopenage/CMakeLists.txt +++ b/libopenage/CMakeLists.txt @@ -26,7 +26,6 @@ pxdgen( engine.h ) - # add subsystem folders add_subdirectory("audio") add_subdirectory("console") @@ -37,8 +36,8 @@ add_subdirectory("gui") add_subdirectory("error") add_subdirectory("gamestate") add_subdirectory("input") -add_subdirectory("log") add_subdirectory("job") +add_subdirectory("log") add_subdirectory("pathfinding") add_subdirectory("pyinterface") add_subdirectory("renderer") @@ -49,7 +48,6 @@ add_subdirectory("testing") add_subdirectory("unit") add_subdirectory("util") - # run codegen, add files to executable codegen_run() add_sources(libopenage GENERATED ${CODEGEN_TARGET_TUS}) @@ -150,6 +148,35 @@ else() have_config_option(inotify INOTIFY false) endif() +# opengl support +if(WANT_OPENGL) + find_package(OpenGL) +endif() + +# vulkan support +if(WANT_VULKAN) + find_package(Vulkan) +endif() + +if(WANT_OPENGL AND OPENGL_FOUND) + have_config_option(opengl OPENGL true) + include_directories(${OPENGL_INCLUDE_DIR}) +else() + have_config_option(opengl OPENGL false) +endif() + +if(WANT_VULKAN AND VULKAN_FOUND) + have_config_option(vulkan VULKAN true) + include_directories(${Vulkan_INCLUDE_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() + + get_config_option_string() configure_file(config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/config.h) @@ -181,6 +208,7 @@ target_link_libraries(libopenage ${EPOXY_LIBRARIES} ${MATH_LIB} ${OPENGL_LIBRARY} + ${Vulkan_LIBRARIES} ${OPUS_LIBRARIES} ${PNG_LIBRARIES} ${SDL2IMAGE_LIBRARIES} @@ -190,6 +218,7 @@ target_link_libraries(libopenage ${RT_LIB} ${OGG_LIB} ${EXECINFO_LIB} + pthread # TODO: change to PUBLIC (or, alternatively, remove all keywords # of this type) when qt cmake scripts change declarations of the # IMPORTED libraries to GLOBAL. diff --git a/libopenage/config.h.in b/libopenage/config.h.in index 403b3d97c5..5315990538 100644 --- a/libopenage/config.h.in +++ b/libopenage/config.h.in @@ -8,6 +8,8 @@ #define WITH_BACKTRACE ${WITH_BACKTRACE} #define WITH_INOTIFY ${WITH_INOTIFY} +#define WITH_OPENGL ${WITH_OPENGL} +#define WITH_VULKAN ${WITH_VULKAN} #define WITH_GPERFTOOLS_PROFILER ${WITH_GPERFTOOLS_PROFILER} #define WITH_GPERFTOOLS_TCMALLOC ${WITH_GPERFTOOLS_TCMALLOC} diff --git a/libopenage/engine.cpp b/libopenage/engine.cpp index 88fbd01bd7..656bcbb96b 100644 --- a/libopenage/engine.cpp +++ b/libopenage/engine.cpp @@ -226,6 +226,11 @@ Engine::Engine(const util::Path &root_dir, } return false; }); + global_input_context.bind(action.get("TOGGLE_FILLMODE"), [this](const input::action_arg_t &) { + static bool isLine = false; + glPolygonMode(GL_FRONT_AND_BACK, isLine ? GL_FILL : GL_LINE); + isLine = !isLine; + }); this->text_renderer = std::make_unique(); this->unit_selection = std::make_unique(this); diff --git a/libopenage/input/action.h b/libopenage/input/action.h index ca6acb028e..2df32e37ec 100644 --- a/libopenage/input/action.h +++ b/libopenage/input/action.h @@ -60,6 +60,7 @@ private : "TOGGLE_MENU", "TOGGLE_ITEM", "TOGGLE_BLENDING", + "TOGGLE_FILLMODE", "TOGGLE_PROFILER", "TOGGLE_CONSTRUCT_MODE", "TOGGLE_UNIT_DEBUG", diff --git a/libopenage/renderer/CMakeLists.txt b/libopenage/renderer/CMakeLists.txt index fbaa233231..905e4481f0 100644 --- a/libopenage/renderer/CMakeLists.txt +++ b/libopenage/renderer/CMakeLists.txt @@ -1,6 +1,27 @@ add_sources(libopenage - color.cpp - text.cpp + #camera.cpp + color.cpp + #game_renderer.cpp + geometry.cpp + renderer.h + sdl_global.cpp + shader_program.h + #terrain_renderer.cpp + tests.cpp + batch_test.cpp + text.cpp + texture.cpp + window.cpp + +) + +pxdgen( + tests.h + batch_test.h ) -add_subdirectory(font) +add_subdirectory(font/) +add_subdirectory(opengl/) +add_subdirectory(resources/) +#add_subdirectory(vulkan/) +#add_subdirectory(shaders/) diff --git a/libopenage/renderer/backup/tests.cpp b/libopenage/renderer/backup/tests.cpp new file mode 100644 index 0000000000..98b45a8cfc --- /dev/null +++ b/libopenage/renderer/backup/tests.cpp @@ -0,0 +1,221 @@ +// 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" +#include "opengl/context.h" +//#include "perspective.h" +#include "opengl/sprite.h" +#include "opengl/shader_program.h" +#include +namespace openage { +namespace renderer { +namespace tests { + + + +void renderer_demo_0(util::Path path) { + opengl::GlWindow window("openage renderer test", { 1920, 1080 } ); + + auto renderer = window.make_renderer(); + + auto vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + path / "/assets/test_shaders/vshader_src.vert.glsl"); + + auto fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + path / "assets/test_shaders/fshader_src.frag.glsl"); + + auto vshader_display_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + path / "assets/test_shaders/vshader_display_src.vert.glsl"); + + auto fshader_display_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + path / "assets/test_shaders/fshader_display_src.frag.glsl"); + + auto vshader_alpha = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + path / "assets/test_shaders/alphamask.vert.glsl"); + + auto fshader_alpha = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + path / "assets/test_shaders/alphamask.frag.glsl"); + auto size = window.get_size(); + auto shader = renderer->add_shader( { vshader_src, fshader_src } ); + auto shader_display = renderer->add_shader( { vshader_display_src, fshader_display_src } ); + auto alpha_shader = renderer->add_shader({vshader_alpha,fshader_alpha}); + //start of experimental part + float aspect = (float)size.y/(float)size.x; + + //start of experimental area + opengl::Sprite sprite; + + //load the texture that we will be using + auto terrain_texture = sprite.make_texture(path,"/assets/terrain/textures/g_m02_00_color.png",false,renderer); + auto paladin = sprite.make_texture(path,"/assets/converted/graphics/795.slp.png",true,renderer); + auto alpha_texture = sprite.make_texture(path,"/assets/terrain/blends/watershore.png",false,renderer); + auto shore_texture = sprite.make_texture(path,"/assets/terrain/textures/g_bch_00_color.png",false,renderer); + auto water_texture = sprite.make_texture(path,"/assets/terrain/textures/g_wtr_00_color_1.png",false,renderer); + + //now to choose which subtexture as well as the transformation matrix. Both of these can + //be fed in the same function and updated everytime as this does not consume as many resources. + + auto paladin_1 = sprite.make_render_obj(paladin,false,15,shader,aspect,(float)size.y,0,0); + float left_terr = 0.125f*7; + float top_terr = 0.125f*1; + auto alpha_test = sprite.make_terrain(water_texture,alpha_texture,alpha_shader,aspect,(float)size.y,0,512-64,left_terr,top_terr); + auto shore_1 = sprite.make_render_obj(shore_texture,true,0,shader,aspect,(float)size.y,0,0); + auto water_1 = sprite.make_render_obj(water_texture,false,0,shader,aspect,(float)size.y,0,0); + auto water_2 = sprite.make_render_obj(shore_texture,true,0,shader,aspect,(float)size.y,0,1024); + auto water_3 = sprite.make_render_obj(shore_texture,true,0,shader,aspect,(float)size.y,1024,0); + auto water_4 = sprite.make_render_obj(shore_texture,true,0,shader,aspect,(float)size.y,1024,1024); + std::vector mix_tex = {paladin_1}; + //mix_tex.insert(water_1); + //mix_tex.insert(water_2); + //mix_tex.insert(water_3); + //mix_tex.insert(water_4); + + /*for(int i=-3;i<5;i++){ + mix_tex.push_back(sprite.make_terrain(shore_texture,alpha_texture,alpha_shader,aspect,(float)size.y,i*128,512-64,left_terr,top_terr)); + }*/ + log::log(INFO << "what is path "<add_texture(resources::TextureInfo(size.x, size.y, resources::pixel_format::rgba8)); + auto id_texture = renderer->add_texture(resources::TextureInfo(size.x, size.y, resources::pixel_format::r32ui)); + auto depth_texture = renderer->add_texture(resources::TextureInfo(size.x, size.y, resources::pixel_format::depth24)); + //one of the targets + 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()); + + resources::TextureData id_texture_data = id_texture->into_data(); + bool texture_data_valid = false; + + auto quad = renderer->add_mesh_geometry(resources::MeshData::make_quad()); + + Renderable_test display_obj { + color_texture_uniform, + quad, + false, + false, + }; + + /*RenderPass_test pastu{ + {terrain_1,terrain_2,terrain_3,terrain_4,terrain_5,terrain_6,terrain_7,terrain_8,terrain_9,paladin_1},//,test_obj5,test_obj6,test_obj7}, + fbo.get(), + };*/ + /*RenderPass_test alpha_pass{ + {water_1,water_2,water_3,water_4,alpha_test}, + fbo.get(), + };*/ + RenderPass_test alpha_pass{ + mix_tex, + fbo.get(), + }; + RenderPass_test render_main{ + {display_obj}, + renderer->get_display_target(), + }; + + + 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([&] (coord::window new_size) { + // Calculate projection matrix + float aspectRatio = float(new_size.x)/float(new_size.y); + float xScale = 1.0/aspectRatio; + + Eigen::Matrix4f pmat; + pmat << 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1; + + // resize fbo + color_texture = renderer->add_texture(resources::TextureInfo(new_size.x, new_size.y, resources::pixel_format::rgba8)); + id_texture = renderer->add_texture(resources::TextureInfo(new_size.x, new_size.y, resources::pixel_format::r32ui)); + depth_texture = renderer->add_texture(resources::TextureInfo(new_size.x, new_size.y, resources::pixel_format::depth24)); + fbo = renderer->create_texture_target( { color_texture.get(), id_texture.get(), depth_texture.get() } ); + texture_data_valid = false; + + shader_display->update_uniform_input(color_texture_uniform.get(), "color_texture", color_texture.get(), "proj", pmat); + alpha_pass.target = fbo.get(); + } ); + time_t curr_time,prev_time; + int frame = 0; + time(&prev_time); + while (!window.should_close()) { + time(&curr_time); + frame++; + if(curr_time-prev_time >= 1){ + prev_time = curr_time; + log::log(INFO << frame); + frame = 0; + } + + renderer->render_test(alpha_pass); + renderer->render_test(render_main); + 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/backup/tests.h b/libopenage/renderer/backup/tests.h new file mode 100644 index 0000000000..467ec256f3 --- /dev/null +++ b/libopenage/renderer/backup/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/batch_test.cpp b/libopenage/renderer/batch_test.cpp new file mode 100644 index 0000000000..f9d058fa79 --- /dev/null +++ b/libopenage/renderer/batch_test.cpp @@ -0,0 +1,259 @@ +#include +#include "../input/input_manager.h" +#include "opengl/window.h" +#include "../log/log.h" +#include "../error/error.h" +#include "batch_test.h" +#include "opengl/render_target.h" +#include +#include +#include "opengl/terrainmanager.h" +//#include "opengl/texturearray.h" +#include "resources/texture_data.h" +#include +#include "../util/externalprofiler.h" +namespace openage { +namespace renderer { +namespace batch_test{ + +bool compareSprite(opengl::Sprite_2* i1,opengl::Sprite_2* i2) +{ + return (i1->tex_id < i2->tex_id); +} + +void batch_demo(int demo_id,util::Path path){ + + util::ExternalProfiler external_profiler; + + opengl::GlWindow window("hello world",{1920, 1080}); + + std::vector sprites; + std::vector sprites_2; + std::vector terrains; + std::vector trees; + + auto renderer = window.make_batchrenderer(path); + auto renderer_2 = window.make_batchrenderer(path); + auto renderer_3 = window.make_batchrenderer(path); + auto vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + path / "/assets/test_shaders/batch.vert.glsl"); + + auto fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + path / "assets/test_shaders/batch.frag.glsl"); + auto shade = renderer->add_shader({ vshader_src, fshader_src }); + + auto vshader_batch = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + path / "/assets/test_shaders/not_batch.vert.glsl"); + + auto fshader_batch = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + path / "assets/test_shaders/not_batch.frag.glsl"); + auto shadebatch = renderer->add_shader({ vshader_batch, fshader_batch }); + + log::log(INFO << RENDERER_BUFFER_SIZE); + float zfar = 0.1f; + float znear = 100.0f; + Eigen::Matrix4f pers2; + pers2 << 2.0f/(1920.0f),0,0,-1.0f, + 0,-2.0f/(1080.0f),0,1.0f, + 0, 0,1.0f,0, + 0.0f,0.0f, 1.0f, 1; + Eigen::Matrix4f dimet; /// This is the DIMETRIC Perspective. Used in age of empires (Reference: https://gamedev.stackexchange.com/questions/16746/what-is-the-name-of-perspective-of-age-of-empires-ii) + dimet << 1.0,1.0,0,0, + 0.5,-0.5,1.0,0, + 0,0,0,0, + 0,0,0,1; + log::log(INFO << "hello 4"); + log::log(INFO << sizeof(opengl::Sprite_2)<<" sprite size"); + float x,y; + int closed = 0; + SDL_Event event; + int frame = 0,j = 0,m=0; + + int tex_ids[40] = {2,5,12,689,695,716,779,795,859,855,849,351,343,342,330,339,320,326,354,361,357,363,499,576,578,581,584,591,594,601,600,805,61,64,67,71,171,179,181,186}; + int tree_list[6] = {1251,1254,1256,1258,1260,1262}; + for(int j = -6;j<4;j++){ + for(int z = 0;z<8;z++){ + terrains.push_back(new opengl::Sprite_2(250 + 512*z,250 + 512*j,512.0f,512.0f,(rand()%1000)/1000.0f,(rand()%1000)/1000.0f,(rand()%1000)/1000.0f,1.0f)); + //terrains.back()->set_terrain(6009 + (abs(j)*8 + z)%32); + //terrains.push_back(new opengl::Sprite_2(250 + 512*z,250 + 512*j,512.0f,512.0f,(rand()%1000)/1000.0f,(rand()%1000)/1000.0f,(rand()%1000)/1000.0f,1.0f)); + //terrains.back()->set_terrain(6010); + } + } + + + opengl::Sprite_2 test_terrain(600,0,512.0,512.0,0.0,0.0,0.0,1.0); + + + for(int j = 0;j<20;j++){ + for(int z = 0;z<10;z++){ + trees.push_back(new opengl::Sprite_2((float)(rand()%1920),(float)(rand()%1080),(float)100,(float)100,(float)(rand()%1000)/1000.0,(float)(rand()%1000)/1000.0,(float)(rand()%1000)/1000.0,(float)1.0)); + trees.back()->set_texture(tree_list[rand()%6],true); + trees.back()->set_subtex(0); + } + } + + for(int j = 0;j<100;j++){ + for(int z = 0;z<10;z++){ + sprites.push_back(new opengl::Sprite_2((float)(rand()%1920),(float)(rand()%1080),(float)100,(float)100,(float)(rand()%1000)/1000.0,(float)(rand()%1000)/1000.0,(float)(rand()%1000)/1000.0,(float)1.0)); + //sprites.back()->set_texture(tex_ids[rand()%40],true); + sprites.back()->set_texture(795,true); + } + } + + /*for(int j = 0;j<10;j++){ + for(int z = 0;z<10;z++){ + sprites_2.push_back(new opengl::Sprite_2((float)(rand()%1920),(float)(rand()%1080),(float)100,(float)100,(float)(rand()%1000)/1000.0,(float)(rand()%1000)/1000.0,(float)(rand()%1000)/1000.0,(float)1.0)); + sprites_2.back()->set_texture(tex_ids[10 + rand()%22],true); + //sprites.back()->set_subtex(rand()%6); + } + }*/ + + opengl::Sprite_2 archery(900.0f,100.0f,200.0f,200.0f,1.0f,1.0f,0.0f,1.0f); + archery.set_texture(21,true); + opengl::Sprite_2 castle(100.0f,400.0f,200.0f,200.0f,1.0f,1.0f,0.0f,1.0f); + castle.set_texture(305,false); + opengl::Sprite_2 blacksmith(100.0f,700.0f,200.0f,200.0f,1.0f,1.0f,0.0f,1.0f); + blacksmith.set_texture(92,true); + opengl::Sprite_2 barrack(200.0f,50.0f,200.0f,200.0f,1.0f,1.0f,0.0f,1.0f); + barrack.set_texture(145,true); + opengl::Sprite_2 monastery(300.0f,700.0f,200.0f,200.0f,1.0f,1.0f,0.0f,1.0f); + monastery.set_texture(280,true); + opengl::Sprite_2 market(500.0f,400.0f,200.0f,200.0f,1.0f,1.0f,0.0f,1.0f); + market.set_texture(818,true); + opengl::Sprite_2 workshop(1000.0f,500.0f,200.0f,200.0f,1.0f,1.0f,0.0f,1.0f); + workshop.set_texture(956,true); + opengl::Sprite_2 viking_ship(1000.0f,800.0f,200.0f,200.0f,1.0f,1.0f,0.0f,1.0f); + viking_ship.set_texture(699,true); + opengl::Sprite_2 elephant(1800.0f,100.0f,200.0f,200.0f,1.0f,1.0f,0.0f,1.0f); + elephant.set_texture(805,true); + opengl::Sprite_2 paladin(1800.0f,900.0f,200.0f,200.0f,1.0f,1.0f,0.0f,1.0f); + paladin.set_texture(673,true); + opengl::Sprite_2 trade_cart(1800.0f,700.0f,200.0f,200.0f,1.0f,1.0f,0.0f,1.0f); + trade_cart.set_texture(4486,true); + + std::sort(terrains.begin(),terrains.end(),compareSprite); + std::sort(sprites.begin(),sprites.end(),compareSprite); + std::sort(trees.begin(),trees.end(),compareSprite); + shade->use(); + shade->texture_array(); + //shadebatch->use(); + + /// texture array test + auto test_data = resources::TextureData(path / "assets/terrain/textures/62.png",false); + auto test_data_2 = resources::TextureData(path / "assets/terrain/textures/2.png",false); + auto test_data_3 = resources::TextureData(path / "assets/terrain/textures/23.png",false); + //auto tex_array = opengl::GlTextureArray(test_data); + //auto new_array = opengl::GlTextureArray(3,512,512,resources::pixel_format::rgba8); + auto terr_manager = opengl::TerrainManager(window.get_context(),path); + terr_manager.init_terrain(); + //new_array.submit_texture(test_data); + //new_array.submit_texture(test_data_2); + //new_array.submit_texture(test_data_3); + + glActiveTexture(GL_TEXTURE0); + terr_manager.bind(); + //new_array.bind(); + //tex_array.bind(); + //shadebatch->sampler_array(0); + /// end texture array test + glDepthFunc(GL_LEQUAL); + glDepthRange(0.0, 1.0); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // important to remove the black square around the textures or the transperent area. + glEnable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + int depth = 0; + + uint32_t time1,time2,time3; + time1 = SDL_GetTicks(); + time2 = time1; + time3 = time1; + while(!closed){ + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + + frame++; + time1 = SDL_GetTicks(); + if(time1 - time2 >= 1000){ + time2 = time1; + log::log(INFO <<" frames " << frame); + frame = 0; + depth++; + } + + if(time1 - time3 >= 50){ + //log::log(INFO << m); + time3 = time1; + viking_ship.set_subtex(m%9); + elephant.set_subtex(0 + m%10); + paladin.set_subtex(20 + m%10); + trade_cart.set_subtex(20 + m%10); + trade_cart.x -= 2.0; + elephant.y += 1.5; + paladin.x -= 2.0; + m++; + } + //terr_manager.render(); + shade->use(); + auto new_uniform = shade->new_uniform_input("mouse_pos",Eigen::Vector2f(x,y),"ortho",pers2,"dimet",dimet); + auto lala = dynamic_cast(new_uniform.get()); + shade->execute_with(lala,nullptr); + renderer_2->begin(); + for(int k=0;k<1000;k++){ + sprites[k]->x = rand()%1920; + sprites[k]->y = rand()%1080; + renderer_2->submit(*(sprites[k])); + } + /*for(int k=0;kx = rand()%1920; + //sprites[k]->y = rand()%1080; + renderer_2->submit(*(trees[k])); + }*/ + renderer_2->end(); + renderer_2->render(); + /*renderer_3->begin(); + renderer_3->submit(monastery); + renderer_3->submit(archery); + renderer_3->submit(castle); + renderer_3->submit(barrack); + renderer_3->submit(market); + renderer_3->submit(elephant); + renderer_3->submit(paladin); + renderer_3->submit(workshop); + renderer_3->submit(blacksmith); + renderer_3->submit(trade_cart); + renderer_3->end(); + renderer_3->render();*/ + + SDL_PollEvent(&event); + if(event.type == SDL_MOUSEMOTION){ + x = event.motion.x; + y = event.motion.y; + } + + if(event.type == SDL_QUIT){ + closed = 1; + } + //log::log(INFO << "Mouse X:"<get_raw_context()); + SDL_Quit(); + + + +} + + + + +}}} \ No newline at end of file diff --git a/libopenage/renderer/batch_test.h b/libopenage/renderer/batch_test.h new file mode 100644 index 0000000000..0392eacb05 --- /dev/null +++ b/libopenage/renderer/batch_test.h @@ -0,0 +1,11 @@ +#pragma once +// pxd: from libopenage.util.path cimport Path +#include "../util/path.h" + + +namespace openage { +namespace renderer { +namespace batch_test{ + // pxd: void batch_demo(int demo_id, Path path) except + +void batch_demo(int demo_id,util::Path path); +}}} // openage::renderer::batch_test \ No newline at end of file diff --git a/libopenage/renderer/camera.cpp b/libopenage/renderer/camera.cpp new file mode 100644 index 0000000000..7d9ffc0c0b --- /dev/null +++ b/libopenage/renderer/camera.cpp @@ -0,0 +1,114 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "camera.h" + +#include + +#define _USE_MATH_DEFINES +#include + + +namespace openage { +namespace renderer { + +const Eigen::Vector3f Camera::LOCAL_LOOK_DIRECTION_2D = -Eigen::Vector3f::UnitY(); +const Eigen::Vector3f Camera::LOCAL_LOOK_DIRECTION_3D = -Eigen::Vector3f::UnitZ(); +const Eigen::Vector3f Camera::UP_VECTOR = Eigen::Vector3f::UnitY(); + +Camera::Camera() + : aspect_ratio{1.0f}, translate_transform{Eigen::Matrix4f::Identity()}, rotate_transform{Eigen::Matrix4f::Identity()} +{ + this->translate(Eigen::Vector3f(0.0f, 1.0f, 0.0f)); + const float rads = 45.0f * M_PI / 180.0f; + this->rotate_x(rads); +} + +void Camera::set_aspect_ratio(const float ratio) { + assert(ratio > 0.0f); + this->aspect_ratio = ratio; + + this->calculate_view_project_transform(); +} + +void Camera::translate(const Eigen::Vector3f &translate_vector) { + auto transform = Eigen::Affine3f::Identity(); + transform.pretranslate(translate_vector); + this->translate_transform = transform.matrix() * this->translate_transform; + + calculate_view_project_transform(); +} + +void Camera::rotate_x(const float radians) { + this->rotate_axis(radians, Eigen::Vector3f::UnitX()); +} + +void Camera::rotate_axis(const float radians, const Eigen::Vector3f &axis) { + auto transform = Eigen::Affine3f::Identity(); + transform.prerotate(Eigen::AngleAxisf(radians, axis)); + this->rotate_transform = transform.matrix() * this->rotate_transform; + calculate_view_project_transform(); +} + +Eigen::Matrix4f Camera::get_view_project_transform_2d() const { + return this->view_project_transform_2d; +} + +void Camera::calculate_project_transform() { + static const float WANTED_WIDTH = 16.0f; + const float height = WANTED_WIDTH / this->aspect_ratio; + + this->project_transform = this->ortho(-WANTED_WIDTH / 2.0f, WANTED_WIDTH / 2.0f, height / 2.0f, -height / 2.0f, 0.0f, 20.0f); +} + +void Camera::calculate_view_transform() { + this->view_transform_2d = this->look_at_direction(this->translate_transform * Eigen::Vector4f(0.0f, 0.0f, 0.0f, 1.0f), this->LOCAL_LOOK_DIRECTION_2D, this->UP_VECTOR); + this->view_transform_3d = this->look_at_direction(this->translate_transform * Eigen::Vector4f(0.0f, 0.0f, 0.0f, 1.0f), + this->rotate_transform * this->LOCAL_LOOK_DIRECTION_3D, this->UP_VECTOR); +} + +void Camera::calculate_view_project_transform() { + this->calculate_view_transform(); + this->calculate_project_transform(); + + this->view_project_transform_2d = this->project_transform * this->view_transform_2d; + this->view_project_transform_3d = this->project_transform * this->view_transform_3d; +} + +Eigen::Matrix4f Camera::look_at_direction(const Eigen::Vector3f &eye, const Eigen::Vector3f &look_direction, const Eigen::Vector3f &up) { + auto look_direction_norm = look_direction.normalized(); + auto camera_x = look_direction_norm.cross(up).normalized(); + auto camera_up = camera_x.cross(look_direction_norm); + + Eigen::Matrix4f result = Eigen::Matrix4f::Identity(); + result(0, 0) = camera_x(0); + result(1, 0) = camera_x(1); + result(2, 0) = camera_x(2); + + result(0, 1) = camera_up(0); + result(1, 1) = camera_up(1); + result(1, 2) = camera_up(2); + + result(0, 2) = -look_direction_norm(0); + result(1, 2) = -look_direction_norm(1); + result(2, 2) = -look_direction_norm(2); + + result(3, 0) = -camera_x.dot(eye); + result(3, 1) = -camera_up.dot(eye); + result(3, 2) = look_direction_norm.dot(eye); + + return result; +} + +Eigen::Matrix4f Camera::ortho(const float left, const float right, const float top, const float bottom, const float near, const float far) { + Eigen::Matrix4f result = Eigen::Matrix4f::Identity(); + result(0, 0) = 2.0f / (right - left); + result(1, 1) = 2.0f / (top - bottom); + result(2, 2) = - 2.0f / (far - near); + result(3, 0) = - (right + left) / (right - left); + result(3, 1) = - (top + bottom) / (top - bottom); + result(3, 2) = - (far + near) / (far - near); + + return result; +} + +}} // openage::renderer diff --git a/libopenage/renderer/camera.h b/libopenage/renderer/camera.h new file mode 100644 index 0000000000..733f266963 --- /dev/null +++ b/libopenage/renderer/camera.h @@ -0,0 +1,46 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + + +namespace openage { +namespace renderer { + +class Camera { +public: + Camera(); + + void set_aspect_ratio(const float ratio); + + void translate(const Eigen::Vector3f &translate_vector); + void rotate_x(const float radians); + void rotate_axis(const float radians, const Eigen::Vector3f &axis); + + Eigen::Matrix4f get_view_project_transform_2d() const; + +private: + static const Eigen::Vector3f LOCAL_LOOK_DIRECTION_2D; + static const Eigen::Vector3f LOCAL_LOOK_DIRECTION_3D; + static const Eigen::Vector3f UP_VECTOR; + + float aspect_ratio; + Eigen::Matrix4f translate_transform; + // do we need rotate? there's only one viewing direction + Eigen::Matrix4f rotate_transform; + Eigen::Matrix4f view_transform_3d; + Eigen::Matrix4f view_transform_2d; + Eigen::Matrix4f project_transform; + Eigen::Matrix4f view_project_transform_3d; + Eigen::Matrix4f view_project_transform_2d; + + void calculate_view_project_transform(); + void calculate_view_transform(); + void calculate_project_transform(); + + static Eigen::Matrix4f look_at_direction(const Eigen::Vector3f &eye, const Eigen::Vector3f &look_direction, const Eigen::Vector3f &up); + static Eigen::Matrix4f ortho(const float left, const float right, const float top, const float bottom, const float near, const float far); +}; + +}} // openage::renderer diff --git a/libopenage/renderer/color.cpp b/libopenage/renderer/color.cpp index cac7f9a1ed..657631211f 100644 --- a/libopenage/renderer/color.cpp +++ b/libopenage/renderer/color.cpp @@ -10,11 +10,10 @@ Color::Color() r{0}, g{0}, b{0}, - a{255} { - // Empty -} + a{255} {} -Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +Color::Color(color_channel_t r, color_channel_t g, + color_channel_t b, color_channel_t a) : r{r}, g{g}, diff --git a/libopenage/renderer/color.h b/libopenage/renderer/color.h index dfd44666b9..e1edb57589 100644 --- a/libopenage/renderer/color.h +++ b/libopenage/renderer/color.h @@ -8,20 +8,20 @@ namespace openage { namespace renderer { class Color { + using color_channel_t = uint8_t; + public: Color(); - - Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + Color(color_channel_t r, color_channel_t g, color_channel_t b, color_channel_t a); bool operator==(const Color &other) const; - bool operator!=(const Color &other) const; - - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; + bool operator !=(const Color &other) const; + color_channel_t r; + color_channel_t g; + color_channel_t b; + color_channel_t a; }; }} // openage::renderer diff --git a/libopenage/renderer/game_renderer.cpp b/libopenage/renderer/game_renderer.cpp new file mode 100644 index 0000000000..6b23f3c942 --- /dev/null +++ b/libopenage/renderer/game_renderer.cpp @@ -0,0 +1,54 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "game_renderer.h" + +namespace openage { +namespace renderer { + +GameRenderer::GameRenderer(const size_t window_width, const size_t window_height) + : window_width{window_width}, window_height{window_height}, terrain_renderer{*this}, unit_quad{geometry_t::quad} +{ + update_camera_aspect_ratio(); +} + +void GameRenderer::change_window_size(const size_t width, const size_t height) { + assert(width > 0 && height > 0); + this->window_width = width; + this->window_height = height; + update_camera_aspect_ratio(); +} + +void GameRenderer::render() const { + auto view_project_transform = this->camera.view_project_transform_2d(); + render_terrain(view_project_transform); +} + +void GameRenderer::add_tiles(const std::vector& tiles) { + this->terrain_renderer.add_tiles(tiles); +} + +void GameRenderer::remove_tiles(const std::vector& tiles) { + this->terrain_renderer.remove_tiles(tiles); +} + +void GameRenderer::add_terrain_texture(const Tile::terrain_id_t id, const std::shared_ptr& texturePtr) { + this->terrain_renderer.add_terrain_texture(id, texturePtr); +} + +void GameRenderer::move_camera(const Eigen::Vector3f &translate_vector) { + this->camera.translate(translate_vector); +} + +void GameRenderer::render_terrain(const Eigen::Matrix4f &view_project_transform) const { + this->terrain_renderer.render(view_project_transform); +} + +void GameRenderer::update_camera_aspect_ratio() { + this->camera.aspect_ratio(static_cast(this->window_width) / this->window_height); +} + +Geometry* GameRenderer::unit_quad_ptr() { + return &(this->unit_quad); +} + +}} diff --git a/libopenage/renderer/game_renderer.h b/libopenage/renderer/game_renderer.h new file mode 100644 index 0000000000..ce109a5620 --- /dev/null +++ b/libopenage/renderer/game_renderer.h @@ -0,0 +1,55 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "renderer.h" +#include "terrain_renderer.h" +#include "camera.h" +#include "opengl/geometry.h" + +#include + +namespace openage { +namespace renderer { + +class Texture; + +class GameRenderer { +public: + GameRenderer(const size_t window_width, const size_t window_height); + + void change_window_size(const size_t width, const size_t height); + + void render() const; + + void add_tiles(const std::vector& tiles); + void remove_tiles(const std::vector& tiles); + + void add_terrain_texture(const Tile::terrain_id_t id, const std::shared_ptr& texturePtr); + + void move_camera(const Eigen::Vector3f &translate_vector); + + Geometry* unit_quad_ptr(); + +private: + Renderer* renderer; + struct InitState { + Renderer renderer; + TerrainRenderer terrain_renderer; + Camera camera; + + size_t window_width; + size_t window_height; + + opengl::GlGeometry unit_quad; + }; + + /// The state of the renderer is contained within an optional, because it is set in the constructor body. + /// After the constructor, this will always be present. + std::experimental::optional; + + void render_terrain(const Eigen::Matrix4f &view_project_transform) const; + void update_camera_aspect_ratio(); +}; + +}} 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..6d02824409 --- /dev/null +++ b/libopenage/renderer/opengl/CMakeLists.txt @@ -0,0 +1,24 @@ +add_sources(libopenage + buffer.cpp + indexbuffer.cpp + context.cpp + framebuffer.cpp + geometry.cpp + render_target.cpp + + scrapped.h + shader.cpp + shader_program.cpp + simple_object.cpp + texture.cpp + texturearray.cpp + uniform_input.h + vertex_array.cpp + window.cpp + sprite.cpp + sprite_2.cpp + texturemanager.cpp + terrainmanager.cpp + renderer.cpp + batchrenderer.cpp +) diff --git a/libopenage/renderer/opengl/batchrenderer.cpp b/libopenage/renderer/opengl/batchrenderer.cpp new file mode 100644 index 0000000000..a827282154 --- /dev/null +++ b/libopenage/renderer/opengl/batchrenderer.cpp @@ -0,0 +1,167 @@ +#include "batchrenderer.h" +#include "../../log/log.h" +#include "../../error/error.h" + + + +namespace openage{ +namespace renderer{ +namespace opengl{ + BatchRenderer::BatchRenderer(GlContext* context,util::Path& path) + :gl_context(context),root(path) + { + m_VBO = new opengl::GlBuffer(RENDERER_BUFFER_SIZE,GL_DYNAMIC_DRAW); + m_Vao = new opengl::GlVertexArray(); + m_Vao->bind(); + tex_mngr = new TextureManager(path); + + // position attribute + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 10* sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + // color attribute + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 10 * sizeof(float), (void*)(2 * sizeof(float))); + glEnableVertexAttribArray(1); + //texture coord attribute + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 10 * sizeof(float), (void*)(6 * sizeof(float))); + glEnableVertexAttribArray(2); + //add active ID + glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, 10 * sizeof(float), (void*)(8 * sizeof(float))); + glEnableVertexAttribArray(3); + //add terrain tranform + glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, 10 * sizeof(float), (void*)(9 * sizeof(float))); + glEnableVertexAttribArray(4); + m_VBO->unbind(GL_ARRAY_BUFFER); + + GLuint indices_2[RENDERER_INDICES_SIZE]; + + int offset = 0; + for(int i = 0;iunbind(); + + } + + void BatchRenderer::submit(Sprite_2& sprite){ + if(tex_mngr->check_texture(sprite)){ + this->end(); + this->render(); + this->clear_textures(); + this->begin(); + } + else{ + //log::log(INFO<<"false"); + } + tex_mngr->get_activeID(sprite); + if(sprite.is_tex == true) + tex_mngr->getUV(sprite); + //log::log(INFO<<"here "<x = sprite.x - sprite.w/2.0f; + m_buffer->y = sprite.y - sprite.h/2.0f; + m_buffer->r = sprite.r; + m_buffer->g = sprite.g; + m_buffer->b = sprite.b; + m_buffer->a = sprite.a; + m_buffer->u = sprite.left; + m_buffer->v = sprite.bottom; + m_buffer->active_id = sprite.active_id; + m_buffer->is_terrain = temp_is_terrain; + m_buffer++; + + m_buffer->x = sprite.x - sprite.w/2.0f; + m_buffer->y = sprite.y+ sprite.h - sprite.h/2.0f; + m_buffer->r = sprite.r; + m_buffer->g = sprite.g; + m_buffer->b = sprite.b; + m_buffer->a = sprite.a; + m_buffer->u = sprite.left; + m_buffer->v = sprite.top; + m_buffer->active_id = sprite.active_id; + m_buffer->is_terrain = temp_is_terrain; + m_buffer++; + + m_buffer->x = sprite.x + sprite.w - sprite.w/2.0f; + m_buffer->y = sprite.y + sprite.h - sprite.h/2.0f; + m_buffer->r = sprite.r; + m_buffer->g = sprite.g; + m_buffer->b = sprite.b; + m_buffer->a = sprite.a; + m_buffer->u = sprite.right; + m_buffer->v = sprite.top; + m_buffer->active_id = sprite.active_id; + m_buffer->is_terrain = temp_is_terrain; + m_buffer++; + + m_buffer->x = sprite.x + sprite.w - sprite.w/2.0f; + m_buffer->y = sprite.y - sprite.h/2.0f; + m_buffer->r = sprite.r; + m_buffer->g = sprite.g; + m_buffer->b = sprite.b; + m_buffer->a = sprite.a; + m_buffer->u = sprite.right; + m_buffer->v = sprite.bottom; + m_buffer->active_id = sprite.active_id; + m_buffer->is_terrain = temp_is_terrain; + m_buffer++; + + m_indexcount += 6; + } + void BatchRenderer::begin(){ + m_VBO->bind(GL_ARRAY_BUFFER); + m_buffer = (VertexData*)glMapBuffer(GL_ARRAY_BUFFER,GL_WRITE_ONLY); + } + + void BatchRenderer::end(){ + glUnmapBuffer(GL_ARRAY_BUFFER); + m_VBO->unbind(GL_ARRAY_BUFFER); + } + + void BatchRenderer::render(){ + + tex_mngr->bind_textures(); + m_Vao->bind(); + //m_IBO->bind(); + + glDrawElements(GL_TRIANGLES, m_indexcount, GL_UNSIGNED_INT, 0); + + //m_IBO->unbind(); + m_Vao->unbind(); + + m_indexcount = 0; + + + } + + BatchRenderer::~BatchRenderer(){ + + } + + std::shared_ptr BatchRenderer::add_shader(std::vector const& srcs) { + log::log(INFO << "max texture slots "<gl_context->get_capabilities().max_texture_slots); + return std::make_shared(srcs, this->gl_context->get_capabilities()); + } + + std::unique_ptr BatchRenderer::add_texture(const resources::TextureData& data) { + return std::make_unique(data); + } + + void BatchRenderer::clear_textures(){ + tex_mngr->current_textures.clear(); + } +} +} +} \ No newline at end of file diff --git a/libopenage/renderer/opengl/batchrenderer.h b/libopenage/renderer/opengl/batchrenderer.h new file mode 100644 index 0000000000..c4845a9059 --- /dev/null +++ b/libopenage/renderer/opengl/batchrenderer.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +#include + +#include "context.h" +#include "render_target.h" +#include "vertex_array.h" +#include "buffer.h" +#include "indexbuffer.h" +#include +#include "shader_program.h" +#include "texture.h" +#include "texturemanager.h" + +#define RENDERER_MAX_SPRITES 100000 +#define RENDERER_VERTEX_SIZE 40 +#define RENDERER_SPRITE_SIZE RENDERER_VERTEX_SIZE*4 +#define RENDERER_BUFFER_SIZE RENDERER_SPRITE_SIZE * RENDERER_MAX_SPRITES +#define RENDERER_INDICES_SIZE RENDERER_MAX_SPRITES*6 + +#define SHADER_VERTEX_INDEX 0 +#define SHADER_COLOR_INDEX 1 + + +namespace openage{ +namespace renderer{ +namespace opengl{ + + struct VertexData{ + float x; + float y; + float r; + float g; + float b; + float a; + float u; + float v; + float active_id; + float is_terrain = 0.0f; + }; + + class BatchRenderer{ + + public: + + BatchRenderer(GlContext*,util::Path& path); + ~BatchRenderer(); + + void begin(); + void submit(Sprite_2& sprite); + void end(); + + void render(); + void clear_textures(); + std::shared_ptr add_shader(std::vector const& srcs); + std::unique_ptr add_texture(const resources::TextureData& data); + GlRenderTarget display; + util::Path root; + private: + /// The GL context. + GlContext *gl_context; + GlBuffer* m_VBO; + GlVertexArray* m_Vao; + GlIndexBuffer* m_IBO; + TextureManager* tex_mngr; + int average = 0; + VertexData* m_buffer; + int m_indexcount = 0; + }; + + + + +} +} +} \ No newline at end of file diff --git a/libopenage/renderer/opengl/buffer.cpp b/libopenage/renderer/opengl/buffer.cpp new file mode 100644 index 0000000000..1266b94457 --- /dev/null +++ b/libopenage/renderer/opengl/buffer.cpp @@ -0,0 +1,69 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "buffer.h" + +#include "../../error/error.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +GlBuffer::GlBuffer(size_t size, GLenum usage) + : GlSimpleObject([] (GLuint handle) { glDeleteBuffers(1, &handle); } ) + , size(size) + , usage(usage) { + GLuint handle; + glGenBuffers(1, &handle); + this->handle = handle; + + this->bind(GL_ARRAY_BUFFER); + glBufferData(GL_ARRAY_BUFFER, size, 0, usage); +} + +GlBuffer::GlBuffer(const float *data, size_t size, GLenum usage) + : GlSimpleObject([] (GLuint handle) { glDeleteBuffers(1, &handle); } ) + , size(size) + , usage(usage) { + GLuint handle; + glGenBuffers(1, &handle); + this->handle = handle; + + this->bind(GL_ARRAY_BUFFER); + glBufferData(GL_ARRAY_BUFFER, size, data, usage); +} + +GlBuffer::GlBuffer(const uint8_t *data, size_t size, GLenum usage) + : GlSimpleObject([] (GLuint handle) { glDeleteBuffers(1, &handle); } ) + , size(size) + , usage(usage) { + GLuint handle; + glGenBuffers(1, &handle); + this->handle = handle; + + this->bind(GL_ARRAY_BUFFER); + glBufferData(GL_ARRAY_BUFFER, size, data, usage); +} + +size_t GlBuffer::get_size() const { + return this->size; +} + +void GlBuffer::upload_data(const uint8_t *data, size_t offset, size_t size) { + if (unlikely(offset + size > this->size)) { + throw Error(MSG(err) << "Tried to upload more data to OpenGL buffer than can fit."); + } + + this->bind(GL_ARRAY_BUFFER); + glBufferSubData(GL_ARRAY_BUFFER, offset, size, data); +} + +void GlBuffer::bind(GLenum target) const { + glBindBuffer(target, *this->handle); +} + +void GlBuffer::unbind(GLenum target) const { + glBindBuffer(target, 0); +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/buffer.h b/libopenage/renderer/opengl/buffer.h new file mode 100644 index 0000000000..6327db235c --- /dev/null +++ b/libopenage/renderer/opengl/buffer.h @@ -0,0 +1,42 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// Represents an OpenGL buffer of memory +/// allocated on the GPU. +class GlBuffer final : public GlSimpleObject { +public: + /// Creates an empty buffer of the specified size. + GlBuffer(size_t size, GLenum usage = GL_STATIC_DRAW); + + /// Creates a buffer of the specified size and fills it with the given data. + GlBuffer(const uint8_t *data, size_t size, GLenum usage = GL_STATIC_DRAW); + GlBuffer(const float *data, size_t size, GLenum usage = GL_STATIC_DRAW); + + /// The size in bytes of this buffer. + size_t get_size() const; + + /// Uploads `size` bytes of new data starting at `offset`. + /// `offset + size` has to be less than or equal to `get_size()`. + void upload_data(const uint8_t *data, size_t offset, size_t size); + + /// Bind this buffer to the specified GL target. + void bind(GLenum target) const; + void unbind(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..940de01bb2 --- /dev/null +++ b/libopenage/renderer/opengl/context.cpp @@ -0,0 +1,222 @@ +// 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..f975c01cb1 --- /dev/null +++ b/libopenage/renderer/opengl/framebuffer.cpp @@ -0,0 +1,50 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "framebuffer.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +// TODO the validity of this object is contingent +// on its texture existing. use shared_ptr? +GlFramebuffer::GlFramebuffer(std::vector textures) + : GlSimpleObject([] (GLuint handle) { glDeleteFramebuffers(1, &handle); } ) +{ + GLuint handle; + glGenFramebuffers(1, &handle); + this->handle = handle; + + glBindFramebuffer(GL_FRAMEBUFFER, handle); + + std::vector drawBuffers; + + size_t colorTextureCount = 0; + for (size_t i = 0; i < textures.size(); i++) { + // TODO figure out attachment points from pixel formats + if (textures[i]->get_info().get_format() == resources::pixel_format::depth24) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textures[i]->get_handle(), 0); + } else { + auto attachmentPoint = GL_COLOR_ATTACHMENT0 + colorTextureCount++; + glFramebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint, GL_TEXTURE_2D, textures[i]->get_handle(), 0); + drawBuffers.push_back(attachmentPoint); + } + } + + glDrawBuffers(drawBuffers.size(), drawBuffers.data()); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + throw Error(MSG(err) << "Could not create OpenGL framebuffer."); + } +} + +void GlFramebuffer::bind_read() const { + glBindFramebuffer(GL_READ_FRAMEBUFFER, *this->handle); +} + +void GlFramebuffer::bind_write() const { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, *this->handle); +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/framebuffer.h b/libopenage/renderer/opengl/framebuffer.h new file mode 100644 index 0000000000..65ffc82710 --- /dev/null +++ b/libopenage/renderer/opengl/framebuffer.h @@ -0,0 +1,32 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "texture.h" +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// Represents an OpenGL Framebuffer Object. +/// It is a collection of bitmap targets that can be drawn into +/// and read from. +class GlFramebuffer final : public GlSimpleObject { +public: + /// Construct a framebuffer pointing at the given textures. + /// Texture are attached to points specific to their pixel format, + /// e.g. a depth texture will be set as the depth target. + GlFramebuffer(std::vector textures); + + /// Bind this framebuffer to GL_READ_FRAMEBUFFER. + void bind_read() const; + + /// Bind this framebuffer to GL_DRAW_FRAMEBUFFER. + void bind_write() const; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/geometry.cpp b/libopenage/renderer/opengl/geometry.cpp new file mode 100644 index 0000000000..8b8917ae13 --- /dev/null +++ b/libopenage/renderer/opengl/geometry.cpp @@ -0,0 +1,85 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "geometry.h" + +#include + +#include "../../error/error.h" +#include "../../datastructure/constexpr_map.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +static constexpr auto gl_prim = datastructure::create_const_map( + std::make_pair(resources::vertex_primitive_t::POINTS, GL_POINTS), + std::make_pair(resources::vertex_primitive_t::LINES, GL_LINES), + std::make_pair(resources::vertex_primitive_t::LINE_STRIP, GL_LINE_STRIP), + std::make_pair(resources::vertex_primitive_t::TRIANGLES, GL_TRIANGLES), + std::make_pair(resources::vertex_primitive_t::TRIANGLE_STRIP, GL_TRIANGLE_STRIP), + std::make_pair(resources::vertex_primitive_t::TRIANGLE_FAN, GL_TRIANGLE_FAN) +); + +static constexpr auto gl_idx_t = datastructure::create_const_map( + std::make_pair(resources::index_t::U8, GL_UNSIGNED_BYTE), + std::make_pair(resources::index_t::U16, GL_UNSIGNED_SHORT), + std::make_pair(resources::index_t::U32, GL_UNSIGNED_INT) +); + +GlGeometry::GlGeometry() + : Geometry(geometry_t::bufferless_quad) {} + +GlGeometry::GlGeometry(const resources::MeshData &mesh) + : Geometry(geometry_t::mesh) { + GlBuffer verts(mesh.get_data().data(), mesh.get_data().size()); + + this->mesh = GlMesh { + std::move(verts), + GlVertexArray (verts, mesh.get_info()), + {}, + {}, + mesh.get_data().size() / mesh.get_info().vert_size(), + gl_prim.get(mesh.get_info().get_primitive()), + }; + + if (mesh.get_ids()) { + this->mesh->indices = GlBuffer(mesh.get_ids()->data(), mesh.get_ids()->size()); + this->mesh->index_type = gl_idx_t.get(*mesh.get_info().get_index_type()); + this->mesh->vert_count = mesh.get_ids()->size() / sizeof(GLuint); + } +} + +void GlGeometry::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/indexbuffer.cpp b/libopenage/renderer/opengl/indexbuffer.cpp new file mode 100644 index 0000000000..015d64f33d --- /dev/null +++ b/libopenage/renderer/opengl/indexbuffer.cpp @@ -0,0 +1,57 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "indexbuffer.h" + +#include "../../error/error.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +GlIndexBuffer::GlIndexBuffer(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(); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, 0, usage); +} + +GlIndexBuffer::GlIndexBuffer(const GLuint *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(); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, data, usage); +} + +size_t GlIndexBuffer::get_size() const { + return this->size; +} + +void GlIndexBuffer::upload_data(const GLuint *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(); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, size, data); +} + +void GlIndexBuffer::bind() const { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, *this->handle); +} + +void GlIndexBuffer::unbind() const { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/indexbuffer.h b/libopenage/renderer/opengl/indexbuffer.h new file mode 100644 index 0000000000..a477c23c27 --- /dev/null +++ b/libopenage/renderer/opengl/indexbuffer.h @@ -0,0 +1,42 @@ +// 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 GlIndexBuffer final : public GlSimpleObject { +public: + /// Creates an empty buffer of the specified size. + GlIndexBuffer(size_t size, GLenum usage = GL_STATIC_DRAW); + + /// Creates a buffer of the specified size and fills it with the given data. + GlIndexBuffer(const GLuint *data, size_t size, GLenum usage = GL_STATIC_DRAW); + + + void upload_data(const GLuint *data, size_t offset, size_t size); + + /// The size in bytes of this buffer. + size_t get_size() const; + + + /// Bind this buffer to the specified GL target. + void bind() const; + + void unbind() 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/render_target.cpp b/libopenage/renderer/opengl/render_target.cpp new file mode 100644 index 0000000000..3ceff4ee67 --- /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..a5d5c02c6e --- /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..7fc4d2e2cf --- /dev/null +++ b/libopenage/renderer/opengl/renderer.cpp @@ -0,0 +1,152 @@ +// 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" +//#include "sprite.h" + +namespace openage { +namespace renderer { +namespace opengl { + + +GlRenderer::GlRenderer(GlContext *ctx) + : gl_context(ctx) + , display() +{ + log::log(MSG(info) << "Created OpenGL renderer"); +} + +std::unique_ptr GlRenderer::add_texture(const resources::TextureData& data) { + return std::make_unique(data); +} + +std::unique_ptr GlRenderer::add_texture(const resources::TextureInfo& info) { + return std::make_unique(info); +} + +std::shared_ptr GlRenderer::add_shader(std::vector const& srcs) { + log::log(INFO << "max texture slots "<gl_context->get_capabilities().max_texture_slots); + return std::make_shared(srcs, this->gl_context->get_capabilities()); +} + +std::shared_ptr GlRenderer::add_mesh_geometry(resources::MeshData const& mesh) { + return std::make_shared(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); +} + +/*Renderable make_sprite(util::Path path,char tex_path[],int subtex,bool use_metafile,std::vector const& srcs,float aspect,float scale,Eigen::Vector3f center_coord){ + Sprite sprite; + auto obj = sprite.create(path, tex_path,subtex,use_metafile, srcs,aspect,scale,center_coord); + return obj; + //sprite.hello(); +}*/ + + +RenderTarget const* GlRenderer::get_display_target() { + return &this->display; +} + +resources::TextureData GlRenderer::display_into_data() { + GLint params[4]; + glGetIntegerv(GL_VIEWPORT, params); + + GLint width = params[2]; + GLint height = params[3]; + + resources::TextureInfo 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::TextureData img(std::move(tex_info), std::move(data)); + return img.flip_y(); +} + +void GlRenderer::render(RenderPass const& pass) { + log::log(INFO << "Render 0"); + auto gl_target = dynamic_cast(pass.target); + gl_target->bind_write(); + log::log(INFO << "Render 1"); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + for (auto obj : pass.renderables) { + log::log(INFO << "Render 3"); + if (obj.alpha_blending) { + log::log(INFO << "Render 4"); + glEnable(GL_BLEND); + } + else { + glDisable(GL_BLEND); + } + + if (obj.depth_test) { + glEnable(GL_DEPTH_TEST); + } + else { + glDisable(GL_DEPTH_TEST); + } + log::log(INFO << "Render 5"); + + + auto in = dynamic_cast(obj.unif_in); + log::log(INFO << "Render 5.5"); + auto geom = dynamic_cast(obj.geometry); + + log::log(INFO << "Render 6"); + in->program->execute_with(in, geom); + log::log(INFO << "Render 7"); + } +} + +void GlRenderer::render_test(RenderPass_test 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).get()); + auto geom = dynamic_cast((obj.geometry).get()); + + 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..239c46ad44 --- /dev/null +++ b/libopenage/renderer/opengl/renderer.h @@ -0,0 +1,48 @@ +// 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 { +class Sprite; +/// The OpenGL specialization of the rendering interface. +class GlRenderer final : public Renderer { +public: + GlRenderer(GlContext*); + + std::unique_ptr add_texture(resources::TextureData const&) override; + //std::unique_ptr make_sprite(util::Path& path,char tex_path[],int subtex,bool use_metafile,std::vector const& srcs,float aspect,float scale,Eigen::Vector3f center_coord); + std::unique_ptr add_texture(resources::TextureInfo const&) override; + + std::shared_ptr add_shader(std::vector const&) override; + + std::shared_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::TextureData display_into_data() override; + + void render(RenderPass const&) override; + void render_test(RenderPass_test const&) override; + +private: + /// The GL context. + GlContext *gl_context; + + GlRenderTarget display; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/scrapped.h b/libopenage/renderer/opengl/scrapped.h new file mode 100644 index 0000000000..6a7c3ae1fd --- /dev/null +++ b/libopenage/renderer/opengl/scrapped.h @@ -0,0 +1,65 @@ +/* +THIS IS AN EXTENSION TO UNIFORM PARSING +ALSO PARSE VERTEX ATTRIBUTES AND PROVIDE INFORMATION ABOUT THEM + +GLint Program::get_uniformbuffer_id(const char *name) { + this->check_is_linked("Uniform buffer requested"); + return glGetUniformBlockIndex(this->id, name); +} + +/// Return the opengl layout id for a given vertex attribute name. +GLint Program::get_attribute_id(const char *name) { + this->check_is_linked("Vertex attribute requested"); + + GLint aid = glGetAttribLocation(this->id, name); + + if (unlikely(aid == -1)) { + this->dump_attributes(); + throw Error{MSG(err) << "Attribute " << name + << " queried but not found or active" + << " (optimized out by the compiler?).", true}; + } + + return aid; +} + +/// Set vertex attribute with given name to have a custom id. +void Program::set_attribute_id(const char *name, GLuint id) { + if (unlikely(this->is_linked)) { + throw Error{MSG(err) + << "you assigned attribute '" << name << " = " + << id << "' after program was linked!", true}; + } + else { + glBindAttribLocation(this->id, id, name); + } +} + +/// Query OpenGL which of the vertex attributes are actually active +/// and haven't been optimized out by the compiler. +void Program::dump_attributes() { + auto msg = MSG(info); + msg << "Dumping shader program " << this->id << " active attribute list:"; + + GLint num_attribs; + glGetProgramiv(this->id, GL_ACTIVE_ATTRIBUTES, &num_attribs); + + GLint attrib_max_length; + glGetProgramiv(this->id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &attrib_max_length); + + for (int i = 0; i < num_attribs; i++) { + GLsizei attrib_length; + GLint attrib_size; + GLenum attrib_type; + auto attrib_name = std::make_unique(attrib_max_length); + + glGetActiveAttrib(this->id, i, attrib_max_length, &attrib_length, &attrib_size, &attrib_type, attrib_name.get()); + + msg << "\n -> attribute " << attrib_name + << ": type=" << attrib_type << ", size=" << attrib_size + << ", id=" << this->get_attribute_id(attrib_name.get()); + } + + log::log(msg); +} +*/ diff --git a/libopenage/renderer/opengl/shader.cpp b/libopenage/renderer/opengl/shader.cpp new file mode 100644 index 0000000000..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..ba20a55851 --- /dev/null +++ b/libopenage/renderer/opengl/shader_program.cpp @@ -0,0 +1,436 @@ +// Copyright 2013-2017 the openage authors. See copying.md for legal info. + +#include "shader_program.h" + +#include "../../error/error.h" +#include "../../log/log.h" +#include "../../datastructure/constexpr_map.h" + +#include "texture.h" +#include "shader.h" +#include "geometry.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +static constexpr auto glsl_to_gl_type = datastructure::create_const_map( + std::make_pair("int", GL_INT), + std::make_pair("uint", GL_UNSIGNED_INT), + std::make_pair("float", GL_FLOAT), + std::make_pair("double", GL_DOUBLE), + std::make_pair("vec2", GL_FLOAT_VEC2), + std::make_pair("vec3", GL_FLOAT_VEC3), + std::make_pair("mat3", GL_FLOAT_MAT3), + std::make_pair("mat4", GL_FLOAT_MAT4), + std::make_pair("ivec2", GL_INT_VEC2), + std::make_pair("ivec3", GL_INT_VEC3), + std::make_pair("sampler2D", GL_SAMPLER_2D), + std::make_pair("sampler2DArray", GL_SAMPLER_2D_ARRAY) +); + +static void check_program_status(GLuint program, GLenum what_to_check) { + GLint status; + glGetProgramiv(program, what_to_check, &status); + + if (status != GL_TRUE) { + GLint loglen; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &loglen); + + std::vector infolog(loglen); + glGetProgramInfoLog(program, loglen, 0, infolog.data()); + + const char *what_str = [=] { + switch (what_to_check) { + case GL_LINK_STATUS: + return "linking"; + case GL_VALIDATE_STATUS: + return "validation"; + case GL_COMPILE_STATUS: + return "compilation"; + default: + return "unknown shader creation task"; + } + }(); + + throw Error(MSG(err) << "OpenGL shader program " << what_str << " failed:\n" << infolog.data(), true); + } +} + +static constexpr auto gl_type_size = datastructure::create_const_map( + std::make_pair(GL_FLOAT, 4), + std::make_pair(GL_FLOAT_VEC2, 8), + std::make_pair(GL_FLOAT_VEC3, 12), + std::make_pair(GL_FLOAT_VEC4, 16), + std::make_pair(GL_INT, 4), + std::make_pair(GL_INT_VEC2, 8), + std::make_pair(GL_INT_VEC3, 12), + std::make_pair(GL_INT_VEC4, 16), + std::make_pair(GL_UNSIGNED_INT, 4), + std::make_pair(GL_UNSIGNED_INT_VEC2, 8), + std::make_pair(GL_UNSIGNED_INT_VEC3, 12), + std::make_pair(GL_UNSIGNED_INT_VEC4, 16), + std::make_pair(GL_BOOL, 1), + std::make_pair(GL_BOOL_VEC2, 2), + std::make_pair(GL_BOOL_VEC3, 3), + std::make_pair(GL_BOOL_VEC4, 4), + std::make_pair(GL_FLOAT_MAT2, 16), + std::make_pair(GL_FLOAT_MAT3, 36), + std::make_pair(GL_FLOAT_MAT4, 64), + std::make_pair(GL_SAMPLER_1D, 4), + std::make_pair(GL_SAMPLER_2D, 4), + std::make_pair(GL_SAMPLER_2D_ARRAY, 4), + std::make_pair(GL_SAMPLER_3D, 4), + std::make_pair(GL_SAMPLER_CUBE, 4) +); + +GlShaderProgram::GlShaderProgram(const std::vector &srcs, const gl_context_capabilities &caps) + : GlSimpleObject([] (GLuint handle) { glDeleteProgram(handle); } ) { + GLuint handle = glCreateProgram(); + this->handle = handle; + + std::vector shaders; + for (auto src : srcs) { + GlShader shader(src); + glAttachShader(handle, shader.get_handle()); + shaders.push_back(std::move(shader)); + } + + glLinkProgram(handle); + check_program_status(handle, GL_LINK_STATUS); + + glValidateProgram(handle); + check_program_status(handle, GL_VALIDATE_STATUS); + + // after linking we can delete the shaders + for (auto const& shdr : shaders) { + glDetachShader(handle, shdr.get_handle()); + } + + // query program information + GLint val; + glGetProgramiv(handle, GL_ACTIVE_ATTRIBUTES, &val); + size_t attrib_count = val; + glGetProgramiv(handle, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &val); + size_t attrib_maxlen = val; + glGetProgramiv(handle, GL_ACTIVE_UNIFORMS, &val); + size_t unif_count = val; + glGetProgramiv(handle, GL_ACTIVE_UNIFORM_MAX_LENGTH, &val); + size_t unif_maxlen = val; + + std::vector name(std::max(unif_maxlen, attrib_maxlen)); + + GLuint tex_unit = 0; + for (GLuint i_unif = 0; i_unif < unif_count; ++i_unif) { + GLint count; + GLenum type; + glGetActiveUniform( + handle, + i_unif, + name.size(), + 0, + &count, + &type, + name.data() + ); + + this->uniforms.insert(std::make_pair( + name.data(), + GlUniform { + type, + GLint(i_unif), + size_t(count), + size_t(count) * gl_type_size.get(type), + } + )); + + if (count != 1) { + // TODO support them + log::log(MSG(warn) << "Found array uniform " << name.data() << " in shader. Arrays are unsupported."); + } + + if (type == GL_SAMPLER_2D) { + if (tex_unit >= caps.max_texture_slots) { + throw Error(MSG(err) + << "Tried to create shader that uses more texture sampler uniforms " + << "than there are texture unit slots available."); + } + this->texunits_per_unifs.insert(std::make_pair(name.data(), tex_unit)); + tex_unit += 1; + } + + // TODO optimized away detection + if (0 == -1) { + log::log(MSG(warn) + << "OpenGL shader uniform " << name.data() << " was present in the source, but isn't present in the program. Probably optimized away."); + continue; + } + } + + for (GLuint i_attrib = 0; i_attrib < attrib_count; ++i_attrib) { + GLint size; + GLenum type; + glGetActiveAttrib( + handle, + i_attrib, + name.size(), + 0, + &size, + &type, + name.data() + ); + + this->attribs.insert(std::make_pair( + name.data(), + GlVertexAttrib { + type, + GLint(i_attrib), + size, + } + )); + } + + log::log(MSG(info) << "Created OpenGL shader program"); + + log::log(MSG(dbg) << "Uniforms: "); + for (auto const &pair : this->uniforms) { + log::log(MSG(dbg) << "(" << pair.second.location << ") " << pair.first << ": " << pair.second.type); + } + log::log(MSG(dbg) << "Vertex attributes: "); + for (auto const &pair : this->attribs) { + log::log(MSG(dbg) << "(" << pair.second.location << ") " << pair.first << ": " << pair.second.type); + } +} + +void GlShaderProgram::use() const { + glUseProgram(*this->handle); + +} + +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); + //log::log(INFO << "tex_unit "<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(); + } +} + + +void GlShaderProgram::send_uniform(const GlUniformInput *unif_in) { + 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: { + + //glUniform1i(loc, pair.first); + break; + } + default: + throw Error(MSG(err) << "Tried to upload unknown uniform type to GL shader."); + } + } + +} + +std::shared_ptr GlShaderProgram::new_unif_in() { + auto in = std::make_shared(); + 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_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, Texture const* val) { + auto const& tex = *static_cast(val); + GLuint handle = tex.get_handle(); + this->set_unif(in, unif, &handle, GL_SAMPLER_2D); +} + +int GlShaderProgram::texture_array(){ + std::string temp = "texture_"; + for(int i = 0;i<32;i++){ + std::string temp_2 = temp + std::to_string(i); + auto loc = this->uniforms[temp_2].location; + glUniform1i(loc,i); + //log::log(INFO << temp_2<<" "<uniforms[temp].location; + glUniform1i(loc,active_id); + //log::log(INFO << temp<<" "< +#include + +#include "../shader_program.h" +#include "../resources/shader_source.h" +#include "../renderer.h" + +#include "uniform_input.h" +#include "context.h" +#include "geometry.h" +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// A handle to an OpenGL shader program. +class GlShaderProgram final : public ShaderProgram, public GlSimpleObject { +public: + /// Tries to create a shader program from the given sources. + /// Throws an exception on compile/link errors. + explicit GlShaderProgram(const std::vector&, const gl_context_capabilities&); + + /// Bind this program as the currently used one in the OpenGL context. + void use() const; + + /// Does what the description of Renderable specifies - updates the uniform values + /// and draws the Geometry if it's not nullptr. + void execute_with(const GlUniformInput*, const GlGeometry*); + + void send_uniform(const GlUniformInput *unif_in); + + bool has_uniform(const char*) override; + + int texture_array(); + int sampler_array(int active_id); + + +protected: + std::shared_ptr new_unif_in() override; + void set_i32(UniformInput*, const char*, int32_t) override; + void set_u32(UniformInput*, const char*, uint32_t) override; + void set_f32(UniformInput*, const char*, float) override; + void set_f64(UniformInput*, const char*, double) override; + void set_v2f32(UniformInput*, const char*, Eigen::Vector2f const&) override; + void set_v3f32(UniformInput*, const char*, Eigen::Vector3f const&) override; + void set_v4f32(UniformInput*, const char*, Eigen::Vector4f const&) override; + void set_m4f32(UniformInput*, const char*, Eigen::Matrix4f const&) override; + void set_tex(UniformInput*, const char*, Texture const*) override; + +private: + void set_unif(UniformInput*, const char*, void const*, GLenum); + + /// Represents a uniform location in the shader program. + struct GlUniform { + GLenum type; + GLint location; + /// For arrays, the number of elements. For scalars, 1. + size_t count; + /// The size in bytes of the whole uniform (whole array if it's one). + size_t size; + }; + + /// Represents a per-vertex input to the shader program. + struct GlVertexAttrib { + GLenum type; + GLint location; + // TODO what is this? + GLint size; + }; + + /// A map of uniform names to their descriptions. + std::unordered_map uniforms; + + /// A map of per-vertex attribute names to their descriptions. + std::unordered_map attribs; + + // TODO parse uniform buffer structure ugh + // std::unordered_map uniform_buffers; + // GlVertexInputInfo; + + /// A map from sampler uniform names to their assigned texture units. + std::unordered_map texunits_per_unifs; + /// A map from texture units to the texture handles that are currently bound to them. + std::unordered_map textures_per_texunits; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/simple_object.cpp b/libopenage/renderer/opengl/simple_object.cpp new file mode 100644 index 0000000000..8a68ee171b --- /dev/null +++ b/libopenage/renderer/opengl/simple_object.cpp @@ -0,0 +1,41 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +GlSimpleObject::GlSimpleObject(std::function delete_fun) + : delete_fun(delete_fun) {} + +GlSimpleObject::GlSimpleObject(GlSimpleObject&& other) + : handle(other.handle) + , delete_fun(std::move(other.delete_fun)) { + other.handle = {}; +} + +GlSimpleObject &GlSimpleObject::operator =(GlSimpleObject&& other) { + if (this->handle) { + this->delete_fun(*this->handle); + } + + this->handle = other.handle; + this->delete_fun = std::move(other.delete_fun); + other.handle = {}; + + return *this; +} + +GlSimpleObject::~GlSimpleObject() { + if (this->handle) { + this->delete_fun(*this->handle); + } +} + +GLuint GlSimpleObject::get_handle() const { + return *this->handle; +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/simple_object.h b/libopenage/renderer/opengl/simple_object.h new file mode 100644 index 0000000000..a4d4fb18eb --- /dev/null +++ b/libopenage/renderer/opengl/simple_object.h @@ -0,0 +1,47 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include + + +namespace openage { +namespace renderer { +namespace opengl { + +/// A base class for all classes representing OpenGL Objects to inherit from. +/// It allows moving the object, but not copying it through the copy constructor. +/// It has unique_ptr-like semantics. It is called 'simple', because in the future +/// we might want to add collections of objects and similar more advanced features. +class GlSimpleObject { +public: + // Moving the representation is okay. + GlSimpleObject(GlSimpleObject&&); + GlSimpleObject &operator =(GlSimpleObject&&); + + // Generally, copying GL objects is costly and if we want to allow it, + // we do so through an explicit copy() function. + GlSimpleObject(GlSimpleObject const&) = delete; + GlSimpleObject &operator =(GlSimpleObject const&) = delete; + + /// Uses delete_fun to destroy the underlying object, + /// but only if the handle is present (hasn't been moved out). + virtual ~GlSimpleObject(); + + /// Returns the handle to the underlying OpenGL Object. + GLuint get_handle() const; + +protected: + explicit GlSimpleObject(std::function delete_fun); + + /// The handle to the OpenGL Object that this class represents. + std::experimental::optional handle; + + /// The function that deletes the underlying OpenGL Object. + std::function delete_fun; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/sprite.cpp b/libopenage/renderer/opengl/sprite.cpp new file mode 100644 index 0000000000..6b8693e742 --- /dev/null +++ b/libopenage/renderer/opengl/sprite.cpp @@ -0,0 +1,164 @@ +#include "sprite.h" +#include +#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 "context.h" +//#include "texture.h" +#include "shader_program.h" +#include "../../util/path.h" +#include "../renderer.h" +#include "texture.h" +namespace openage{ + namespace renderer{ + namespace opengl{ + +Renderable_test Sprite::make_render_obj(sprite_texture texture,bool pers_bool,int subtex,std::shared_ptr shader,float X,float Y){ + + auto info_tex = texture.data.get_info(); + auto image_info = info_tex.get_subtexture_size(subtex); + float image_aspect = (float)image_info.first/(float)image_info.second; + auto transform = make_transform(pers_bool,Eigen::Vector3f(X,Y,0)); + auto unif_in1 = shader->new_uniform_input( + "mvp", transform.matrix(), + "u_id", 2u, + "tex", texture.tex_handle.get() + ); + auto quad = this->get_subtex(texture.data,subtex, X,Y); + return {unif_in1,quad,true,true}; +} + +std::shared_ptr Sprite::get_uniform(sprite_texture texture,bool pers_bool,std::shared_ptr shader,float X,float Y){ + auto transform = make_transform(pers_bool,Eigen::Vector3f(X,Y,0)); + auto unif_in1 = shader->new_uniform_input( + "mvp", transform.matrix(), + "u_id", 2u, + "tex", texture.tex_handle.get() + ); + + return unif_in1; +} + +std::shared_ptr Sprite::get_uniform2(sprite_texture texture,bool pers_bool,std::shared_ptr shader,float X,float Y){ + auto unif_in1 = shader->new_uniform_input( + "pos", Eigen::Vector2f(X,Y), + "u_id", 2u, + "tex", texture.tex_handle.get() + ); + + return unif_in1; +} + + +std::shared_ptr Sprite::get_subtex(resources::TextureData tex,int subtex,float X,float Y){ + resources::TextureInfo tex_info = tex.get_info(); + size_t count = tex_info.get_subtexture_count(); + //log::log(INFO << "Number of sprites in spritesheet : "< sprite_coord = tex_info.get_subtexture_coordinates(subtex); + //log::log(INFO << "Coordinate of Sprite "<(sprite_coord)<<" "<(sprite_coord)<<" "<(sprite_coord)<<" "<(sprite_coord)); + float left = std::get<0>(sprite_coord); + float right = std::get<1>(sprite_coord); + float bottom = std::get<2>(sprite_coord); + float top = std::get<3>(sprite_coord); + float position[2] = {X,Y}; + int size_img[2] = {tex_info.get_subtexture_size(subtex).first,tex_info.get_subtexture_size(subtex).second}; + std::array quad_data = { { + -100.0f, 100.0f, left, bottom,//bottom left + -100.0f, -100.0f, left, top,//top left + 100.0f, 100.0f, right, bottom,//bottom right + 100.0f, -100.0f, right, top//top right + } }; + std::array quad_data2 = { { + position[0], position[1], left, bottom,//bottom left + position[0], position[1] + size_img[1], left, top,//top left + position[0] + size_img[0], position[1], right, bottom,//bottom right + position[0] + size_img[0], position[1] + size_img[1], right, top//top right + } }; + std::array quad_data3 = { { + 0, 0, left, bottom,//bottom left + 0, size_img[1], left, top,//top left + size_img[0], 0, right, bottom,//bottom right + size_img[0], size_img[1], right, top//top right + } }; + auto mesh = resources::MeshData::make_quad(quad_data3); + return std::make_shared(mesh); + } +Eigen::Transform Sprite::make_transform(bool pers_bool,Eigen::Vector3f center_coord){ + float zfar = 0.1f; // for now these are constant + float znear = 100.0f; + auto transform = Eigen::Affine3f::Identity(); + //transform.prescale(Eigen::Vector3f(0.5, 0.5,0.0f)); + transform.pretranslate(center_coord); + Eigen::Matrix4f dimet; /// This is the DIMETRIC Perspective. Used in age of empires (Reference: https://gamedev.stackexchange.com/questions/16746/what-is-the-name-of-perspective-of-age-of-empires-ii) + dimet << 1.0,-1,0,0, + 0.5,0.5,0.75,0, + 0,0,0,0, + 0,0,0,1; + + Eigen::Matrix4f pers,pers2; //this is the Perspective Matrix, Currently Used to normalise the aspect ratio. Read about UV coordinates to see why this is needed. + pers << (float)1.0,0,0,0, + 0,1.0,0,0, + 0, 0,-2*(float)(zfar*znear)/(zfar-znear),0, + 0,0, -1*(float)(zfar+znear)/(zfar-znear), 1; + + pers2 << 2.0f/(1366.0f),0,0,-1.0f, + 0,-2.0f/(768.0f),0,1.0f, + 0, 0,1.0,0, + 0.0f,0.0f, 1.0, 1; + Eigen::Transform transform_temp; + if(pers_bool){ + transform_temp = pers2*dimet*transform; +} + else{ + transform_temp = pers2*transform; + } + return transform_temp; + + } + + sprite_texture Sprite::make_texture(util::Path path,char tex_path[],bool use_metafile,std::shared_ptr renderer){ + + auto tex = resources::TextureData(path / tex_path,use_metafile); + auto gltex = renderer->add_texture(tex); + return {std::move(gltex),tex}; + } + + Renderable_test Sprite::make_terrain(sprite_texture base,sprite_texture alpha_mask,std::shared_ptr alpha_shader,float aspect,float Y_screen,float X,float Y,float top_terr,float left_terr){ + float left = left_terr; + float right = left_terr+0.125f; + float top = top_terr; + float bottom = top_terr+0.125f; + static const std::array alpha_data = { { + -1.0f, 1.0f,left, bottom,left, bottom, + -1.0f, -1.0f, left, top,left, top, + 1.0f, 1.0f, right, bottom,right, bottom, + 1.0f, -1.0f, right, top, right, top + } }; + auto info_tex = base.data.get_info(); + auto image_info = info_tex.get_subtexture_size(0); + float image_aspect = (float)image_info.first/(float)image_info.second; + log::log(INFO << "this is image aspect : "<new_uniform_input( + "base_texture",base.tex_handle.get(), + "mask_texture",alpha_mask.tex_handle.get(), + "mvp_matrix",transform.matrix() + //"show_mask",true + ); + auto mesh_alpha = resources::MeshData::make_alpha(alpha_data); + auto quad = std::make_shared(mesh_alpha); + return {alpha_uniform,quad,true,true}; + } + } + } +} diff --git a/libopenage/renderer/opengl/sprite.h b/libopenage/renderer/opengl/sprite.h new file mode 100644 index 0000000000..2636124d08 --- /dev/null +++ b/libopenage/renderer/opengl/sprite.h @@ -0,0 +1,42 @@ +#pragma once +#include "../renderer.h" +#include +#include +#include "../texture.h" +#include "../geometry.h" +#include "shader_program.h" +namespace openage{ + namespace renderer{ + namespace opengl{ + +struct sprite_texture { + std::shared_ptr tex_handle; + resources::TextureData data; +}; + +class Sprite { + +public: + int image_size[2] = {0,0}; + //std::shared_ptr make_unif(util::Path path,char tex_path[],std::shared_ptr shader,std::shared_ptr renderer); + Renderable_test create(util::Path path,bool pers_bool,char tex_path[],int subtex,bool use_metafile,std::shared_ptr shader,std::shared_ptr renderer,Eigen::Vector3f center_coord); + /// this returns the geometry + std::shared_ptr get_subtex(resources::TextureData tex,int subtex,float X,float Y); + //creates the transformation matrix + Eigen::Transform make_transform(bool pers_bool,Eigen::Vector3f center_coord); + std::shared_ptr get_uniform(sprite_texture texture,bool pers_bool,std::shared_ptr shader,float X,float Y); + //only sends positions instead of the entire transformation matrix + std::shared_ptr get_uniform2(sprite_texture texture,bool pers_bool,std::shared_ptr shader,float X,float Y); + + //the very efficient remake + //this makes a reusable texture which contains the data as well as the handle + //to the uploaded texture + sprite_texture make_texture(util::Path path,char tex_path[],bool use_metafile,std::shared_ptr renderer); + + Renderable_test make_render_obj(sprite_texture texture,bool pers_bool,int subtex,std::shared_ptr shader,float X,float Y); + Renderable_test make_terrain(sprite_texture base,sprite_texture alpha_mask,std::shared_ptr alpha_shader,float aspect,float Y_screen,float X,float Y,float top_terr,float left_terr); + + +}; + +}}} diff --git a/libopenage/renderer/opengl/sprite_2.cpp b/libopenage/renderer/opengl/sprite_2.cpp new file mode 100644 index 0000000000..67ee2bae22 --- /dev/null +++ b/libopenage/renderer/opengl/sprite_2.cpp @@ -0,0 +1,44 @@ +#include "../../log/log.h" +#include "../../error/error.h" +#include "sprite_2.h" + +namespace openage{ +namespace renderer{ +namespace opengl{ + + Sprite_2::Sprite_2(float x, float y,float w, float h, float r,float g, float b, float a) + :x(x),y(y),w(w),h(h),r(r),g(g),b(b),a(a) + { + } + + /*Sprite_2::Sprite_2(float x, float y,float w, float h, float r,float g, float b, float a,float left,float right,float top,float bottom) + :x(x),y(y),w(w),h(h),r(r),g(g),b(b),a(a),left(left),right(right),top(top),bottom(bottom) + { + + }*/ + + void Sprite_2::set_texture(int id,bool use_metafile){ + this->meta = use_metafile; + //std::string path = "/assets/converted/graphics/"+ std::to_string(id) +".slp.png"; + //this->texture_data = new resources::TextureData(root / path,use_metafile); + this->is_tex = true; + this->tex_id = id; + } + + void Sprite_2::set_subtex(int tex){ + + this->subtex = tex; + } + + void Sprite_2::set_terrain(int id){ + this->tex_id = id; + this->is_tex = true; + //this->meta = true; + is_terrain = true; + } + + + +} +} +} \ No newline at end of file diff --git a/libopenage/renderer/opengl/sprite_2.h b/libopenage/renderer/opengl/sprite_2.h new file mode 100644 index 0000000000..606540817d --- /dev/null +++ b/libopenage/renderer/opengl/sprite_2.h @@ -0,0 +1,41 @@ +#ifndef SPRITE_2_H +#define SPRITE_2_H +#include +#include "../resources/texture_data.h" +#include "../../util/path.h" + +namespace openage{ +namespace renderer{ +namespace opengl{ + +class Sprite_2{ + public: + //Sprite_2(float x, float y,float w, float h, float r,float g, float b, float a,float left,float right,float top,float bottom); + Sprite_2(float x, float y,float w, float h, float r,float g, float b, float a); + void set_texture(int id,bool use_metafile); + void set_terrain(int id); + void set_subtex(int tex); + float x,y,w,h,r,g,b,a; + float left = 0.0; + float bottom = 1.0; + float top = 0.0; + float right = 1.0; + bool is_tex = false; + int tex_id; + int active_id = -1; + bool meta = false; + int subtex = 0; + int vec_id; + bool is_terrain = false; + resources::TextureData* texture_data; + + private: + + + +}; + +} +} +} +#endif diff --git a/libopenage/renderer/opengl/terrainmanager.cpp b/libopenage/renderer/opengl/terrainmanager.cpp new file mode 100644 index 0000000000..cfe4130eac --- /dev/null +++ b/libopenage/renderer/opengl/terrainmanager.cpp @@ -0,0 +1,171 @@ +#include "terrainmanager.h" +#include "../../log/log.h" +#include "../../error/error.h" + +namespace openage{ +namespace renderer{ +namespace opengl{ + + TerrainManager::TerrainManager(GlContext* context,util::Path path) + :gl_context(context){ + tex_array = new opengl::GlTextureArray(32,512,512,resources::pixel_format::rgba8); + std::string url; + for(int i = 45; i <=76;i++){ + url = "/assets/terrain/textures/" + std::to_string(i) + ".png"; + textures.push_back(resources::TextureData(path/url,false)); + tex_array->submit_texture(textures.back()); + } + + auto vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + path / "/assets/test_shaders/terrain.vert.glsl"); + + auto fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + path / "assets/test_shaders/terrain.frag.glsl"); + shade = this->add_shader({ vshader_src, fshader_src }); + + // vertex info setup + m_VBO = new opengl::GlBuffer(RENDERER_BUFFER_SIZE,GL_DYNAMIC_DRAW); + m_Vao = new opengl::GlVertexArray(); + m_Vao->bind(); + + // position attribute + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 6* sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + //texture coord attribute + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(2 * sizeof(float))); + glEnableVertexAttribArray(1); + //add terrain index + glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(4 * sizeof(float))); + glEnableVertexAttribArray(2); + // alpha mask bool + glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(5 * sizeof(float))); + glEnableVertexAttribArray(3); + m_VBO->unbind(GL_ARRAY_BUFFER); + + GLuint indices_2[RENDERER_INDICES_SIZE]; + + int offset = 0; + for(int i = 0;iunbind(); + + } + + void TerrainManager::bind(){ + tex_array->bind(); + } + std::shared_ptr TerrainManager::add_shader(std::vector const& srcs) { + log::log(INFO << "max texture slots "<gl_context->get_capabilities().max_texture_slots); + return std::make_shared(srcs, this->gl_context->get_capabilities()); + } + + void TerrainManager::init_terrain(){ + float offset_x = 0; + float offset_y = -1100; + for(int i = 0;i<35;i++){ + for(int j = 0;j<35;j++){ + //terrain_layout[i][j].tex = rand()%32; + terrain_layout[i][j].tex = 17 + (i/8)*(j/8); + terrain_layout[i][j].subtex =(35*i + j)%64; + terrain_layout[i][j].x = offset_x + 64*j; + terrain_layout[i][j].y = offset_y + 64*i; + } + } + } + + void TerrainManager::submit(){ + + + for(int i = 0;i<35;i++){ + for(int j = 0;j<35;j++){ + float left = 0.125*(terrain_layout[i][j].subtex%8); + float top = 0.125*(terrain_layout[i][j].subtex/8); + m_buffer->x = terrain_layout[i][j].x; + m_buffer->y = terrain_layout[i][j].y; + m_buffer->u = left; + m_buffer->v = top + 0.125; + m_buffer->is_alpha = 0; + m_buffer->tex_index = terrain_layout[i][j].tex; + m_buffer++; + m_buffer->x = terrain_layout[i][j].x; + m_buffer->y = terrain_layout[i][j].y + 64; + m_buffer->u = left; + m_buffer->v = top; + m_buffer->is_alpha = 0; + m_buffer->tex_index = terrain_layout[i][j].tex; + m_buffer++; + m_buffer->x = terrain_layout[i][j].x + 64; + m_buffer->y = terrain_layout[i][j].y + 64; + m_buffer->u = left + 0.125; + m_buffer->v = top; + m_buffer->is_alpha = 0; + m_buffer->tex_index = terrain_layout[i][j].tex; + m_buffer++; + m_buffer->x = terrain_layout[i][j].x + 64; + m_buffer->y = terrain_layout[i][j].y; + m_buffer->u = left + 0.125; + m_buffer->v = top + 0.125; + m_buffer->is_alpha = 0; + m_buffer->tex_index = terrain_layout[i][j].tex; + m_buffer++; + m_indexcount += 6; + } + } + } + void TerrainManager::begin(){ + m_VBO->bind(GL_ARRAY_BUFFER); + m_buffer = (TerrainData*)glMapBuffer(GL_ARRAY_BUFFER,GL_WRITE_ONLY); + } + + void TerrainManager::end(){ + glUnmapBuffer(GL_ARRAY_BUFFER); + m_VBO->unbind(GL_ARRAY_BUFFER); + } + + void TerrainManager::render(){ + Eigen::Matrix4f pers2; + pers2 << 2.0f/(1920.0f),0,0,-1.0f, + 0,-2.0f/(1080.0f),0,1.0f, + 0, 0,1.0f,0, + 0.0f,0.0f, 1.0f, 1; + Eigen::Matrix4f dimet; /// This is the DIMETRIC Perspective. Used in age of empires (Reference: https://gamedev.stackexchange.com/questions/16746/what-is-the-name-of-perspective-of-age-of-empires-ii) + dimet << 1.0,1.0,0,0, + 0.5,-0.5,0.75,0, + 0,0,0,0, + 0,0,0,1; + auto transform = Eigen::Affine3f::Identity(); + transform.rotate(Eigen::AngleAxis(M_PI/2.0, Eigen::Vector3f::UnitZ())); + shade->use(); + shade->sampler_array(0); + auto batch_uniform = shade->new_uniform_input("ortho",transform*pers2,"dimet",dimet); + auto lala_batch = dynamic_cast(batch_uniform.get()); + shade->execute_with(lala_batch,nullptr); + //this->init_terrain(); + this->begin(); + this->submit(); + this->end(); + m_Vao->bind(); + //m_IBO->bind(); + + glDrawElements(GL_TRIANGLES, m_indexcount, GL_UNSIGNED_INT, 0); + + //m_IBO->unbind(); + m_Vao->unbind(); + m_indexcount = 0; + } + +}}} \ No newline at end of file diff --git a/libopenage/renderer/opengl/terrainmanager.h b/libopenage/renderer/opengl/terrainmanager.h new file mode 100644 index 0000000000..e31c619b9e --- /dev/null +++ b/libopenage/renderer/opengl/terrainmanager.h @@ -0,0 +1,61 @@ +#pragma once +#include +#include "texturearray.h" +#include "../../util/path.h" +#include "shader_program.h" +#include "vertex_array.h" +#include "buffer.h" +#include "indexbuffer.h" +#include "sprite_2.h" +#include +#define RENDERER_MAX_SPRITES 100000 +#define RENDERER_VERTEX_SIZE 40 +#define RENDERER_SPRITE_SIZE RENDERER_VERTEX_SIZE*4 +#define RENDERER_BUFFER_SIZE RENDERER_SPRITE_SIZE * RENDERER_MAX_SPRITES +#define RENDERER_INDICES_SIZE RENDERER_MAX_SPRITES*6 + +namespace openage{ +namespace renderer{ +namespace opengl{ +struct TerrainData{ + float x; + float y; + float u; + float v; + float tex_index; + float is_alpha; + }; +struct terrain_array{ + float x; + float y; + float tex; + int subtex; +}; +class TerrainManager{ + public: + TerrainManager(GlContext* context,util::Path path); + std::shared_ptr add_shader(std::vector const& srcs); + void bind(); + void init_terrain(); + void begin(); + void end(); + void submit(); + void render(); + private: + std::vector textures; + GlTextureArray* tex_array; + GlBuffer* m_VBO; + GlVertexArray* m_Vao; + GlIndexBuffer* m_IBO; + GlContext *gl_context; + terrain_array terrain_layout[35][35]; + TerrainData* m_buffer; + int m_indexcount; + std::shared_ptr shade; + +}; + + + + +}}} \ No newline at end of file diff --git a/libopenage/renderer/opengl/texture.cpp b/libopenage/renderer/opengl/texture.cpp new file mode 100644 index 0000000000..f3e87806c7 --- /dev/null +++ b/libopenage/renderer/opengl/texture.cpp @@ -0,0 +1,106 @@ +// 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" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// The input and output formats for GL. +static constexpr auto gl_format = datastructure::create_const_map>( + // TODO check correctness of formats here + std::make_pair(resources::pixel_format::r16ui, std::make_tuple(GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_INT)), + std::make_pair(resources::pixel_format::r32ui, std::make_tuple(GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT)), + std::make_pair(resources::pixel_format::rgb8, std::make_tuple(GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE)), + std::make_pair(resources::pixel_format::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)) +); + +GlTexture::GlTexture(const resources::TextureData& data) + : Texture(data.get_info()) + , GlSimpleObject([] (GLuint handle) { glDeleteTextures(1, &handle); } ) +{ + // generate opengl texture handle + glGenTextures(1, &*this->handle); + glBindTexture(GL_TEXTURE_2D, *this->handle); + + // select pixel format + auto fmt_in_out = gl_format.get(this->info.get_format()); + + // store raw pixels to gpu + auto size = this->info.get_size(); + + glPixelStorei(GL_UNPACK_ALIGNMENT, this->info.get_row_alignment()); + + glTexImage2D( + GL_TEXTURE_2D, 0, + std::get<0>(fmt_in_out), size.first, size.second, 0, + std::get<1>(fmt_in_out), std::get<2>(fmt_in_out), data.get_data() + ); + + // drawing settings + // TODO these are outdated, use sampler settings + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + log::log(MSG(dbg) << "Created OpenGL texture from data"); +} + +GlTexture::GlTexture(const resources::TextureInfo &info) + : Texture(info) + , GlSimpleObject([] (GLuint handle) { glDeleteTextures(1, &handle); } ) +{ + // generate opengl texture handle + glGenTextures(1, &*this->handle); + glBindTexture(GL_TEXTURE_2D, *this->handle); + + auto fmt_in_out = gl_format.get(this->info.get_format()); + + auto dims = this->info.get_size(); + + glPixelStorei(GL_UNPACK_ALIGNMENT, this->info.get_row_alignment()); + + glTexImage2D( + GL_TEXTURE_2D, 0, + std::get<0>(fmt_in_out), dims.first, dims.second, 0, + std::get<1>(fmt_in_out), std::get<2>(fmt_in_out), nullptr + ); + + // TODO these are outdated, use sampler settings + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + log::log(MSG(dbg) << "Created OpenGL texture from parameters"); +} + +resources::TextureData GlTexture::into_data() { + auto fmt_in_out = gl_format.get(this->info.get_format()); + std::vector data(this->info.get_data_size()); + + glPixelStorei(GL_PACK_ALIGNMENT, this->info.get_row_alignment()); + glBindTexture(GL_TEXTURE_2D, *this->handle); + // TODO use a Pixel Buffer Object instead + glGetTexImage(GL_TEXTURE_2D, 0, std::get<1>(fmt_in_out), std::get<2>(fmt_in_out), data.data()); + + return resources::TextureData(resources::TextureInfo(this->info), std::move(data)); +} + +void GlTexture::bind(){ + glBindTexture(GL_TEXTURE_2D, *this->handle); +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/texture.h b/libopenage/renderer/opengl/texture.h new file mode 100644 index 0000000000..0a5ad5a062 --- /dev/null +++ b/libopenage/renderer/opengl/texture.h @@ -0,0 +1,29 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../resources/texture_data.h" +#include "../texture.h" +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// A handle to an OpenGL texture. +class GlTexture final : public Texture, public GlSimpleObject { +public: + /// Constructs a texture and fills it with the given data. + explicit GlTexture(const resources::TextureData&); + + /// Constructs an empty texture with the given parameters. + GlTexture(resources::TextureInfo const&); + + /// Downloads the texture from the GPU and returns it as CPU-accessible data. + resources::TextureData into_data() override; + + void bind() override; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/texturearray.cpp b/libopenage/renderer/opengl/texturearray.cpp new file mode 100644 index 0000000000..a4dde11edc --- /dev/null +++ b/libopenage/renderer/opengl/texturearray.cpp @@ -0,0 +1,139 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "texturearray.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" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// The input and output formats for GL. +static constexpr auto gl_format = datastructure::create_const_map>( + // TODO check correctness of formats here + std::make_pair(resources::pixel_format::r16ui, std::make_tuple(GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_INT)), + std::make_pair(resources::pixel_format::r32ui, std::make_tuple(GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT)), + std::make_pair(resources::pixel_format::rgb8, std::make_tuple(GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE)), + std::make_pair(resources::pixel_format::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)) +); + +GlTextureArray::GlTextureArray(const resources::TextureData& data) + : GlSimpleObject([] (GLuint handle) { glDeleteTextures(1, &handle); } ) +{ + // set info + this->info = data.get_info(); + // generate opengl texture handle + glGenTextures(1, &*this->handle); + glBindTexture(GL_TEXTURE_2D_ARRAY, *this->handle); + // select pixel format + auto fmt_in_out = gl_format.get(this->info.get_format()); + //log::log(INFO << "format : "<<(int)this->info.get_format()); + log::log(INFO << "format : "<< (int)this->info.get_format() << " " << std::get<0>(fmt_in_out) << " " << std::get<1>(fmt_in_out) << " " << std::get<2>(fmt_in_out)); + + //log::log(INFO << std::get<0>(fmt_in_out) << " " << std::get<1>(fmt_in_out) << " " << std::get<2>(fmt_in_out) << " " ); + // store raw pixels to gpu + auto size = this->info.get_size(); + log::log(INFO << size.first << "hey " << size.second); + uint len_z = 40; + + glTexImage3D(GL_TEXTURE_2D_ARRAY, + 0, // mipmap level + std::get<0>(fmt_in_out), // gpu texel format + size.first, // width + size.second, // height + len_z, // depth + 0, // border + std::get<1>(fmt_in_out), // cpu pixel format + std::get<2>(fmt_in_out), // cpu pixel coord type + 0); + + log::log(INFO<<"what up 100"); + for(uint i = 0; i < len_z;++i) + { + //Choose a random color for the i-essim image + GLubyte color[4] = {rand()%255,rand()%255,rand()%255,1}; + log::log(INFO<<"what up "<(fmt_in_out), //format + std::get<2>(fmt_in_out), //type + data.get_data()); //pointer to data + } + log::log(INFO<<"what up 100"); + + //glPixelStorei(GL_UNPACK_ALIGNMENT, this->info.get_row_alignment()); + + // drawing settings + // 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); + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + log::log(MSG(dbg) << "Created OpenGL texture array for the terrain"); +} + + +GlTextureArray::GlTextureArray(int layers,int height, int width,resources::pixel_format format) +: GlSimpleObject([] (GLuint handle) { glDeleteTextures(1, &handle); } ), + depth(layers),height(height),width(width),format(format) +{ + //auto fmt_in_out = gl_format.get(this->info.get_format()); + auto fmt_in_out = gl_format.get(format); + glGenTextures(1, &*this->handle); + glBindTexture(GL_TEXTURE_2D_ARRAY, *this->handle); + int len_z = layers; + glTexImage3D(GL_TEXTURE_2D_ARRAY, + 0, // mipmap level + std::get<0>(fmt_in_out), // gpu texel format + width, // width + height, // height + len_z, // depth + 0, // border + std::get<1>(fmt_in_out), // cpu pixel format + std::get<2>(fmt_in_out), // cpu pixel coord type + 0); + //glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + //glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); +} + +int GlTextureArray::submit_texture(resources::TextureData& data){ + this->bind(); + auto fmt_in_out = gl_format.get(this->format); + glTexSubImage3D( GL_TEXTURE_2D_ARRAY, + 0, //Mipmap number + 0,0,current_layers, //xoffset, yoffset, zoffset + width,height,1, //width, height, depth + std::get<1>(fmt_in_out), //format + std::get<2>(fmt_in_out), //type + data.get_data()); //pointer to data + + log::log(INFO<<"what up -----" << current_layers); + + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + current_layers++; + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + return current_layers; +} + +void GlTextureArray::bind(){ + glBindTexture(GL_TEXTURE_2D_ARRAY, *this->handle); +} + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/texturearray.h b/libopenage/renderer/opengl/texturearray.h new file mode 100644 index 0000000000..32fe7b3bac --- /dev/null +++ b/libopenage/renderer/opengl/texturearray.h @@ -0,0 +1,31 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../resources/texture_data.h" +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// A handle to an OpenGL texture. +class GlTextureArray final : public GlSimpleObject { +public: + /// Constructs a texture and fills it with the given data. + GlTextureArray(const resources::TextureData&); + + GlTextureArray(int layers,int height, int width,resources::pixel_format format); + + int submit_texture(resources::TextureData& data); + void bind(); + private: + int width,height; + int depth; + int current_layers = 0; + resources::pixel_format format; + resources::TextureInfo info; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/texturemanager.cpp b/libopenage/renderer/opengl/texturemanager.cpp new file mode 100644 index 0000000000..6de2c3a571 --- /dev/null +++ b/libopenage/renderer/opengl/texturemanager.cpp @@ -0,0 +1,237 @@ +#include "texturemanager.h" + + +namespace openage{ +namespace renderer{ +namespace opengl{ + + TextureManager::TextureManager(util::Path& path){ + this->root = path; + } + +int TextureManager::get_activeID(Sprite_2& sprite){ + //log::log(INFO << "A"); + if(sprite.is_tex == false){ + //log::log(INFO << "is_tex == false"); + sprite.active_id = -1; + return -1; + } + else if(sprite.is_tex == true && sprite.active_id == -1){ + //log::log(INFO << "which sprite is: tex_id in true and -1 "<tex_id == sprite.tex_id){ + is_loaded = true; + sprite.vec_id = i; + log::log(INFO << "found true "<add_texture(sprite.tex_id,sprite.meta,sprite.is_terrain); // load the texture + texture.tex->tex_id = sprite.tex_id; + textures.push_back(texture); + sprite.vec_id = textures.size() - 1; + //log::log(INFO << "just after the shared ptr fuckup"); + + if(current_textures.size() < 32){ + current_textures.push_back(sprite.vec_id); + sprite.active_id = current_textures.size() - 1; + } + + else{ + //log::log(INFO<<"fatal error 3"); + // we need to remove a texture and replace this texture with it + //for now this will be the first texture or textures[0] + //log::log(INFO<<"fatal error loaded false"); + current_textures[0] = sprite.vec_id; + sprite.active_id = 0; + } + + } + } + + else if(sprite.is_tex == true && sprite.active_id != -1){ + //log::log(INFO << "is tex true and active id not -1"); + //log::log(INFO << "is_tex and not active id "< TextureManager::add_tex(resources::TextureData& data) { + return std::make_unique(data); +} + +int TextureManager::bind_textures(){ + //if(is_change == true){ + //log::log(INFO<<"current textures"<bind(); + } + //} + //is_change = false; +} + + +TextureStruct TextureManager::add_texture(int tex_id,bool meta_file, bool is_terrain){ + std::string path; + if(!is_terrain) + path = "/assets/converted/graphics/"+ std::to_string(tex_id) +".slp.png"; + else{ + path = "/assets/terrain/textures/" + std::to_string(tex_id - 6000) + ".png"; + //path = "/assets/converted/terrain/" + std::to_string(tex_id) + ".slp.png"; + } + resources::TextureData data(root/path,meta_file); + auto texture = this->add_tex(data); + return {data,std::move(texture)}; +} + +void TextureManager::getUV(Sprite_2& sprite){ + int temp; + for(int i = 0;i< textures.size();i++){ + if(sprite.tex_id == textures[i].tex->tex_id){ + temp = i; + break; + } + } + + std::tuple sprite_coord = textures[temp].data.get_info().get_subtexture_coordinates(sprite.subtex); + auto size = textures[temp].data.get_info().get_subtexture_size(sprite.subtex); + float left = std::get<0>(sprite_coord); + float right = std::get<1>(sprite_coord); + float bottom = std::get<2>(sprite_coord); + float top = std::get<3>(sprite_coord); + sprite.left = left; + sprite.right = right; + sprite.top = top; + sprite.bottom = bottom; + sprite.w = size.first; + sprite.h = size.second; +} + + + +bool TextureManager::check_texture(Sprite_2& sprite){ + if(sprite.is_tex == false){ + return false; + } + + + else if(sprite.is_tex == true && sprite.active_id == -1){ + //log::log(INFO << "which sprite is: tex_id in true and -1 "<tex_id == sprite.tex_id){ + is_loaded = true; + break; + } + } + + if(is_loaded == true){ + bool is_bound = false; + for(uint i = 0;i< current_textures.size();i++){ + if(current_textures[i] == sprite.vec_id){ + is_bound = true; + return false; + break; + } + } + if(is_bound == false){ + if(current_textures.size() >= 32) + return true; + else + return false; + } + + } + else if(is_loaded == false){ + if(current_textures.size() >= 32) + return true; + else + return false; + } + } + + else if(sprite.is_tex == true && sprite.active_id != -1){ + bool is_bound = false; + for(uint i = 0;i< current_textures.size();i++){ + if(current_textures[i] == sprite.vec_id){ + is_bound = true; + return false; + break; + } + } + if(is_bound == false){ + if(current_textures.size() >= 32) + return true; + else + return false; + } + + } +} + +}}} \ No newline at end of file diff --git a/libopenage/renderer/opengl/texturemanager.h b/libopenage/renderer/opengl/texturemanager.h new file mode 100644 index 0000000000..b668fe445f --- /dev/null +++ b/libopenage/renderer/opengl/texturemanager.h @@ -0,0 +1,36 @@ +#pragma once + +#include "texture.h" +#include "../../util/path.h" +#include "sprite_2.h" +#include "../../log/log.h" +#include "../../error/error.h" +#include +namespace openage{ +namespace renderer{ +namespace opengl{ +struct TextureStruct{ + resources::TextureData data; + std::shared_ptr tex; +}; +class TextureManager{ + + public: + TextureManager(util::Path& path); + int get_activeID(Sprite_2& sprite); + std::unique_ptr add_tex(resources::TextureData& data); + TextureStruct add_texture(int tex_id,bool meta_file,bool is_terrain); + int bind_textures(); + void getUV(Sprite_2& sprite); + bool check_texture(Sprite_2& sprite); + std::vector current_textures; + private: + std::vector textures; + bool is_change = true; + util::Path root; + int forget_index = 0; + + +}; + +}}} \ No newline at end of file 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..4a1fd0f153 --- /dev/null +++ b/libopenage/renderer/opengl/vertex_array.cpp @@ -0,0 +1,109 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include "vertex_array.h" + +#include "../../error/error.h" +#include "../../datastructure/constexpr_map.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// The type of a single element in a per-vertex attribute. +static constexpr auto gl_types = datastructure::create_const_map( + std::make_pair(resources::vertex_input_t::V2F32, GL_FLOAT), + std::make_pair(resources::vertex_input_t::V3F32, GL_FLOAT) +); + +GlVertexArray::GlVertexArray(std::vector> buffers) + : GlSimpleObject([] (GLuint handle) { glDeleteVertexArrays(1, &handle); } ) +{ + GLuint handle; + glGenVertexArrays(1, &handle); + this->handle = handle; + + this->bind(); + + GLuint attrib = 0; + for (auto buf_info : buffers) { + auto const &buf = buf_info.first; + auto const &info = buf_info.second; + + buf.bind(GL_ARRAY_BUFFER); + + if (info.get_layout() == resources::vertex_layout_t::AOS) { + size_t offset = 0; + + for (auto in : info.get_inputs()) { + glEnableVertexAttribArray(attrib); + + glVertexAttribPointer( + attrib, + resources::vertex_input_count(in), + gl_types.get(in), + GL_FALSE, + info.vert_size(), + reinterpret_cast(offset) + ); + + offset += resources::vertex_input_size(in); + attrib += 1; + } + } + else if (info.get_layout() == resources::vertex_layout_t::SOA) { + size_t offset = 0; + size_t vert_count = buf.get_size() / info.vert_size(); + + for (auto in : info.get_inputs()) { + glEnableVertexAttribArray(attrib); + + glVertexAttribPointer( + attrib, + resources::vertex_input_count(in), + gl_types.get(in), + GL_FALSE, + 0, + reinterpret_cast(offset) + ); + + offset += resources::vertex_input_size(in) * vert_count; + attrib += 1; + } + + } + else { + throw Error(MSG(err) << "Unknown vertex layout."); + } + } +} + +GlVertexArray::GlVertexArray(GlBuffer const& buf, resources::VertexInputInfo const& info) + : GlVertexArray( { { buf, info } } ) {} + +GlVertexArray::GlVertexArray() + : GlSimpleObject([] (GLuint handle) { glDeleteVertexArrays(1, &handle); } ) +{ + GLuint handle; + glGenVertexArrays(1, &handle); + this->handle = handle; +} + +void GlVertexArray::bind() const { + glBindVertexArray(*this->handle); + + // TODO necessary? + /* + // then enable all contained attribute ids + for (auto &attr_id : this->bound_attributes) { + glEnableVertexAttribArray(attr_id); + } + */ +} + +void GlVertexArray::unbind() const { + glBindVertexArray(0); + +} + +}}} // 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..33e767cc1e --- /dev/null +++ b/libopenage/renderer/opengl/vertex_array.h @@ -0,0 +1,48 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../resources/mesh_data.h" +#include "buffer.h" +#include "simple_object.h" + + +namespace openage { +namespace renderer { +namespace opengl { + +/// An OpenGL Vertex Array Object. It represents a mapping between a buffer +/// that contains vertex data and a way for the context to interpret it. +class GlVertexArray final : public GlSimpleObject { +public: + /// Initializes a vertex array from the given mesh data. + /// The inputs are parsed as follows - each subsequent (GlBuffer, VertexInputInfo) pair is added + /// to the VAO in the same order as they appear in the vector. Each buffer is bound according to its + /// input info and each subsequent vertex attribute is attached to an id in ascending order. + /// For example, the inputs: + /// in vec3 pos; + /// in vec2 uv; + /// in mat3 rot; + /// could be bound to two buffers like so: + /// GlBuffer buf1(data1, size1); + /// GlBuffer buf2(data2, size2); + /// GlVertexArray( { + /// std::make_pair(buf1, { { vertex_input_t::V3F32, vertex_input_t::V2F32 }, vertex_layout_t::SOA }, + /// std::make_pair(buf2, { { vertex_input_t::M3F32 }, vertex_input_t::AOS /*or ::SOA, doesn't matter*/ }, + /// } ); + /// Not that anyone would ever want to bind more than one buffer to a single mesh.. + GlVertexArray(std::vector> buffers); + + /// Executes the buffer list constructor with one element. + GlVertexArray(GlBuffer const&, resources::VertexInputInfo const&); + + /// The default constructor initializes an empty VAO with no attributes. + /// This is useful for bufferless drawing. + GlVertexArray(); + + /// Make this vertex array object the current one. + void bind() const; + void unbind() const; +}; + +}}} // openage::renderer::opengl diff --git a/libopenage/renderer/opengl/window.cpp b/libopenage/renderer/opengl/window.cpp new file mode 100644 index 0000000000..8c91734766 --- /dev/null +++ b/libopenage/renderer/opengl/window.cpp @@ -0,0 +1,113 @@ +// 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, coord::window size) + : Window(size) +{ + 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.x, + this->size.y, + window_flags + ); + + if (this->window == nullptr) { + throw Error{MSG(err) << "Failed to create SDL window: " << SDL_GetError()}; + } + + this->context = opengl::GlContext(this->window); + this->context->set_vsync(false); + this->add_resize_callback([] (coord::window new_size) { glViewport(0, 0, new_size.x, new_size.y); } ); + + log::log(MSG(info) << "Created SDL window with OpenGL context."); +} + +GlWindow::~GlWindow() { + SDL_DestroyWindow(this->window); +} + +void GlWindow::set_size(const coord::window &new_size) { + if (this->size.x != new_size.x || this->size.y != new_size.y) { + SDL_SetWindowSize(this->window, this->size.x, this->size.y); + this->size = new_size; + } + + for (auto& cb : this->on_resize) { + cb(new_size); + } +} + +void GlWindow::update() { + SDL_Event event; + + while (SDL_PollEvent(&event)) { + if (event.type == SDL_WINDOWEVENT) { + if (event.window.event == SDL_WINDOWEVENT_RESIZED) { + coord::window new_size { event.window.data1, event.window.data2 }; + log::log(MSG(dbg) << "Window resized to: " << new_size.x << " x " << new_size.y); + this->size = new_size; + for (auto& cb : this->on_resize) { + cb(new_size); + } + } + } 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::shared_ptr GlWindow::make_renderer() { + return std::make_shared(this->get_context()); +} + +std::shared_ptr GlWindow::make_batchrenderer(util::Path& path) { + return std::make_shared(this->get_context(),path); +} + +/* std::shared_ptr GlWindow::make_vertexrenderer(util::Path& path) { + return std::make_shared(this->get_context(),path); +} */ + +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..3c0d389ed8 --- /dev/null +++ b/libopenage/renderer/opengl/window.h @@ -0,0 +1,49 @@ +// Copyright 2018-2018 the openage authors. See copying.md for legal info. + +#pragma once + +#include "../window.h" + +#include + +#include "context.h" +#include "batchrenderer.h" +//#include "vertexrenderer.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, coord::window size); + ~GlWindow(); + + void set_size(const coord::window &new_size) override; + + void update() override; + + std::shared_ptr make_renderer() override; + std::shared_ptr make_batchrenderer(util::Path& path); + //std::shared_ptr make_vertexrenderer(util::Path& path); + + + /// 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(); + /// The SDL struct representing this window. + SDL_Window *window; + +private: + + + /// 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/pipeline.cpp b/libopenage/renderer/pipeline.cpp new file mode 100644 index 0000000000..8a0bcc0eaa --- /dev/null +++ b/libopenage/renderer/pipeline.cpp @@ -0,0 +1,210 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +/** @file + * common code for all pipeline programs. + */ + +#include "pipeline.h" + +#include "context.h" +#include "vertex_buffer.h" +#include "vertex_state.h" +#include "../log/log.h" +#include "../util/compiler.h" + +#include +#include + +namespace openage { +namespace renderer { + +PipelineVariable::PipelineVariable(const std::string &name, + Pipeline *pipeline) + : + pipeline{pipeline}, + name{name} {} + +const std::string &PipelineVariable::get_name() { + return this->name; +} + +const char *PipelineVariable::get_name_cstr() { + return this->name.c_str(); +} + + +template<> +void Uniform::upload_value() { + this->pipeline->get_program()->set_uniform_2dtexture(this->get_name_cstr(), this->value); +} + + +template<> +void Uniform::upload_value() { + this->pipeline->get_program()->set_uniform_1i(this->get_name_cstr(), this->value); +} + + +template<> +void Uniform::upload_value() { + unsigned val = this->value ? 1 : 0; + this->pipeline->get_program()->set_uniform_1ui(this->get_name_cstr(), val); +} + + +Pipeline::Pipeline(Program *prg) + : + program{prg} {} + + +Pipeline::Pipeline(Pipeline &&other) + : + program{other.program} { + + this->update_proplists(other); +} + +Pipeline::Pipeline(const Pipeline &other) + : + program{other.program} { + + this->update_proplists(other); +} + +Pipeline &Pipeline::operator =(Pipeline &&other) { + this->program = other.program; + this->update_proplists(other); + return *this; +} + +Pipeline &Pipeline::operator =(const Pipeline &other) { + this->program = other.program; + this->update_proplists(other); + return *this; +} + +Program *Pipeline::get_program() { + return this->program; +} + +void Pipeline::add_var(PipelineVariable *var) { + if (BaseAttribute *attr = dynamic_cast(var)) { + this->attributes.push_back(attr); + } + else if (BaseUniform *unif = dynamic_cast(var)) { + this->uniforms.push_back(unif); + } + else { + // unknown variable, ignore it. + } +} + + +VertexBuffer Pipeline::create_attribute_buffer() { + // create a fresh buffer + VertexBuffer vbuf{this->program->context}; + + // fill the buffer with the current vertex data. + this->update_buffer(&vbuf); + + return vbuf; +} + + +void Pipeline::update_buffer(VertexBuffer *vbuf) { + // TODO: use buffersubdata to only transfer the modified + // parts of the buffer. + + // determine vertex attribute buffer size + size_t buf_size = 0; + + // number of vertices configured + size_t vertex_count = 0; + + // we'll overwrite the whole buffer, so reset the metadata. + vbuf->reset(); + + // gather the expected buffer section and sizes. + for (auto &var : this->attributes) { + size_t entry_count = var->entry_count(); + + // the first attribute determines the expected size. + // all other attribute-definitions will have to provide the same + // number of entries (so that all vertices can get the attributes). + if (unlikely(vertex_count == 0)) { + vertex_count = entry_count; + } + else if (unlikely(vertex_count != entry_count)) { + throw error::Error{MSG(err) + << "inconsistent vertex attribute count: expected " + << vertex_count << " but " << var->get_name() + << " has " << entry_count << " entries."}; + } + + // a new section in the big vertex buffer + VertexBuffer::vbo_section section{ + var->get_attr_id(), + var->type(), + var->dimension(), + buf_size // current buffer size, increased for each section. + }; + vbuf->add_section(section); + + buf_size += var->entry_size() * entry_count; + } + + // allocate a buffer to hold all the values. + // the next loop will fill into that memory. + vbuf->alloc(buf_size); + + auto sections = vbuf->get_sections(); + + // pack the values to the buffer. + for (size_t i = 0; i < this->attributes.size(); i++) { + BaseAttribute *var = this->attributes[i]; + VertexBuffer::vbo_section *section = §ions[i]; + + // raw pointer to to-be-submitted data buffer. + char *pos = vbuf->get(true); + pos += section->offset; + + // store the attribute section to the buffer + var->pack(pos, var->entry_size() * vertex_count); + } +} + + +void Pipeline::upload_uniforms() { + for (auto &uniform : this->uniforms) { + uniform->upload(); + } +} + + +void Pipeline::update_proplists(const Pipeline &source) { + this->update_proplistptr<>(&this->attributes, &source, source.attributes); + this->update_proplistptr<>(&this->uniforms, &source, source.uniforms); +} + + +template +void Pipeline::update_proplistptr(std::vector *proplist_dest, + const Pipeline *source, + const std::vector &proplist_src) { + // the proplist stores pointers to class members. + // now we're a new class: the pointers need to be updated + // to point to the members of the new class. + // The offsets are the same, the base address is different (a new 'this'). + proplist_dest->clear(); + + // beware: ultra dirty hackery. + // the new variable offset is calculated from the old one. + // all because c++ has no reflection to iterate over member variables. + size_t new_base = reinterpret_cast(this); + for (auto &entry : proplist_src) { + size_t distance = reinterpret_cast(entry) - reinterpret_cast(source); + proplist_dest->push_back(reinterpret_cast(new_base + distance)); + } +} + +}} // openage::renderer diff --git a/libopenage/renderer/pipeline.h b/libopenage/renderer/pipeline.h new file mode 100644 index 0000000000..44017527d9 --- /dev/null +++ b/libopenage/renderer/pipeline.h @@ -0,0 +1,376 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_PIPELINE_H_ +#define OPENAGE_RENDERER_PIPELINE_H_ + +#include +#include +#include +#include + +#include "program.h" +#include "vertex_buffer.h" + +#include "../error/error.h" +#include "../util/compiler.h" +#include "../util/constexpr.h" +#include "../util/vector.h" + +namespace openage { +namespace renderer { + +class Pipeline; + +/** + * A pipeline property. Wraps GPU state to be set. + */ +class PipelineVariable { +public: + PipelineVariable(const std::string &name, + Pipeline *pipeline); + virtual ~PipelineVariable() = default; + + /** + * Return the associated shader variable name. + */ + const std::string &get_name(); + + /** + * Return the shader variable name as a C string. + */ + const char *get_name_cstr(); + +protected: + /** + * The associated pipeline metadata container. + */ + Pipeline *const pipeline; + + /** + * Shader variable name. + */ + std::string name; +}; + + +/** + * Base class for all uniform properties of the pipeline. + */ +class BaseUniform : public PipelineVariable { +public: + BaseUniform(const std::string &name, Pipeline *pipeline) + : + PipelineVariable{name, pipeline}, + dirty{true} {} + + virtual ~BaseUniform() = default; + + /** + * Push the value to the GPU, if necessary. + * + * TODO: another pipeline could have changed it for the same program. + */ + void upload() { + if (this->dirty) { + this->upload_value(); + this->dirty = false; + } + } + +protected: + /** + * Per-type specialization of how to set the uniform value. + * Calls the backend-dependent function to push the data to the gpu. + */ + virtual void upload_value() = 0; + + /** + * True when the value has changed and needs to be reuploaded. + */ + bool dirty; +}; + + +/** + * Pipeline uniform variable, + * which is a global value for all pipeline stages. + */ +template +class Uniform : public BaseUniform { +public: + Uniform(const std::string &name, Pipeline *pipeline) + : + BaseUniform{name, pipeline} {} + + virtual ~Uniform() = default; + + /** + * Store the uniform value so it can be applied when requested. + */ + void set(const T &value) { + this->value = value; + this->dirty = true; + } + +protected: + /** + * Per-type specialization of how to set the uniform value. + * Calls the backend-dependent function to push the data to the gpu. + */ + void upload_value() override; + + /** + * Stored value to be uploaded to the gpu. + */ + T value; +}; + + +/** + * Boilerplate base class for templated vertex attributes. + * Stores the attributes until they are packed to a uploadable buffer. + */ +class BaseAttribute : public PipelineVariable { +public: + BaseAttribute(const std::string &name, Pipeline *pipeline) + : + PipelineVariable{name, pipeline} {} + + virtual ~BaseAttribute() = default; + + /** + * Pack all the stored attributes to the given buffer. + * Write a maximum of n chars. + */ + virtual void pack(char *buffer, size_t n) = 0; + + /** + * Return the number of attribute entries, + * aka the number of configured vertices. + */ + virtual size_t entry_count() = 0; + + /** + * Return the size in chars of one attribute entry. + * This equals dimension() * sizeof(attr_type) + */ + virtual size_t entry_size() = 0; + + /** + * Return the dimension of a single attribute entry. + * For a vec2 this is two. + */ + virtual size_t dimension() = 0; + + /** + * Return the vertex attribute type. + */ + virtual vertex_attribute_type type() = 0; + + /** + * Return the glsl layout id for this attribute. + */ + virtual int get_attr_id() = 0; +}; + + +/** + * Vertex attribute property. + * + * All vertex attributes of a shader have the same number of entries! + * All of those attribtes are merged into a single buffer on request. + * The buffer merging is done in the respective context. + * This buffer is then transmitted to the GPU. + * + * You may only use types for T that can be copied by memcpy to a + * char buffer. + * + * We could extend this to support any class by specializing + * the pack method for POD types and some other magic base class type + * that provides the availability of a specific packing method. + */ +template +class Attribute : public BaseAttribute { +public: + Attribute(const std::string &name, Pipeline *pipeline) + : + BaseAttribute{name, pipeline}, + attr_id{-1} {} + + virtual ~Attribute() = default; + + // as we wanna copy the values to the gpu, they need to be + // easily copyable. + static_assert(std::is_trivially_copyable::value, + "only trivially copyable types supported as attributes"); + + /** + * Set this attribute to some value array. + */ + void set(const std::vector &values) { + this->values = values; + } + + /** + * Set the glsl layout position. + * if unset, determine the position automatically. + */ + void set_layout(unsigned int position) { + this->attr_id = position; + } + + /** + * return the glsl layout position. + * If it's unknown until now, determine it by querying the gpu. + */ + int get_attr_id() override { + if (unlikely(this->attr_id < 0)) { + this->attr_id = this->pipeline->get_program()->get_attribute_id(this->get_name_cstr()); + } + + return this->attr_id; + } + + /** + * Store the data to the given buffer. + * This could be extended to also support other than POD types. + */ + void pack(char *buffer, size_t n) override { + memcpy(buffer, &this->values[0], n); + } + + /** + * Return the number of configured vertices. + */ + size_t entry_count() override { + return this->values.size(); + } + + /** + * Compute the size of a full vertex attribute data value. + * Equals dimensions * sizeof(entry_type) + */ + size_t entry_size() override { + return this->dimension() * util::compiletime(); + } + + /** + * Return the dimension of one vertex configuration entry. + */ + size_t dimension() override { + return dimensions; + } + + /** + * Provide the type of one component of one vertex attribute entry. + */ + vertex_attribute_type type() override { + return attribute_type; + } + +protected: + /** + * The vertex attribute values to be packed and submitted. + * We need to cache the values as the buffer packing and transfer + * is done after multiple attributes were set. + */ + std::vector values; + + /** + * glsl layout position of this attribute. + */ + int attr_id; +}; + + +/** + * Inherit from this class to create statically known pipeline properties. + * + * Add members of PipelineVariable to the inherited class + * to make pipeline variables available to the outside. + * This buffer is then transmitted to the GPU when the time has come. + */ +class Pipeline { +public: + Pipeline(Program *prg); + + Pipeline(Pipeline &&other); + Pipeline(const Pipeline &other); + Pipeline &operator =(Pipeline &&other); + Pipeline &operator =(const Pipeline &other); + + virtual ~Pipeline() = default; + + /** + * Fetch the program associated with this pipeline. + */ + Program *get_program(); + + /** + * Add the given program variable to the list of maintained + * pipeline attributes. + */ + void add_var(PipelineVariable *var); + + /** + * Create a vertex buffer that stores the attribute + * values set in this pipeline. + */ + VertexBuffer create_attribute_buffer(); + + /** + * Update the given vertex buffer with attributes set in this + * pipeline instance. + */ + void update_buffer(VertexBuffer *vbuf); + + /** + * Apply all the uniform values + */ + void upload_uniforms(); + +protected: + /** + * The pipeline program associated with this property definition class. + */ + Program *program; + + /** + * Keeps track of all registered attribute properties. + * + * Used to sync attribute entry lengths. + * Each attribute has to be supplied for each vertex once. + * e.g. vec3 1337 color entries require 1337 vec4 positions. + * These have different per-attribute sizes but the same lengths. + */ + std::vector attributes; + + /** + * Keeps track of all uniform properties. + */ + std::vector uniforms; + +private: + /** + * Update all membervariable-pointer-lists with offsets + * from another pipeline to have their base to 'this'. + * + * As C++ has no reflection, this needs to be done each time + * a Pipeline object is relocated somewhere else. + */ + void update_proplists(const Pipeline &source); + + /** + * Update the oneproperty list from another pipeline. + */ + template + void update_proplistptr(std::vector *proplist_dest, + const Pipeline *source, + const std::vector &proplist_src); +}; + +}} // openage::renderer + +#endif diff --git a/libopenage/renderer/renderer.h b/libopenage/renderer/renderer.h new file mode 100644 index 0000000000..5235c3cddc --- /dev/null +++ b/libopenage/renderer/renderer.h @@ -0,0 +1,132 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + + +namespace openage { +namespace renderer { + +namespace resources { +class TextureData; +class TextureInfo; +class ShaderSource; +class MeshData; +} + +class ShaderProgram; +class Geometry; +class Texture; + +/// The abstract base for uniform input. 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; +}; + +struct Renderable_test { + /// 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. + std::shared_ptr 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. + std::shared_ptr 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; +}; + +struct RenderPass_test { + /// 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::TextureData const&) = 0; + + /// Creates a new empty texture with the given parameters on the graphics hardware. + virtual std::unique_ptr add_texture(resources::TextureInfo const&) = 0; + + /// Compiles the given shader source code into a shader program. A shader program is the main tool used + /// for graphics rendering. + virtual std::shared_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::shared_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::TextureData display_into_data() = 0; + + /// Executes a render pass. + virtual void render(RenderPass const&) = 0; + virtual void render_test(RenderPass_test 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..9408b26ffa --- /dev/null +++ b/libopenage/renderer/resources/mesh_data.cpp @@ -0,0 +1,135 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "mesh_data.h" + +#include + +#include "../../error/error.h" +#include "../../datastructure/constexpr_map.h" + + +namespace openage { +namespace renderer { +namespace resources { + +static constexpr auto vin_size = datastructure::create_const_map( + std::make_pair(vertex_input_t::V2F32, 8), + std::make_pair(vertex_input_t::V3F32, 12), + std::make_pair(vertex_input_t::M3F32, 36) +); + +static constexpr auto vin_count = datastructure::create_const_map( + std::make_pair(vertex_input_t::V2F32, 2), + std::make_pair(vertex_input_t::V3F32, 3), + std::make_pair(vertex_input_t::M3F32, 9) +); + +size_t vertex_input_size(vertex_input_t in) { + return vin_size.get(in); +} + +size_t vertex_input_count(vertex_input_t in) { + return vin_count.get(in); +} + +VertexInputInfo::VertexInputInfo(std::vector inputs, vertex_layout_t layout, vertex_primitive_t primitive) + : inputs(inputs) + , layout(layout) + , primitive(primitive) {} + +VertexInputInfo::VertexInputInfo(std::vector inputs, vertex_layout_t layout, vertex_primitive_t primitive, index_t index_type) + : inputs(inputs) + , layout(layout) + , primitive(primitive) + , index_type(index_type) {} + +size_t VertexInputInfo::vert_size() const { + size_t size = 0; + for (auto in : this->inputs) { + size += vertex_input_size(in); + } + return size; +} + +const std::vector &VertexInputInfo::get_inputs() const { + return this->inputs; +} + +vertex_layout_t VertexInputInfo::get_layout() const { + return this->layout; +} + +vertex_primitive_t VertexInputInfo::get_primitive() const { + return this->primitive; +} + +std::experimental::optional VertexInputInfo::get_index_type() const { + return this->index_type; +} + +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(const std::array quad_data) { + 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); +} + +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); +} + +MeshData MeshData::make_alpha(const std::array quad_data) { + 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_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..c8db9c6e03 --- /dev/null +++ b/libopenage/renderer/resources/mesh_data.h @@ -0,0 +1,137 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include +#include + +#include "../../util/path.h" + + +namespace openage { +namespace renderer { +namespace resources { + +/// The type of a single per-vertex input to the shader program. +enum class vertex_input_t { + V2F32, + V3F32, + M3F32, +}; + +/// The primitive type that the vertices in a mesh combine into. +enum class vertex_primitive_t { + POINTS, + LINES, + LINE_STRIP, + TRIANGLES, + TRIANGLE_STRIP, + TRIANGLE_FAN, +}; + +/// The type of indices used for indexed rendering. +enum class index_t { + U8, + U16, + U32, +}; + +/// Returns the size in bytes of a per-vertex input. +size_t vertex_input_size(vertex_input_t); + +/// Returns the number of elements of the underlying type in a given per-vertex input type. +/// For example, V3F32 has 3 float elements, so the value is 3. +size_t vertex_input_count(vertex_input_t); + +enum class vertex_layout_t { + /// Array of structs. XYZUV, XYZUV, XYZUV + AOS, + /// Struct of arrays. XYZXYZXYZ, UVUVUV + SOA, +}; + +/// Information about vertex input data - which components a vertex contains +/// and how vertices are laid out in memory. +class VertexInputInfo { +public: + /// Initialize an input info for array rendering. + VertexInputInfo(std::vector, vertex_layout_t, vertex_primitive_t); + + /// Initialize an input info for indexed rendering. + VertexInputInfo(std::vector, vertex_layout_t, vertex_primitive_t, index_t); + + /// Returns the list of per-vertex inputs. + const std::vector &get_inputs() const; + + /// Returns the layout of vertices in memory. + vertex_layout_t get_layout() const; + + /// Returns the size of a single vertex. + size_t vert_size() const; + + /// Returns the primitive interpretation mode. + vertex_primitive_t get_primitive() const; + + /// Returns the type of indices used for indexed drawing, + /// which may not be present if array drawing is used. + std::experimental::optional get_index_type() const; + +private: + /// What kind of data the vertices contain and how it is laid out in memory. + std::vector inputs; + + /// How the vertices are laid out in the data buffer. + vertex_layout_t layout; + + /// The primitive type. + vertex_primitive_t primitive; + + /// The type of indices if they exist. + std::experimental::optional index_type; +}; + +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. + static MeshData make_quad(const std::array quad_data); + + static MeshData make_quad(); + //for alpha blending + static MeshData make_alpha(const std::array quad_data); + +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..103f677b72 --- /dev/null +++ b/libopenage/renderer/resources/texture_data.cpp @@ -0,0 +1,196 @@ +// 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" +#include + +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; +} + +TextureData::TextureData(const util::Path &path, bool use_metafile) { + std::string native_path = path.resolve_native_path(); + SDL_Surface *surface = IMG_Load(native_path.c_str()); + + if (!surface) { + throw Error(MSG(err) << + "Could not load texture from " << + native_path << ": " << IMG_GetError()); + } else { + log::log(MSG(dbg) << "Texture has been loaded from " << native_path); + } + + auto 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(); + std::string suffix = ".slp.docx"; + meta = meta.with_suffix(suffix); + 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 = TextureInfo(w, h, pix_fmt, align, std::move(subtextures)); +} + +TextureData::TextureData(TextureInfo &&info, std::vector &&data) + : info(std::move(info)) + , data(std::move(data)) {} + +TextureData TextureData::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; + + TextureInfo new_info(this->info); + + return TextureData(std::move(new_info), std::move(new_data)); +} + +const TextureInfo& TextureData::get_info() const { + return this->info; +} + +const uint8_t *TextureData::get_data() const { + return this->data.data(); +} + +void TextureData::store(const util::Path& file) const { + log::log(MSG(info) << "Saving texture data to " << file); + + 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..bf29b29a5f --- /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 CPU-accessible texture data in a byte buffer. Can be loaded and stored from/onto disk +/// and passed to or read from graphics hardware. In a sense it provides a mediator between +/// the filesystem, the CPU and graphics hardware. +class TextureData { +public: + /// Create a texture from an image file. + /// @param[in] use_metafile determines whether the loading should read an accompanying + /// metadata file to split the texture into subtextures + /// + /// Uses SDL Image internally. For supported image file types, + /// see the SDL_Image initialization in the engine. + TextureData(const util::Path &path, bool use_metafile = false); + + /// Construct by moving the information and raw texture data from somewhere else. + TextureData(TextureInfo &&info, std::vector &&data); + + /// Flips the texture along the Y-axis and returns the flipped data with the same info. + /// Sometimes necessary when converting between storage modes. + TextureData flip_y(); + + /// Returns the information describing this texture dataead + const TextureInfo& get_info() const; + + /// Returns a pointer to the raw texture data. + const uint8_t *get_data() const; + + /// Reads the pixel at the given position and casts it to the given type. + /// The texture is _not_ read as if it consisted of pixels of the given type, + /// but rather according to its original pixel format. + template + T read_pixel(size_t x, size_t y) const { + const uint8_t *data = this->data.data(); + auto dims = this->info.get_size(); + size_t off = (dims.second - y) * this->info.get_row_size(); + off += x * pixel_size(this->info.get_format()); + + if ((off + sizeof(T)) > this->info.get_data_size()) { + throw Error(MSG(err) << "Pixel position (" << x << ", " << y << ") is outside texture."); + } + + return *reinterpret_cast(data + off); + } + + /// Stores this texture data in the given file in the PNG format. + void store(const util::Path& file) const; + +private: + /// Information about this texture data. + TextureInfo info; + + /// The raw texture data. + std::vector data; +}; + +}}} // namespace openage::renderer::resources diff --git a/libopenage/renderer/resources/texture_info.cpp b/libopenage/renderer/resources/texture_info.cpp new file mode 100644 index 0000000000..1c1ad17c6e --- /dev/null +++ b/libopenage/renderer/resources/texture_info.cpp @@ -0,0 +1,90 @@ +// 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); +} + +TextureInfo::TextureInfo(size_t width, size_t height, pixel_format fmt, size_t row_alignment, std::vector &&subs) + : w(width) + , h(height) + , format(fmt) + , row_alignment(row_alignment) + , subtextures(std::move(subs)) {} + +std::pair TextureInfo::get_size() const { + return std::make_pair(this->w, this->h); +} + +pixel_format TextureInfo::get_format() const { + return this->format; +} + +size_t TextureInfo::get_row_alignment() const { + return this->row_alignment; +} + +size_t TextureInfo::get_row_size() const { + size_t px_size = pixel_size(this->format); + size_t row_size = this->w * px_size; + + 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 TextureInfo::get_data_size() const { + return this->get_row_size() * this->h; +} + +size_t TextureInfo::get_subtexture_count() const { + return this->subtextures.size(); +} + +const gamedata::subtexture& TextureInfo::get_subtexture(size_t subid) const { + if (subid < this->subtextures.size()) { + return this->subtextures[subid]; + } + else { + throw Error(MSG(err) << "Unknown subtexture requested: " << subid); + } +} + +std::pair TextureInfo::get_subtexture_size(size_t subid) const { + auto subtex = this->get_subtexture(subid); + return std::make_pair(subtex.w, subtex.h); +} + +std::tuple TextureInfo::get_subtexture_coordinates(size_t subid) const { + auto tx = this->get_subtexture(subid); + return std::make_tuple( + ((float)tx.x) / this->w, + ((float)(tx.x + tx.w)) / this->w, + ((float)tx.y) / this->h, + ((float)(tx.y + tx.h)) / this->h + ); +} + +}}} diff --git a/libopenage/renderer/resources/texture_info.h b/libopenage/renderer/resources/texture_info.h new file mode 100644 index 0000000000..36448133f6 --- /dev/null +++ b/libopenage/renderer/resources/texture_info.h @@ -0,0 +1,100 @@ +// 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); + +/// Information for texture processing. +/// The class supports subtextures, so that one big texture ("texture atlas") +/// can contain several smaller images. These are the ones actually to be +/// rendered. +class TextureInfo { +public: + /// Constructs a TextureInfo with the given information. + TextureInfo(size_t width, size_t height, pixel_format, size_t row_alignment = 1, std::vector&& = std::vector()); + + TextureInfo() = default; + TextureInfo(TextureInfo const&) = default; + ~TextureInfo() = default; + + /// Returns the dimensions of the whole texture bitmap + /// @returns tuple(width, height) + std::pair get_size() const; + + /// @returns the format of pixels in this texture + pixel_format get_format() const; + + /// Returns the alignment of texture rows to byte boundaries. + size_t get_row_alignment() const; + + /// Returns the size in bytes of a single row, + /// including possible padding at its end. + size_t get_row_size() const; + + /// Returns the size in bytes of the raw pixel data. It is equal to + /// get_row_size() * get_size().second. + size_t get_data_size() const; + + /// Returns the number of available subtextures + size_t get_subtexture_count() const; + + /// Returns the coordinates of the subtexture with the given ID + const gamedata::subtexture& get_subtexture(size_t subid) const; + + /// Returns the size of the subtexture with the given ID + std::pair get_subtexture_size(size_t subid) const; + + /// Returns atlas subtexture coordinates. + /// + /// @returns left, right, top and bottom bounds as coordinates these pick + /// the requested area out of the big texture. returned as floats in + /// range 0.0 to 1.0, relative to the whole surface size. + std::tuple get_subtexture_coordinates(size_t subid) const; + +private: + /// Width and height of this texture. + int32_t w, h; + + /// The pixel format of this texture. + pixel_format format; + + /// The alignment of texture rows to byte boundaries. Can be 1, 2, 4 or 8. + /// There is padding at the end of each row to match thze 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..23a01f8536 --- /dev/null +++ b/libopenage/renderer/sdl_global.cpp @@ -0,0 +1,58 @@ +// 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..9267265386 --- /dev/null +++ b/libopenage/renderer/shader_program.h @@ -0,0 +1,108 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include + +#include "../error/error.h" + + +namespace openage { +namespace renderer { + +class UniformInput; +class Texture; +class ShaderProgram { +public: + // Template dispatches for uniform variable setting. + void update_uniform_input(UniformInput*) {} + + void update_uniform_input(UniformInput *input, const char *unif, int32_t val) { + this->set_i32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, uint32_t val) { + this->set_u32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, float val) { + this->set_f32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, double val) { + this->set_f64(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, Eigen::Vector2f const &val) { + this->set_v2f32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, Eigen::Vector3f const &val) { + this->set_v3f32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, Eigen::Vector4f const &val) { + this->set_v4f32(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, Texture const *val) { + this->set_tex(input, unif, val); + } + + void update_uniform_input(UniformInput *input, const char *unif, Texture *val) { + this->set_tex(input, unif, val); + } + + + + void update_uniform_input(UniformInput *input, const char *unif, Eigen::Matrix4f const &val) { + this->set_m4f32(input, unif, val); + } + + template + void update_uniform_input(UniformInput*, const char *unif, T) { + throw Error(MSG(err) << "Tried to set uniform " << unif << " using unknown type."); + } + + /// Returns whether the shader program contains a uniform variable with the given name. + virtual bool has_uniform(const char *unif) = 0; + + /// Creates a new uniform input (a binding of uniform names to values) for this shader + /// and optionally sets some uniform values. To do that, just pass two arguments - + /// - a string literal and the value for that uniform for any uniform you want to set. + /// For example new_uniform_input("color", { 0.5, 0.5, 0.5, 1.0 }, "num", 5) will set + /// "color" to { 0.5, 0.5, 0.5, 0.5 } and "num" to 5. Types are important here and a type + /// mismatch between the uniform variable and the input might result in an error. + template + std::shared_ptr new_uniform_input(Ts... vals) { + auto input = this->new_unif_in(); + this->update_uniform_input(input.get(), vals...); + return input; + } + + /// Updates the given uniform input with new uniform values similarly to new_uniform_input. + /// For example, update_uniform_input(in, "awesome", true) will set the "awesome" uniform + /// in addition to whatever values were in the uniform input before. + template + void update_uniform_input(UniformInput *input, const char *unif, T val, Ts... vals) { + this->update_uniform_input(input, unif, val); + this->update_uniform_input(input, vals...); + } + +protected: + // Virtual dispatches to the actual shader program implementation. + virtual std::shared_ptr new_unif_in() = 0; + virtual void set_i32(UniformInput*, const char*, int32_t) = 0; + virtual void set_u32(UniformInput*, const char*, uint32_t) = 0; + virtual void set_f32(UniformInput*, const char*, float) = 0; + virtual void set_f64(UniformInput*, const char*, double) = 0; + virtual void set_v2f32(UniformInput*, const char*, Eigen::Vector2f const&) = 0; + virtual void set_v3f32(UniformInput*, const char*, Eigen::Vector3f const&) = 0; + virtual void set_v4f32(UniformInput*, const char*, Eigen::Vector4f const&) = 0; + virtual void set_m4f32(UniformInput*, const char*, Eigen::Matrix4f const&) = 0; + virtual void set_tex(UniformInput*, const char*, Texture const*) = 0; +}; + +}} diff --git a/libopenage/renderer/shaders/CMakeLists.txt b/libopenage/renderer/shaders/CMakeLists.txt new file mode 100644 index 0000000000..c8bc7d39bb --- /dev/null +++ b/libopenage/renderer/shaders/CMakeLists.txt @@ -0,0 +1,4 @@ +add_sources(libopenage + alphamask.cpp + simpletexture.cpp +) diff --git a/libopenage/renderer/shaders/alphamask.cpp b/libopenage/renderer/shaders/alphamask.cpp new file mode 100644 index 0000000000..c6106aedcc --- /dev/null +++ b/libopenage/renderer/shaders/alphamask.cpp @@ -0,0 +1,31 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "alphamask.h" + +namespace openage { +namespace renderer { + +AlphamaskMaterial::AlphamaskMaterial(Program *prg) + : + Material{prg}, + position{"position", this}, + base_texture{"base_tex", this}, + mask_texture{"mask_tex", this}, + show_mask{"show_mask", this}, + base_tex_coordinates{"base_tex_coordinates", this}, + mask_tex_coordinates{"mask_tex_coordinates", this} { + + this->add_var(&this->position); + this->add_var(&this->base_texture); + this->add_var(&this->mask_texture); + this->add_var(&this->show_mask); + this->add_var(&this->base_tex_coordinates); + this->add_var(&this->mask_tex_coordinates); +} + +void AlphamaskMaterial::set_positions(mesh_t positions) { + this->position.set(positions); +} + + +}} // openage::renderer diff --git a/libopenage/renderer/shaders/alphamask.h b/libopenage/renderer/shaders/alphamask.h new file mode 100644 index 0000000000..a38529ba19 --- /dev/null +++ b/libopenage/renderer/shaders/alphamask.h @@ -0,0 +1,38 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_SHADERS_ALPHAMASK_H_ +#define OPENAGE_RENDERER_SHADERS_ALPHAMASK_H_ + +#include + +#include "../material.h" +#include "../../util/vector.h" + +namespace openage { +namespace renderer { + +/** + * A simple plain texture material. + */ +class AlphamaskMaterial : public Material { +public: + AlphamaskMaterial(Program *prg); + virtual ~AlphamaskMaterial() = default; + + void set_positions(mesh_t positions) override; + + // shader variables: + Attribute position; + Uniform base_texture; + Uniform mask_texture; + + Uniform show_mask; + Attribute, 2> base_tex_coordinates; + Attribute, 2> mask_tex_coordinates; +}; + + +}} // openage::renderer + + +#endif diff --git a/libopenage/renderer/shaders/simpletexture.cpp b/libopenage/renderer/shaders/simpletexture.cpp new file mode 100644 index 0000000000..ee2e791d03 --- /dev/null +++ b/libopenage/renderer/shaders/simpletexture.cpp @@ -0,0 +1,26 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#include "simpletexture.h" + +namespace openage { +namespace renderer { + +SimpleTextureMaterial::SimpleTextureMaterial(Program *prg) + : + Material{prg}, + tex{"tex", this}, + position{"position", this}, + texcoord{"texcoord", this} { + + // register the variables to the "active" list + this->add_var(&this->tex); + this->add_var(&this->position); + this->add_var(&this->texcoord); +} + +void SimpleTextureMaterial::set_positions(mesh_t positions) { + this->position.set(positions); +} + + +}} // openage::renderer diff --git a/libopenage/renderer/shaders/simpletexture.h b/libopenage/renderer/shaders/simpletexture.h new file mode 100644 index 0000000000..a84cff8353 --- /dev/null +++ b/libopenage/renderer/shaders/simpletexture.h @@ -0,0 +1,34 @@ +// Copyright 2015-2015 the openage authors. See copying.md for legal info. + +#ifndef OPENAGE_RENDERER_SHADERS_SIMPLETEXTURE_H_ +#define OPENAGE_RENDERER_SHADERS_SIMPLETEXTURE_H_ + +#include + +#include "../material.h" +#include "../../util/vector.h" + +namespace openage { +namespace renderer { + +/** + * A simple plain texture material. + */ +class SimpleTextureMaterial : public Material { +public: + SimpleTextureMaterial(Program *prg); + virtual ~SimpleTextureMaterial() = default; + + void set_positions(mesh_t positions) override; + + // shader variables: + Uniform tex; + Attribute position; + Attribute, 2> texcoord; +}; + + +}} // openage::renderer + + +#endif diff --git a/libopenage/renderer/terrain/terrain.cpp b/libopenage/renderer/terrain/terrain.cpp new file mode 100644 index 0000000000..bb5a4aca85 --- /dev/null +++ b/libopenage/renderer/terrain/terrain.cpp @@ -0,0 +1,4 @@ +#include "terrain.h" +//so how the terrain will work is as a set of 9 terrain "chunks". Basically each time we check where the camera center is +// and then we do divide by 1024 each coordinate which gives us the integer coodinates.Once we get that +// coordinate, we basically draw the neighbouring 8 chunks. \ No newline at end of file diff --git a/libopenage/renderer/terrain/terrain.h b/libopenage/renderer/terrain/terrain.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libopenage/renderer/terrain_renderer.cpp b/libopenage/renderer/terrain_renderer.cpp new file mode 100644 index 0000000000..f8ef08a4ef --- /dev/null +++ b/libopenage/renderer/terrain_renderer.cpp @@ -0,0 +1,122 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "terrain_renderer.h" +#include "game_renderer.h" + +#include "../log/log.h" + +#include + +// Terrain texture in ao2hd format, square with subtextures +// + +namespace openage { +namespace renderer { + +static const Eigen::Matrix4f calc_tile_scale_transform() { + static const Eigen::Vector3f TILE_SCALE_VECTOR(1.0f, 0.5, 1.0f); + auto scaleTransform = Eigen::Affine3f::Identity(); + scaleTransform.prescale(TILE_SCALE_VECTOR); + return scaleTransform.matrix(); +} + +const Eigen::Matrix4f TerrainRenderer::TILE_SCALE_TRANSFORM{calc_tile_scale_transform()}; +const std::string TerrainRenderer::MVP_UNIFORM_NAME{"u_model_view_project"}; +const std::string TerrainRenderer::TEXTURE_ATLAS_UNIFORM_NAME{"u_texture_atlas"}; +const std::string TerrainRenderer::TEXCOORD_TRANSFORM_UNIFORM_NAME{"u_texcoord_transform"}; + +TerrainRenderer::TerrainRenderer(GameRenderer& renderer) + : game_renderer(renderer) +{ + // TODO this should be an FBO + this->render_pass.target = this->game_renderer.renderer().get_display_target(); +} + + +void TerrainRenderer::render(const Eigen::Matrix4f& view_project_transform) const { + for (const auto& tile : tiles) { + auto mvp_matrix = view_project_transform * tile.model_transform; + this->terrain_shader_ptr->update_uniform_input(tile.renderable.unif_in, MVP_UNIFORM_NAME, mvp_matrix); + } + // TODO retrieve a RenderPass from TerrainRenderer instead of using Renderer directly + game_renderer->renderer().render(render_pass); +} + +void TerrainRenderer::add_tiles(const std::vector& tiles) { + for (const auto& tile : tiles) { + tiles.emplace_back(); + StoredTile& internal_tile = tiles.back(); + + auto translate_transform = Eigen::Affine3f::Identity(); + translate_transform.pretranslate(tile.position); + internal_tile.model_transform = translate_transform.matrix() * TILE_SCALE_TRANSFORM; + + internal_tile.uniform_input_ptr = this->terrain_shader_ptr->new_uniform_input( + MVP_UNIFORM_NAME, internal_tile.model_transform, + TEXTURE_ATLAS_UNIFORM_NAME, terrain_texture_map[tile.terrain_id].get(), + TEXCOORD_TRANSFORM_UNIFORM_NAME, calculate_texcoord_transform(tile) + ); + auto unit_quad_ptr = game_renderer->unit_quad_ptr(); + internal_tile.renderable = Renderable{ + internal_tile.uniform_input_ptr.get(), + unit_quad_ptr, + false, + false + }; + + this->render_pass.renderables.push_back(internal_tile.renderable); + } +} + +Eigen::Matrix3f TerrainRenderer::calculate_texcoord_transform(const Tile& tile) { + const auto& texture_atlas = *(this->terrain_texture_map.at(tile.terrain_id)); + const auto texture_atlas_size = texture_atlas.get_size(); + const auto& subtexture = texture_atlas.get_subtexture(tile.subtexture_id); + + const float position_x_normalized = static_cast(subtexture.x) / texture_atlas_size.first; + const float position_y_normalized = static_cast(subtexture.y) / texture_atlas_size.second; + + const float scale_x = static_cast(subtexture.w) / texture_atlas_size.first; + const float scale_y = static_cast(subtexture.h) / texture_atlas_size.second; + + auto transform = Eigen::Affine2f::Identity(); + transform.prescale(Eigen::Vector2f(scale_x, scale_y)); + transform.pretranslate(Eigen::Vector2f(position_x_normalized, position_y_normalized)); + return transform.matrix(); +} + +void TerrainRenderer::remove_tiles(const std::vector& tiles) { + for (const auto& tile : tiles) { + remove_tile(tile); + } +} + +void TerrainRenderer::remove_tile(const Tile& tile) { + //remove the corresponding internal tile for the given tile and the renderables from the renderpass + auto iter_tiles = this->tiles.begin(); + auto iter_renderables = this->render_pass.renderables.begin(); + + while (iter_tiles != this->tiles.end() && iter_renderables != this->render_pass.renderables.end()) { + //remove if the position of the given tile matches the position of the current element. + if (iter_tiles->model_tranform.block<1, 3>(3, 0) == tile.position) { + iter_tiles = this->tiles.erase(iter_tiles); + iter_renderables = this->renderpass.renderables.erase(iter_renderables); + } else { + ++iter_tiles; + ++iter_renderables; + } + } + assert(iter_tiles == this->tiles.end()); + assert(iter_drawables == this->render_pass.renderables.end()); +} + +void TerrainRenderer::add_terrain_texture(const Tile::terrain_id_t id, const std::shared_ptr texturePtr) { + if (this->terrain_texture_map.count(id) == 1) { + log::log(WARN << "Replacing terrain texture atlas for terrain id: " << id); + } + this->terrain_texture_map[id] = texturePtr; +} + +}} diff --git a/libopenage/renderer/terrain_renderer.h b/libopenage/renderer/terrain_renderer.h new file mode 100644 index 0000000000..1a9eda2975 --- /dev/null +++ b/libopenage/renderer/terrain_renderer.h @@ -0,0 +1,61 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include + +#include + + +namespace openage { +namespace renderer { + +struct Tile { + using terrain_id_t = size_t; + + terrain_id_t terrain_id; + size_t subtexture_id; + Eigen::Vector3f position; //y is up, z is depth +}; + +class GameRenderer; + +class TerrainRenderer { +public: + TerrainRenderer(GameRenderer& renderer); + + void render(const Eigen::Matrix4f& view_project_transform) const; + + void add_tiles(const std::vector& tiles); + void remove_tiles(const std::vector& tiles); + + void add_terrain_texture(const Tile::terrain_id_t id, const std::shared_ptr texturePtr); + +private: + struct StoredTile { + Tile tile; + std::unique_ptr uniform_input_ptr; + Renderable renderable; + Eigen::Matrix4f model_transform; + }; + + static const Eigen::Matrix4f TILE_SCALE_TRANSFORM; + static const std::string MVP_UNIFORM_NAME; + static const std::string TEXTURE_ATLAS_UNIFORM_NAME; + static const std::string TEXCOORD_TRANSFORM_UNIFORM_NAME; + + GameRenderer& game_renderer; + + std::vector tiles; //can be changed to better datastructure (octree, kd-tree, bsp-tree, ...) later + std::unordered_map> terrain_texture_map; + + std::unique_ptr terrain_shader_ptr; + RenderPass render_pass; + + Eigen::Matrix3f calculate_texcoord_transform(const Tile& tile); + + void remove_tile(const Tile& tile); +}; + +}} diff --git a/libopenage/renderer/tests.cpp b/libopenage/renderer/tests.cpp new file mode 100644 index 0000000000..5ce1c3deb4 --- /dev/null +++ b/libopenage/renderer/tests.cpp @@ -0,0 +1,276 @@ +// 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" +#include "opengl/context.h" +#include "opengl/sprite.h" +#include "opengl/shader_program.h" +#include +namespace openage { +namespace renderer { +namespace tests { + + + +void renderer_demo_0(util::Path path) { + opengl::GlWindow window("openage renderer test", { 1366, 768 } ); + + auto renderer = window.make_renderer(); + + auto vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + path / "/assets/test_shaders/vshader_src.vert.glsl"); + + auto fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + path / "assets/test_shaders/fshader_src.frag.glsl"); + + auto vshader_display_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + path / "assets/test_shaders/vshader_display_src.vert.glsl"); + + auto fshader_display_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + path / "assets/test_shaders/fshader_display_src.frag.glsl"); + + auto vshader_alpha = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + path / "assets/test_shaders/alphamask.vert.glsl"); + + auto fshader_alpha = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + path / "assets/test_shaders/alphamask.frag.glsl"); + auto size = window.get_size(); + auto shader = renderer->add_shader( { vshader_src, fshader_src } ); + auto shader_display = renderer->add_shader( { vshader_display_src, fshader_display_src } ); + auto alpha_shader = renderer->add_shader({vshader_alpha,fshader_alpha}); + //start of experimental part + float aspect = (float)size.y/(float)size.x; + + //start of experimental area + opengl::Sprite sprite; + + //load the texture that we will be using + auto terrain_texture = sprite.make_texture(path,"/assets/terrain/textures/g_m02_00_color.png",false,renderer); + auto paladin = sprite.make_texture(path,"/assets/converted/graphics/795.slp.png",true,renderer); + auto paladin_2 = sprite.make_texture(path,"/assets/converted/graphics/805.slp.png",true,renderer); + auto alpha_texture = sprite.make_texture(path,"/assets/terrain/blends/watershore.png",false,renderer); + auto shore_texture = sprite.make_texture(path,"/assets/terrain/textures/g_bch_00_color.png",false,renderer); + auto water_texture = sprite.make_texture(path,"/assets/terrain/textures/g_wtr_00_color_1.png",false,renderer); + auto dust_texture = sprite.make_texture(path,"/assets/terrain/textures/g_ds3_00_color.png",false,renderer); + auto road_texture = sprite.make_texture(path,"/assets/terrain/textures/62.png",false,renderer); + //now to choose which subtexture as well as the transformation matrix. Both of these can + //be fed in the same function and updated everytime as this does not consume as many resources. + + + + float left_terr = 0.125f*7; + float top_terr = 0.125f*1; + auto alpha_test = sprite.make_terrain(water_texture,alpha_texture,alpha_shader,aspect,(float)size.y,0,512-64,left_terr,top_terr); + auto shore_1 = sprite.make_render_obj(shore_texture,true,0,shader,0,0); + auto water_1 = sprite.make_render_obj(water_texture,true,0,shader,0,0); + auto dust = sprite.make_render_obj(dust_texture,true,0,shader,600,50); + auto water_2 = sprite.make_render_obj(shore_texture,true,0,shader,0,1024); + auto water_3 = sprite.make_render_obj(shore_texture,true,0,shader,1024,0); + auto water_4 = sprite.make_render_obj(shore_texture,true,0,shader,1024,1024); + auto elephant = sprite.make_render_obj(paladin,false,0,shader,0,0); + std::vector mix_tex; + std::vector terrain_tex; + + Renderable_test elephants[80]; + for(int i = 0;i<50;i++){ + elephants[i] = sprite.make_render_obj(paladin_2,false,i,shader,0,0); + } + /*for(int i = 50;i<80;i++){ + elephants[i] = sprite.make_render_obj(paladin,false,i%30,shader,0,0); + }*/ + + //mix_tex.push_back(dust); + + //mix_tex.push_back(water_2); + //mix_tex.push_back(water_3); + //mix_tex.push_back(water_4); + //mix_tex.push_back(alpha_test); + + /*for(int z = 0;z<1000;z++){ + + mix_tex.push_back(sprite.make_render_obj(paladin,false,rand()%20,shader,aspect,(float)size.y,rand()%1366,rand()%768)); + //mix_tex.push_back(sprite.make_render_obj(paladin_2,false,15,shader,aspect,(float)size.y,rand()%1024,rand()%1024)); + + }*/ + auto new_unif = shader->new_uniform_input("pos",Eigen::Vector2f(500,200)); + mix_tex.push_back({new_unif,nullptr,true,true}); + mix_tex.push_back(elephant); + /*for(int i = 0;i<1000;i++){ + auto unif_temp = sprite.get_uniform(paladin_2,false,30,shader,rand()%1366,rand()%768); + if(rand()%2 == 0){ + unif_temp = sprite.get_uniform(paladin,false,0,shader,rand()%1366,rand()%768); + + } + mix_tex.push_back({unif_temp,elephant.geometry,true,true}); + }*/ + for(int j = 0; j<4;j++){ + for(int i=0;i<6;i++){ + auto terr_unif = sprite.get_uniform(road_texture,true,shader,512*j,512*(i-3)); + if(rand()%3 == 0) + terr_unif = sprite.get_uniform(dust_texture,true,shader,512*j,512*(i-3)); + if(rand()%3 == 1) + terr_unif = sprite.get_uniform(shore_texture,true,shader,512*j,512*(i-3)); + terrain_tex.push_back({terr_unif,water_4.geometry,true,true}); + + } + } + + log::log(INFO << "what is path "<add_texture(resources::TextureInfo(size.x, size.y, resources::pixel_format::rgba8)); + auto id_texture = renderer->add_texture(resources::TextureInfo(size.x, size.y, resources::pixel_format::r32ui)); + auto depth_texture = renderer->add_texture(resources::TextureInfo(size.x, size.y, resources::pixel_format::depth24)); + //one of the targets + 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()); + + resources::TextureData id_texture_data = id_texture->into_data(); + bool texture_data_valid = false; + + auto quad = renderer->add_mesh_geometry(resources::MeshData::make_quad()); + + Renderable_test display_obj { + color_texture_uniform, + quad, + false, + false, + }; + RenderPass_test alpha_pass{ + mix_tex, + fbo.get(), + }; + RenderPass_test terrain_pass{ + terrain_tex, + fbo.get(), + }; + RenderPass_test render_main{ + {display_obj}, + renderer->get_display_target(), + }; + /*RenderPass_test render_main{ + mix_tex, + renderer->get_display_target(), + };*/ + + //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([&] (coord::window new_size) { + // Calculate projection matrix + float aspectRatio = float(new_size.x)/float(new_size.y); + float xScale = 1.0/aspectRatio; + + Eigen::Matrix4f pmat; + pmat << 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1; + + // resize fbo + color_texture = renderer->add_texture(resources::TextureInfo(new_size.x, new_size.y, resources::pixel_format::rgba8)); + id_texture = renderer->add_texture(resources::TextureInfo(new_size.x, new_size.y, resources::pixel_format::r32ui)); + depth_texture = renderer->add_texture(resources::TextureInfo(new_size.x, new_size.y, resources::pixel_format::depth24)); + fbo = renderer->create_texture_target( { color_texture.get(), id_texture.get(), depth_texture.get() } ); + texture_data_valid = false; + + shader_display->update_uniform_input(color_texture_uniform.get(), "color_texture", color_texture.get(), "proj", pmat); + alpha_pass.target = fbo.get(); + terrain_pass.target = fbo.get(); + } ); + time_t curr_time,prev_time; + + std::shared_ptr uni_array[1000]; + std::vector loop_tex; + loop_tex.push_back(elephant); + for(int i = 0;i<1000;i++){ + uni_array[i] = sprite.get_uniform2(paladin_2,false,shader,rand()%1366,rand()%768); + loop_tex.push_back({uni_array[i],elephants[i/50].geometry,true,true}); + } + int frame = 0; + time(&prev_time); + while (!window.should_close()) { + time(&curr_time); + frame++; + if(curr_time-prev_time >= 1){ + prev_time = curr_time; + log::log(INFO << frame); + frame = 0; + } + for(int i = 0;i<1000;i++){ + shader->update_uniform_input(uni_array[i].get(),"pos",Eigen::Vector2f(rand()%1366,rand()%768)); + loop_tex[i+1].unif_in = uni_array[i]; + } + alpha_pass.renderables = loop_tex; + //render_main.renderables = loop_tex; + //renderer->render_test(terrain_pass); + renderer->render_test(alpha_pass); + renderer->render_test(render_main); + 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..8368a96b7d --- /dev/null +++ b/libopenage/renderer/texture.cpp @@ -0,0 +1,21 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#include + +#include "texture.h" +#include "../error/error.h" + + +namespace openage { +namespace renderer { + +Texture::Texture(resources::TextureInfo info) + : info(info) {} + +Texture::~Texture() {} + +const resources::TextureInfo& Texture::get_info() const { + return this->info; +} + +}} // namespace openage::renderer diff --git a/libopenage/renderer/texture.h b/libopenage/renderer/texture.h new file mode 100644 index 0000000000..a1db7df512 --- /dev/null +++ b/libopenage/renderer/texture.h @@ -0,0 +1,34 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include "resources/texture_data.h" + + +namespace openage { +namespace renderer { + +/// An abstract base for a handle to a texture buffer allocated in graphics hardware. +/// Can be obtained by passing texture data to the renderer. +class Texture { +public: + virtual ~Texture(); + + /// Returns the texture information. + const resources::TextureInfo& get_info() const; + + /// Copies this texture's data from graphics hardware into a CPU-accessible + /// TextureData buffer. + virtual resources::TextureData into_data() = 0; + virtual void bind() = 0; + int tex_id; + +protected: + /// Constructs the base with the given information. + Texture(resources::TextureInfo); + + /// Information about the size, format, etc. of this texture. + resources::TextureInfo info; +}; + +}} diff --git a/libopenage/renderer/vulkan/CMakeLists.txt b/libopenage/renderer/vulkan/CMakeLists.txt new file mode 100644 index 0000000000..249016e460 --- /dev/null +++ b/libopenage/renderer/vulkan/CMakeLists.txt @@ -0,0 +1,7 @@ +add_sources(libopenage + #extension_funcs.cpp + 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..7c782caa37 --- /dev/null +++ b/libopenage/renderer/vulkan/README.md @@ -0,0 +1,2 @@ +Seems most structures are only valid as long as their VkInstance exists - how to account for this? +Maybe factor out count; enumerate(&count); vector<>(count); enumerate(vector); into a single generic method diff --git a/libopenage/renderer/vulkan/extension_funcs.cpp b/libopenage/renderer/vulkan/extension_funcs.cpp new file mode 100644 index 0000000000..f91505b32c --- /dev/null +++ b/libopenage/renderer/vulkan/extension_funcs.cpp @@ -0,0 +1,110 @@ +#include +#include +#include + +#include + + +/// This whole file is a _HORRIBLE HACK_ to get extensions functions to work easily. +/// It lazily creates a VkInstance with all possible extensions and gets the function +/// addresses from that. Missing extension functions are then defined here with a dispatch +/// to the proper address. This might fail, crash and burn if you use an extension function +/// with an instance that doesn't support it or if two extensions provide the same function. + +static bool inited = false; +static PFN_vkCreateDebugReportCallbackEXT pCreateDebugReportCallbackEXT; +static PFN_vkDestroyDebugReportCallbackEXT pDestroyDebugReportCallbackEXT; + +static void make_ext_available() { + if (!inited) { + uint32_t count = 0; + vkEnumerateInstanceLayerProperties(&count, nullptr); + std::vector layers(count); + vkEnumerateInstanceLayerProperties(&count, layers.data()); + + std::set layer_names; + for (auto const& lr : layers) { + layer_names.insert(lr.layerName); + } + + std::vector props(32); + + vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); + if (props.size() < count) { props.resize(count); } + vkEnumerateInstanceExtensionProperties(nullptr, &count, props.data()); + + std::set extension_names; + for (size_t i = 0; i < count; i++) { + extension_names.emplace(props[i].extensionName); + } + + for (auto const& lr : layers) { + vkEnumerateInstanceExtensionProperties(lr.layerName, &count, nullptr); + if (props.size() < count) { props.resize(count); } + vkEnumerateInstanceExtensionProperties(lr.layerName, &count, props.data()); + + for (size_t i = 0; i < count; i++) { + extension_names.emplace(props[i].extensionName); + } + } + + VkApplicationInfo app_info {}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pApplicationName = "HACKHACKHACK"; + app_info.apiVersion = VK_MAKE_VERSION(1, 0, 57); + + std::vector c_layer_names; + for (auto const& lr : layer_names) { + c_layer_names.push_back(lr.c_str()); + } + std::vector c_ext_names; + for (auto const& ext : extension_names) { + c_ext_names.push_back(ext.c_str()); + } + VkInstanceCreateInfo inst_info {}; + inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + inst_info.pApplicationInfo = &app_info; + inst_info.enabledExtensionCount = c_ext_names.size(); + inst_info.ppEnabledExtensionNames = c_ext_names.data(); + inst_info.enabledLayerCount = c_layer_names.size(); + inst_info.ppEnabledLayerNames = c_layer_names.data(); + + VkInstance instance; + VkResult res = vkCreateInstance(&inst_info, nullptr, &instance); + if (res != VK_SUCCESS) { + throw std::string("Failed to HACKHACKHACK."); + } + + pCreateDebugReportCallbackEXT = PFN_vkCreateDebugReportCallbackEXT(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); + pDestroyDebugReportCallbackEXT = PFN_vkDestroyDebugReportCallbackEXT(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")); + + inited = true; + } +} + +/// -- VK_EXT_debug_report -- +VkResult vkCreateDebugReportCallbackEXT( + VkInstance instance, + const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugReportCallbackEXT* pCallback) { + make_ext_available(); + + if (pCreateDebugReportCallbackEXT != nullptr) { + return pCreateDebugReportCallbackEXT(instance, pCreateInfo, pAllocator, pCallback); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void vkDestroyDebugReportCallbackEXT( + VkInstance instance, + VkDebugReportCallbackEXT callback, + const VkAllocationCallbacks* pAllocator +) { + make_ext_available(); + + if (pDestroyDebugReportCallbackEXT != nullptr) { + pDestroyDebugReportCallbackEXT(instance, callback, pAllocator); + } +} 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/graphics_pipeline.h b/libopenage/renderer/vulkan/graphics_pipeline.h new file mode 100644 index 0000000000..4e26d24c5a --- /dev/null +++ b/libopenage/renderer/vulkan/graphics_pipeline.h @@ -0,0 +1,15 @@ +#pragma once + +namespace openage { +namespace renderer { +namespace vulkan { + +class VlkGraphicsPipeline { +public: + VlkGraphicsPipeline() { + + + } +}; + +}}} // 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..1936bf581e --- /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(coord::window size) + : size(size) {} + +coord::window 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..965e399c46 --- /dev/null +++ b/libopenage/renderer/window.h @@ -0,0 +1,63 @@ +// Copyright 2015-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include + +#include "../coord/window.h" +#include "renderer.h" + + +namespace openage { +namespace renderer { + +class Window { +public: + virtual ~Window() = default; + + /// Returns the dimensions of this window. + coord::window 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(const coord::window &new_size) = 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::shared_ptr make_renderer() = 0; + +protected: + Window(coord::window size); + + bool should_be_closed = false; + + /// The current size of the framebuffer. + coord::window 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/libopenage/terrain/terrain.cpp b/libopenage/terrain/terrain.cpp index f607fc1d5a..6412b361d0 100644 --- a/libopenage/terrain/terrain.cpp +++ b/libopenage/terrain/terrain.cpp @@ -63,6 +63,7 @@ std::vector Terrain::used_chunks() const { } bool Terrain::fill(const int *data, coord::tile_delta size) { + log::log(MSG(dbg) << "Fill called!"); bool was_cut = false; coord::tile pos = {0, 0}; diff --git a/libopenage/util/constexpr.h b/libopenage/util/constexpr.h index bca775119d..930e68e9f4 100644 --- a/libopenage/util/constexpr.h +++ b/libopenage/util/constexpr.h @@ -8,6 +8,17 @@ namespace openage { namespace util { + +/** + * Evaluate `value` at compiletime and return it. + * This can force constexpr evaluation. + */ +template +constexpr inline T compiletime() { + return value; +} + + /** * this namespace contains constexpr functions, i.e. C++11 functions that are designed * to run at compile-time. @@ -150,7 +161,6 @@ constexpr const char *strip_prefix(const char *str, const char *prefix) { return strip_prefix(str, create_truncated_string_literal(prefix)); } - } // namespace constexpr_ } // namespace util } // namespace openage diff --git a/openage/CMakeLists.txt b/openage/CMakeLists.txt index 12d46f6f5c..9464b77826 100644 --- a/openage/CMakeLists.txt +++ b/openage/CMakeLists.txt @@ -21,4 +21,5 @@ add_subdirectory(cvar) add_subdirectory(game) add_subdirectory(log) add_subdirectory(util) +add_subdirectory(renderer) add_subdirectory(testing) diff --git a/openage/cvar/config_file.py b/openage/cvar/config_file.py index 9982adbe23..8254aa0753 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..ae7dfab16f --- /dev/null +++ b/openage/renderer/CMakeLists.txt @@ -0,0 +1,9 @@ +add_cython_modules( + renderer_cpp.pyx + tests.pyx + batch_test.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/batch_test.pyx b/openage/renderer/batch_test.pyx new file mode 100644 index 0000000000..f6c1c87657 --- /dev/null +++ b/openage/renderer/batch_test.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.batch_test cimport batch_demo as batch_demo_c + +def batch_demo(list argv): + """ + invokes the available render demos. + """ + + cmd = argparse.ArgumentParser( + prog='... batch_demo', + description='Demo of the new batch 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 batch_test_id = args.test_id + + cdef Path_cpp root_cpp = Path_cpp(PyObj(root.fsobj), + root.parts) + + with nogil: + batch_demo_c(batch_test_id, root_cpp) 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 2f230b38ff..ea8dba1c4a 100644 --- a/openage/testing/testlist.py +++ b/openage/testing/testlist.py @@ -46,6 +46,10 @@ def demos_py(): "translates a C++ exception and its causes to python") yield ("openage.log.tests.demo", "demonstrates the translation of Python log messages") + yield ("openage.renderer.tests.renderer_demo", + "showcases the new renderer") + yield ("openage.renderer.batch_test.batch_demo","showcases the new batch renderer") + def benchmark_py(): @@ -105,6 +109,9 @@ def demos_cpp(): "translates a Python exception to C++") yield ("openage::pyinterface::tests::pyobject_demo", "a tiny interactive interpreter using PyObjectRef") + #yield ("openage::renderer::batch_test::batch_demo","showcases the new batch renderer") + + def benchmark_cpp(): diff --git a/terrain.h b/terrain.h new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/terrain.h @@ -0,0 +1 @@ +