diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 80bdca6..d47a59b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -118,6 +118,7 @@ set(SOURCES anbox/graphics/layer_composer.cpp anbox/graphics/program_family.cpp anbox/graphics/primitives.h + anbox/graphics/renderer.h anbox/graphics/emugl/ColorBuffer.cpp anbox/graphics/emugl/DisplayManager.cpp diff --git a/src/anbox/graphics/emugl/Renderable.cpp b/src/anbox/graphics/emugl/Renderable.cpp index da32471..e75d1a2 100644 --- a/src/anbox/graphics/emugl/Renderable.cpp +++ b/src/anbox/graphics/emugl/Renderable.cpp @@ -47,3 +47,9 @@ void Renderable::set_screen_position( const anbox::graphics::Rect &screen_position) { screen_position_ = screen_position; } + +std::ostream &operator<<(std::ostream &out, const Renderable &r) { + return out << "{ name " << r.name() << " buffer " << r.buffer() + << " screen position " << r.screen_position() << " crop " + << r.crop() << " alpha " << r.alpha(); +} diff --git a/src/anbox/graphics/emugl/Renderable.h b/src/anbox/graphics/emugl/Renderable.h index bdf8a12..656b0b2 100644 --- a/src/anbox/graphics/emugl/Renderable.h +++ b/src/anbox/graphics/emugl/Renderable.h @@ -43,6 +43,16 @@ class Renderable { void set_screen_position(const anbox::graphics::Rect &screen_position); + inline bool operator==(const Renderable &rhs) const { + return (name_ == rhs.name() && buffer_ == rhs.buffer() && + screen_position_ == rhs.screen_position() && crop_ == rhs.crop() && + transformation_ == rhs.transformation() && alpha_ == rhs.alpha()); + } + + inline bool operator!=(const Renderable &rhs) const { + return !operator==(rhs); + } + private: std::string name_; std::uint32_t buffer_; @@ -52,6 +62,8 @@ class Renderable { float alpha_; }; +std::ostream &operator<<(std::ostream &out, const Renderable &r); + typedef std::vector RenderableList; #endif diff --git a/src/anbox/graphics/emugl/Renderer.h b/src/anbox/graphics/emugl/Renderer.h index 973c8ae..5258639 100644 --- a/src/anbox/graphics/emugl/Renderer.h +++ b/src/anbox/graphics/emugl/Renderer.h @@ -27,6 +27,7 @@ #include "anbox/graphics/primitives.h" #include "anbox/graphics/program_family.h" +#include "anbox/graphics/renderer.h" #include @@ -71,10 +72,10 @@ struct RendererWindow; // There is only one global instance, that can be retrieved with getFB(), // and which must be previously setup by calling initialize(). // -class Renderer { +class Renderer : public anbox::graphics::Renderer { public: Renderer(); - ~Renderer(); + virtual ~Renderer(); // Initialize the global instance. // |width| and |height| are the dimensions of the emulator GPU display @@ -223,7 +224,7 @@ class Renderer { bool draw(EGLNativeWindowType native_window, const anbox::graphics::Rect& window_frame, - const RenderableList& renderables); + const RenderableList& renderables) override; // Return the host EGLDisplay used by this instance. EGLDisplay getDisplay() const { return m_eglDisplay; } diff --git a/src/anbox/graphics/gl_renderer_server.cpp b/src/anbox/graphics/gl_renderer_server.cpp index 24049aa..54ead08 100644 --- a/src/anbox/graphics/gl_renderer_server.cpp +++ b/src/anbox/graphics/gl_renderer_server.cpp @@ -43,10 +43,9 @@ void logger_write(const char *format, ...) { namespace anbox { namespace graphics { GLRendererServer::GLRendererServer(const std::shared_ptr &wm) - : renderer_(std::make_shared()), + : renderer_(std::make_shared<::Renderer>()), wm_(wm), composer_(std::make_shared(renderer_, wm)) { - if (utils::is_env_set("USE_HOST_GLES")) { // Force the host EGL/GLES libraries as translator implementation ::setenv("ANDROID_EGL_LIB", "libEGL.so.1", 0); @@ -83,8 +82,6 @@ GLRendererServer::GLRendererServer(const std::shared_ptr &wm) registerLayerComposer(composer_); } -GLRendererServer::~GLRendererServer() { - renderer_->finalize(); -} +GLRendererServer::~GLRendererServer() { renderer_->finalize(); } } // namespace graphics } // namespace anbox diff --git a/src/anbox/graphics/layer_composer.cpp b/src/anbox/graphics/layer_composer.cpp index 066779d..cdb5dfd 100644 --- a/src/anbox/graphics/layer_composer.cpp +++ b/src/anbox/graphics/layer_composer.cpp @@ -22,7 +22,8 @@ namespace anbox { namespace graphics { -LayerComposer::LayerComposer(const std::shared_ptr renderer, const std::shared_ptr &wm) +LayerComposer::LayerComposer(const std::shared_ptr renderer, + const std::shared_ptr &wm) : renderer_(renderer), wm_(wm) {} LayerComposer::~LayerComposer() {} @@ -85,10 +86,9 @@ void LayerComposer::submit_layers(const RenderableList &renderables) { final_renderables.push_back(new_renderable); } - renderer_->draw( - window->native_handle(), - Rect{0, 0, window->frame().width(), window->frame().height()}, - final_renderables); + renderer_->draw(window->native_handle(), Rect{0, 0, window->frame().width(), + window->frame().height()}, + final_renderables); } } } // namespace graphics diff --git a/src/anbox/graphics/layer_composer.h b/src/anbox/graphics/layer_composer.h index 3b8ded0..f71c351 100644 --- a/src/anbox/graphics/layer_composer.h +++ b/src/anbox/graphics/layer_composer.h @@ -18,12 +18,10 @@ #ifndef ANBOX_GRAPHICS_LAYER_COMPOSER_H_ #define ANBOX_GRAPHICS_LAYER_COMPOSER_H_ -#include "anbox/graphics/emugl/Renderable.h" +#include "anbox/graphics/renderer.h" #include -class Renderer; - namespace anbox { namespace wm { class Manager; @@ -31,7 +29,8 @@ class Manager; namespace graphics { class LayerComposer { public: - LayerComposer(const std::shared_ptr renderer, const std::shared_ptr &wm); + LayerComposer(const std::shared_ptr renderer, + const std::shared_ptr &wm); ~LayerComposer(); void submit_layers(const RenderableList &renderables); diff --git a/src/anbox/graphics/renderer.h b/src/anbox/graphics/renderer.h new file mode 100644 index 0000000..74c91b1 --- /dev/null +++ b/src/anbox/graphics/renderer.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +#ifndef ANBOX_GRAPHICS_RENDERER_H_ +#define ANBOX_GRAPHICS_RENDERER_H_ + +#include "anbox/graphics/emugl/Renderable.h" + +#include + +namespace anbox { +namespace graphics { +class Renderer { + public: + virtual ~Renderer() {} + + virtual bool draw(EGLNativeWindowType native_window, + const anbox::graphics::Rect& window_frame, + const RenderableList& renderables) = 0; +}; +} // namespace graphics +} // namespace anbox + +#endif diff --git a/src/anbox/wm/default_platform_policy.cpp b/src/anbox/wm/default_platform_policy.cpp index 8ab33b5..cfd11e4 100644 --- a/src/anbox/wm/default_platform_policy.cpp +++ b/src/anbox/wm/default_platform_policy.cpp @@ -24,7 +24,7 @@ class NullWindow : public anbox::wm::Window { public: NullWindow(const anbox::wm::Task::Id &task, const anbox::graphics::Rect &frame) - : anbox::wm::Window(nullptr, 0, frame) {} + : anbox::wm::Window(nullptr, task, frame) {} }; } @@ -34,7 +34,6 @@ DefaultPlatformPolicy::DefaultPlatformPolicy() {} std::shared_ptr DefaultPlatformPolicy::create_window( const anbox::wm::Task::Id &task, const anbox::graphics::Rect &frame) { - DEBUG(""); return std::make_shared<::NullWindow>(task, frame); } } // namespace wm diff --git a/tests/anbox/graphics/CMakeLists.txt b/tests/anbox/graphics/CMakeLists.txt index 8e7c8ad..85b07c2 100644 --- a/tests/anbox/graphics/CMakeLists.txt +++ b/tests/anbox/graphics/CMakeLists.txt @@ -1,2 +1,3 @@ ANBOX_ADD_TEST(buffer_queue_tests buffer_queue_tests.cpp) ANBOX_ADD_TEST(buffered_io_stream_tests buffered_io_stream_tests.cpp) +ANBOX_ADD_TEST(layer_composer_tests layer_composer_tests.cpp) diff --git a/tests/anbox/graphics/layer_composer_tests.cpp b/tests/anbox/graphics/layer_composer_tests.cpp new file mode 100644 index 0000000..c0eff82 --- /dev/null +++ b/tests/anbox/graphics/layer_composer_tests.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2016 Simon Fels + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + */ + +// Both includes need to go first as otherwise they can conflict with EGL.h +// being included by the following includes. +#include +#include + +#include "anbox/wm/default_platform_policy.h" +#include "anbox/wm/manager.h" +#include "anbox/wm/window_state.h" + +#include "anbox/graphics/layer_composer.h" + +using namespace ::testing; + +namespace { +class MockRenderer : public anbox::graphics::Renderer { + public: + MOCK_METHOD3(draw, bool(EGLNativeWindowType, const anbox::graphics::Rect&, + const RenderableList&)); +}; +} + +namespace anbox { +namespace graphics { +TEST(LayerComposer, FindsNoSuitableWindowForLayer) { + auto renderer = std::make_shared(); + + // The default policy will create a dumb window instance when requested + // from the manager. + auto platform_policy = std::make_shared(); + auto wm = std::make_shared(platform_policy); + + auto single_window = wm::WindowState{ + wm::Display::Id{1}, + true, + graphics::Rect{0, 0, 1024, 768}, + "org.anbox.test.1", + wm::Task::Id{1}, + wm::Stack::Freeform, + }; + + wm->apply_window_state_update({single_window}, {}); + + LayerComposer composer(renderer, wm); + + // A single renderable which has a different task id then the window we know + // about + RenderableList renderables = { + {"org.anbox.surface.2", 0, {0, 0, 1024, 768}, {0, 0, 1024, 768}}, + }; + + // The renderer should not be called for a layer which doesn't exist + EXPECT_CALL(*renderer, draw(_, _, _)).Times(0); + + composer.submit_layers(renderables); +} + +TEST(LayerComposer, MapsLayersToWindows) { + auto renderer = std::make_shared(); + + // The default policy will create a dumb window instance when requested + // from the manager. + auto platform_policy = std::make_shared(); + auto wm = std::make_shared(platform_policy); + + auto first_window = wm::WindowState{ + wm::Display::Id{1}, + true, + graphics::Rect{0, 0, 1024, 768}, + "org.anbox.foo", + wm::Task::Id{1}, + wm::Stack::Freeform, + }; + + auto second_window = wm::WindowState{ + wm::Display::Id{1}, + true, + graphics::Rect{300, 400, 1324, 1168}, + "org.anbox.bar", + wm::Task::Id{2}, + wm::Stack::Freeform, + }; + + wm->apply_window_state_update({first_window, second_window}, {}); + + LayerComposer composer(renderer, wm); + + // A single renderable which has a different task id then the window we know + // about + RenderableList renderables = { + {"org.anbox.surface.1", 0, {0, 0, 1024, 768}, {0, 0, 1024, 768}}, + {"org.anbox.surface.2", 1, {0, 0, 1024, 768}, {0, 0, 1024, 768}}, + }; + + RenderableList first_window_renderables{ + {"org.anbox.surface.1", 0, {0, 0, 1024, 768}, {0, 0, 1024, 768}}, + }; + + RenderableList second_window_renderables{ + {"org.anbox.surface.2", 1, {0, 0, 1024, 768}, {0, 0, 1024, 768}}, + }; + + EXPECT_CALL(*renderer, draw(_, Rect{0, 0, first_window.frame().width(), + first_window.frame().height()}, + first_window_renderables)) + .Times(1) + .WillOnce(Return(true)); + EXPECT_CALL(*renderer, draw(_, Rect{0, 0, second_window.frame().width(), + second_window.frame().height()}, + second_window_renderables)) + .Times(1) + .WillOnce(Return(true)); + + composer.submit_layers(renderables); +} + +TEST(LayerComposer, WindowPartiallyOffscreen) { + auto renderer = std::make_shared(); + + // The default policy will create a dumb window instance when requested + // from the manager. + auto platform_policy = std::make_shared(); + auto wm = std::make_shared(platform_policy); + + auto window = wm::WindowState{ + wm::Display::Id{1}, + true, + graphics::Rect{-100, -100, 924, 668}, + "org.anbox.foo", + wm::Task::Id{1}, + wm::Stack::Freeform, + }; + + wm->apply_window_state_update({window}, {}); + + LayerComposer composer(renderer, wm); + + // Window is build out of two layers where one is placed inside the other + // but the layer covering the whole window is placed with its top left + // origin outside of the visible display area. + RenderableList renderables = { + {"org.anbox.surface.1", 0, {-100, -100, 924, 668}, {0, 0, 1024, 768}}, + {"org.anbox.surface.1", 1, {0, 0, 100, 200}, {0, 0, 100, 200}}, + }; + + RenderableList expected_renderables{ + {"org.anbox.surface.1", 0, {0, 0, 1024, 768}, {0, 0, 1024, 768}}, + {"org.anbox.surface.1", 1, {100, 100, 200, 300}, {0, 0, 100, 200}}, + }; + + EXPECT_CALL(*renderer, draw(_, Rect{0, 0, + window.frame().width(), + window.frame().height()}, + expected_renderables)) + .Times(1) + .WillOnce(Return(true)); + + composer.submit_layers(renderables); +} + +TEST(LayerComposer, PopupShouldNotCauseWindowLayerOffset) { + auto renderer = std::make_shared(); + + // The default policy will create a dumb window instance when requested + // from the manager. + auto platform_policy = std::make_shared(); + auto wm = std::make_shared(platform_policy); + + auto window = wm::WindowState{ + wm::Display::Id{1}, + true, + graphics::Rect{1120, 270, 2144, 1038}, + "org.anbox.foo", + wm::Task::Id{3}, + wm::Stack::Freeform, + }; + + wm->apply_window_state_update({window}, {}); + + LayerComposer composer(renderer, wm); + + // Having two renderables where the second smaller one overlaps the bigger + // one and goes a bit offscreen. This should be still placed correctly and + // the small layer should go offscreen as it is supposed to. This typically + // happens when a popup window appears which has a shadow attached which goes + // out of the window area. In our case this is not possible as the area the + // window has available is static. + RenderableList renderables = { + {"org.anbox.surface.3", 0, {1120,270,2144,1038}, {0, 0, 1024, 768}}, + {"org.anbox.surface.3", 1, {1904, 246, 2164, 406}, {0, 0, 260, 160}}, + }; + + RenderableList expected_renderables{ + {"org.anbox.surface.3", 0, {0, 0, 1024, 768}, {0, 0, 1024, 768}}, + {"org.anbox.surface.3", 1, {784, -24, 1044, 136}, {0, 0, 260, 160}}, + }; + + EXPECT_CALL(*renderer, draw(_, Rect{0, 0, + window.frame().width(), + window.frame().height()}, + expected_renderables)) + .Times(1) + .WillOnce(Return(true)); + + composer.submit_layers(renderables); +} + +} // namespace graphics +} // namespace anbox