pt2game/pinetab2_framework.cpp

688 lines
22 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;
struct savefile savefile;
#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 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) {
top_scene.handle_tap_fn(&top_scene, 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;
uint32_t *curfb; // used during render cycle
void blit(int x, int y, int width, int height, uint32_t *pixels) {
uint32_t *inbase = pixels;
//printf("blit %dx%d at %d,%d\n", spr->width, spr->height, x, y);
y = PLAYFIELD_HEIGHT - height - y;
if(y >= PLAYFIELD_HEIGHT || x >= PLAYFIELD_WIDTH || y <= -height || x <= -width) return;
if(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 < width; dx++, inbase += 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, (height-offset)*4);
} else {
int ncopy = PLAYFIELD_HEIGHT-y;
if(ncopy > height) ncopy = height;
memcpy(outbase, inbase, ncopy*4);
}
}
}
void fillrect(int x1, int y1, int width, int height, uint32_t pixel) {
int x2 = x1 + width;
int y2 = y1 + height;
if(x1 < 0) x1 = 0;
if(y1 < 0) y1 = 0;
if(x2 >= PLAYFIELD_WIDTH) x2 = PLAYFIELD_WIDTH;
if(y2 >= PLAYFIELD_HEIGHT) y2 = PLAYFIELD_HEIGHT;
y1 = PLAYFIELD_HEIGHT-1-y1;
y2 = PLAYFIELD_HEIGHT-1-y2;
for(int x = x1; x < x2; x++) {
uint32_t *outbase = curfb + x*PLAYFIELD_HEIGHT + y2;
for(int y = y2; y < y1; y++) {
*outbase++ = pixel;
}
}
}
static void scene_clear_and_setup(int scene, int fromscene, scene_setup_fn setup_fn) {
struct scene *me = &top_scene; // setup_fn might push additional scenes such as textboxes
memset(me, 0, sizeof(*me));
me->id = scene;
me->navmesh = &null_navmesh;
me->handle_tap_fn = standard_handle_tap;
me->render_fn = standard_scene_render;
me->use_standard_inventory = true;
setup_fn(me, scene, fromscene);
if(me->use_standard_inventory) {
create_standard_inventory(me);
}
need_rerender = true;
}
void push_scene(int scene, scene_setup_fn setup_fn) {
scene_depth++;
if(scene_depth > MAX_STACKED_SCENES) error(1, 0, "maximum scene depth exceeded");
scene_clear_and_setup(scene, -1, setup_fn);
}
void push_scene(int scene) {
push_scene(scene, scene_setup);
}
void replace_scene(int scene) {
scene_clear_and_setup(scene, top_scene.id, scene_setup);
}
void pop_scene() {
if(scene_depth == 1) error(1, 0, "no scenes left");
scene_depth--;
// any cleanup?
need_rerender = true;
}
struct object *scene_add_object(struct scene *sc, 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 = sc->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(struct scene *sc, const struct level_predef_data *predef) {
for(const struct level_clickregion *cr = predef->clickregions; cr->num_edges; cr++) {
struct object *obj = scene_add_object(sc, cr->id, cr->x, cr->y, cr->width, cr->height, 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 standard_handle_tap(struct scene *scene, int x, int y) {
uint32_t walkgen = player_walk_generation;
int sceneid = 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]);
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) {
if(s->dim_background) {
uint32_t *ptr = curfb;
for(int i = 0; i < 1280*800; i++, ptr++) {
*ptr = (*ptr >> 2) & 0x3f3f3f;
}
}
struct object *objects = s->objects;
for(int i = 0; i < MAX_OBJECTS_PER_SCENE; i++) {
if(objects[i].id != 0 && objects[i].pixels) {
blit(objects[i].x, objects[i].y, objects[i].width, objects[i].height, (uint32_t*)objects[i].pixels);
}
}
}
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");
{
#define ANIMTIMER_INTERVAL 75000000
struct itimerspec tspec = {.it_interval = {.tv_nsec = ANIMTIMER_INTERVAL}, .it_value = {.tv_nsec = ANIMTIMER_INTERVAL}};
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);
if(top_scene.animtimer_fn) {
top_scene.animtimer_fn(&top_scene);
}
}
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)
}