mirror of
https://github.com/Relintai/pandemonium_engine_minimal.git
synced 2024-12-28 18:57:15 +01:00
513 lines
13 KiB
C++
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
|