/*
* \brief Simple framework for rendering an animated scene
* \author Norman Feske
* \date 2015-06-22
*
* The 'Scene' class template contains the code for setting up a GUI
* view with a triple-buffer for rendering tearing-free animations.
* A derrived class implements the to-be-displayed content in the virtual
* 'render' method.
*/
/*
* Copyright (C) 2015-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _INCLUDE__NANO3D__SCENE_H_
#define _INCLUDE__NANO3D__SCENE_H_
/* Genode includes */
#include
#include
#include
#include
#include
#include
#include
namespace Nano3d {
struct Input_handler;
template class Scene;
}
struct Nano3d::Input_handler : Genode::Interface
{
virtual void handle_input(Input::Event const [], unsigned num_events) = 0;
};
template
class Nano3d::Scene
{
public:
class Unsupported_color_depth { };
using Pixel_alpha8 = Genode::Pixel_alpha8;
virtual void render(Genode::Surface &pixel_surface,
Genode::Surface &alpha_surface) = 0;
private:
/**
* Noncopyable
*/
Scene(Scene const &);
Scene &operator = (Scene const &);
Genode::Env &_env;
/**
* Position and size of GUI view
*/
Gui::Point const _pos;
Gui::Area const _size;
Gui::Connection _gui { _env };
struct Mapped_framebuffer
{
enum { NUM_BUFFERS = 3 };
Genode::Region_map &rm;
static Framebuffer::Session &
_init_framebuffer(Gui::Connection &gui,
Gui::Area const size)
{
/*
* Dimension the virtual framebuffer 3 times as high as the
* visible view because it contains the visible buffer, the
* front buffer, and the back buffer.
*/
bool const use_alpha = true;
unsigned const height = size.h*NUM_BUFFERS;
gui.buffer(Framebuffer::Mode { .area = { size.w, height } },
use_alpha);
return *gui.framebuffer();
}
Framebuffer::Session &framebuffer;
Framebuffer::Mode const mode = framebuffer.mode();
/**
* Return visible size
*/
Gui::Area size() const
{
return Gui::Area(mode.area.w, mode.area.h/NUM_BUFFERS);
}
Genode::Attached_dataspace ds { rm, framebuffer.dataspace() };
PT *pixel_base(unsigned i)
{
return (PT *)(ds.local_addr() + i*size().count());
}
Pixel_alpha8 *alpha_base(unsigned i)
{
Pixel_alpha8 * const alpha_base =
(Pixel_alpha8 *)(ds.local_addr() + NUM_BUFFERS*size().count());
return alpha_base + i*size().count();
}
/**
* Set or clear the input mask for the virtual framebuffer
*/
void input_mask(bool input_enabled)
{
/*
* The input-mask buffer follows the alpha buffer. Hence, we
* can obtain the base address by requesting the base of
* the (non-exiting) alpha buffer (using NUM_BUFFERS as index)
* beyond the actual alpha buffers.
*/
Genode::memset(alpha_base(NUM_BUFFERS), input_enabled,
NUM_BUFFERS*size().count());
}
Mapped_framebuffer(Gui::Connection &gui, Gui::Area size,
Genode::Region_map &rm)
:
rm(rm), framebuffer(_init_framebuffer(gui, size))
{ }
} _framebuffer { _gui, _size, _env.rm() };
Gui::Session::View_handle _view_handle = _gui.create_view();
using Pixel_surface = Genode::Surface;
using Alpha_surface = Genode::Surface;
struct Surface
{
Pixel_surface pixel;
Alpha_surface alpha;
Surface(PT *pixel_base, Genode::Pixel_alpha8 *alpha_base,
Genode::Surface_base::Area size)
:
pixel(pixel_base, size), alpha(alpha_base, size)
{ }
Genode::Surface_base::Area size() const { return pixel.size(); }
template
void _clear(Genode::Surface &surface)
{
Genode::size_t n = surface.size().count();
for (T *dst = surface.addr(); n--; dst++)
*dst = { };
}
void clear()
{
_clear(pixel);
_clear(alpha);
}
};
Surface _surface_0 { _framebuffer.pixel_base(0), _framebuffer.alpha_base(0),
_framebuffer.size() };
Surface _surface_1 { _framebuffer.pixel_base(1), _framebuffer.alpha_base(1),
_framebuffer.size() };
Surface _surface_2 { _framebuffer.pixel_base(2), _framebuffer.alpha_base(2),
_framebuffer.size() };
Surface *_surface_visible = &_surface_0;
Surface *_surface_front = &_surface_1;
Surface *_surface_back = &_surface_2;
bool _do_sync = false;
Timer::Connection _timer { _env };
Genode::Attached_dataspace _input_ds { _env.rm(), _gui.input()->dataspace() };
Input_handler *_input_handler_callback = nullptr;
void _handle_input()
{
if (!_input_handler_callback)
return;
while (int num = _gui.input()->flush()) {
auto const *ev_buf = _input_ds.local_addr();
if (_input_handler_callback)
_input_handler_callback->handle_input(ev_buf, num);
}
}
Genode::Signal_handler _input_handler {
_env.ep(), *this, &Scene::_handle_input };
void _swap_back_and_front_surfaces()
{
Surface *tmp = _surface_back;
_surface_back = _surface_front;
_surface_front = tmp;
}
void _swap_visible_and_front_surfaces()
{
Surface *tmp = _surface_visible;
_surface_visible = _surface_front;
_surface_front = tmp;
}
void _handle_period()
{
if (_do_sync)
return;
_surface_back->clear();
render(_surface_back->pixel, _surface_back->alpha);
_swap_back_and_front_surfaces();
/* swap front and back buffers on next sync */
_do_sync = true;
}
Genode::Signal_handler _periodic_handler {
_env.ep(), *this, &Scene::_handle_period };
void _handle_sync()
{
/* rendering of scene is not complete, yet */
if (!_do_sync)
return;
_swap_visible_and_front_surfaces();
_swap_back_and_front_surfaces();
int const h = _framebuffer.size().h;
int const buf_y = (_surface_visible == &_surface_0) ? 0
: (_surface_visible == &_surface_1) ? -h
: -2*h;
Gui::Point const offset(0, buf_y);
_gui.enqueue(_view_handle, offset);
_gui.execute();
_do_sync = false;
}
Genode::Signal_handler _sync_handler {
_env.ep(), *this, &Scene::_handle_sync };
using Command = Gui::Session::Command;
public:
Scene(Genode::Env &env, Genode::uint64_t update_rate_ms,
Gui::Point pos, Gui::Area size)
:
_env(env), _pos(pos), _size(size)
{
using View_handle = Gui::Session::View_handle;
Gui::Rect rect(_pos, _size);
_gui.enqueue(_view_handle, rect);
_gui.enqueue(_view_handle, View_handle());
_gui.execute();
_gui.input()->sigh(_input_handler);
_timer.sigh(_periodic_handler);
_timer.trigger_periodic(1000*update_rate_ms);
_framebuffer.framebuffer.sync_sigh(_sync_handler);
}
virtual ~Scene() { }
Genode::uint64_t elapsed_ms() const { return _timer.elapsed_ms(); }
void input_handler(Input_handler *input_handler)
{
_framebuffer.input_mask(input_handler ? true : false);
_input_handler_callback = input_handler;
}
};
#endif /* _INCLUDE__NANO3D__SCENE_H_ */