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-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 ;
# 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 ;
printf ( " blit %dx%d at %d,%d \n " , spr - > width , spr - > height , x , 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 ) ;
}
}
}
struct object objects [ MAX_GAME_OBJECTS ] ;
bool need_rerender = false ;
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 ) ;
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-17 19:11:44 +00:00
void handle_tap ( int x , int y ) {
uint32_t gencount = scene_generation_count ;
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 ) {
if ( gencount ! = scene_generation_count ) return ; // early exit if scene changed
onclick ( curscene , objects [ i ] . id ) ;
}
}
}
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 + + ;
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 ( need_rerender & & ! awaiting_vblank ) {
cur_buffer = 1 - cur_buffer ; // for testing, just keep writing into the same buffer
curfb = fbs [ cur_buffer ] ;
//printf("%d %d\n", sizeof basement_pixels, 1280*800*4);
/*struct sprite bgsprite = {
. width = 1280 ,
. height = 800 ,
. pixels = ( uint32_t * ) _binary_sprite_lobby_raw_start
} ;
memset ( curfb , 0 , 1280 * 800 * 4 ) ;
blit ( mtslots [ 0 ] . y - 640 , mtslots [ 0 ] . x - 400 , & bgsprite ) ; */
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)
}