pandemonium_engine_minimal/platform/frt/video_bcm.cpp
2023-12-14 21:54:22 +01:00

513 lines
13 KiB
C++

// video_bcm.cc
/*
* FRT - A Godot platform targeting single board computers
* Copyright (c) 2017-2019 Emanuele Fornara
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "frt.h"
#include <stdio.h>
#include <sys/time.h>
#include "dl/bcm.gen.h"
#include "dl/gles2.gen.h"
#include "bits/egl_base_context.h"
#define ELEMENT_CHANGE_DEST_RECT (1 << 2)
#define NULL_ALPHA NULL
#define NULL_CLAMP NULL
namespace frt {
class Display {
private:
static const int lcd = 0;
DISPMANX_DISPLAY_HANDLE_T handle;
Vec2 size;
bool initialized;
public:
Display()
: initialized(false) {}
DISPMANX_DISPLAY_HANDLE_T get_handle() const { return handle; }
Vec2 get_size() const { return size; }
bool init() {
if (initialized)
return size.x;
bcm_host_init();
initialized = true;
uint32_t width, height;
if (graphics_get_display_size(lcd, &width, &height) >= 0) {
size.x = (int)width;
size.y = (int)height;
}
return size.x;
}
bool open() {
handle = vc_dispmanx_display_open(lcd);
// TODO: 0 for error?
return true;
}
void cleanup() {
vc_dispmanx_display_close(handle);
}
};
class Element {
private:
bool has_resource;
bool added;
protected:
DISPMANX_RESOURCE_HANDLE_T resource;
DISPMANX_ELEMENT_HANDLE_T element;
VC_RECT_T src;
VC_RECT_T dst;
int layer;
void create_resource(VC_IMAGE_TYPE_T type, int width, int height, int pitch, uint8_t *data) {
uint32_t ptr;
resource = vc_dispmanx_resource_create(type, width, height, &ptr);
VC_RECT_T rect;
vc_dispmanx_rect_set(&rect, 0, 0, width, height);
vc_dispmanx_resource_write_data(resource, type, pitch, data, &rect);
has_resource = true;
}
public:
Element(int layer_)
: has_resource(false), added(false), layer(layer_) {}
DISPMANX_ELEMENT_HANDLE_T get_element() const { return element; }
void add(DISPMANX_UPDATE_HANDLE_T update, const Display &display) {
element = vc_dispmanx_element_add(
update, display.get_handle(), layer, &dst, resource, &src,
DISPMANX_PROTECTION_NONE, NULL_ALPHA, NULL_CLAMP,
DISPMANX_NO_ROTATE);
added = true;
}
void remove(DISPMANX_UPDATE_HANDLE_T update) {
if (!added)
return;
vc_dispmanx_element_remove(update, element);
added = false;
}
void delete_resource() {
if (!has_resource)
return;
vc_dispmanx_resource_delete(resource);
has_resource = false;
}
void show(DISPMANX_UPDATE_HANDLE_T update, const Display &display, bool enable) {
if (added_to_display() && !enable)
remove(update);
else if (!added_to_display() && enable)
add(update, display);
}
bool added_to_display() const { return added; }
};
class Background : public Element {
private:
static const VC_IMAGE_TYPE_T type = VC_IMAGE_RGB565;
static const int pixel_size = 2;
static const int width = 16;
static const int height = 16;
static const int pitch = width * pixel_size;
uint8_t data[width * height * pixel_size];
public:
Background()
: Element(100) {
memset(data, 0, sizeof(data));
}
void create_resource() {
vc_dispmanx_rect_set(&src, 0, 0, width << 16, height << 16);
Element::create_resource(type, width, height, pitch, data);
}
void set_metrics(const Display &display) {
Vec2 dpy_size = display.get_size();
vc_dispmanx_rect_set(&dst, 0, 0, dpy_size.x, dpy_size.y);
}
};
static void update_cb(DISPMANX_UPDATE_HANDLE_T u, void *arg) {
}
class View : public Element {
private:
enum Gravity {
Center,
TopLeft,
TopRight,
BottomRight,
BottomLeft,
};
friend class Pointer;
Vec2 size;
Vec2 dpy_size;
int ox, oy;
float scalex, scaley;
bool fullscreen;
Gravity gravity;
void set_dst_fullscreen() {
Vec2 win;
double req_aspect = (double)size.x / size.y;
double scr_aspect = (double)dpy_size.x / dpy_size.y;
if (req_aspect >= scr_aspect) {
win.x = dpy_size.x;
win.y = (int)(dpy_size.x / req_aspect);
} else {
win.x = (int)(dpy_size.y * req_aspect);
win.y = dpy_size.y;
}
ox = (dpy_size.x - win.x) / 2;
oy = (dpy_size.y - win.y) / 2;
scalex = (float)size.x / win.x;
scaley = (float)size.y / win.y;
vc_dispmanx_rect_set(&dst, ox, oy, win.x, win.y);
}
void set_dst_window() {
switch (gravity) {
case Center:
ox = (dpy_size.x - size.x) / 2;
break;
case TopLeft:
case BottomLeft:
ox = 0;
break;
case TopRight:
case BottomRight:
ox = dpy_size.x - size.x;
break;
}
switch (gravity) {
case Center:
oy = (dpy_size.y - size.y) / 2;
break;
case TopLeft:
case TopRight:
oy = 0;
break;
case BottomLeft:
case BottomRight:
oy = dpy_size.y - size.y;
break;
}
scalex = 1.0f;
scaley = 1.0f;
vc_dispmanx_rect_set(&dst, ox, oy, size.x, size.y);
}
void set_dst() {
if (fullscreen)
set_dst_fullscreen();
else
set_dst_window();
}
void schedule_update(DISPMANX_UPDATE_HANDLE_T update) {
vc_dispmanx_element_change_attributes(
update, element, ELEMENT_CHANGE_DEST_RECT, 0, 255, &dst,
&src, 0, DISPMANX_NO_ROTATE);
}
public:
View()
: Element(101), fullscreen(true), gravity(Center) {}
Vec2 get_size() const { return size; }
void set_metrics(const Display &display, const Vec2 &size) {
this->size = size;
vc_dispmanx_rect_set(&src, 0, 0, size.x << 16, size.y << 16);
dpy_size = display.get_size();
set_dst();
}
bool toggle_fullscreen(DISPMANX_UPDATE_HANDLE_T update) {
fullscreen = !fullscreen;
set_dst();
schedule_update(update);
return fullscreen;
}
bool toggle_window(DISPMANX_UPDATE_HANDLE_T update) {
if (fullscreen)
fullscreen = false;
else if (gravity == BottomLeft)
gravity = Center;
else
gravity = (Gravity)((int)gravity + 1);
set_dst();
schedule_update(update);
return fullscreen;
}
};
#include "import/cursor.h"
class Pointer : public Element {
private:
static const VC_IMAGE_TYPE_T type = VC_IMAGE_RGBA32;
static const int pixel_size = 4;
static const int width = 16;
static const int height = 16;
static const int pitch = width * pixel_size;
static const uint32_t shape_color = 0xff000000;
static const uint32_t mask_color = 0xffffffff;
static const uint32_t transparent_color = 0x00000000;
static const int layer_visible = 102;
static const int min_dt_us = 16667;
uint8_t data[width * height * pixel_size];
int x, y;
bool visible, need_updating;
struct timeval last_update;
inline uint8_t *fill_byte(uint8_t *p, int shape, int mask) {
uint32_t color;
int bitmask = 0x01;
for (int i = 0; i < 8; i++) {
if (shape & bitmask)
color = shape_color;
else if (mask & bitmask)
color = mask_color;
else
color = transparent_color;
memcpy(p, &color, 4);
p += 4;
bitmask <<= 1;
}
return p;
}
void convert_bitmaps(const uint8_t *shape, const uint8_t *mask) {
uint8_t *p = data;
for (int y = 0; y < 32; y++)
p = fill_byte(p, *shape++, *mask++);
}
void fill_dst() {
vc_dispmanx_rect_set(&dst, x - left_ptr_x_hot, y - left_ptr_y_hot,
width, height);
}
public:
Pointer()
: Element(layer_visible), visible(true) {
convert_bitmaps(left_ptr_bits, left_ptrmsk_bits);
}
void create_resource() {
vc_dispmanx_rect_set(&src, 0, 0, width << 16, height << 16);
Element::create_resource(type, width, height, pitch, data);
}
void set_metrics(const Display &display) {
Vec2 dpy_size = display.get_size();
x = dpy_size.x - 1;
y = dpy_size.y - 1;
fill_dst();
gettimeofday(&last_update, 0);
}
Vec2 set_pos(const View &view, const Vec2 &screen) {
Vec2 res;
res.x = (int)((screen.x - view.ox) * view.scalex);
res.y = (int)((screen.y - view.oy) * view.scaley);
x = screen.x;
y = screen.y;
fill_dst();
if (visible)
need_updating = true;
return res;
}
void set_visible(const Display &display, bool visible) {
if (this->visible == visible)
return;
this->visible = visible;
DISPMANX_UPDATE_HANDLE_T update;
update = vc_dispmanx_update_start(1);
show(update, display, visible);
vc_dispmanx_update_submit_sync(update);
}
void schedule_update_if_needed(bool vsync) {
if (!need_updating || !visible)
return;
if (!vsync) {
struct timeval now;
struct timeval dt;
gettimeofday(&now, 0);
timersub(&now, &last_update, &dt);
if (dt.tv_sec == 0 && dt.tv_usec < min_dt_us)
return;
last_update = now;
}
DISPMANX_UPDATE_HANDLE_T update;
update = vc_dispmanx_update_start(1);
vc_dispmanx_element_change_attributes(
update, element, ELEMENT_CHANGE_DEST_RECT, 0, 255, &dst,
&src, 0, DISPMANX_NO_ROTATE);
vc_dispmanx_update_submit(update, update_cb, 0);
need_updating = false;
}
};
class EGLDispmanxContext : public EGLBaseContext {
private:
EGL_DISPMANX_WINDOW_T nativewindow;
public:
void create_surface(const View &view) {
nativewindow.element = view.get_element();
Vec2 size = view.get_size();
nativewindow.width = size.x;
nativewindow.height = size.y;
surface = eglCreateWindowSurface(display, config,
(EGLNativeWindowType)&nativewindow, 0);
if (surface == EGL_NO_SURFACE)
fatal("video_bcm: eglCreateWindowSurface failed.");
}
};
class VideoBCM : public Video, public ContextGL {
private:
Display dpymnx;
EGLDispmanxContext egl;
Background background;
View view;
Pointer pointer;
bool initialized;
Vec2 screen_size;
Vec2 view_size;
bool vsync;
void init_egl_and_dpymnx(Vec2 size) {
egl.init(2);
dpymnx.open();
background.create_resource();
pointer.create_resource();
background.set_metrics(dpymnx);
view.set_metrics(dpymnx, size);
pointer.set_metrics(dpymnx);
DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0);
background.add(update, dpymnx);
view.add(update, dpymnx);
pointer.add(update, dpymnx);
vc_dispmanx_update_submit_sync(update);
egl.create_surface(view);
egl.make_current();
initialized = true;
}
void cleanup_egl_and_dpymnx() {
if (!initialized)
return;
egl.destroy_surface();
DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0);
background.remove(update);
view.remove(update);
pointer.remove(update);
vc_dispmanx_update_submit_sync(update);
background.delete_resource();
pointer.delete_resource();
dpymnx.cleanup();
egl.cleanup();
initialized = false;
}
public:
// Module
VideoBCM()
: initialized(false), vsync(true) {}
const char *get_id() const { return "video_bcm"; }
bool probe() {
if (!frt_load_bcm("libbcm_host.so"))
return false;
if (!frt_load_gles2("libbrcmGLESv2.so"))
return false;
if (!frt_load_egl("libbrcmEGL.so"))
return false;
if (!dpymnx.init())
return false;
screen_size = dpymnx.get_size();
return true;
}
void cleanup() {
cleanup_egl_and_dpymnx();
}
bool handle_meta(int gd_code, bool pressed) {
DISPMANX_UPDATE_HANDLE_T update;
bool fullscreen;
if (!pressed)
return false;
switch (gd_code) {
case 'F':
update = vc_dispmanx_update_start(1);
fullscreen = view.toggle_fullscreen(update);
background.show(update, dpymnx, fullscreen);
vc_dispmanx_update_submit(update, update_cb, 0);
return true;
case 'W':
update = vc_dispmanx_update_start(1);
fullscreen = view.toggle_window(update);
background.show(update, dpymnx, fullscreen);
vc_dispmanx_update_submit(update, update_cb, 0);
return true;
default:
return false;
}
}
// Video
Vec2 get_screen_size() const { return screen_size; }
Vec2 get_view_size() const { return view_size; }
void set_title(const char *title) {}
Vec2 move_pointer(const Vec2 &screen) {
return pointer.set_pos(view, screen);
}
void show_pointer(bool enable) {
pointer.set_visible(dpymnx, enable);
}
ContextGL *create_the_gl_context(int version, Vec2 size) {
if (App::instance()->get_bool_param("blacklist_video_bcm"))
fatal("video_bcm: this game requires the vc4 driver.");
if (version != 2)
return 0;
view_size = size;
return this;
}
bool provides_quit() { return false; }
// ContextGL
void release_current() {
egl.release_current();
}
void make_current() {
egl.make_current();
}
void swap_buffers() {
pointer.schedule_update_if_needed(vsync);
egl.swap_buffers();
}
int get_window_width() { return view_size.x; }
int get_window_height() { return view_size.y; }
bool initialize() {
init_egl_and_dpymnx(view_size);
return true;
}
void set_use_vsync(bool use) {
egl.swap_interval(use ? 1 : 0);
vsync = use;
}
bool is_using_vsync() const { return vsync; }
};
FRT_REGISTER(VideoBCM)
} // namespace frt