2025-02-17 16:03:14 +00:00
# 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>
2025-02-17 19:11:44 +00:00
# include <inttypes.h>
# include "engine.h"
2025-02-18 01:04:17 +00:00
# include "compiled_structures.h"
2025-02-17 16:03:14 +00:00
2025-02-17 19:11:44 +00:00
# define PLAYFIELD_WIDTH 1280
# define PLAYFIELD_HEIGHT 800
2025-02-17 16:03:14 +00:00
static int touchscreen_fd ;
2025-02-18 01:04:17 +00:00
static struct navmesh null_navmesh = { 0 } ;
struct navmesh * cur_navmesh = & null_navmesh ;
struct object objects [ MAX_GAME_OBJECTS ] ;
struct script scripts [ MAX_GAME_SCRIPTS ] ;
bool need_rerender = false ;
2025-02-17 16:03:14 +00:00
# define MAX_MT_SLOTS 16
static struct mtslot {
2025-02-17 19:11:44 +00:00
int x , y , touch , lasttouch ;
2025-02-17 16:03:14 +00:00
int lastx , lasty ;
} mtslots [ MAX_MT_SLOTS ] = { 0 } ;
static struct mtslot * curslot = & mtslots [ 0 ] ; // TODO: query the current value of ABS_MT_SLOT
2025-02-17 19:11:44 +00:00
static void handle_tap ( int x , int y ) ; // x and y in landscape orientation
2025-02-17 16:03:14 +00:00
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);
2025-02-17 19:11:44 +00:00
/*uint32_t col;
2025-02-17 16:03:14 +00:00
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 ;
}
}
}
}
2025-02-17 19:11:44 +00:00
} */
if ( ! s - > lasttouch ) {
handle_tap ( s - > y , 799 - s - > x ) ;
2025-02-17 16:03:14 +00:00
}
}
2025-02-17 19:11:44 +00:00
s - > lasttouch = s - > touch ;
s - > lastx = s - > x ;
s - > lasty = s - > y ;
2025-02-17 16:03:14 +00:00
}
}
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 ;
}
}
}
}
2025-02-17 19:11:44 +00:00
// 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 ;
2025-02-18 01:04:17 +00:00
//printf("blit %dx%d at %d,%d\n", spr->width, spr->height, x, y);
y = PLAYFIELD_HEIGHT - spr - > height - y ;
2025-02-17 19:11:44 +00:00
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 ) ;
}
}
}
2025-02-18 01:04:17 +00:00
void scene_set_navmesh ( struct navmesh * navmesh ) {
cur_navmesh = navmesh ;
}
2025-02-17 16:03:14 +00:00
2025-02-17 19:11:44 +00:00
static uint32_t scene_generation_count = 0 ;
static int curscene ;
void transition_scene ( int scene ) {
scene_generation_count + + ;
curscene = scene ;
memset ( objects , 0 , sizeof objects ) ;
2025-02-18 01:04:17 +00:00
memset ( scripts , 0 , sizeof scripts ) ; // script state is local to scene, so no cancel notification
cur_navmesh = & null_navmesh ;
2025-02-17 19:11:44 +00:00
scene_setup ( scene ) ;
}
void 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 " ) ;
for ( int i = 0 ; i < MAX_GAME_OBJECTS ; 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 ;
}
}
error ( 1 , 0 , " too many game objects " ) ;
}
2025-02-17 16:03:14 +00:00
2025-02-18 01:04:17 +00:00
struct object * find_object_by_id ( int id ) {
for ( int i = 0 ; i < MAX_GAME_OBJECTS ; i + + ) {
if ( objects [ i ] . id = = id )
return & objects [ i ] ;
}
return nullptr ;
}
struct script * scene_add_script ( int id , bool interrupt_existing ) {
for ( int i = 0 ; i < MAX_GAME_SCRIPTS ; 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 ;
2025-02-18 15:26:16 +00:00
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.
2025-02-18 01:04:17 +00:00
}
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 + + ) {
2025-02-18 16:22:39 +00:00
for ( int i = 0 ; i < tri - > num_verts ; i + + ) {
2025-02-18 01:04:17 +00:00
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 ;
}
}
}
2025-02-18 16:22:39 +00:00
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 ;
}
}
/*
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 16:22:39 +00:00
} */
2025-02-18 01:04:17 +00:00
// 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 ) {
2025-02-18 16:22:39 +00:00
/*double edgedist[3]; // positive if outside
2025-02-18 01:04:17 +00:00
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
2025-02-18 16:22:39 +00:00
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?
2025-02-18 01:04:17 +00:00
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 ] ;
2025-02-18 16:22:39 +00:00
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 ] ;
2025-02-18 01:04:17 +00:00
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 ;
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 ) ;
2025-02-18 15:26:16 +00:00
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 ) ;
}
2025-02-17 19:11:44 +00:00
void handle_tap ( int x , int y ) {
uint32_t gencount = scene_generation_count ;
2025-02-18 15:26:16 +00:00
uint32_t walkgen = player_walk_generation ;
2025-02-17 19:11:44 +00:00
for ( int i = 0 ; i < MAX_GAME_OBJECTS ; i + + ) {
if ( objects [ i ] . id & & ( unsigned int ) ( x - objects [ i ] . x ) < objects [ i ] . width & & ( unsigned int ) ( y - objects [ i ] . y ) < objects [ i ] . height ) {
onclick ( curscene , objects [ i ] . id ) ;
2025-02-18 15:26:16 +00:00
if ( gencount ! = scene_generation_count ) return ; // early exit if scene changed, and don't walk the player either
2025-02-17 19:11:44 +00:00
}
}
2025-02-18 01:04:17 +00:00
2025-02-18 15:26:16 +00:00
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 ) ;
}
2025-02-18 01:04:17 +00:00
}
}
}
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 ) {
2025-02-18 15:26:16 +00:00
int script_id = scr - > id ;
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 15:26:16 +00:00
deliver_script_wakeup ( SCRIPT_WAKEUP_OTHER_SCRIPT , script_id , SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED , 0 , 0 , 0 , 0 ) ;
2025-02-18 01:04:17 +00:00
return ;
}
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 ;
2025-02-18 15:26:16 +00:00
deliver_script_wakeup ( SCRIPT_WAKEUP_OTHER_SCRIPT , script_id , SCRIPT_WAKEUP_OTHER_SCRIPT , 0 , 0 , 0 , 0 ) ;
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 16:22:39 +00:00
for ( int edge = 0 ; edge < cur_navmesh - > tris [ nmtri ] . num_verts ; edge + + ) {
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 15:26:16 +00:00
deliver_script_wakeup ( SCRIPT_WAKEUP_OTHER_SCRIPT , script_id , SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED , 0 , 0 , 0 , 0 ) ;
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 15:26:16 +00:00
deliver_script_wakeup ( SCRIPT_WAKEUP_OTHER_SCRIPT , script_id , SCRIPT_WAKEUP_OTHER_SCRIPT , 0 , 0 , 0 , 0 ) ;
2025-02-18 01:04:17 +00:00
} 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 ) {
uint32_t gen = scene_generation_count ;
for ( int i = 0 ; i < MAX_GAME_SCRIPTS ; 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_generation_count ! = gen ) return true ; // scene changed - abort early
}
}
return false ;
2025-02-17 19:11:44 +00:00
}
2025-02-17 16:03:14 +00:00
2025-02-18 01:04:17 +00:00
2025-02-17 16:03:14 +00:00
int main ( int argc , char * * argv ) {
if ( getuid ( ) ! = 0 ) {
execlp ( " sudo " , " sudo " , argv [ 0 ] , NULL ) ;
error ( 1 , errno , " execlp " ) ;
return 1 ;
}
2025-02-17 19:11:44 +00:00
int fd = open ( " /dev/dri/card0 " , O_RDWR | O_NONBLOCK ) ;
2025-02-17 16:03:14 +00:00
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
2025-02-17 19:11:44 +00:00
// 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 ) ;
}
2025-02-17 16:03:14 +00:00
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 ;
2025-02-17 19:11:44 +00:00
crtc . fb_id = fb_ids [ 0 ] ;
2025-02-17 16:03:14 +00:00
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 " ) ;
}
2025-02-17 19:11:44 +00:00
transition_scene ( SCENE_LOBBY ) ;
2025-02-17 16:03:14 +00:00
2025-02-17 19:11:44 +00:00
int cur_buffer = 0 ;
need_rerender = true ;
bool awaiting_vblank = false ;
2025-02-17 16:03:14 +00:00
for ( ; ; ) {
struct pollfd polls [ ] = {
{ . fd = touchscreen_fd , . events = POLLIN } ,
{ . fd = timerfd , . events = POLLIN } ,
2025-02-17 19:11:44 +00:00
{ . fd = fd , . events = POLLIN } ,
2025-02-17 16:03:14 +00:00
} ;
2025-02-17 19:11:44 +00:00
const int NPOLLS = sizeof ( polls ) / sizeof ( polls [ 0 ] ) ;
static const char * pollfd_labels [ NPOLLS ] = { " touchscreen " , " animation timer " , " display " } ;
int npoll = poll ( polls , NPOLLS , 0 ) ;
2025-02-17 16:03:14 +00:00
if ( npoll < 0 ) error ( 1 , errno , " poll " ) ;
// POLLERR, POLLHUP, POLLNVAL can always be returned
2025-02-17 19:11:44 +00:00
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 ] ) ;
}
}
2025-02-17 16:03:14 +00:00
if ( polls [ 0 ] . revents & POLLIN ) {
poll_touchscreen ( ) ;
2025-02-17 19:11:44 +00:00
need_rerender = true ; // TODO not always
2025-02-17 16:03:14 +00:00
}
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) " ) ;
2025-02-17 19:11:44 +00:00
current_animframe + + ;
2025-02-18 01:04:17 +00:00
//printf("animation frame %u\n", (unsigned int)current_animframe);
2025-02-17 19:11:44 +00:00
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 ;
}
}
}
2025-02-18 01:04:17 +00:00
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 ) ;
}
2025-02-17 19:11:44 +00:00
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 i = 0 ; i < MAX_GAME_OBJECTS ; 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 ) ;
}
}
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 ;
2025-02-17 16:03:14 +00:00
}
}
//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)
}