fb_sdl: move SDL event loop to dedicated thread

This patch is a preparatory step for relaxing the strictly periodic
operation of fb_sdl. With the new design, the SDL event loop can block
for events while Genode's main entrypoint stays receptive for I/O.
The main entrypoint can interact with the SDL thread by injecting
SDL user events.

The patch also replaces the full-screen clearing and update of the
SDL window by an update of the captured bounding box only. This reduces
the CPU load of fb_sdl when idle. When updating a small part of the
screen (e.g., when moving the mouse only), the load is still rather
heavy though.

Issue #5344
This commit is contained in:
Norman Feske 2024-09-16 15:46:17 +02:00 committed by Christian Helmuth
parent 3f1759a4d1
commit 2d3c2fc258

View File

@ -6,7 +6,7 @@
*/
/*
* Copyright (C) 2006-2017 Genode Labs GmbH
* Copyright (C) 2006-2024 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.
@ -31,63 +31,100 @@
namespace Fb_sdl {
class Main;
class Sdl;
using namespace Genode;
}
/* fatal exceptions */
struct Sdl_init_failed : Genode::Exception { };
struct Sdl_videodriver_not_supported : Genode::Exception { };
struct Sdl_createwindow_failed : Genode::Exception { };
struct Sdl_createrenderer_failed : Genode::Exception { };
struct Sdl_creatergbsurface_failed : Genode::Exception { };
struct Sdl_createtexture_failed : Genode::Exception { };
struct Fb_sdl::Main
{
Env &_env;
Attached_rom_dataspace _config { _env, "config" };
Timer::Connection _timer { _env };
Event::Connection _event { _env };
using Area = Capture::Area;
using Point = Capture::Area;
using Rect = Capture::Rect;
using Pixel = Capture::Pixel;
using Affected_rects = Capture::Session::Affected_rects;
using Event_batch = Event::Session_client::Batch;
void _init_sdl()
static constexpr int USER_EVENT_CAPTURE_WAKEUP = 99;
}
/**
* Interplay with libSDL
*/
struct Fb_sdl::Sdl : Noncopyable
{
Event::Connection &_event;
Capture::Connection &_capture;
Region_map &_rm;
struct Ticks { Uint32 ms; };
struct Attr
{
/*
* Initialize libSDL window
*/
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
error("SDL_Init failed (", Genode::Cstring(SDL_GetError()), ")");
throw Sdl_init_failed();
Area initial_size;
double fps; /* frames per second */
unsigned idle; /* disable capturing after 'idle' frames of no progress */
static Attr from_xml(Xml_node const &node)
{
return {
.initial_size = { .w = node.attribute_value("width", 1024u),
.h = node.attribute_value("height", 768u) },
.fps = node.attribute_value("fps", 60.0),
.idle = node.attribute_value("idle", ~0U)
};
}
SDL_ShowCursor(0);
Ticks period() const
{
return { (fps > 0) ? unsigned(1000.0/fps) : 20u };
}
};
Attr const _attr;
/* fatal exceptions */
struct Init_failed : Exception { };
struct Createthread_failed : Exception { };
struct Videodriver_not_supported : Exception { };
struct Createwindow_failed : Exception { };
struct Createrenderer_failed : Exception { };
struct Creatergbsurface_failed : Exception { };
struct Createtexture_failed : Exception { };
void _thread();
static int _entry(void *data_ptr)
{
((Sdl *)data_ptr)->_thread();
return 0;
}
bool const _sdl_initialized = ( _init_sdl(), true );
SDL_Thread &_init_thread()
{
SDL_Thread *ptr = SDL_CreateThread(_entry, "SDL", this);
if (ptr)
return *ptr;
struct Sdl_window
throw Createthread_failed();
}
SDL_Thread &_sdl_thread = _init_thread();
struct Window
{
Area const _initial_size;
SDL_Renderer &_sdl_renderer = _init_sdl_renderer();
SDL_Renderer &renderer = _init_renderer();
SDL_Renderer &_init_sdl_renderer()
SDL_Renderer &_init_renderer()
{
unsigned const window_flags = 0;
SDL_Window *window_ptr = SDL_CreateWindow("fb_sdl", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, _initial_size.w, _initial_size.h, window_flags);
SDL_Window * const window_ptr =
SDL_CreateWindow("fb_sdl", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
_initial_size.w, _initial_size.h, window_flags);
if (!window_ptr) {
error("SDL_CreateWindow failed (", Genode::Cstring(SDL_GetError()), ")");
throw Sdl_createwindow_failed();
error("SDL_CreateWindow failed (", Cstring(SDL_GetError()), ")");
throw Createwindow_failed();
}
SDL_SetWindowResizable(window_ptr, SDL_TRUE);
@ -96,35 +133,30 @@ struct Fb_sdl::Main
unsigned const renderer_flags = SDL_RENDERER_SOFTWARE;
SDL_Renderer *renderer_ptr = SDL_CreateRenderer(window_ptr, index, renderer_flags);
if (!renderer_ptr) {
error("SDL_CreateRenderer failed (", Genode::Cstring(SDL_GetError()), ")");
throw Sdl_createrenderer_failed();
error("SDL_CreateRenderer failed (", Cstring(SDL_GetError()), ")");
throw Createrenderer_failed();
}
return *renderer_ptr;
}
Sdl_window(Area size) : _initial_size(size) { }
Window(Area size) : _initial_size(size) { }
~Sdl_window()
~Window()
{
SDL_DestroyRenderer(&_sdl_renderer);
}
SDL_Renderer &renderer()
{
return _sdl_renderer;
SDL_DestroyRenderer(&renderer);
}
};
struct Sdl_screen
struct Screen
{
Area const size;
SDL_Renderer &renderer;
SDL_Surface &_sdl_surface = _init_sdl_surface();
SDL_Texture &_sdl_texture = _init_sdl_texture();
SDL_Surface &_surface = _init_surface();
SDL_Texture &_texture = _init_texture();
SDL_Surface &_init_sdl_surface()
SDL_Surface &_init_surface()
{
unsigned const flags = 0;
unsigned const bpp = 32;
@ -133,132 +165,215 @@ struct Fb_sdl::Main
unsigned const blue_mask = 0x000000FF;
unsigned const alpha_mask = 0xFF000000;
SDL_Surface *surface_ptr = SDL_CreateRGBSurface(flags, size.w, size.h, bpp, red_mask, green_mask, blue_mask, alpha_mask);
SDL_Surface * const surface_ptr =
SDL_CreateRGBSurface(flags, size.w, size.h, bpp,
red_mask, green_mask, blue_mask, alpha_mask);
if (!surface_ptr) {
error("SDL_CreateRGBSurface failed (", Genode::Cstring(SDL_GetError()), ")");
throw Sdl_creatergbsurface_failed();
error("SDL_CreateRGBSurface failed (", Cstring(SDL_GetError()), ")");
throw Creatergbsurface_failed();
}
return *surface_ptr;
}
SDL_Texture &_init_sdl_texture()
SDL_Texture &_init_texture()
{
SDL_Texture *texture_ptr = SDL_CreateTexture(&renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, size.w, size.h);
SDL_Texture * const texture_ptr =
SDL_CreateTexture(&renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, size.w, size.h);
if (!texture_ptr) {
error("SDL_CreateTexture failed (", Genode::Cstring(SDL_GetError()), ")");
throw Sdl_createtexture_failed();
error("SDL_CreateTexture failed (", Cstring(SDL_GetError()), ")");
throw Createtexture_failed();
}
return *texture_ptr;
}
Sdl_screen(Area size, SDL_Renderer &renderer) : size(size), renderer(renderer) { }
Screen(Area size, SDL_Renderer &renderer) : size(size), renderer(renderer) { }
~Sdl_screen()
~Screen()
{
SDL_FreeSurface(&_sdl_surface);
SDL_DestroyTexture(&_sdl_texture);
SDL_FreeSurface(&_surface);
SDL_DestroyTexture(&_texture);
}
template <typename FN>
void with_surface(FN const &fn)
void with_surface(auto const &fn)
{
Surface<Pixel> surface { (Pixel *)_sdl_surface.pixels, size };
Surface<Pixel> surface { (Pixel *)_surface.pixels, size };
fn(surface);
}
void flush()
void flush(Capture::Rect const bounding_box)
{
SDL_UpdateTexture(&_sdl_texture, nullptr, _sdl_surface.pixels, _sdl_surface.pitch);
SDL_RenderClear(&renderer);
SDL_RenderCopy(&renderer, &_sdl_texture, nullptr, nullptr);
SDL_Rect const rect { .x = bounding_box.at.x,
.y = bounding_box.at.y,
.w = int(bounding_box.area.w),
.h = int(bounding_box.area.h) };
SDL_UpdateTexture(&_texture, nullptr, _surface.pixels, _surface.pitch);
SDL_RenderCopy(&renderer, &_texture, &rect, &rect);
SDL_RenderPresent(&renderer);
}
void flush_all() { flush(Rect { { 0, 0 }, size }); }
};
Constructible<Sdl_window> _sdl_window { };
Constructible<Sdl_screen> _sdl_screen { };
Capture::Connection _capture { _env };
Constructible<Window> _window { };
Constructible<Screen> _screen { };
Constructible<Capture::Connection::Screen> _captured_screen { };
Signal_handler<Main> _timer_handler {
_env.ep(), *this, &Main::_handle_timer };
int _mx = 0, _my = 0;
void _handle_sdl_event(Event_batch &, SDL_Event const &);
void _handle_sdl_events();
unsigned _capture_woken_up = 0;
void _update_sdl_screen_from_capture()
struct Previous_frame
{
Affected_rects const affected = _capture.capture_at(Capture::Point(0, 0));
Ticks timestamp;
Ticks remaining; /* remaining ticks to next frame */
unsigned idle; /* capture attempts without progress */
_sdl_screen->with_surface([&] (Surface<Pixel> &surface) {
Ticks age() const { return { SDL_GetTicks() - timestamp.ms }; }
};
_captured_screen->with_texture([&] (Texture<Pixel> const &texture) {
/* if constructed, the processing of a next frame is scheduled */
Constructible<Previous_frame> _previous_frame { };
affected.for_each_rect([&] (Capture::Rect const rect) {
void _schedule_next_frame()
{
_previous_frame.construct(
Previous_frame { .timestamp = { SDL_GetTicks() },
.remaining = { _attr.period() },
.idle = { } });
}
surface.clip(rect);
void _handle_event(Event_batch &, SDL_Event const &);
Blit_painter::paint(surface, texture, Capture::Point(0, 0));
});
});
bool _update_screen_from_capture()
{
bool progress = false;
_screen->with_surface([&] (Surface<Pixel> &surface) {
Rect const bounding_box = _captured_screen->apply_to_surface(surface);
progress = (bounding_box.area.count() > 0);
if (progress)
_screen->flush(bounding_box);
});
/* flush pixels in SDL window */
_sdl_screen->flush();
return progress;
}
void _handle_timer()
void _resize(Area const size)
{
_handle_sdl_events();
_update_sdl_screen_from_capture();
}
void _resize(Area size)
{
_sdl_screen.construct(size, _sdl_window->renderer());
_screen.construct(size, _window->renderer);
using Attr = Capture::Connection::Screen::Attr;
_captured_screen.construct(_capture, _env.rm(), Attr {
_captured_screen.construct(_capture, _rm, Attr {
.px = size,
.mm = { } });
_update_sdl_screen_from_capture();
_update_screen_from_capture();
_schedule_next_frame();
}
Main(Env &env) : _env(env)
{
Area size = Area(_config.xml().attribute_value("width", 1024U),
_config.xml().attribute_value("height", 768U));
_sdl_window.construct(size);
_resize(size);
_timer.sigh(_timer_handler);
_timer.trigger_periodic(100000000 / 5994); /* 59.94Hz */
}
/*
* Construction executed by the main thread
*/
Sdl(Event::Connection &event, Capture::Connection &capture, Region_map &rm,
Attr const attr)
:
_event(event), _capture(capture), _rm(rm), _attr(attr)
{ }
};
void Fb_sdl::Main::_handle_sdl_event(Event_batch &batch, SDL_Event const &event)
void Fb_sdl::Sdl::_thread()
{
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
error("SDL_Init failed (", Cstring(SDL_GetError()), ")");
throw Init_failed();
}
SDL_ShowCursor(0);
_window.construct(_attr.initial_size);
_resize(_attr.initial_size);
/* mainloop */
for (;;) {
if (_previous_frame.constructed())
SDL_WaitEventTimeout(nullptr, _previous_frame->remaining.ms);
else
SDL_WaitEvent(nullptr);
unsigned const orig_capture_woken_up = _capture_woken_up;
_event.with_batch([&] (Event_batch &batch) {
SDL_Event event { };
while (SDL_PollEvent(&event))
_handle_event(batch, event); });
Ticks const period = _attr.period();
bool const woken_up = (_capture_woken_up != orig_capture_woken_up);
bool const frame_elapsed = _previous_frame.constructed()
&& _previous_frame->age().ms >= period.ms;
if (woken_up || frame_elapsed) {
bool const progress = _update_screen_from_capture();
bool const idle = !progress && !woken_up;
if (idle) {
if (_previous_frame.constructed()) {
_previous_frame->idle++;
if ((_attr.idle < ~0u) && (_previous_frame->idle > _attr.idle))
_previous_frame.destruct(); /* stop capturing */
}
} else {
_schedule_next_frame();
}
} else {
/*
* Events occurred in-between two frames.
* Update timeout for next call of 'SDL_WaitEventTimeout'.
*/
if (_previous_frame.constructed())
_previous_frame->remaining = {
min(period.ms, period.ms - _previous_frame->age().ms) };
}
}
}
void Fb_sdl::Sdl::_handle_event(Event_batch &batch, SDL_Event const &event)
{
using namespace Input;
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
if (event.type == SDL_WINDOWEVENT) {
int w = event.window.data1;
int h = event.window.data2;
if (w < 0 || h < 0) {
warning("attempt to resize to negative size");
return;
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
int const w = event.window.data1,
h = event.window.data2;
if (w <= 0 || h <= 0) {
warning("attempt to resize to invalid size");
return;
}
_resize({ unsigned(w), unsigned(h) });
}
_resize(Area((unsigned)w, (unsigned)h));
_screen->flush_all();
return;
}
if (event.type == SDL_USEREVENT) {
if (event.user.code == USER_EVENT_CAPTURE_WAKEUP)
_capture_woken_up++;
return;
}
@ -299,7 +414,6 @@ void Fb_sdl::Main::_handle_sdl_event(Event_batch &batch, SDL_Event const &event)
}
}
/* determine event type */
switch (event.type) {
case SDL_KEYUP:
@ -328,16 +442,33 @@ void Fb_sdl::Main::_handle_sdl_event(Event_batch &batch, SDL_Event const &event)
}
void Fb_sdl::Main::_handle_sdl_events()
struct Fb_sdl::Main
{
SDL_Event event { };
Env &_env;
_event.with_batch([&] (Event_batch &batch) {
Attached_rom_dataspace _config { _env, "config" };
while (SDL_PollEvent(&event))
_handle_sdl_event(batch, event);
});
}
Event::Connection _event { _env };
Capture::Connection _capture { _env };
void _handle_capture_wakeup()
{
SDL_Event ev { };
ev.user = SDL_UserEvent { .type = SDL_USEREVENT,
.timestamp = SDL_GetTicks(),
.windowID = { },
.code = USER_EVENT_CAPTURE_WAKEUP,
.data1 = { },
.data2 = { } };
if (SDL_PushEvent(&ev) == 0)
warning("SDL_PushEvent failed (", Cstring(SDL_GetError()), ")");
}
Sdl _sdl { _event, _capture, _env.rm(), Sdl::Attr::from_xml(_config.xml()) };
Main(Env &env) : _env(env) { }
};
void Component::construct(Genode::Env &env) { static Fb_sdl::Main inst(env); }