diff --git a/headers/enduro2d/core/render.hpp b/headers/enduro2d/core/render.hpp index 2857adf0..f0dcd42d 100644 --- a/headers/enduro2d/core/render.hpp +++ b/headers/enduro2d/core/render.hpp @@ -17,6 +17,8 @@ namespace e2d class texture; class index_buffer; class vertex_buffer; + class render_target; + class pixel_declaration; class index_declaration; class vertex_declaration; @@ -24,6 +26,50 @@ namespace e2d using texture_ptr = std::shared_ptr; using index_buffer_ptr = std::shared_ptr; using vertex_buffer_ptr = std::shared_ptr; + using render_target_ptr = std::shared_ptr; + + // + // pixel_declaration + // + + class pixel_declaration final { + public: + enum class pixel_type : u8 { + rgba8, + depth24_stencil8, + + dxt1, + dxt3, + dxt5, + + rgb_pvrtc2, + rgb_pvrtc4, + + rgba_pvrtc2, + rgba_pvrtc4 + }; + public: + pixel_declaration() = default; + ~pixel_declaration() noexcept = default; + + pixel_declaration(const pixel_declaration&) noexcept = default; + pixel_declaration& operator=(const pixel_declaration&) noexcept = default; + + pixel_declaration(pixel_type type) noexcept; + + pixel_type type() const noexcept; + bool is_compressed() const noexcept; + std::size_t bits_per_pixel() const noexcept; + private: + pixel_type type_ = pixel_type::rgba8; + }; + + bool operator==( + const pixel_declaration& l, + const pixel_declaration& r) noexcept; + bool operator!=( + const pixel_declaration& l, + const pixel_declaration& r) noexcept; // // index_declaration @@ -35,19 +81,6 @@ namespace e2d unsigned_byte, unsigned_short }; - - class index_info final { - public: - index_type type = index_type::unsigned_short; - public: - index_info() = default; - ~index_info() noexcept = default; - - index_info(const index_info&) noexcept = default; - index_info& operator=(const index_info&) noexcept = default; - - explicit index_info(index_type type) noexcept; - }; public: index_declaration() = default; ~index_declaration() noexcept = default; @@ -55,12 +88,12 @@ namespace e2d index_declaration(const index_declaration&) noexcept = default; index_declaration& operator=(const index_declaration&) noexcept = default; - explicit index_declaration(index_type index_type) noexcept; + index_declaration(index_type type) noexcept; - const index_info& index() const noexcept; - std::size_t index_size() const noexcept; + index_type type() const noexcept; + std::size_t bytes_per_index() const noexcept; private: - index_info index_; + index_type type_ = index_type::unsigned_short; }; bool operator==( @@ -69,12 +102,6 @@ namespace e2d bool operator!=( const index_declaration& l, const index_declaration& r) noexcept; - bool operator==( - const index_declaration::index_info& l, - const index_declaration::index_info& r) noexcept; - bool operator!=( - const index_declaration::index_info& l, - const index_declaration::index_info& r) noexcept; // // vertex_declaration @@ -138,12 +165,12 @@ namespace e2d const attribute_info& attribute(std::size_t i) const noexcept; std::size_t attribute_count() const noexcept; - std::size_t vertex_size() const noexcept; + std::size_t bytes_per_vertex() const noexcept; private: constexpr static std::size_t max_attribute_count = 8; array attributes_; std::size_t attribute_count_ = 0; - std::size_t vertex_size_ = 0; + std::size_t bytes_per_vertex_ = 0; }; bool operator==( @@ -187,8 +214,9 @@ namespace e2d public: explicit texture(internal_state_uptr); ~texture() noexcept; + public: const v2u& size() const noexcept; - image_data_format format() const noexcept; + const pixel_declaration& decl() const noexcept; private: internal_state_uptr state_; }; @@ -211,10 +239,11 @@ namespace e2d public: explicit index_buffer(internal_state_uptr); ~index_buffer() noexcept; + public: void update(const buffer& indices, std::size_t offset) noexcept; - const index_declaration& decl() const noexcept; std::size_t buffer_size() const noexcept; std::size_t index_count() const noexcept; + const index_declaration& decl() const noexcept; private: internal_state_uptr state_; }; @@ -229,7 +258,7 @@ namespace e2d using internal_state_uptr = std::unique_ptr; const internal_state& state() const noexcept; public: - enum class usage : u8{ + enum class usage : u8 { static_draw, stream_draw, dynamic_draw @@ -237,10 +266,37 @@ namespace e2d public: explicit vertex_buffer(internal_state_uptr); ~vertex_buffer() noexcept; + public: void update(const buffer& vertices, std::size_t offset) noexcept; - const vertex_declaration& decl() const noexcept; std::size_t buffer_size() const noexcept; std::size_t vertex_count() const noexcept; + const vertex_declaration& decl() const noexcept; + private: + internal_state_uptr state_; + }; + + // + // render target + // + + class render_target final : noncopyable { + public: + class internal_state; + using internal_state_uptr = std::unique_ptr; + const internal_state& state() const noexcept; + public: + enum class type : u8 { + color = (1 << 0), + depth = (1 << 1), + color_and_depth = color | depth + }; + public: + explicit render_target(internal_state_uptr); + ~render_target() noexcept; + public: + const v2u& size() const noexcept; + const texture_ptr& color() const noexcept; + const texture_ptr& depth() const noexcept; private: internal_state_uptr state_; }; @@ -285,9 +341,9 @@ namespace e2d }; enum class culling_face : u8 { - back, - front, - back_and_front + back = (1 << 0), + front = (1 << 1), + back_and_front = back | front }; enum class blending_factor : u8 { @@ -647,6 +703,10 @@ namespace e2d texture_ptr create_texture( const input_stream_uptr& image_stream); + texture_ptr create_texture( + const v2u& size, + const pixel_declaration& decl); + index_buffer_ptr create_index_buffer( const buffer& indices, const index_declaration& decl, @@ -657,6 +717,10 @@ namespace e2d const vertex_declaration& decl, vertex_buffer::usage usage); + render_target_ptr create_render_target( + const v2u& size, + render_target::type type); + void draw( const material& mat, const geometry& geo); @@ -670,6 +734,7 @@ namespace e2d render& clear_stencil_buffer(u8 value) noexcept; render& clear_color_buffer(const color& value) noexcept; render& set_viewport(u32 x, u32 y, u32 w, u32 h) noexcept; + render& set_render_target(const render_target_ptr& rt) noexcept; private: class internal_state; std::unique_ptr state_; diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index c9060ec4..93e4139b 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -45,3 +45,4 @@ endfunction(add_e2d_sample) add_e2d_sample(00) add_e2d_sample(01) +add_e2d_sample(02) diff --git a/samples/sources/sample_00/sample_00.cpp b/samples/sources/sample_00/sample_00.cpp index a3fd0984..3f534df2 100644 --- a/samples/sources/sample_00/sample_00.cpp +++ b/samples/sources/sample_00/sample_00.cpp @@ -9,44 +9,46 @@ using namespace e2d; namespace { - const char* vs_source_cstr = - "#version 120 \n" - " \n" - "attribute vec3 a_position; \n" - "attribute vec2 a_uv; \n" - "attribute vec4 a_color; \n" - " \n" - "uniform float u_time; \n" - "uniform mat4 u_MVP; \n" - " \n" - "varying vec4 v_color; \n" - "varying vec2 v_uv; \n" - " \n" - "void main(){ \n" - " v_color = a_color; \n" - " v_uv = a_uv; \n" - " \n" - " float s = 0.7 + 0.3 * (cos(u_time * 0.003) + 1); \n" - " gl_Position = vec4(a_position * s, 1.0) * u_MVP; \n" - "} \n"; + const char* vs_source_cstr = R"glsl( + #version 120 - const char* fs_source_cstr = - "#version 120 \n" - " \n" - "uniform float u_time; \n" - "uniform sampler2D u_texture1; \n" - "uniform sampler2D u_texture2; \n" - "varying vec4 v_color; \n" - "varying vec2 v_uv; \n" - " \n" - "void main(){ \n" - " vec2 uv = vec2(v_uv.s, 1.0 - v_uv.t); \n" - " if ( u_time > 2000 ) { \n" - " gl_FragColor = v_color * texture2D(u_texture2, uv); \n" - " } else { \n" - " gl_FragColor = v_color * texture2D(u_texture1, uv); \n" - " } \n" - "} \n"; + attribute vec3 a_position; + attribute vec2 a_uv; + attribute vec4 a_color; + + uniform float u_time; + uniform mat4 u_MVP; + + varying vec4 v_color; + varying vec2 v_uv; + + void main(){ + v_color = a_color; + v_uv = a_uv; + + float s = 0.7 + 0.3 * (cos(u_time * 0.003) + 1); + gl_Position = vec4(a_position * s, 1.0) * u_MVP; + } + )glsl"; + + const char* fs_source_cstr = R"glsl( + #version 120 + + uniform float u_time; + uniform sampler2D u_texture1; + uniform sampler2D u_texture2; + varying vec4 v_color; + varying vec2 v_uv; + + void main(){ + vec2 uv = vec2(v_uv.s, 1.0 - v_uv.t); + if ( u_time > 2000 ) { + gl_FragColor = v_color * texture2D(u_texture2, uv); + } else { + gl_FragColor = v_color * texture2D(u_texture1, uv); + } + } + )glsl"; struct vertex1 { v3f position; @@ -124,7 +126,7 @@ int e2d_main() { const auto indices = generate_quad_indices(); const auto index_buffer = the().create_index_buffer( buffer(indices.data(), indices.size() * sizeof(indices[0])), - index_declaration(index_declaration::index_type::unsigned_byte), + index_declaration::index_type::unsigned_byte, index_buffer::usage::static_draw); const auto vertices1 = generate_quad_vertices(texture1->size()); diff --git a/samples/sources/sample_01/sample_01.cpp b/samples/sources/sample_01/sample_01.cpp index e3c647d1..959847f9 100644 --- a/samples/sources/sample_01/sample_01.cpp +++ b/samples/sources/sample_01/sample_01.cpp @@ -9,36 +9,38 @@ using namespace e2d; namespace { - const char* vs_source_cstr = - "#version 120 \n" - " \n" - "attribute vec3 a_position; \n" - "attribute vec2 a_uv; \n" - "attribute vec4 a_color; \n" - " \n" - "uniform mat4 u_MVP; \n" - " \n" - "varying vec4 v_color; \n" - "varying vec2 v_uv; \n" - " \n" - "void main(){ \n" - " v_color = a_color; \n" - " v_uv = a_uv; \n" - " \n" - " gl_Position = vec4(a_position, 1.0) * u_MVP; \n" - "} \n"; + const char* vs_source_cstr = R"glsl( + #version 120 - const char* fs_source_cstr = - "#version 120 \n" - " \n" - "uniform sampler2D u_texture; \n" - "varying vec4 v_color; \n" - "varying vec2 v_uv; \n" - " \n" - "void main(){ \n" - " vec2 uv = vec2(v_uv.s, 1.0 - v_uv.t); \n" - " gl_FragColor = v_color * texture2D(u_texture, uv); \n" - "} \n"; + attribute vec3 a_position; + attribute vec2 a_uv; + attribute vec4 a_color; + + uniform mat4 u_MVP; + + varying vec4 v_color; + varying vec2 v_uv; + + void main(){ + v_color = a_color; + v_uv = a_uv; + + gl_Position = vec4(a_position, 1.0) * u_MVP; + } + )glsl"; + + const char* fs_source_cstr = R"glsl( + #version 120 + + uniform sampler2D u_texture; + varying vec4 v_color; + varying vec2 v_uv; + + void main(){ + vec2 uv = vec2(v_uv.s, 1.0 - v_uv.t); + gl_FragColor = v_color * texture2D(u_texture, uv); + } + )glsl"; struct vertex1 { v3f position; @@ -182,7 +184,7 @@ int e2d_main() { const auto indices = generate_cube_indices(); const auto index_buffer = the().create_index_buffer( buffer(indices.data(), indices.size() * sizeof(indices[0])), - index_declaration(index_declaration::index_type::unsigned_byte), + index_declaration::index_type::unsigned_byte, index_buffer::usage::static_draw); const auto vertices1 = generate_cube_vertices(make_vec3(1.f)); diff --git a/samples/sources/sample_02/sample_02.cpp b/samples/sources/sample_02/sample_02.cpp new file mode 100644 index 00000000..0258837f --- /dev/null +++ b/samples/sources/sample_02/sample_02.cpp @@ -0,0 +1,243 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "../common.hpp" +using namespace e2d; + +namespace +{ + const char* vs_source_cstr = R"glsl( + #version 120 + + attribute vec3 a_position; + attribute vec2 a_uv; + + uniform mat4 u_MVP; + + varying vec2 v_uv; + + void main(){ + v_uv = a_uv; + + gl_Position = vec4(a_position, 1.0) * u_MVP; + } + )glsl"; + + const char* fs_source_cstr = R"glsl( + #version 120 + + uniform sampler2D u_texture; + varying vec2 v_uv; + + void main(){ + vec2 uv = vec2(v_uv.s, 1.0 - v_uv.t); + gl_FragColor = texture2D(u_texture, uv); + } + )glsl"; + + struct vertex { + v3f position; + v2hu uv; + static vertex_declaration decl() noexcept { + return vertex_declaration() + .add_attribute("a_position") + .add_attribute("a_uv"); + } + }; + + array generate_cube_indices() noexcept { + return { + 0, 1, 2, + 2, 3, 0, + + 4, 5, 6, + 6, 7, 4, + + 8, 9, 10, + 10, 11, 8, + + 12, 13, 14, + 14, 15, 12, + + 16, 17, 18, + 18, 19, 16, + + 20, 21, 22, + 22, 23, 20}; + } + + array generate_cube_vertices(const v3f& size) noexcept { + f32 x = size.x * 0.5f; + f32 y = size.y * 0.5f; + f32 z = size.z * 0.5f; + return { + vertex{{-x, -y, -z}, {0, 1}}, + vertex{{ x, -y, -z}, {1, 1}}, + vertex{{ x, y, -z}, {1, 0}}, + vertex{{-x, y, -z}, {0, 0}}, + + vertex{{-x, -y, z}, {0, 1}}, + vertex{{ x, -y, z}, {1, 1}}, + vertex{{ x, -y, -z}, {1, 0}}, + vertex{{-x, -y, -z}, {0, 0}}, + + vertex{{ x, -y, z}, {0, 1}}, + vertex{{-x, -y, z}, {1, 1}}, + vertex{{-x, y, z}, {1, 0}}, + vertex{{ x, y, z}, {0, 0}}, + + vertex{{-x, y, -z}, {0, 1}}, + vertex{{ x, y, -z}, {1, 1}}, + vertex{{ x, y, z}, {1, 0}}, + vertex{{-x, y, z}, {0, 0}}, + + vertex{{ x, -y, -z}, {0, 1}}, + vertex{{ x, -y, z}, {1, 1}}, + vertex{{ x, y, z}, {1, 0}}, + vertex{{ x, y, -z}, {0, 0}}, + + vertex{{-x, -y, z}, {0, 1}}, + vertex{{-x, -y, -z}, {1, 1}}, + vertex{{-x, y, -z}, {1, 0}}, + vertex{{-x, y, z}, {0, 0}}}; + } +} + +int e2d_main() { + { + modules::initialize() + .register_scheme("file"); + modules::initialize() + .register_sink(); + modules::initialize(); + modules::initialize(v2u{640, 480}, "Enduro2D", false) + .register_event_listener(the()); + modules::initialize(the(), the()); + } + { + str resources; + filesystem::extract_predef_path(resources, filesystem::predef_path::resources); + the().register_scheme_alias( + "resources", + url{"file", resources}); + the().register_scheme( + "piratepack", + the().open(url("resources://bin/kenney_piratepack.zip"))); + the().register_scheme_alias("ships", url("piratepack://PNG/Retina/Ships")); + } + + auto texture = the().create_texture( + the().open(url("ships://ship (3).png"))); + + const auto shader = the().create_shader( + make_memory_stream(buffer(vs_source_cstr, std::strlen(vs_source_cstr))), + make_memory_stream(buffer(fs_source_cstr, std::strlen(fs_source_cstr)))); + + const auto indices = generate_cube_indices(); + const auto index_buffer = the().create_index_buffer( + buffer(indices.data(), indices.size() * sizeof(indices[0])), + index_declaration::index_type::unsigned_byte, + index_buffer::usage::static_draw); + + const auto vertices = generate_cube_vertices(make_vec3(1.f)); + const auto vertex_buffer = the().create_vertex_buffer( + buffer(vertices.data(), vertices.size() * sizeof(vertices[0])), + vertex::decl(), + vertex_buffer::usage::static_draw); + + if ( !texture || !shader || !index_buffer || !vertex_buffer ) { + return 1; + } + + auto material = render::material() + .add_pass(render::pass_state() + .states(render::state_block() + .capabilities(render::capabilities_state() + .blending(true) + .culling(true) + .depth_test(true)) + .blending(render::blending_state() + .src_factor(render::blending_factor::src_alpha) + .dst_factor(render::blending_factor::one_minus_src_alpha)) + .culling(render::culling_state() + .mode(render::culling_mode::ccw) + .face(render::culling_face::back))) + .shader(shader)) + .properties(render::property_block() + .sampler("u_texture", render::sampler_state() + .texture(texture) + .min_filter(render::sampler_min_filter::linear) + .mag_filter(render::sampler_mag_filter::linear))); + + auto geometry = render::geometry() + .indices(index_buffer) + .add_vertices(vertex_buffer); + + const auto begin_game_time = time::now_ms(); + const auto framebuffer_size = the().real_size().cast_to(); + const auto projection = math::make_perspective_lh_matrix4( + make_deg(45.f), + framebuffer_size.x / framebuffer_size.y, + 0.1f, + 100.f); + + const auto rt = the().create_render_target( + the().real_size() / 10u, + render_target::type::color_and_depth); + + const keyboard& k = the().keyboard(); + while ( !the().should_close() && !k.is_key_just_released(keyboard_key::escape) ) { + const auto game_time = (time::now_ms() - begin_game_time).cast_to().value; + + const auto MVP = + math::make_rotation_matrix4(make_rad(game_time) * 0.001f, 1.f, 0.f, 0.f) * + math::make_rotation_matrix4(make_rad(game_time) * 0.001f, 0.f, 1.f, 0.f) * + math::make_rotation_matrix4(make_rad(game_time) * 0.001f, 0.f, 0.f, 1.f) * + math::make_translation_matrix4(0.f, 0.f, 0.f) * + math::make_loot_at_lh_matrix4({0.f,0.f,-2.f}, v3f::zero(), v3f::unit_y()) * + projection; + + material.properties() + .property("u_time", game_time) + .property("u_MVP", MVP); + + material.properties() + .sampler("u_texture", render::sampler_state() + .texture(texture) + .min_filter(render::sampler_min_filter::linear) + .mag_filter(render::sampler_mag_filter::linear)); + + the() + .set_render_target(rt) + .set_viewport(0, 0, rt->size().x, rt->size().y) + .clear_depth_buffer(1.f) + .clear_stencil_buffer(0) + .clear_color_buffer({0.f, 0.4f, 0.f, 1.f}) + .draw(material, geometry); + + material.properties() + .sampler("u_texture", render::sampler_state() + .texture(rt->color()) + .min_filter(render::sampler_min_filter::linear) + .mag_filter(render::sampler_mag_filter::linear)); + + the() + .set_render_target(nullptr) + .set_viewport(0, 0, the().real_size().x, the().real_size().y) + .clear_depth_buffer(1.f) + .clear_stencil_buffer(0) + .clear_color_buffer({1.f, 0.4f, 0.f, 1.f}) + .draw(material, geometry); + + std::this_thread::sleep_for(time::to_chrono(make_milliseconds(10))); + + the().swap_buffers(true); + the().frame_tick(); + window::poll_events(); + } + + return 0; +} diff --git a/sources/enduro2d/core/render.cpp b/sources/enduro2d/core/render.cpp index a561f8ba..bf775f02 100644 --- a/sources/enduro2d/core/render.cpp +++ b/sources/enduro2d/core/render.cpp @@ -10,6 +10,37 @@ namespace { using namespace e2d; + struct pixel_type_description { + u32 minimal_size; + u32 bits_per_pixel; + pixel_declaration::pixel_type type; + bool compressed; + bool must_be_square; + bool must_be_power_of_two; + }; + + const pixel_type_description pixel_type_descriptions[] = { + {1, 32, pixel_declaration::pixel_type::rgba8, false, false, false}, + {1, 32, pixel_declaration::pixel_type::depth24_stencil8, false, false, false}, + + {4, 4, pixel_declaration::pixel_type::dxt1, true, false, true}, + {4, 8, pixel_declaration::pixel_type::dxt3, true, false, true}, + {4, 8, pixel_declaration::pixel_type::dxt5, true, false, true}, + + {8, 2, pixel_declaration::pixel_type::rgb_pvrtc2, true, true, true}, + {8, 4, pixel_declaration::pixel_type::rgb_pvrtc4, true, true, true}, + {8, 2, pixel_declaration::pixel_type::rgba_pvrtc2, true, true, true}, + {8, 4, pixel_declaration::pixel_type::rgba_pvrtc4, true, true, true} + }; + + const pixel_type_description& get_pixel_type_description(pixel_declaration::pixel_type type) noexcept { + const std::size_t index = math::numeric_cast(math::enum_to_number(type)); + E2D_ASSERT(index < E2D_COUNTOF(pixel_type_descriptions)); + const pixel_type_description& desc = pixel_type_descriptions[index]; + E2D_ASSERT(desc.type == type); + return desc; + } + std::size_t index_element_size(index_declaration::index_type it) noexcept { #define DEFINE_CASE(x,y) case index_declaration::index_type::x: return y; switch ( it ) { @@ -41,50 +72,55 @@ namespace namespace e2d { // - // index_declaration::index_info + // pixel_declaration // - index_declaration::index_info::index_info(index_type ntype) noexcept - : type(ntype) {} + pixel_declaration::pixel_declaration(pixel_type type) noexcept + : type_(type) {} + + pixel_declaration::pixel_type pixel_declaration::type() const noexcept { + return type_; + } + + bool pixel_declaration::is_compressed() const noexcept { + return get_pixel_type_description(type_).compressed; + } + + std::size_t pixel_declaration::bits_per_pixel() const noexcept { + return get_pixel_type_description(type_).bits_per_pixel; + } + + bool operator==(const pixel_declaration& l, const pixel_declaration& r) noexcept { + return l.type() == r.type(); + } + + bool operator!=(const pixel_declaration& l, const pixel_declaration& r) noexcept { + return !(l == r); + } // // index_declaration // index_declaration::index_declaration(index_type type) noexcept - : index_(type) {} + : type_(type) {} - const index_declaration::index_info& index_declaration::index() const noexcept { - return index_; + index_declaration::index_type index_declaration::type() const noexcept { + return type_; } - std::size_t index_declaration::index_size() const noexcept { - return index_element_size(index_.type); + std::size_t index_declaration::bytes_per_index() const noexcept { + return index_element_size(type_); } bool operator==(const index_declaration& l, const index_declaration& r) noexcept { - return l.index_size() == r.index_size() - && l.index() == r.index(); + return l.type() == r.type(); } bool operator!=(const index_declaration& l, const index_declaration& r) noexcept { return !(l == r); } - bool operator==( - const index_declaration::index_info& l, - const index_declaration::index_info& r) noexcept - { - return l.type == r.type; - } - - bool operator!=( - const index_declaration::index_info& l, - const index_declaration::index_info& r) noexcept - { - return !(l == r); - } - // // vertex_declaration::attribute_info // @@ -118,7 +154,7 @@ namespace e2d } vertex_declaration& vertex_declaration::skip_bytes(std::size_t bytes) noexcept { - vertex_size_ += bytes; + bytes_per_vertex_ += bytes; return *this; } @@ -130,7 +166,7 @@ namespace e2d bool normalized) noexcept { E2D_ASSERT(attribute_count_ < attributes_.size()); - const std::size_t stride = vertex_size_; + const std::size_t stride = bytes_per_vertex_; attributes_[attribute_count_] = attribute_info( stride, name, @@ -138,7 +174,7 @@ namespace e2d columns, type, normalized); - vertex_size_ += attribute_element_size(type) * rows * columns; + bytes_per_vertex_ += attribute_element_size(type) * rows * columns; ++attribute_count_; return *this; } @@ -152,12 +188,12 @@ namespace e2d return attribute_count_; } - std::size_t vertex_declaration::vertex_size() const noexcept { - return vertex_size_; + std::size_t vertex_declaration::bytes_per_vertex() const noexcept { + return bytes_per_vertex_; } bool operator==(const vertex_declaration& l, const vertex_declaration& r) noexcept { - if ( l.vertex_size() != r.vertex_size() ) { + if ( l.bytes_per_vertex() != r.bytes_per_vertex() ) { return false; } if ( l.attribute_count() != r.attribute_count() ) { @@ -695,6 +731,11 @@ namespace e2d return pass_count_; } + render::material& render::material::properties(const property_block& properties) noexcept { + properties_ = properties; + return *this; + } + render::pass_state& render::material::pass(std::size_t index) noexcept { return passes_[index]; } diff --git a/sources/enduro2d/core/render_impl/render_none.cpp b/sources/enduro2d/core/render_impl/render_none.cpp index 6fa5c1b2..2664130b 100644 --- a/sources/enduro2d/core/render_impl/render_none.cpp +++ b/sources/enduro2d/core/render_impl/render_none.cpp @@ -50,6 +50,16 @@ namespace e2d ~internal_state() noexcept = default; }; + // + // render_target::internal_state + // + + class render_target::internal_state final : private e2d::noncopyable { + public: + internal_state() noexcept = default; + ~internal_state() noexcept = default; + }; + // // render::internal_state // @@ -94,8 +104,9 @@ namespace e2d return size; } - image_data_format texture::format() const noexcept { - return image_data_format::rgba8; + const pixel_declaration& texture::decl() const noexcept { + static pixel_declaration decl; + return decl; } // @@ -122,6 +133,29 @@ namespace e2d : state_(std::move(state)) {} vertex_buffer::~vertex_buffer() noexcept = default; + // + // render_target + // + + render_target::render_target(internal_state_uptr state) + : state_(std::move(state)) {} + render_target::~render_target() noexcept = default; + + const v2u& render_target::size() const noexcept { + static v2u size; + return size; + } + + const texture_ptr& render_target::color() const noexcept { + static texture_ptr color; + return color; + } + + const texture_ptr& render_target::depth() const noexcept { + static texture_ptr depth; + return depth; + } + // // render // @@ -148,6 +182,11 @@ namespace e2d return nullptr; } + texture_ptr render::create_texture(const v2u& size, const pixel_declaration& decl) { + E2D_UNUSED(size, decl); + return nullptr; + } + index_buffer_ptr render::create_index_buffer( const buffer& indices, const index_declaration& decl, @@ -166,6 +205,11 @@ namespace e2d return nullptr; } + render_target_ptr render::create_render_target(const v2u& size, render_target::type type) { + E2D_UNUSED(size, type); + return nullptr; + } + void render::draw( const material& mat, const geometry& geo) @@ -200,6 +244,11 @@ namespace e2d E2D_UNUSED(x, y, w, h); return *this; } + + render& render::set_render_target(const render_target_ptr& rt) noexcept { + E2D_UNUSED(rt); + return *this; + } } #endif diff --git a/sources/enduro2d/core/render_impl/render_opengl.cpp b/sources/enduro2d/core/render_impl/render_opengl.cpp index cace081c..795df556 100644 --- a/sources/enduro2d/core/render_impl/render_opengl.cpp +++ b/sources/enduro2d/core/render_impl/render_opengl.cpp @@ -192,7 +192,7 @@ namespace math::numeric_cast(vai.columns), convert_attribute_type(vai.type), vai.normalized ? GL_TRUE : GL_FALSE, - math::numeric_cast(decl.vertex_size()), + math::numeric_cast(decl.bytes_per_vertex()), reinterpret_cast(vai.stride + row * vai.row_size()))); } }); @@ -225,7 +225,7 @@ namespace GL_CHECK_CODE(debug, glDrawElements( convert_topology(tp), math::numeric_cast(ib->index_count()), - convert_index_type(decl.index().type), + convert_index_type(decl.type()), nullptr)); }); } @@ -321,8 +321,8 @@ namespace e2d return state_->size(); } - image_data_format texture::format() const noexcept { - return state_->format(); + const pixel_declaration& texture::decl() const noexcept { + return state_->decl(); } // @@ -340,9 +340,9 @@ namespace e2d index_buffer::~index_buffer() noexcept = default; void index_buffer::update(const buffer& indices, std::size_t offset) noexcept { - const std::size_t buffer_offset = offset * state_->decl().index_size(); + const std::size_t buffer_offset = offset * state_->decl().bytes_per_index(); E2D_ASSERT(indices.size() + buffer_offset <= state_->size()); - E2D_ASSERT(indices.size() % state_->decl().index_size() == 0); + E2D_ASSERT(indices.size() % state_->decl().bytes_per_index() == 0); opengl::with_gl_bind_buffer(state_->dbg(), state_->id(), [this, &indices, &buffer_offset]() noexcept { GL_CHECK_CODE(state_->dbg(), glBufferSubData( @@ -353,17 +353,17 @@ namespace e2d }); } - const index_declaration& index_buffer::decl() const noexcept { - return state_->decl(); - } - std::size_t index_buffer::buffer_size() const noexcept { return state_->size(); } std::size_t index_buffer::index_count() const noexcept { - E2D_ASSERT(state_->size() % state_->decl().index_size() == 0); - return state_->size() / state_->decl().index_size(); + E2D_ASSERT(state_->size() % state_->decl().bytes_per_index() == 0); + return state_->size() / state_->decl().bytes_per_index(); + } + + const index_declaration& index_buffer::decl() const noexcept { + return state_->decl(); } // @@ -381,9 +381,9 @@ namespace e2d vertex_buffer::~vertex_buffer() noexcept = default; void vertex_buffer::update(const buffer& vertices, std::size_t offset) noexcept { - const std::size_t buffer_offset = offset * state_->decl().vertex_size(); + const std::size_t buffer_offset = offset * state_->decl().bytes_per_vertex(); E2D_ASSERT(vertices.size() + buffer_offset <= state_->size()); - E2D_ASSERT(vertices.size() % state_->decl().vertex_size() == 0); + E2D_ASSERT(vertices.size() % state_->decl().bytes_per_vertex() == 0); opengl::with_gl_bind_buffer(state_->dbg(), state_->id(), [this, &vertices, &buffer_offset]() noexcept { GL_CHECK_CODE(state_->dbg(), glBufferSubData( @@ -394,17 +394,43 @@ namespace e2d }); } - const vertex_declaration& vertex_buffer::decl() const noexcept { - return state_->decl(); - } - std::size_t vertex_buffer::buffer_size() const noexcept { return state_->size(); } std::size_t vertex_buffer::vertex_count() const noexcept { - E2D_ASSERT(state_->size() % state_->decl().vertex_size() == 0); - return state_->size() / state_->decl().vertex_size(); + E2D_ASSERT(state_->size() % state_->decl().bytes_per_vertex() == 0); + return state_->size() / state_->decl().bytes_per_vertex(); + } + + const vertex_declaration& vertex_buffer::decl() const noexcept { + return state_->decl(); + } + + // + // render_target + // + + const render_target::internal_state& render_target::state() const noexcept { + return *state_; + } + + render_target::render_target(internal_state_uptr state) + : state_(std::move(state)) { + E2D_ASSERT(state_); + } + render_target::~render_target() noexcept = default; + + const v2u& render_target::size() const noexcept { + return state_->size(); + } + + const texture_ptr& render_target::color() const noexcept { + return state_->color(); + } + + const texture_ptr& render_target::depth() const noexcept { + return state_->depth(); } // @@ -448,17 +474,54 @@ namespace e2d state_->dbg(), std::move(ps))); } - texture_ptr render::create_texture(const image& image) { - gl_texture_id id = gl_compile_texture(state_->dbg(), image); + texture_ptr render::create_texture( + const image& image) + { + gl_texture_id id = gl_texture_id::create( + state_->dbg(), GL_TEXTURE_2D); if ( id.empty() ) { + state_->dbg().error("RENDER: Failed to create texture: %0", + "failed to create texture id"); return nullptr; } + const pixel_declaration decl = + convert_image_data_format_to_pixel_declaration(image.format()); + with_gl_bind_texture(state_->dbg(), id, [this, &id, &image, &decl]() noexcept { + if ( decl.is_compressed() ) { + GL_CHECK_CODE(state_->dbg(), glCompressedTexImage2D( + id.target(), + 0, + convert_pixel_type_to_compressed_format(decl.type()), + math::numeric_cast(image.size().x), + math::numeric_cast(image.size().y), + 0, + math::numeric_cast(image.data().size()), + image.data().data())); + } else { + GL_CHECK_CODE(state_->dbg(), glTexImage2D( + id.target(), + 0, + convert_pixel_type_to_internal_format(decl.type()), + math::numeric_cast(image.size().x), + math::numeric_cast(image.size().y), + 0, + convert_image_data_format_to_external_format(image.format()), + convert_image_data_format_to_external_data_type(image.format()), + image.data().data())); + } + #if !defined(GL_ES_VERSION_2_0) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + #endif + }); return std::make_shared( std::make_unique( - state_->dbg(), std::move(id), image.size(), image.format())); + state_->dbg(), std::move(id), image.size(), decl)); } - texture_ptr render::create_texture(const input_stream_uptr& image_stream) { + texture_ptr render::create_texture( + const input_stream_uptr& image_stream) + { image image; if ( !images::try_load_image(image, image_stream) ) { return nullptr; @@ -466,13 +529,59 @@ namespace e2d return create_texture(image); } + texture_ptr render::create_texture( + const v2u& size, + const pixel_declaration& decl) + { + gl_texture_id id = gl_texture_id::create( + state_->dbg(), GL_TEXTURE_2D); + if ( id.empty() ) { + state_->dbg().error("RENDER: Failed to create texture: %0", + "failed to create texture id"); + return nullptr; + } + with_gl_bind_texture(state_->dbg(), id, [this, &id, &size, &decl]() noexcept { + if ( decl.is_compressed() ) { + buffer empty_data(decl.bits_per_pixel() * size.x * size.y / 8); + GL_CHECK_CODE(state_->dbg(), glCompressedTexImage2D( + id.target(), + 0, + convert_pixel_type_to_compressed_format(decl.type()), + math::numeric_cast(size.x), + math::numeric_cast(size.y), + 0, + math::numeric_cast(empty_data.size()), + empty_data.data())); + } else { + GL_CHECK_CODE(state_->dbg(), glTexImage2D( + id.target(), + 0, + convert_pixel_type_to_internal_format(decl.type()), + math::numeric_cast(size.x), + math::numeric_cast(size.y), + 0, + convert_pixel_type_to_external_format(decl.type()), + convert_pixel_type_to_external_data_type(decl.type()), + nullptr)); + } + #if !defined(GL_ES_VERSION_2_0) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + #endif + }); + return std::make_shared( + std::make_unique( + state_->dbg(), std::move(id), size, decl)); + } + index_buffer_ptr render::create_index_buffer( const buffer& indices, const index_declaration& decl, index_buffer::usage usage) { - E2D_ASSERT(indices.size() % decl.index_size() == 0); - gl_buffer_id id = gl_compile_index_buffer(state_->dbg(), indices, usage); + E2D_ASSERT(indices.size() % decl.bytes_per_index() == 0); + gl_buffer_id id = gl_compile_index_buffer( + state_->dbg(), indices, usage); if ( id.empty() ) { return nullptr; } @@ -486,8 +595,9 @@ namespace e2d const vertex_declaration& decl, vertex_buffer::usage usage) { - E2D_ASSERT(vertices.size() % decl.vertex_size() == 0); - gl_buffer_id id = gl_compile_vertex_buffer(state_->dbg(), vertices, usage); + E2D_ASSERT(vertices.size() % decl.bytes_per_vertex() == 0); + gl_buffer_id id = gl_compile_vertex_buffer( + state_->dbg(), vertices, usage); if ( id.empty() ) { return nullptr; } @@ -496,6 +606,83 @@ namespace e2d state_->dbg(), std::move(id), vertices.size(), decl)); } + render_target_ptr render::create_render_target( + const v2u& size, + render_target::type type) + { + gl_framebuffer_id id = gl_framebuffer_id::create( + state_->dbg(), GL_FRAMEBUFFER); + if ( id.empty() ) { + state_->dbg().error("RENDER: Failed to create framebuffer: %0", + "failed to create framebuffer id"); + return nullptr; + } + + bool need_color = math::enum_to_number(type) & math::enum_to_number(render_target::type::color); + bool need_depth = math::enum_to_number(type) & math::enum_to_number(render_target::type::depth); + + texture_ptr color; + texture_ptr depth; + + gl_renderbuffer_id color_rb(state_->dbg()); + gl_renderbuffer_id depth_rb(state_->dbg()); + + if ( need_color ) { + color = create_texture(size, pixel_declaration::pixel_type::rgba8); + if ( !color ) { + state_->dbg().error("RENDER: Failed to create framebuffer: %0", + "failed to create color texture"); + return nullptr; + } + gl_attach_texture(state_->dbg(), id, color->state().id(), GL_COLOR_ATTACHMENT0); + } else { + color_rb = gl_compile_renderbuffer(state_->dbg(), size, GL_RGBA8); + if ( color_rb.empty() ) { + state_->dbg().error("RENDER: Failed to create framebuffer: %0", + "failed to create color renderbuffer"); + return nullptr; + } + gl_attach_renderbuffer(state_->dbg(), id, color_rb, GL_COLOR_ATTACHMENT0); + } + + if ( need_depth ) { + depth = create_texture(size, pixel_declaration::pixel_type::depth24_stencil8); + if ( !depth ) { + state_->dbg().error("RENDER: Failed to create framebuffer: %0", + "failed to create depth texture"); + return nullptr; + } + gl_attach_texture(state_->dbg(), id, depth->state().id(), GL_DEPTH_ATTACHMENT); + gl_attach_texture(state_->dbg(), id, depth->state().id(), GL_STENCIL_ATTACHMENT); + } else { + depth_rb = gl_compile_renderbuffer(state_->dbg(), size, GL_DEPTH24_STENCIL8); + if ( depth_rb.empty() ) { + state_->dbg().error("RENDER: Failed to create framebuffer: %0", + "failed to create depth renderbuffer"); + return nullptr; + } + gl_attach_renderbuffer(state_->dbg(), id, depth_rb, GL_DEPTH_ATTACHMENT); + gl_attach_renderbuffer(state_->dbg(), id, depth_rb, GL_STENCIL_ATTACHMENT); + } + + GLenum fb_status = GL_FRAMEBUFFER_COMPLETE; + if ( !gl_check_framebuffer(state_->dbg(), id, &fb_status) ) { + state_->dbg().error("RENDER: Failed to create framebuffer: %0", + gl_framebuffer_status_to_cstr(fb_status)); + return nullptr; + } + + return std::make_shared( + std::make_unique( + state_->dbg(), + std::move(id), + size, + std::move(color), + std::move(depth), + std::move(color_rb), + std::move(depth_rb))); + } + void render::draw( const material& mat, const geometry& geo) @@ -524,26 +711,35 @@ namespace e2d } render& render::clear_depth_buffer(f32 value) noexcept { - GL_CHECK_CODE(state_->dbg(), glClearDepth( - math::numeric_cast(math::saturate(value)))); - GL_CHECK_CODE(state_->dbg(), glClear(GL_DEPTH_BUFFER_BIT)); + const render_target_ptr& rt = state_->render_target(); + if ( !rt || rt->state().depth() || !rt->state().depth_rb().empty() ) { + GL_CHECK_CODE(state_->dbg(), glClearDepth( + math::numeric_cast(math::saturate(value)))); + GL_CHECK_CODE(state_->dbg(), glClear(GL_DEPTH_BUFFER_BIT)); + } return *this; } render& render::clear_stencil_buffer(u8 value) noexcept { - GL_CHECK_CODE(state_->dbg(), glClearStencil( - math::numeric_cast(value))); - GL_CHECK_CODE(state_->dbg(), glClear(GL_STENCIL_BUFFER_BIT)); + const render_target_ptr& rt = state_->render_target(); + if ( !rt || rt->state().depth() || !rt->state().depth_rb().empty() ) { + GL_CHECK_CODE(state_->dbg(), glClearStencil( + math::numeric_cast(value))); + GL_CHECK_CODE(state_->dbg(), glClear(GL_STENCIL_BUFFER_BIT)); + } return *this; } render& render::clear_color_buffer(const color& value) noexcept { - GL_CHECK_CODE(state_->dbg(), glClearColor( - math::numeric_cast(math::saturate(value.r)), - math::numeric_cast(math::saturate(value.g)), - math::numeric_cast(math::saturate(value.b)), - math::numeric_cast(math::saturate(value.a)))); - GL_CHECK_CODE(state_->dbg(), glClear(GL_COLOR_BUFFER_BIT)); + const render_target_ptr& rt = state_->render_target(); + if ( !rt || rt->state().color() || !rt->state().color_rb().empty() ) { + GL_CHECK_CODE(state_->dbg(), glClearColor( + math::numeric_cast(math::saturate(value.r)), + math::numeric_cast(math::saturate(value.g)), + math::numeric_cast(math::saturate(value.b)), + math::numeric_cast(math::saturate(value.a)))); + GL_CHECK_CODE(state_->dbg(), glClear(GL_COLOR_BUFFER_BIT)); + } return *this; } @@ -555,6 +751,11 @@ namespace e2d math::numeric_cast(h))); return *this; } + + render& render::set_render_target(const render_target_ptr& rt) noexcept { + state_->set_render_target(rt); + return *this; + } } #endif diff --git a/sources/enduro2d/core/render_impl/render_opengl_base.cpp b/sources/enduro2d/core/render_impl/render_opengl_base.cpp index 0d4f8413..071c45bf 100644 --- a/sources/enduro2d/core/render_impl/render_opengl_base.cpp +++ b/sources/enduro2d/core/render_impl/render_opengl_base.cpp @@ -38,6 +38,11 @@ namespace *loc = glGetAttribLocation(program, name); } + void gl_check_framebuffer_status(GLenum target, GLenum* res) noexcept { + E2D_ASSERT(res); + *res = glCheckFramebufferStatus(target); + } + bool process_shader_compilation_result(debug& debug, GLuint shader) noexcept { E2D_ASSERT(glIsShader(shader)); GLint success = GL_FALSE; @@ -169,12 +174,10 @@ namespace e2d { namespace opengl } GLuint gl_buffer_id::operator*() const noexcept { - E2D_ASSERT(!empty()); return id_; } GLenum gl_buffer_id::target() const noexcept { - E2D_ASSERT(!empty()); return target_; } @@ -247,12 +250,10 @@ namespace e2d { namespace opengl } GLuint gl_shader_id::operator*() const noexcept { - E2D_ASSERT(!empty()); return id_; } GLenum gl_shader_id::type() const noexcept { - E2D_ASSERT(!empty()); return type_; } @@ -325,7 +326,6 @@ namespace e2d { namespace opengl } GLuint gl_program_id::operator*() const noexcept { - E2D_ASSERT(!empty()); return id_; } @@ -405,12 +405,10 @@ namespace e2d { namespace opengl } GLuint gl_texture_id::operator*() const noexcept { - E2D_ASSERT(!empty()); return id_; } GLenum gl_texture_id::target() const noexcept { - E2D_ASSERT(!empty()); return target_; } @@ -489,12 +487,10 @@ namespace e2d { namespace opengl } GLuint gl_framebuffer_id::operator*() const noexcept { - E2D_ASSERT(!empty()); return id_; } GLenum gl_framebuffer_id::target() const noexcept { - E2D_ASSERT(!empty()); return target_; } @@ -573,12 +569,10 @@ namespace e2d { namespace opengl } GLuint gl_renderbuffer_id::operator*() const noexcept { - E2D_ASSERT(!empty()); return id_; } GLenum gl_renderbuffer_id::target() const noexcept { - E2D_ASSERT(!empty()); return target_; } @@ -693,44 +687,109 @@ namespace e2d { namespace opengl namespace e2d { namespace opengl { - GLint convert_format_to_internal_format(image_data_format idf) noexcept { - #define DEFINE_CASE(x,y) case image_data_format::x: return y; - switch ( idf ) { - DEFINE_CASE(g8, GL_ALPHA); + GLenum convert_image_data_format_to_external_format(image_data_format f) noexcept { + #define DEFINE_CASE(x,y) case image_data_format::x: return y + switch ( f ) { + DEFINE_CASE(g8, GL_LUMINANCE); DEFINE_CASE(ga8, GL_LUMINANCE_ALPHA); DEFINE_CASE(rgb8, GL_RGB); DEFINE_CASE(rgba8, GL_RGBA); default: E2D_ASSERT_MSG(false, "unexpected image data format"); - return 0; + return GL_RGBA; } #undef DEFINE_CASE } - GLenum convert_format_to_external_format(image_data_format idf) noexcept { - #define DEFINE_CASE(x,y) case image_data_format::x: return y; - switch ( idf ) { - DEFINE_CASE(g8, GL_ALPHA); - DEFINE_CASE(ga8, GL_LUMINANCE_ALPHA); - DEFINE_CASE(rgb8, GL_RGB); - DEFINE_CASE(rgba8, GL_RGBA); - default: - E2D_ASSERT_MSG(false, "unexpected image data format"); - return 0; - } - #undef DEFINE_CASE - } - - GLenum convert_format_to_external_data_type(image_data_format idf) noexcept { - #define DEFINE_CASE(x,y) case image_data_format::x: return y; - switch ( idf ) { + GLenum convert_image_data_format_to_external_data_type(image_data_format f) noexcept { + #define DEFINE_CASE(x,y) case image_data_format::x: return y + switch ( f ) { DEFINE_CASE(g8, GL_UNSIGNED_BYTE); DEFINE_CASE(ga8, GL_UNSIGNED_BYTE); DEFINE_CASE(rgb8, GL_UNSIGNED_BYTE); DEFINE_CASE(rgba8, GL_UNSIGNED_BYTE); default: E2D_ASSERT_MSG(false, "unexpected image data format"); - return 0; + return GL_UNSIGNED_BYTE; + } + #undef DEFINE_CASE + } + + GLenum convert_pixel_type_to_external_format(pixel_declaration::pixel_type f) noexcept { + #define DEFINE_CASE(x,y) case pixel_declaration::pixel_type::x: return y + switch ( f ) { + DEFINE_CASE(rgba8, GL_RGBA); + DEFINE_CASE(depth24_stencil8, GL_DEPTH_STENCIL); + default: + E2D_ASSERT_MSG(false, "unexpected pixel type"); + return GL_RGBA; + } + #undef DEFINE_CASE + } + + GLenum convert_pixel_type_to_external_data_type(pixel_declaration::pixel_type f) noexcept { + #define DEFINE_CASE(x,y) case pixel_declaration::pixel_type::x: return y + switch ( f ) { + DEFINE_CASE(rgba8, GL_UNSIGNED_BYTE); + DEFINE_CASE(depth24_stencil8, GL_UNSIGNED_INT_24_8); + default: + E2D_ASSERT_MSG(false, "unexpected pixel type"); + return GL_UNSIGNED_BYTE; + } + #undef DEFINE_CASE + } + + GLint convert_pixel_type_to_internal_format(pixel_declaration::pixel_type f) noexcept { + #define DEFINE_CASE(x,y) case pixel_declaration::pixel_type::x: return y + switch ( f ) { + DEFINE_CASE(rgba8, GL_RGBA); + DEFINE_CASE(depth24_stencil8, GL_DEPTH24_STENCIL8); + default: + E2D_ASSERT_MSG(false, "unexpected pixel type"); + return GL_RGBA; + } + #undef DEFINE_CASE + } + + GLenum convert_pixel_type_to_compressed_format(pixel_declaration::pixel_type f) noexcept { + #define DEFINE_CASE(x,y) case pixel_declaration::pixel_type::x: return y + switch ( f ) { + DEFINE_CASE(dxt1, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT); + DEFINE_CASE(dxt3, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT); + DEFINE_CASE(dxt5, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT); + + DEFINE_CASE(rgb_pvrtc2, GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG); + DEFINE_CASE(rgb_pvrtc4, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG); + + DEFINE_CASE(rgba_pvrtc2, GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG); + DEFINE_CASE(rgba_pvrtc4, GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG); + default: + E2D_ASSERT_MSG(false, "unexpected pixel type"); + return GL_RGBA; + } + #undef DEFINE_CASE + } + + pixel_declaration convert_image_data_format_to_pixel_declaration(image_data_format f) noexcept { + #define DEFINE_CASE(x,y) case image_data_format::x: return pixel_declaration(pixel_declaration::pixel_type::y) + switch ( f ) { + DEFINE_CASE(g8, rgba8); + DEFINE_CASE(ga8, rgba8); + DEFINE_CASE(rgb8, rgba8); + DEFINE_CASE(rgba8, rgba8); + + DEFINE_CASE(dxt1, dxt1); + DEFINE_CASE(dxt3, dxt3); + DEFINE_CASE(dxt5, dxt5); + + DEFINE_CASE(rgb_pvrtc2, rgb_pvrtc2); + DEFINE_CASE(rgb_pvrtc4, rgb_pvrtc4); + + DEFINE_CASE(rgba_pvrtc2, rgba_pvrtc2); + DEFINE_CASE(rgba_pvrtc4, rgba_pvrtc4); + default: + E2D_ASSERT_MSG(false, "unexpected image data format"); + return pixel_declaration(pixel_declaration::pixel_type::rgba8); } #undef DEFINE_CASE } @@ -1071,6 +1130,19 @@ namespace e2d { namespace opengl #undef DEFINE_CASE } + const char* gl_framebuffer_status_to_cstr(GLenum s) noexcept { + #define DEFINE_CASE(x) case x: return #x + switch ( s ) { + DEFINE_CASE(GL_FRAMEBUFFER_COMPLETE); + DEFINE_CASE(GL_FRAMEBUFFER_UNSUPPORTED); + DEFINE_CASE(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT); + DEFINE_CASE(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT); + default: + return "GL_FRAMEBUFFER_UNKNOWN"; + } + #undef DEFINE_CASE + } + GLenum gl_target_to_get_target(GLenum t) noexcept { #define DEFINE_CASE(x,y) case x: return y switch ( t ) { @@ -1181,31 +1253,6 @@ namespace e2d { namespace opengl : gl_program_id(debug); } - gl_texture_id gl_compile_texture(debug& debug, const image& image) { - gl_texture_id id = gl_texture_id::create( - debug, GL_TEXTURE_2D); - if ( id.empty() ) { - return id; - } - with_gl_bind_texture(debug, id, [&debug, &id, &image]() noexcept { - GL_CHECK_CODE(debug, glTexImage2D( - id.target(), - 0, - convert_format_to_internal_format(image.format()), - math::numeric_cast(image.size().x), - math::numeric_cast(image.size().y), - 0, - convert_format_to_external_format(image.format()), - convert_format_to_external_data_type(image.format()), - image.data().data())); - #if !defined(GL_ES_VERSION_2_0) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - #endif - }); - return id; - } - gl_buffer_id gl_compile_index_buffer( debug& debug, const buffer& indices, index_buffer::usage usage) { @@ -1241,6 +1288,76 @@ namespace e2d { namespace opengl }); return id; } + + bool gl_check_framebuffer( + debug& debug, + const gl_framebuffer_id& fb, + GLenum* out_status) noexcept + { + E2D_ASSERT(!fb.empty()); + GLenum status = GL_FRAMEBUFFER_COMPLETE; + with_gl_bind_framebuffer(debug, fb, [&debug, &fb, &status]() noexcept { + GL_CHECK_CODE(debug, gl_check_framebuffer_status( + fb.target(), &status)); + }); + if ( out_status ) { + *out_status = status; + } + return status == GL_FRAMEBUFFER_COMPLETE; + } + + void gl_attach_texture( + debug& debug, + const gl_framebuffer_id& fb, + const gl_texture_id& tex, + GLenum attachment) noexcept + { + E2D_ASSERT(!fb.empty() && !tex.empty()); + with_gl_bind_framebuffer(debug, fb, [&debug, &fb, &tex, &attachment]() noexcept { + GL_CHECK_CODE(debug, glFramebufferTexture2D( + fb.target(), + attachment, + tex.target(), + *tex, + 0)); + }); + } + + void gl_attach_renderbuffer( + debug& debug, + const gl_framebuffer_id& fb, + const gl_renderbuffer_id& rb, + GLenum attachment) noexcept + { + E2D_ASSERT(!fb.empty() && !rb.empty()); + with_gl_bind_framebuffer(debug, fb, [&debug, &fb, &rb, &attachment]() noexcept { + GL_CHECK_CODE(debug, glFramebufferRenderbuffer( + fb.target(), + attachment, + rb.target(), + *rb)); + }); + } + + gl_renderbuffer_id gl_compile_renderbuffer( + debug& debug, + const v2u& size, + GLenum format) + { + gl_renderbuffer_id id = gl_renderbuffer_id::create( + debug, GL_RENDERBUFFER); + if ( id.empty() ) { + return id; + } + with_gl_bind_renderbuffer(debug, id, [&debug, &id, &size, &format]() noexcept { + GL_CHECK_CODE(debug, glRenderbufferStorage( + id.target(), + format, + math::numeric_cast(size.x), + math::numeric_cast(size.y))); + }); + return id; + } }} namespace e2d { namespace opengl diff --git a/sources/enduro2d/core/render_impl/render_opengl_base.hpp b/sources/enduro2d/core/render_impl/render_opengl_base.hpp index 07271787..ae6a5545 100644 --- a/sources/enduro2d/core/render_impl/render_opengl_base.hpp +++ b/sources/enduro2d/core/render_impl/render_opengl_base.hpp @@ -14,6 +14,91 @@ # include #endif +// +// https://www.khronos.org/registry/OpenGL/extensions/ +// OES/OES_rgb8_rgba8.txt +// + +#ifndef GL_RGB8 +# define GL_RGB8 0x8051 +#endif + +#ifndef GL_RGBA8 +# define GL_RGBA8 0x8058 +#endif + +// +// https://www.khronos.org/registry/OpenGL/extensions/ +// OES/OES_depth_texture.txt +// + +#ifndef GL_UNSIGNED_INT +# define GL_UNSIGNED_INT 0x1405 +#endif + +#ifndef GL_UNSIGNED_SHORT +# define GL_UNSIGNED_SHORT 0x1403 +#endif + +#ifndef GL_DEPTH_COMPONENT +# define GL_DEPTH_COMPONENT 0x1902 +#endif + +// +// https://www.khronos.org/registry/OpenGL/extensions/ +// OES/OES_packed_depth_stencil.txt +// + +#ifndef GL_DEPTH_STENCIL +# define GL_DEPTH_STENCIL 0x84F9 +#endif + +#ifndef GL_DEPTH24_STENCIL8 +# define GL_DEPTH24_STENCIL8 0x88F0 +#endif + +#ifndef GL_UNSIGNED_INT_24_8 +# define GL_UNSIGNED_INT_24_8 0x84FA +#endif + +// +// https://www.khronos.org/registry/OpenGL/extensions/ +// EXT/EXT_texture_compression_s3tc.txt +// + +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT +# define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif + +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT +# define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#endif + +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT +# define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +// +// https://www.khronos.org/registry/OpenGL/extensions/ +// IMG/IMG_texture_compression_pvrtc.txt +// + +#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG +# define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#endif + +#ifndef GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG +# define GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG 0x8C01 +#endif + +#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG +# define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#endif + +#ifndef GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG +# define GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG 0x8C03 +#endif + #define GL_FLUSH_ERRORS(dbg)\ for ( GLenum err = glGetError(); err != GL_NO_ERROR; err = glGetError() ) {\ E2D_ASSERT_MSG(false, "RENDER: GL_FLUSH_ERRORS()");\ @@ -229,9 +314,15 @@ namespace e2d { namespace opengl namespace e2d { namespace opengl { - GLint convert_format_to_internal_format(image_data_format idf) noexcept; - GLenum convert_format_to_external_format(image_data_format idf) noexcept; - GLenum convert_format_to_external_data_type(image_data_format idf) noexcept; + GLenum convert_image_data_format_to_external_format(image_data_format f) noexcept; + GLenum convert_image_data_format_to_external_data_type(image_data_format f) noexcept; + + GLenum convert_pixel_type_to_external_format(pixel_declaration::pixel_type f) noexcept; + GLenum convert_pixel_type_to_external_data_type(pixel_declaration::pixel_type f) noexcept; + + GLint convert_pixel_type_to_internal_format(pixel_declaration::pixel_type f) noexcept; + GLenum convert_pixel_type_to_compressed_format(pixel_declaration::pixel_type f) noexcept; + pixel_declaration convert_image_data_format_to_pixel_declaration(image_data_format f) noexcept; GLenum convert_index_type(index_declaration::index_type it) noexcept; GLenum convert_attribute_type(vertex_declaration::attribute_type at) noexcept; @@ -258,6 +349,7 @@ namespace e2d { namespace opengl const char* glsl_type_to_cstr(GLenum t) noexcept; const char* gl_error_code_to_cstr(GLenum e) noexcept; + const char* gl_framebuffer_status_to_cstr(GLenum s) noexcept; GLenum gl_target_to_get_target(GLenum t) noexcept; }} @@ -267,9 +359,30 @@ namespace e2d { namespace opengl void gl_trace_limits(debug& debug) noexcept; gl_shader_id gl_compile_shader(debug& debug, const str& source, GLenum type) noexcept; gl_program_id gl_link_program(debug& debug, gl_shader_id vs, gl_shader_id fs) noexcept; - gl_texture_id gl_compile_texture(debug& debug, const image& image); gl_buffer_id gl_compile_index_buffer(debug& debug, const buffer& indices, index_buffer::usage usage); gl_buffer_id gl_compile_vertex_buffer(debug& debug, const buffer& vertices, vertex_buffer::usage usage); + + bool gl_check_framebuffer( + debug& debug, + const gl_framebuffer_id& fb, + GLenum* out_status) noexcept; + + void gl_attach_texture( + debug& debug, + const gl_framebuffer_id& fb, + const gl_texture_id& tex, + GLenum attachment) noexcept; + + void gl_attach_renderbuffer( + debug& debug, + const gl_framebuffer_id& fb, + const gl_renderbuffer_id& rb, + GLenum attachment) noexcept; + + gl_renderbuffer_id gl_compile_renderbuffer( + debug& debug, + const v2u& size, + GLenum format); }} namespace e2d { namespace opengl diff --git a/sources/enduro2d/core/render_impl/render_opengl_impl.cpp b/sources/enduro2d/core/render_impl/render_opengl_impl.cpp index f6378d91..3a540b6c 100644 --- a/sources/enduro2d/core/render_impl/render_opengl_impl.cpp +++ b/sources/enduro2d/core/render_impl/render_opengl_impl.cpp @@ -76,11 +76,11 @@ namespace e2d debug& debug, gl_texture_id id, const v2u& size, - image_data_format format) + const pixel_declaration& decl) : debug_(debug) , id_(std::move(id)) , size_(size) - , format_(format){ + , decl_(decl){ E2D_ASSERT(!id_.empty()); } @@ -96,8 +96,8 @@ namespace e2d return size_; } - image_data_format texture::internal_state::format() const noexcept { - return format_; + const pixel_declaration& texture::internal_state::decl() const noexcept { + return decl_; } // @@ -164,13 +164,63 @@ namespace e2d return decl_; } + // + // render_target::internal_state + // + render_target::internal_state::internal_state( + debug& debug, + opengl::gl_framebuffer_id id, + const v2u& size, + texture_ptr color, + texture_ptr depth, + opengl::gl_renderbuffer_id color_rb, + opengl::gl_renderbuffer_id depth_rb) + : debug_(debug) + , id_(std::move(id)) + , size_(size) + , color_(std::move(color)) + , depth_(std::move(depth)) + , color_rb_(std::move(color_rb)) + , depth_rb_(std::move(depth_rb)){ + E2D_ASSERT(!id_.empty()); + } + + debug& render_target::internal_state::dbg() const noexcept { + return debug_; + } + + const gl_framebuffer_id& render_target::internal_state::id() const noexcept { + return id_; + } + + const v2u& render_target::internal_state::size() const noexcept { + return size_; + } + + const texture_ptr& render_target::internal_state::color() const noexcept { + return color_; + } + + const texture_ptr& render_target::internal_state::depth() const noexcept { + return depth_; + } + + const gl_renderbuffer_id& render_target::internal_state::color_rb() const noexcept { + return color_rb_; + } + + const gl_renderbuffer_id& render_target::internal_state::depth_rb() const noexcept { + return depth_rb_; + } + // // render::internal_state // render::internal_state::internal_state(debug& debug, window& window) : debug_(debug) - , window_(window) {} + , window_(window) + , default_fb_(gl_framebuffer_id::current(debug, GL_FRAMEBUFFER)) {} debug& render::internal_state::dbg() const noexcept { return debug_; @@ -180,6 +230,10 @@ namespace e2d return window_; } + const render_target_ptr& render::internal_state::render_target() const noexcept { + return render_target_; + } + render::internal_state& render::internal_state::set_states(const state_block& rs) noexcept { set_depth_state(rs.depth()); set_stencil_state(rs.stencil()); @@ -258,5 +312,19 @@ namespace e2d GL_CHECK_CODE(debug_, enable_or_disable(GL_STENCIL_TEST, cs.stencil_test())); return *this; } + + render::internal_state& render::internal_state::set_render_target(const render_target_ptr& rt) noexcept { + if ( rt ) { + GL_CHECK_CODE(debug_, glBindFramebuffer( + rt->state().id().target(), + *rt->state().id())); + } else { + GL_CHECK_CODE(debug_, glBindFramebuffer( + default_fb_.target(), + *default_fb_)); + } + render_target_ = rt; + return *this; + } } #endif diff --git a/sources/enduro2d/core/render_impl/render_opengl_impl.hpp b/sources/enduro2d/core/render_impl/render_opengl_impl.hpp index a8fa0ab1..94bbe1b2 100644 --- a/sources/enduro2d/core/render_impl/render_opengl_impl.hpp +++ b/sources/enduro2d/core/render_impl/render_opengl_impl.hpp @@ -64,18 +64,18 @@ namespace e2d debug& debug, opengl::gl_texture_id id, const v2u& size, - image_data_format format); + const pixel_declaration& decl); ~internal_state() noexcept = default; public: debug& dbg() const noexcept; const opengl::gl_texture_id& id() const noexcept; const v2u& size() const noexcept; - image_data_format format() const noexcept; + const pixel_declaration& decl() const noexcept; private: debug& debug_; opengl::gl_texture_id id_; v2u size_; - image_data_format format_; + pixel_declaration decl_; }; // @@ -126,6 +126,39 @@ namespace e2d vertex_declaration decl_; }; + // + // render_target::internal_state + // + + class render_target::internal_state final : private e2d::noncopyable { + public: + internal_state( + debug& debug, + opengl::gl_framebuffer_id id, + const v2u& size, + texture_ptr color, + texture_ptr depth, + opengl::gl_renderbuffer_id color_rb, + opengl::gl_renderbuffer_id depth_rb); + ~internal_state() noexcept = default; + public: + debug& dbg() const noexcept; + const opengl::gl_framebuffer_id& id() const noexcept; + const v2u& size() const noexcept; + const texture_ptr& color() const noexcept; + const texture_ptr& depth() const noexcept; + const opengl::gl_renderbuffer_id& color_rb() const noexcept; + const opengl::gl_renderbuffer_id& depth_rb() const noexcept; + private: + debug& debug_; + opengl::gl_framebuffer_id id_; + v2u size_; + texture_ptr color_; + texture_ptr depth_; + opengl::gl_renderbuffer_id color_rb_; + opengl::gl_renderbuffer_id depth_rb_; + }; + // // render::internal_state // @@ -139,6 +172,7 @@ namespace e2d public: debug& dbg() const noexcept; window& wnd() const noexcept; + const render_target_ptr& render_target() const noexcept; public: internal_state& set_states(const state_block& rs) noexcept; internal_state& set_depth_state(const depth_state& ds) noexcept; @@ -146,9 +180,12 @@ namespace e2d internal_state& set_culling_state(const culling_state& cs) noexcept; internal_state& set_blending_state(const blending_state& bs) noexcept; internal_state& set_capabilities_state(const capabilities_state& cs) noexcept; + internal_state& set_render_target(const render_target_ptr& rt) noexcept; private: debug& debug_; window& window_; + render_target_ptr render_target_; + opengl::gl_framebuffer_id default_fb_; }; } diff --git a/untests/sources/untests_core/render.cpp b/untests/sources/untests_core/render.cpp index 67371983..48a93d00 100644 --- a/untests/sources/untests_core/render.cpp +++ b/untests/sources/untests_core/render.cpp @@ -72,16 +72,16 @@ TEST_CASE("render"){ } SECTION("index_declaration"){ index_declaration id; - REQUIRE(id.index().type == index_declaration::index_type::unsigned_short); - REQUIRE(id.index_size() == 2); + REQUIRE(id.type() == index_declaration::index_type::unsigned_short); + REQUIRE(id.bytes_per_index() == 2); index_declaration id2(index_declaration::index_type::unsigned_short); - REQUIRE(id2.index().type == index_declaration::index_type::unsigned_short); - REQUIRE(id2.index_size() == 2); + REQUIRE(id2.type() == index_declaration::index_type::unsigned_short); + REQUIRE(id2.bytes_per_index() == 2); index_declaration id3(index_declaration::index_type::unsigned_byte); - REQUIRE(id3.index().type == index_declaration::index_type::unsigned_byte); - REQUIRE(id3.index_size() == 1); + REQUIRE(id3.type() == index_declaration::index_type::unsigned_byte); + REQUIRE(id3.bytes_per_index() == 1); REQUIRE(id == id2); REQUIRE_FALSE(id == id3); @@ -98,11 +98,11 @@ TEST_CASE("render"){ SECTION("vertex_declaration"){ vertex_declaration vd; REQUIRE(vd.attribute_count() == 0); - REQUIRE(vd.vertex_size() == 0); + REQUIRE(vd.bytes_per_vertex() == 0); REQUIRE(&vd == &vd.add_attribute("hello")); REQUIRE(vd.attribute_count() == 1); - REQUIRE(vd.vertex_size() == 8); + REQUIRE(vd.bytes_per_vertex() == 8); vertex_declaration::attribute_info ai = vd.attribute(0); REQUIRE(ai.name == make_hash("hello")); @@ -112,11 +112,11 @@ TEST_CASE("render"){ REQUIRE_FALSE(ai.normalized); REQUIRE(&vd == &vd.skip_bytes(4)); - REQUIRE(vd.vertex_size() == 12); + REQUIRE(vd.bytes_per_vertex() == 12); REQUIRE(&vd == &vd.add_attribute("world").normalized()); REQUIRE(vd.attribute_count() == 2); - REQUIRE(vd.vertex_size() == 14); + REQUIRE(vd.bytes_per_vertex() == 14); vertex_declaration::attribute_info ai2 = vd.attribute(1); REQUIRE(ai2.name == make_hash("world")); @@ -138,9 +138,9 @@ TEST_CASE("render"){ REQUIRE_FALSE(vd == vd3); REQUIRE(vd != vd3); - REQUIRE(vd3.vertex_size() == 16); + REQUIRE(vd3.bytes_per_vertex() == 16); vd3.skip_bytes(4); - REQUIRE(vd3.vertex_size() == 20); + REQUIRE(vd3.bytes_per_vertex() == 20); vertex_declaration vd4 = vd2; REQUIRE(vd4 == vd);