#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "engine.h" #define PLAYFIELD_WIDTH 1280 #define PLAYFIELD_HEIGHT 800 static int touchscreen_fd; #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); 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; 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"); } 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); } } } 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"); } transition_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(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; } } //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) }