651 lines
21 KiB
C++
651 lines
21 KiB
C++
#include <libdrm/drm.h>
|
|
#include <libdrm/drm_fourcc.h>
|
|
#include <errno.h>
|
|
#include <error.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/random.h>
|
|
#include <linux/input.h>
|
|
#include <math.h>
|
|
#include <sys/timerfd.h>
|
|
#include <sys/poll.h>
|
|
#include <inttypes.h>
|
|
#include "engine.h"
|
|
#include "compiled_structures.h"
|
|
#include "objids.h"
|
|
|
|
#define PLAYFIELD_WIDTH 1280
|
|
#define PLAYFIELD_HEIGHT 800
|
|
|
|
|
|
static int touchscreen_fd;
|
|
|
|
static struct navmesh null_navmesh = {0};
|
|
struct scene scenes[MAX_STACKED_SCENES];
|
|
int scene_depth = 0;
|
|
bool need_rerender = false;
|
|
|
|
|
|
|
|
#define MAX_MT_SLOTS 16
|
|
static struct mtslot {
|
|
int x, y, touch, lasttouch;
|
|
int lastx, lasty;
|
|
} mtslots[MAX_MT_SLOTS] = {0};
|
|
static struct mtslot *curslot = &mtslots[0]; // TODO: query the current value of ABS_MT_SLOT
|
|
|
|
static void handle_tap(int x, int y); // x and y in landscape orientation
|
|
static void poll_touchscreen() {
|
|
struct input_event buf[64];
|
|
int nread;
|
|
retry_read:
|
|
nread = read(touchscreen_fd, buf, sizeof buf);
|
|
if(nread < 0) {
|
|
if(errno == EINTR)
|
|
goto retry_read;
|
|
if(errno == EAGAIN)
|
|
return;
|
|
error(1, errno, "read touchscreen");
|
|
}
|
|
|
|
if(nread % sizeof(struct input_event))
|
|
error(1, 0, "odd size read from touchscreen");
|
|
|
|
for(int i = 0; i < nread; i += sizeof(struct input_event)) {
|
|
struct input_event *evt = (struct input_event*)((char*)buf + i);
|
|
//printf("type=%d code=%d value=%d\n", evt->type, evt->code, evt->value);
|
|
if(evt->type == EV_SYN && evt->code == SYN_DROPPED) error(0, 0, "got SYN_DROPPED"); // TODO: resynchronize
|
|
if(evt->type == EV_SYN && evt->code == SYN_REPORT) {
|
|
for(int j = 0; j < MAX_MT_SLOTS; j++) {
|
|
struct mtslot *s = &mtslots[j];
|
|
if(s->touch) {
|
|
//printf("slot %d x=%d y=%d\n", j, s->x, s->y);
|
|
/*uint32_t col;
|
|
switch(j & 3) {
|
|
case 0: col = 0xFFFFFFFF; break;
|
|
case 1: col = 0xFF0000FF; break;
|
|
case 2: col = 0x00FF00FF; break;
|
|
case 3: col = 0x0080FFFF; break;
|
|
}
|
|
|
|
int segs = s->lastx<0 ? 1 : (int)(0.9 + 2*sqrt((s->lastx-s->x)*(s->lastx-s->x) + (s->lasty-s->y)*(s->lasty-s->y)));
|
|
if(segs<1) segs=1;
|
|
for(int f = 0; f <= segs; f++) {
|
|
int _x, _y;
|
|
if(s->lastx != -1) {
|
|
_x = (s->lastx + (s->x-s->lastx)*f/segs);
|
|
_y = (s->lasty + (s->y-s->lasty)*f/segs);
|
|
} else {
|
|
_x = s->x;
|
|
_y = s->y;
|
|
}
|
|
for(int dy = -5; dy <= 5; dy++) {
|
|
for(int dx = -5; dx <= 5; dx++) {
|
|
if(dx*dx + dy*dy <= 5*5) {
|
|
int x = dx+_x, y = dy+_y;
|
|
if(x >= 0 && y >= 0 && x < 800 && y < 1280) {
|
|
*(uint32_t*)(fb + (y*800 + x)) = col;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
if(!s->lasttouch) {
|
|
handle_tap(s->y, 799-s->x);
|
|
}
|
|
}
|
|
s->lasttouch = s->touch;
|
|
s->lastx = s->x;
|
|
s->lasty = s->y;
|
|
}
|
|
}
|
|
if(evt->type == EV_ABS && evt->code == ABS_MT_SLOT) {
|
|
if(evt->value >= 0 && evt->value < MAX_MT_SLOTS) curslot = &mtslots[evt->value];
|
|
else curslot = NULL;
|
|
}
|
|
if(evt->type == EV_ABS && evt->code == ABS_MT_POSITION_X) {
|
|
if(curslot) curslot->x = evt->value;
|
|
}
|
|
if(evt->type == EV_ABS && evt->code == ABS_MT_POSITION_Y) {
|
|
if(curslot) curslot->y = evt->value;
|
|
}
|
|
if(evt->type == EV_ABS && evt->code == ABS_MT_TRACKING_ID) {
|
|
if(curslot) {
|
|
if(!curslot->touch) {
|
|
curslot->lastx = curslot->lasty = -1;
|
|
}
|
|
curslot->touch = evt->value != -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rendering is dumb, double-buffered with page flip, always redrawing the whole frame.
|
|
// It could be improved.
|
|
|
|
uint32_t current_animframe;
|
|
|
|
struct sprite {
|
|
int width, height;
|
|
uint32_t *pixels;
|
|
};
|
|
|
|
static uint32_t *curfb; // used during render cycle
|
|
static void blit(int x, int y, const struct sprite *spr) {
|
|
uint32_t *inbase = spr->pixels;
|
|
//printf("blit %dx%d at %d,%d\n", spr->width, spr->height, x, y);
|
|
y = PLAYFIELD_HEIGHT - spr->height - y;
|
|
if(y >= PLAYFIELD_HEIGHT || x >= PLAYFIELD_WIDTH || y <= -spr->height || x <= -spr->width) return;
|
|
if(spr->height > PLAYFIELD_HEIGHT) error(1, 0, "sprite bigger than playfield not supported"); // would need to clip top and bottom simultaneously - not implemented for now
|
|
|
|
uint32_t *outbase = curfb + x*PLAYFIELD_HEIGHT + y;
|
|
for(int dx = 0; dx < spr->width; dx++, inbase += spr->height, outbase += PLAYFIELD_HEIGHT, x++) {
|
|
if(x < 0) continue;
|
|
if(x >= PLAYFIELD_WIDTH) break;
|
|
|
|
if(y < 0) {
|
|
// TODO: loop inversion would make sense
|
|
int offset = -y;
|
|
// must have at least one pixel to copy
|
|
memcpy(outbase+offset, inbase+offset, (spr->height-offset)*4);
|
|
} else {
|
|
int ncopy = PLAYFIELD_HEIGHT-y;
|
|
if(ncopy > spr->height) ncopy = spr->height;
|
|
memcpy(outbase, inbase, ncopy*4);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void scene_clear_and_setup(int scene, int fromscene) {
|
|
memset(&top_scene, 0, sizeof(top_scene));
|
|
top_scene.id = scene;
|
|
top_scene.navmesh = &null_navmesh;
|
|
|
|
scene_setup(scene, fromscene);
|
|
}
|
|
void push_scene(int scene) {
|
|
scene_depth++;
|
|
if(scene_depth > MAX_STACKED_SCENES) error(1, 0, "maximum scene depth exceeded");
|
|
scene_clear_and_setup(scene, -1);
|
|
}
|
|
void replace_scene(int scene) {
|
|
scene_clear_and_setup(scene, top_scene.id);
|
|
}
|
|
void pop_scene() {
|
|
if(scene_depth == 1) error(1, 0, "no scenes left");
|
|
scene_depth--;
|
|
// any cleanup?
|
|
}
|
|
|
|
struct object *scene_add_object(int id, int x, int y, int width, int height, const char *pixels) {
|
|
if(id == 0) error(1, 0, "scene_add_object: id 0 is invalid");
|
|
|
|
struct object *objects = top_scene.objects;
|
|
for(int i = 0; i < MAX_OBJECTS_PER_SCENE; i++) {
|
|
if(objects[i].id == 0) {
|
|
objects[i].id = id;
|
|
objects[i].x = x;
|
|
objects[i].y = y;
|
|
objects[i].width = width;
|
|
objects[i].height = height;
|
|
objects[i].pixels = pixels;
|
|
return &objects[i];
|
|
}
|
|
}
|
|
error(1, 0, "too many game objects");
|
|
}
|
|
|
|
void scene_load_predef(const struct level_predef_data *predef) {
|
|
for(const struct level_clickregion *cr = predef->clickregions; cr->num_edges; cr++) {
|
|
struct object *obj = scene_add_object(cr->id, 0, 0, 0, 0, nullptr);
|
|
obj->clickregion = cr;
|
|
}
|
|
}
|
|
|
|
struct object *find_object_by_id(int id) {
|
|
struct object *objects = top_scene.objects;
|
|
for(int i = 0; i < MAX_OBJECTS_PER_SCENE; i++) {
|
|
if(objects[i].id == id)
|
|
return &objects[i];
|
|
}
|
|
return nullptr;
|
|
}
|
|
struct script *scene_add_script(int id, bool interrupt_existing) {
|
|
struct script *scripts = top_scene.scripts;
|
|
for(int i = 0; i < MAX_SCRIPTS_PER_SCENE; i++) {
|
|
if(scripts[i].id == id && interrupt_existing) {
|
|
// interrupt existing script?
|
|
// player walk script is safe to trivially cancel, at least
|
|
scripts[i].id = 0;
|
|
if(deliver_script_wakeup(SCRIPT_WAKEUP_OTHER_SCRIPT, id, SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED, 0, 0, 0, 0))
|
|
return NULL; // scene changed. this probably crashes the caller. should be tested or we need a better way to cancel all running operations on scene change.
|
|
}
|
|
if(scripts[i].id == 0) {
|
|
memset(&scripts[i], 0, sizeof(scripts[i]));
|
|
scripts[i].id = id;
|
|
return &scripts[i];
|
|
}
|
|
}
|
|
error(1, 0, "too many game scripts");
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
static void closest_point_inside_tri(int *x, int *y, const struct navmesh_tri *tri) {
|
|
double fx = *x, fy = *y;
|
|
int edgeHitMask = 0;
|
|
|
|
// Moving a point outside of one half-plane can move it into another half-plane, so check again.
|
|
// If this happens, we must be in a corner region.
|
|
// Repeated normal-vector moves don't necessarily bring us to the corner, but we can just hardcode the corner.
|
|
for(int j = 0; j < 2; j++) {
|
|
for(int i = 0; i < tri->num_verts; i++) {
|
|
double edgeval = fx*tri->edges[i].a + fy*tri->edges[i].b + tri->edges[i].c;
|
|
if(edgeval <= 0) {
|
|
// edge.a,b is normalized so that moving 1 unit of a and 1 unit of b changes edgeval by 1.
|
|
fx -= tri->edges[i].a*edgeval;
|
|
fy -= tri->edges[i].b*edgeval;
|
|
edgeHitMask |= 1 << i;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < tri->num_verts; i++) {
|
|
int next = (i+1) % tri->num_verts;
|
|
int mask = (1 << i) | (1 << next);
|
|
if((edgeHitMask & mask) == mask) {
|
|
*x = tri->points[next].x;
|
|
*y = tri->points[next].y;
|
|
return;
|
|
}
|
|
}
|
|
/*
|
|
switch(edgeHitMask) {
|
|
case 3: // hit edge 0-1 and 1-2
|
|
*x = tri->points[1].x;
|
|
*y = tri->points[1].y;
|
|
return;
|
|
case 5: // hit edge 0-1 and 2-0
|
|
*x = tri->points[0].x;
|
|
*y = tri->points[0].y;
|
|
return;
|
|
case 6: // hit edge 1-2 and 2-0
|
|
*x = tri->points[2].x;
|
|
*y = tri->points[2].y;
|
|
return;
|
|
}*/
|
|
|
|
// may be slightly outside of triangle due to rounding errors, but good enough for us
|
|
*x = (int)(fx + 0.5);
|
|
*y = (int)(fy + 0.5);
|
|
}
|
|
|
|
static double point_to_point_dist(int x, int y, const struct navmesh_point *pt) {
|
|
x -= pt->x;
|
|
y -= pt->y;
|
|
return sqrt(x*x + y*y);
|
|
}
|
|
|
|
// Returns distance from triangle, 0 if inside.
|
|
static double is_point_in_tri(int x, int y, const struct navmesh_tri *tri) {
|
|
/*double edgedist[3]; // positive if outside
|
|
for(int edge = 0; edge < 3; edge++)
|
|
edgedist[edge] = -(x*tri->edges[edge].a + y*tri->edges[edge].b + tri->edges[edge].c);
|
|
if(edgedist[0] < 0 && edgedist[1] < 0 && edgedist[2] < 0) return 0; // inside
|
|
if(edgedist[0] >= 0 && edgedist[1] >= 0) return point_to_point_dist(x, y, &tri->points[0]); // are these correct? shouldn't it be points 1,2,0?
|
|
if(edgedist[1] >= 0 && edgedist[2] >= 0) return point_to_point_dist(x, y, &tri->points[1]);
|
|
if(edgedist[2] >= 0 && edgedist[0] >= 0) return point_to_point_dist(x, y, &tri->points[2]);
|
|
if(edgedist[0] >= 0) return edgedist[0];
|
|
if(edgedist[1] >= 0) return edgedist[1];
|
|
if(edgedist[2] >= 0) return edgedist[2];*/
|
|
|
|
double edgedist[tri->num_verts];
|
|
bool all_inside = true;
|
|
for(int edge = 0; edge < tri->num_verts; edge++) {
|
|
edgedist[edge] = -(x*tri->edges[edge].a + y*tri->edges[edge].b + tri->edges[edge].c);
|
|
if(edgedist[edge] > 0)
|
|
all_inside = false;
|
|
}
|
|
if(all_inside) return 0;
|
|
for(int edge = 0; edge < tri->num_verts; edge++) {
|
|
int next = (edge+1)%tri->num_verts;
|
|
if(edgedist[edge] >= 0 && edgedist[next] >= 0) return point_to_point_dist(x, y, &tri->points[next]);
|
|
}
|
|
for(int edge = 0; edge < tri->num_verts; edge++)
|
|
if(edgedist[edge] >= 0)
|
|
return edgedist[edge];
|
|
|
|
return 0; // branch should be unreachable; maybe within epsilon of triangle border?
|
|
}
|
|
static int find_navmesh_tri(int x, int y, int tolerance) {
|
|
int best_tri = -1;
|
|
double best_dist = tolerance;
|
|
struct navmesh *cur_navmesh = top_scene.navmesh;
|
|
for(int i = 0; i < cur_navmesh->num_tris; i++) {
|
|
double dist = is_point_in_tri(x, y, &cur_navmesh->tris[i]);
|
|
if(dist == 0)
|
|
return i;
|
|
if(dist < best_dist) {
|
|
best_dist = dist;
|
|
best_tri = i;
|
|
}
|
|
}
|
|
return best_tri;
|
|
}
|
|
|
|
static void update_player_walk_script_on_frame(struct script *scr, int wakeupMode, int, int, int, int);
|
|
|
|
static uint32_t player_walk_generation = 0;
|
|
void start_player_walk_to_point(int targetX, int targetY) {
|
|
player_walk_generation++;
|
|
|
|
struct object *player = find_object_by_id(OBJID_PLAYER);
|
|
if(!player) error(1, 0, "start_player_walk_to_point: no player object");
|
|
|
|
int destTri = find_navmesh_tri(targetX, targetY, 999999);
|
|
if(destTri == -1) error(1, 0, "start_player_walk_to_point: no navmesh");
|
|
|
|
struct script_player_walk *player_walk_script = (struct script_player_walk *)scene_add_script(OBJID_PLAYER_WALK_SCRIPT, true);
|
|
player_walk_script->targetX = targetX;
|
|
player_walk_script->targetY = targetY;
|
|
player_walk_script->targetNavmeshTri = destTri;
|
|
player_walk_script->wakeupMode = SCRIPT_WAKEUP_VIDEO_FRAME;
|
|
player_walk_script->wakeupFn = update_player_walk_script_on_frame;
|
|
|
|
int sourceTri = find_navmesh_tri(player->x+player->width*2, player->y+player->height, 1000000);
|
|
player_walk_script->currentNavmeshTri = sourceTri;
|
|
printf("set course from triangle %d to %d\n", sourceTri, destTri);
|
|
}
|
|
|
|
void handle_tap(int x, int y) {
|
|
uint32_t walkgen = player_walk_generation;
|
|
int sceneid = top_scene.id;
|
|
struct navmesh *cur_navmesh = top_scene.navmesh;
|
|
struct object *objects = top_scene.objects;
|
|
for(int i = 0; i < MAX_OBJECTS_PER_SCENE; i++) {
|
|
if(!objects[i].id) continue;
|
|
if(objects[i].clickregion) {
|
|
const struct level_clickregion *cr = objects[i].clickregion;
|
|
for(int i = 0; i < cr->num_edges; i++) {
|
|
if(cr->edges[i].a*x + cr->edges[i].b*y + cr->edges[i].c < 0)
|
|
goto click_not_on_object;
|
|
}
|
|
} else {
|
|
if((unsigned int)(x - objects[i].x) >= objects[i].width || (unsigned int)(y - objects[i].y) >= objects[i].height)
|
|
goto click_not_on_object;
|
|
}
|
|
|
|
// clicked on the object
|
|
onclick(sceneid, objects[i].id);
|
|
if(top_scene.id != sceneid) return; // early exit if scene changed, and don't walk the player either
|
|
|
|
click_not_on_object:;
|
|
}
|
|
|
|
if(walkgen == player_walk_generation) { // no other handler moved the player
|
|
struct object *player = find_object_by_id(OBJID_PLAYER);
|
|
if(player) {
|
|
int destTri = find_navmesh_tri(x, y, 50);
|
|
if(destTri != -1) {
|
|
int targetX = x, targetY = y;
|
|
closest_point_inside_tri(&targetX, &targetY, &cur_navmesh->tris[destTri]);
|
|
start_player_walk_to_point(targetX, targetY);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int move_towards(int cur, int target, int maxrate) {
|
|
if(target > cur+maxrate) return cur+maxrate;
|
|
if(target < cur-maxrate) return cur-maxrate;
|
|
return target;
|
|
}
|
|
|
|
static void update_player_walk_script_on_frame(struct script *scr, int wakeupMode, int, int, int, int) {
|
|
int script_id = scr->id;
|
|
struct script_player_walk *player_walk_script = (struct script_player_walk *)scr;
|
|
if(!player_walk_script) return;
|
|
struct object *player = find_object_by_id(OBJID_PLAYER);
|
|
if(!player) {
|
|
player_walk_script->id = 0;
|
|
deliver_script_wakeup(SCRIPT_WAKEUP_OTHER_SCRIPT, script_id, SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED, 0, 0, 0, 0);
|
|
return;
|
|
}
|
|
struct navmesh *cur_navmesh = top_scene.navmesh;
|
|
|
|
if(player_walk_script->currentNavmeshTri == -1) {
|
|
// something weird happened. just teleport player and end movement.
|
|
player->x = player_walk_script->targetX - player->width/2;
|
|
player->y = player_walk_script->targetY - player->height;
|
|
player_walk_script->id = 0;
|
|
deliver_script_wakeup(SCRIPT_WAKEUP_OTHER_SCRIPT, script_id, SCRIPT_WAKEUP_OTHER_SCRIPT, 0, 0, 0, 0);
|
|
need_rerender = true;
|
|
} else {
|
|
int x = player->x+player->width/2;
|
|
int y = player->y+player->height;
|
|
int nmtri = player_walk_script->currentNavmeshTri;
|
|
int nextX;
|
|
int nextY;
|
|
int nextTri = -1;
|
|
if(nmtri == player_walk_script->targetNavmeshTri) {
|
|
nextX = player_walk_script->targetX;
|
|
nextY = player_walk_script->targetY;
|
|
} else {
|
|
nextTri = cur_navmesh->pathfindgrid[player_walk_script->targetNavmeshTri * cur_navmesh->num_tris + nmtri];
|
|
nextX = -1;
|
|
for(int edge = 0; edge < cur_navmesh->tris[nmtri].num_verts; edge++) {
|
|
if(cur_navmesh->tris[nmtri].edges[edge].other_tri == nextTri) {
|
|
nextX = cur_navmesh->tris[nmtri].edges[edge].center.x;
|
|
nextY = cur_navmesh->tris[nmtri].edges[edge].center.y;
|
|
goto center_found;
|
|
}
|
|
}
|
|
// shouldn't happen
|
|
player_walk_script->id = 0;
|
|
deliver_script_wakeup(SCRIPT_WAKEUP_OTHER_SCRIPT, script_id, SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED, 0, 0, 0, 0);
|
|
return;
|
|
center_found:;
|
|
}
|
|
if(nextX == x && nextY == y) {
|
|
printf("in triangle %d, next triangle %d\n", nmtri, nextTri);
|
|
if(x == player_walk_script->targetX && y == player_walk_script->targetY) {
|
|
player_walk_script->id = 0;
|
|
deliver_script_wakeup(SCRIPT_WAKEUP_OTHER_SCRIPT, script_id, SCRIPT_WAKEUP_OTHER_SCRIPT, 0, 0, 0, 0);
|
|
} else {
|
|
player_walk_script->currentNavmeshTri = nextTri; // even if -1
|
|
}
|
|
} else {
|
|
x = move_towards(x, nextX, 5);
|
|
y = move_towards(y, nextY, 2);
|
|
player->x = x - player->width/2;
|
|
player->y = y - player->height;
|
|
}
|
|
need_rerender = true;
|
|
}
|
|
}
|
|
|
|
bool deliver_script_wakeup(int wakeupMode, int wakeupArg1, int wakeupType, int arg1, int arg2, int arg3, int arg4) {
|
|
int scene = top_scene.id;
|
|
struct script *scripts = top_scene.scripts;
|
|
for(int i = 0; i < MAX_SCRIPTS_PER_SCENE; i++) {
|
|
if(scripts[i].id && scripts[i].wakeupMode == wakeupMode && scripts[i].wakeupArg1 == wakeupArg1) {
|
|
scripts[i].wakeupFn(&scripts[i], wakeupType, arg1, arg2, arg3, arg4);
|
|
if(scene_depth == 0 || top_scene.id != scene) return true; // scene changed - abort early
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void standard_scene_render(scene *s) {
|
|
struct object *objects = s->objects;
|
|
for(int i = 0; i < MAX_OBJECTS_PER_SCENE; i++) {
|
|
if(objects[i].id != 0 && objects[i].pixels) {
|
|
struct sprite spr = {.width = objects[i].width, .height = objects[i].height, .pixels = (uint32_t*)objects[i].pixels};
|
|
blit(objects[i].x, objects[i].y, &spr);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
if(getuid() != 0) {
|
|
execlp("sudo", "sudo", argv[0], NULL);
|
|
error(1, errno, "execlp");
|
|
return 1;
|
|
}
|
|
|
|
int fd = open("/dev/dri/card0", O_RDWR | O_NONBLOCK);
|
|
if(fd < 0) error(1, errno, "open display");
|
|
touchscreen_fd = open("/dev/input/event1", O_RDONLY | O_NONBLOCK);
|
|
if(touchscreen_fd < 0) error(1, errno, "open touchscreen");
|
|
|
|
if(ioctl(fd, DRM_IOCTL_SET_MASTER, NULL) < 0) error(1, errno, "set master");
|
|
|
|
// attach CRTC 51, connector 56, to a new framebuffer with a new dumbbuffer
|
|
|
|
// create two buffers
|
|
uint32_t *fbs[2];
|
|
uint32_t fb_ids[2];
|
|
for(int i = 0; i < 2; i++) {
|
|
struct drm_mode_create_dumb createdumb = {
|
|
.height = 1280,
|
|
.width = 800,
|
|
.bpp = 32,
|
|
.flags = 0,
|
|
};
|
|
if(ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &createdumb) < 0) error(1, errno, "create dumb buffer");
|
|
|
|
if(createdumb.pitch != 800*4 || createdumb.size != 1280*800*4)
|
|
error(1, 0, "unexpected size for dumb buffer (pitch=%d size=%d)", (int)createdumb.pitch, (int)createdumb.size);
|
|
|
|
struct drm_mode_fb_cmd2 addfb = {
|
|
.width = 800,
|
|
.height = 1280,
|
|
.pixel_format = DRM_FORMAT_XRGB8888,
|
|
.flags = 0,
|
|
.handles = {createdumb.handle, 0, 0, 0},
|
|
.pitches = {createdumb.pitch, 0, 0, 0},
|
|
.offsets = {0, 0, 0, 0},
|
|
};
|
|
if(ioctl(fd, DRM_IOCTL_MODE_ADDFB2, &addfb) < 0) error(1, errno, "add framebuffer");
|
|
|
|
struct drm_mode_map_dumb mapdumb = {
|
|
.handle = createdumb.handle
|
|
};
|
|
if(ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mapdumb) < 0) error(1, errno, "map dumb buffer");
|
|
|
|
fbs[i] = (uint32_t*)mmap(NULL, createdumb.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mapdumb.offset);
|
|
if(!fbs[i]) error(1, errno, "mmap dumb buffer");
|
|
fb_ids[i] = addfb.fb_id;
|
|
|
|
memset(fbs[i], 0, createdumb.size);
|
|
}
|
|
|
|
uint32_t connectors[1] = {56};
|
|
struct drm_mode_crtc crtc = {.crtc_id = 51};
|
|
if(ioctl(fd, DRM_IOCTL_MODE_GETCRTC, &crtc) < 0) error(1, errno, "get crtc");
|
|
//struct drm_mode_crtc oldcrtc = crtc;
|
|
crtc.set_connectors_ptr = (uint64_t)connectors;
|
|
crtc.count_connectors=1;
|
|
crtc.fb_id = fb_ids[0];
|
|
if(ioctl(fd, DRM_IOCTL_MODE_SETCRTC, &crtc) < 0) error(1, errno, "set crtc");
|
|
|
|
int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
|
|
if(timerfd < 0) error(1, errno, "timerfd_create");
|
|
{
|
|
struct itimerspec tspec = {.it_interval = {.tv_nsec = 250000000}, .it_value = {.tv_nsec=250000000}};
|
|
if(timerfd_settime(timerfd, 0, &tspec, NULL) < 0) error(1, errno, "timerfd_settime");
|
|
}
|
|
|
|
push_scene(SCENE_LOBBY);
|
|
|
|
int cur_buffer = 0;
|
|
|
|
need_rerender = true;
|
|
bool awaiting_vblank = false;
|
|
for(;;) {
|
|
struct pollfd polls[] = {
|
|
{.fd = touchscreen_fd, .events = POLLIN},
|
|
{.fd = timerfd, .events = POLLIN},
|
|
{.fd = fd, .events = POLLIN},
|
|
};
|
|
const int NPOLLS = sizeof(polls)/sizeof(polls[0]);
|
|
static const char *pollfd_labels[NPOLLS] = {"touchscreen", "animation timer", "display"};
|
|
int npoll = poll(polls, NPOLLS, 0);
|
|
if(npoll < 0) error(1, errno, "poll");
|
|
|
|
// POLLERR, POLLHUP, POLLNVAL can always be returned
|
|
for(int i = 0; i < NPOLLS; i++) {
|
|
if(polls[i].revents & (POLLNVAL | POLLERR | POLLHUP)) {
|
|
if(polls[i].revents & POLLNVAL) error(1, 0, "poll: got POLLNVAL (%s)", pollfd_labels[i]);
|
|
if(polls[i].revents & POLLERR) error(1, 0, "poll: got POLLERR (%s)", pollfd_labels[i]);
|
|
if(polls[i].revents & POLLHUP) error(1, 0, "poll: got POLLHUP (%s)", pollfd_labels[i]);
|
|
}
|
|
}
|
|
if(polls[0].revents & POLLIN) {
|
|
poll_touchscreen();
|
|
need_rerender = true; // TODO not always
|
|
}
|
|
if(polls[1].revents & POLLIN) {
|
|
uint64_t dummy; // don't care about expiration count
|
|
if(read(timerfd, &dummy, sizeof dummy) < 0) error(1, errno, "read (timerfd)");
|
|
current_animframe++;
|
|
//printf("animation frame %u\n", (unsigned int)current_animframe);
|
|
need_rerender = true;
|
|
}
|
|
if(polls[2].revents & POLLIN) {
|
|
char buf[256];
|
|
int nread = read(fd, buf, sizeof buf);
|
|
if(nread < 0) error(1, errno, "read (display)");
|
|
|
|
char *pos = buf;
|
|
while(pos < buf + nread) {
|
|
struct drm_event *evthdr = (struct drm_event*)pos;
|
|
pos += evthdr->length;
|
|
switch(evthdr->type) {
|
|
case DRM_EVENT_FLIP_COMPLETE:
|
|
awaiting_vblank = false;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "unknown DRM event type 0x%08" PRIx32, (unsigned int)evthdr->type);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!awaiting_vblank) {
|
|
// return value ignored
|
|
// What is this really doing? It's giving scripts the opportunity to trigger a video frame.
|
|
// But if no script actually does, this can run over and over many times in the same video frame.
|
|
// It's not saying a video frame happened, it's saying one can happen.
|
|
deliver_script_wakeup(SCRIPT_WAKEUP_VIDEO_FRAME, 0, SCRIPT_WAKEUP_VIDEO_FRAME, 0, 0, 0, 0);
|
|
}
|
|
|
|
if(need_rerender && !awaiting_vblank) {
|
|
cur_buffer = 1-cur_buffer; // for testing, just keep writing into the same buffer
|
|
curfb = fbs[cur_buffer];
|
|
|
|
for(int s = 0; s < scene_depth; s++) {
|
|
scenes[s].render_fn(&scenes[s]);
|
|
}
|
|
|
|
struct drm_mode_crtc_page_flip flipcmd = {
|
|
.crtc_id = crtc.crtc_id,
|
|
.fb_id = fb_ids[cur_buffer],
|
|
.flags = DRM_MODE_PAGE_FLIP_EVENT, // async flip doesn't work on pinetab
|
|
};
|
|
if(ioctl(fd, DRM_IOCTL_MODE_PAGE_FLIP, &flipcmd) < 0)
|
|
error(1, errno, "DRM page flip");
|
|
awaiting_vblank = true;
|
|
need_rerender = false;
|
|
}
|
|
}
|
|
|
|
//if(ioctl(fd, DRM_IOCTL_MODE_SETCRTC, &oldcrtc) < 0) error(1, errno, "restore crtc"); // doesn't work: EINVAL (maybe because framebuffer id is local to our connection)
|
|
}
|