diff --git a/headers/enduro2d/core/render.hpp b/headers/enduro2d/core/render.hpp index df692fef..709f1c74 100644 --- a/headers/enduro2d/core/render.hpp +++ b/headers/enduro2d/core/render.hpp @@ -49,6 +49,8 @@ namespace e2d depth32, depth24_stencil8, + g8, + ga8, rgb8, rgba8, @@ -956,9 +958,25 @@ namespace e2d render& update_texture( const texture_ptr& tex, - buffer_view data, - v2u size, - v2u offset); + buffer_view pixels, + const b2u& region); + + // very slow + render& grab_texture( + const texture_ptr& tex, + const b2u& region, + image& result); + + // very slow + render& grab_render_target( + const render_target_ptr& rt, + const b2u& region, + image& result); + + // very slow + render& grab_screen( + const b2u& region, + image& result); const device_caps& device_capabilities() const noexcept; bool is_pixel_supported(const pixel_declaration& decl) const noexcept; diff --git a/headers/enduro2d/utils/buffer.hpp b/headers/enduro2d/utils/buffer.hpp index 775c3200..24d52865 100644 --- a/headers/enduro2d/utils/buffer.hpp +++ b/headers/enduro2d/utils/buffer.hpp @@ -37,6 +37,11 @@ namespace e2d u8* data() noexcept; const u8* data() const noexcept; std::size_t size() const noexcept; + + const u8* begin() const noexcept; + const u8* end() const noexcept; + u8* begin() noexcept; + u8* end() noexcept; private: using data_t = std::unique_ptr; data_t data_; diff --git a/sources/enduro2d/core/render.cpp b/sources/enduro2d/core/render.cpp index a6177602..861f7d1c 100644 --- a/sources/enduro2d/core/render.cpp +++ b/sources/enduro2d/core/render.cpp @@ -26,7 +26,9 @@ namespace {"depth24", 24, false, true, false, pixel_declaration::pixel_type::depth24, false, v2u(1)}, {"depth32", 32, false, true, false, pixel_declaration::pixel_type::depth32, false, v2u(1)}, {"depth24_stencil8", 32, false, true, true, pixel_declaration::pixel_type::depth24_stencil8, false, v2u(1)}, - + + {"g8", 8, true, false, false, pixel_declaration::pixel_type::g8, false, v2u(1)}, + {"ga8", 16, true, false, false, pixel_declaration::pixel_type::ga8, false, v2u(1)}, {"rgb8", 24, true, false, false, pixel_declaration::pixel_type::rgb8, false, v2u(1)}, {"rgba8", 32, true, false, false, pixel_declaration::pixel_type::rgba8, false, v2u(1)}, diff --git a/sources/enduro2d/core/render_impl/render_none.cpp b/sources/enduro2d/core/render_impl/render_none.cpp index 9a592aac..b2430e4a 100644 --- a/sources/enduro2d/core/render_impl/render_none.cpp +++ b/sources/enduro2d/core/render_impl/render_none.cpp @@ -280,11 +280,39 @@ namespace e2d render& render::update_texture( const texture_ptr& tex, - buffer_view data, - v2u size, - v2u offset) + buffer_view pixels, + const b2u& region) { - E2D_UNUSED(tex, img, size, offset); + E2D_UNUSED(tex, pixels, region); + return *this; + } + + render& render::grab_texture( + const texture_ptr& tex, + const b2u& region, + image& result) + { + E2D_UNUSED(tex, region); + result.clear(); + return *this; + } + + render& render::grab_render_target( + const render_target_ptr& rt, + const b2u& region, + image& result) + { + E2D_UNUSED(rt, region); + result.clear(); + return *this; + } + + render& render::grab_screen( + const b2u& region, + image& result) + { + E2D_UNUSED(tex); + result.clear(); return *this; } diff --git a/sources/enduro2d/core/render_impl/render_opengl.cpp b/sources/enduro2d/core/render_impl/render_opengl.cpp index b065b2bc..0d8b8f50 100644 --- a/sources/enduro2d/core/render_impl/render_opengl.cpp +++ b/sources/enduro2d/core/render_impl/render_opengl.cpp @@ -1032,57 +1032,161 @@ namespace e2d pixel_declaration::pixel_type_to_cstr(decl.type())); throw bad_render_operation(); } - return update_texture(tex, img.data(), img.size(), offset); + return update_texture(tex, img.data(), b2u(offset, img.size())); } render& render::update_texture( const texture_ptr& tex, - buffer_view data, - v2u size, - v2u offset) + buffer_view pixels, + const b2u& region) { E2D_ASSERT(is_in_main_thread()); E2D_ASSERT(tex); - E2D_ASSERT(offset.x < tex->size().x && offset.y < tex->size().y); - E2D_ASSERT(offset.x + size.x <= tex->size().x); - E2D_ASSERT(offset.y + size.y <= tex->size().y); - E2D_ASSERT(data.size() * 8u == size.y * size.x * tex->decl().bits_per_pixel()); + E2D_ASSERT(region.position.x < tex->size().x && region.position.y < tex->size().y); + E2D_ASSERT(region.position.x + region.size.x <= tex->size().x); + E2D_ASSERT(region.position.y + region.size.y <= tex->size().y); + E2D_ASSERT(pixels.size() == region.size.y * ((region.size.x * tex->decl().bits_per_pixel()) / 8u)); if ( tex->decl().is_compressed() ) { const v2u block_size = tex->decl().compressed_block_size(); - E2D_ASSERT(offset.x % block_size.x == 0 && offset.y % block_size.y == 0); - E2D_ASSERT(size.x % block_size.x == 0 && size.y % block_size.y == 0); + E2D_ASSERT(region.position.x % block_size.x == 0 && region.position.y % block_size.y == 0); + E2D_ASSERT(region.size.x % block_size.x == 0 && region.size.y % block_size.y == 0); opengl::with_gl_bind_texture(state_->dbg(), tex->state().id(), - [&tex, &data, &size, &offset]() noexcept { + [&tex, &pixels, ®ion]() noexcept { GL_CHECK_CODE(tex->state().dbg(), glCompressedTexSubImage2D( tex->state().id().target(), 0, - math::numeric_cast(offset.x), - math::numeric_cast(offset.y), - math::numeric_cast(size.x), - math::numeric_cast(size.y), + math::numeric_cast(region.position.x), + math::numeric_cast(region.position.y), + math::numeric_cast(region.size.x), + math::numeric_cast(region.size.y), convert_pixel_type_to_external_format(tex->state().decl().type()), - math::numeric_cast(data.size()), - data.data())); + math::numeric_cast(pixels.size()), + pixels.data())); }); } else { opengl::with_gl_bind_texture(state_->dbg(), tex->state().id(), - [&tex, &data, &size, &offset]() noexcept { + [&tex, &pixels, ®ion]() noexcept { GL_CHECK_CODE(tex->state().dbg(), glTexSubImage2D( tex->state().id().target(), 0, - math::numeric_cast(offset.x), - math::numeric_cast(offset.y), - math::numeric_cast(size.x), - math::numeric_cast(size.y), + math::numeric_cast(region.position.x), + math::numeric_cast(region.position.y), + math::numeric_cast(region.size.x), + math::numeric_cast(region.size.y), convert_pixel_type_to_external_format(tex->state().decl().type()), convert_pixel_type_to_external_data_type(tex->state().decl().type()), - data.data())); + pixels.data())); }); } return *this; } + static void grab_framebuffer_content( + debug& debug, + const opengl::gl_framebuffer_id& fb, + const b2u& region, + image& result) + { + with_gl_bind_framebuffer(debug, fb, + [&debug, ®ion, &result]() { + GLint format; + GLint type; + GL_CHECK_CODE(debug, glGetIntegerv( + GL_IMPLEMENTATION_COLOR_READ_FORMAT, + &format)); + GL_CHECK_CODE(debug, glGetIntegerv( + GL_IMPLEMENTATION_COLOR_READ_TYPE, + &type)); + + image_data_format img_format; + if ( (format == GL_ALPHA || format == GL_LUMINANCE) && type == GL_UNSIGNED_BYTE ) { + img_format = image_data_format::g8; + } else if ( format == GL_RGB && type == GL_UNSIGNED_BYTE ) { + img_format = image_data_format::rgb8; + } else if ( format == GL_RGBA && type == GL_UNSIGNED_BYTE ) { + img_format = image_data_format::rgba8; + } else { + E2D_ASSERT_MSG(false, "unsupported pixel format"); + // OpenGL ES 2 already supports RGBA8 format for glReadPixels + format = GL_RGBA; + type = GL_UNSIGNED_BYTE; + img_format = image_data_format::rgba8; + } + + pixel_declaration decl = convert_image_data_format_to_pixel_declaration(img_format); + buffer pixels; + pixels.resize(((decl.bits_per_pixel() * region.size.x) / 8u) * region.size.y); + + GL_CHECK_CODE(debug, glReadPixels( + math::numeric_cast(region.position.x), + math::numeric_cast(region.position.y), + math::numeric_cast(region.size.x), + math::numeric_cast(region.size.y), + format, + type, + pixels.data())); + result = image(region.size, img_format, std::move(pixels)); + }); + } + + render& render::grab_texture( + const texture_ptr& tex, + const b2u& region, + image& result) + { + E2D_ASSERT(tex); + E2D_ASSERT(tex->decl().is_color() && !tex->decl().is_compressed()); + E2D_ASSERT(region.position.x + region.size.x <= tex->size().x); + E2D_ASSERT(region.position.y + region.size.y <= tex->size().y); + + gl_framebuffer_id id = gl_framebuffer_id::create(state_->dbg(), GL_FRAMEBUFFER); + if ( id.empty() ) { + throw bad_render_operation(); + } + gl_attach_texture(state_->dbg(), id, tex->state().id(), GL_COLOR_ATTACHMENT0); + GLenum fb_status = GL_FRAMEBUFFER_COMPLETE; + if ( !gl_check_framebuffer(state_->dbg(), id, &fb_status) ) { + throw bad_render_operation(); + } + grab_framebuffer_content( + state_->dbg(), + id, + region, + result); + return *this; + } + + render& render::grab_render_target( + const render_target_ptr& rt, + const b2u& region, + image& result) + { + E2D_ASSERT(rt); + E2D_ASSERT(region.position.x + region.size.x <= rt->size().x); + E2D_ASSERT(region.position.y + region.size.y <= rt->size().y); + grab_framebuffer_content( + state_->dbg(), + rt->state().id(), + region, + result); + return *this; + } + + render& render::grab_screen( + const b2u& region, + image& result) + { + E2D_ASSERT(region.position.x + region.size.x <= state_->wnd().real_size().x); + E2D_ASSERT(region.position.y + region.size.y <= state_->wnd().real_size().y); + grab_framebuffer_content( + state_->dbg(), + state_->default_fb(), + region, + result); + return *this; + } + const render::device_caps& render::device_capabilities() const noexcept { E2D_ASSERT(is_in_main_thread()); return state_->device_capabilities(); @@ -1100,6 +1204,8 @@ namespace e2d case pixel_declaration::pixel_type::depth24_stencil8: return GLEW_OES_packed_depth_stencil || GLEW_EXT_packed_depth_stencil; + case pixel_declaration::pixel_type::g8: + case pixel_declaration::pixel_type::ga8: case pixel_declaration::pixel_type::rgb8: case pixel_declaration::pixel_type::rgba8: return true; diff --git a/sources/enduro2d/core/render_impl/render_opengl_base.cpp b/sources/enduro2d/core/render_impl/render_opengl_base.cpp index 18ec09ff..d5a15088 100644 --- a/sources/enduro2d/core/render_impl/render_opengl_base.cpp +++ b/sources/enduro2d/core/render_impl/render_opengl_base.cpp @@ -723,6 +723,8 @@ namespace e2d::opengl DEFINE_CASE(depth24, GL_DEPTH_COMPONENT); DEFINE_CASE(depth32, GL_DEPTH_COMPONENT); DEFINE_CASE(depth24_stencil8, GL_DEPTH_STENCIL); + DEFINE_CASE(g8, GL_ALPHA); + DEFINE_CASE(ga8, GL_LUMINANCE_ALPHA); DEFINE_CASE(rgb8, GL_RGB); DEFINE_CASE(rgba8, GL_RGBA); default: @@ -739,6 +741,8 @@ namespace e2d::opengl DEFINE_CASE(depth24, GL_UNSIGNED_INT); DEFINE_CASE(depth32, GL_UNSIGNED_INT); DEFINE_CASE(depth24_stencil8, GL_UNSIGNED_INT_24_8); + 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: @@ -755,7 +759,9 @@ namespace e2d::opengl DEFINE_CASE(depth24, GL_DEPTH_COMPONENT24); DEFINE_CASE(depth32, GL_DEPTH_COMPONENT32); DEFINE_CASE(depth24_stencil8, GL_DEPTH24_STENCIL8); - + + DEFINE_CASE(g8, GL_ALPHA); + DEFINE_CASE(ga8, GL_LUMINANCE_ALPHA); DEFINE_CASE(rgb8, GL_RGB); DEFINE_CASE(rgba8, GL_RGBA); @@ -786,8 +792,8 @@ namespace e2d::opengl 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_type::y switch ( f ) { - DEFINE_CASE(g8, rgb8); - DEFINE_CASE(ga8, rgba8); + DEFINE_CASE(g8, g8); + DEFINE_CASE(ga8, ga8); DEFINE_CASE(rgb8, rgb8); DEFINE_CASE(rgba8, rgba8); diff --git a/sources/enduro2d/core/render_impl/render_opengl_impl.cpp b/sources/enduro2d/core/render_impl/render_opengl_impl.cpp index 0baaf463..6540b13f 100644 --- a/sources/enduro2d/core/render_impl/render_opengl_impl.cpp +++ b/sources/enduro2d/core/render_impl/render_opengl_impl.cpp @@ -217,9 +217,7 @@ namespace e2d GL_CHECK_CODE(debug_, glPixelStorei(GL_PACK_ALIGNMENT, 1)); GL_CHECK_CODE(debug_, glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - set_states(state_block_); - set_shader_program(shader_program_); - set_render_target(render_target_); + reset_states(); } debug& render::internal_state::dbg() const noexcept { @@ -238,6 +236,19 @@ namespace e2d return render_target_; } + const opengl::gl_framebuffer_id& render::internal_state::default_fb() const noexcept { + return default_fb_; + } + + render::internal_state& render::internal_state::reset_states() noexcept { + set_depth_state_(state_block_.depth()); + set_stencil_state_(state_block_.stencil()); + set_culling_state_(state_block_.culling()); + set_blending_state_(state_block_.blending()); + set_capabilities_state_(state_block_.capabilities()); + return *this; + } + render::internal_state& render::internal_state::set_states(const state_block& sb) noexcept { set_depth_state(sb.depth()); set_stencil_state(sb.stencil()); @@ -246,12 +257,8 @@ namespace e2d set_capabilities_state(sb.capabilities()); return *this; } - - render::internal_state& render::internal_state::set_depth_state(const depth_state& ds) noexcept { - if ( ds == state_block_.depth() ) { - return *this; - } - + + void render::internal_state::set_depth_state_(const depth_state& ds) noexcept { GL_CHECK_CODE(debug_, glDepthRange( math::numeric_cast(math::saturate(ds.range_near())), math::numeric_cast(math::saturate(ds.range_far())))); @@ -259,16 +266,19 @@ namespace e2d ds.write() ? GL_TRUE : GL_FALSE)); GL_CHECK_CODE(debug_, glDepthFunc( convert_compare_func(ds.func()))); - - state_block_.depth(ds); - return *this; } - render::internal_state& render::internal_state::set_stencil_state(const stencil_state& ss) noexcept { - if ( ss == state_block_.stencil() ) { + render::internal_state& render::internal_state::set_depth_state(const depth_state& ds) noexcept { + if ( ds == state_block_.depth() ) { return *this; } + set_depth_state_(ds); + state_block_.depth(ds); + return *this; + } + + void render::internal_state::set_stencil_state_(const stencil_state& ss) noexcept { GL_CHECK_CODE(debug_, glStencilMask( math::numeric_cast(ss.write()))); GL_CHECK_CODE(debug_, glStencilFunc( @@ -279,30 +289,36 @@ namespace e2d convert_stencil_op(ss.sfail()), convert_stencil_op(ss.zfail()), convert_stencil_op(ss.pass()))); + } + render::internal_state& render::internal_state::set_stencil_state(const stencil_state& ss) noexcept { + if ( ss == state_block_.stencil() ) { + return *this; + } + + set_stencil_state_(ss); state_block_.stencil(ss); return *this; } + + void render::internal_state::set_culling_state_(const culling_state& cs) noexcept { + GL_CHECK_CODE(debug_, glFrontFace( + convert_culling_mode(cs.mode()))); + GL_CHECK_CODE(debug_, glCullFace( + convert_culling_face(cs.face()))); + } render::internal_state& render::internal_state::set_culling_state(const culling_state& cs) noexcept { if ( cs == state_block_.culling() ) { return *this; } - GL_CHECK_CODE(debug_, glFrontFace( - convert_culling_mode(cs.mode()))); - GL_CHECK_CODE(debug_, glCullFace( - convert_culling_face(cs.face()))); - + set_culling_state_(cs); state_block_.culling(cs); return *this; } - - render::internal_state& render::internal_state::set_blending_state(const blending_state& bs) noexcept { - if ( bs == state_block_.blending() ) { - return *this; - } - + + void render::internal_state::set_blending_state_(const blending_state& bs) noexcept { GL_CHECK_CODE(debug_, glBlendColor( math::numeric_cast(math::saturate(bs.constant_color().r)), math::numeric_cast(math::saturate(bs.constant_color().g)), @@ -321,12 +337,19 @@ namespace e2d (utils::enum_to_underlying(bs.color_mask()) & utils::enum_to_underlying(blending_color_mask::g)) != 0, (utils::enum_to_underlying(bs.color_mask()) & utils::enum_to_underlying(blending_color_mask::b)) != 0, (utils::enum_to_underlying(bs.color_mask()) & utils::enum_to_underlying(blending_color_mask::a)) != 0)); + } + render::internal_state& render::internal_state::set_blending_state(const blending_state& bs) noexcept { + if ( bs == state_block_.blending() ) { + return *this; + } + + set_blending_state_(bs); state_block_.blending(bs); return *this; } - - render::internal_state& render::internal_state::set_capabilities_state(const capabilities_state& cs) noexcept { + + void render::internal_state::set_capabilities_state_(const capabilities_state& cs) noexcept { const auto enable_or_disable = [](GLenum cap, bool enable) noexcept { if ( enable ) { glEnable(cap); @@ -335,15 +358,18 @@ namespace e2d } }; - if ( cs == state_block_.capabilities() ) { - return *this; - } - GL_CHECK_CODE(debug_, enable_or_disable(GL_CULL_FACE, cs.culling())); GL_CHECK_CODE(debug_, enable_or_disable(GL_BLEND, cs.blending())); GL_CHECK_CODE(debug_, enable_or_disable(GL_DEPTH_TEST, cs.depth_test())); GL_CHECK_CODE(debug_, enable_or_disable(GL_STENCIL_TEST, cs.stencil_test())); + } + render::internal_state& render::internal_state::set_capabilities_state(const capabilities_state& cs) noexcept { + if ( cs == state_block_.capabilities() ) { + return *this; + } + + set_capabilities_state_(cs); state_block_.capabilities(cs); return *this; } @@ -361,6 +387,14 @@ namespace e2d shader_program_ = sp; return *this; } + + render::internal_state& render::internal_state::reset_shader_program() noexcept { + const gl_program_id& sp_id = shader_program_ + ? shader_program_->state().id() + : default_sp_; + GL_CHECK_CODE(debug_, glUseProgram(*sp_id)); + return *this; + } render::internal_state& render::internal_state::set_render_target(const render_target_ptr& rt) noexcept { if ( rt == render_target_ ) { @@ -375,6 +409,14 @@ namespace e2d render_target_ = rt; return *this; } + + render::internal_state& render::internal_state::reset_render_target() noexcept { + const gl_framebuffer_id& rt_id = render_target_ + ? render_target_->state().id() + : default_fb_; + GL_CHECK_CODE(debug_, glBindFramebuffer(rt_id.target(), *rt_id)); + 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 d097ec04..f9103c6d 100644 --- a/sources/enduro2d/core/render_impl/render_opengl_impl.hpp +++ b/sources/enduro2d/core/render_impl/render_opengl_impl.hpp @@ -175,16 +175,27 @@ namespace e2d window& wnd() const noexcept; const device_caps& device_capabilities() const noexcept; const render_target_ptr& render_target() const noexcept; + const opengl::gl_framebuffer_id& default_fb() const noexcept; public: + internal_state& reset_states() noexcept; internal_state& set_states(const state_block& sb) noexcept; internal_state& set_depth_state(const depth_state& ds) noexcept; internal_state& set_stencil_state(const stencil_state& ss) noexcept; 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& reset_shader_program() noexcept; internal_state& set_shader_program(const shader_ptr& sp) noexcept; + + internal_state& reset_render_target() noexcept; internal_state& set_render_target(const render_target_ptr& rt) noexcept; + private: + void set_depth_state_(const depth_state& ds) noexcept; + void set_stencil_state_(const stencil_state& ss) noexcept; + void set_culling_state_(const culling_state& cs) noexcept; + void set_blending_state_(const blending_state& bs) noexcept; + void set_capabilities_state_(const capabilities_state& cs) noexcept; private: debug& debug_; window& window_; diff --git a/sources/enduro2d/utils/buffer.cpp b/sources/enduro2d/utils/buffer.cpp index 09687519..d4cccba7 100644 --- a/sources/enduro2d/utils/buffer.cpp +++ b/sources/enduro2d/utils/buffer.cpp @@ -107,6 +107,22 @@ namespace e2d std::size_t buffer::size() const noexcept { return size_; } + + const u8* buffer::begin() const noexcept { + return data_.get(); + } + + const u8* buffer::end() const noexcept { + return data_.get() + size_; + } + + u8* buffer::begin() noexcept { + return data_.get(); + } + + u8* buffer::end() noexcept { + return data_.get() + size_; + } } namespace e2d diff --git a/untests/sources/untests_core/render.cpp b/untests/sources/untests_core/render.cpp index a685e444..affaa51d 100644 --- a/untests/sources/untests_core/render.cpp +++ b/untests/sources/untests_core/render.cpp @@ -7,6 +7,23 @@ #include "_core.hpp" using namespace e2d; +namespace +{ + class safe_engine_initializer final : private noncopyable { + public: + safe_engine_initializer() { + modules::initialize(0, nullptr, + engine::parameters("renderer_untests", "enduro2d") + .without_audio(true) + .without_network(true)); + } + + ~safe_engine_initializer() noexcept { + modules::shutdown(); + } + }; +} + TEST_CASE("render"){ SECTION("sampler_state"){ { @@ -169,4 +186,125 @@ TEST_CASE("render"){ REQUIRE(vd4 != vd); REQUIRE(vd4 == vd3); } + SECTION("update_texture"){ + safe_engine_initializer initializer; + render& r = the(); + { + texture_ptr tex = r.create_texture(v2u(128,128), pixel_declaration::pixel_type::rgba8); + REQUIRE(tex != nullptr); + + buffer src; + src.resize(((tex->size().x * tex->decl().bits_per_pixel()) / 8u) * tex->size().y); + for (auto& c : src) { + c = rand() % 255; + } + r.update_texture(tex, src, b2u(0, 0, 128, 128)); + + image dst; + r.grab_texture(tex, b2u(0, 0, 128, 128), dst); + REQUIRE(dst.format() == image_data_format::rgba8); + REQUIRE(src == dst.data()); + } + { + texture_ptr tex = r.create_texture(v2u(128,128), pixel_declaration::pixel_type::g8); + REQUIRE(tex != nullptr); + + buffer src; + src.resize(((tex->size().x * tex->decl().bits_per_pixel()) / 8u) * tex->size().y); + for (auto& c : src) { + c = rand() % 255; + } + r.update_texture(tex, src, b2u(0, 0, 128, 128)); + + image dst; + r.grab_texture(tex, b2u(0, 0, 128, 128), dst); + if ( dst.format() == image_data_format::g8 ) { + REQUIRE(src == dst.data()); + } else { + // OpenGL ES 2 may not support Alpha8 format + REQUIRE(dst.format() == image_data_format::rgba8); + REQUIRE(dst.data().size() == src.size()*4); + bool equal = true; + for (size_t i = 0; i < src.size(); ++i) { + equal &= (src.data()[i] == dst.data().data()[i*4+3]); + } + REQUIRE(equal); + } + } + { + texture_ptr tex = r.create_texture(v2u(128,128), pixel_declaration::pixel_type::rgb8); + REQUIRE(tex != nullptr); + + buffer src; + src.resize(((tex->size().x * tex->decl().bits_per_pixel()) / 8u) * tex->size().y); + for (auto& c : src) { + c = rand() % 255; + } + r.update_texture(tex, src, b2u(0, 0, 128, 128)); + + image dst; + r.grab_texture(tex, b2u(0, 0, 128, 128), dst); + if ( dst.format() == image_data_format::rgb8 ) { + REQUIRE(src == dst.data()); + } else { + // OpenGL ES 2 may not support Alpha8 format + REQUIRE(dst.format() == image_data_format::rgba8); + REQUIRE(dst.data().size() == src.size()*4/3); + bool equal = true; + for (size_t i = 0, j = 0; i < src.size(); i += 3, j += 4) { + equal &= (src.data()[i+0] == dst.data().data()[j+0]); + equal &= (src.data()[i+1] == dst.data().data()[j+1]); + equal &= (src.data()[i+2] == dst.data().data()[j+2]); + } + REQUIRE(equal); + } + } + { + texture_ptr tex = r.create_texture(v2u(57,31), pixel_declaration::pixel_type::rgba8); + REQUIRE(tex != nullptr); + + buffer src; + src.resize(((tex->size().x * tex->decl().bits_per_pixel()) / 8u) * tex->size().y); + for (auto& c : src) { + c = rand() % 255; + } + r.update_texture(tex, src, b2u(0, 0, 57, 31)); + + image dst; + r.grab_texture(tex, b2u(0, 0, 57, 31), dst); + REQUIRE(dst.format() == image_data_format::rgba8); + REQUIRE(src == dst.data()); + } + { + texture_ptr tex = r.create_texture(v2u(128,128), pixel_declaration::pixel_type::rgba8); + REQUIRE(tex != nullptr); + + buffer src; + src.resize(((31 * tex->decl().bits_per_pixel()) / 8u) * 44); + for (auto& c : src) { + c = rand() % 255; + } + r.update_texture(tex, src, b2u(22, 17, 31, 44)); + + image dst; + r.grab_texture(tex, b2u(0, 0, 128, 128), dst); + REQUIRE(dst.format() == image_data_format::rgba8); + + const size_t data_size = ((tex->size().x * tex->decl().bits_per_pixel()) / 8u) * tex->size().y; + REQUIRE(data_size == dst.data().size()); + + bool equal = true; + const size_t bpp = tex->decl().bits_per_pixel() / 8; + for (u32 y = 0; y < 44; ++y) { + const u8* dst_row = dst.data().data() + ((y + 17) * 128 + 22) * bpp; + const u8* src_row = src.data() + (y * 31 * bpp); + for (u32 x = 0; x < 31 * bpp; ++x) { + auto s = src_row[x]; + auto d = dst_row[x]; + equal &= (s == d); + } + } + REQUIRE(equal); + } + } }