two scenes that exchange on tapping on the door position in the first scene
parent
1ca8e611af
commit
621d92a554
30
Makefile
30
Makefile
|
@ -1,6 +1,7 @@
|
|||
PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
|
||||
OBJS = pinetab2_framework.o
|
||||
OBJS = pinetab2_framework.o game.o
|
||||
SPRITES = $(patsubst $(PROJECT_ROOT)sprites/%.xcf,%,$(wildcard $(PROJECT_ROOT)sprites/*.xcf))
|
||||
|
||||
ifeq ($(BUILD_MODE),debug)
|
||||
CFLAGS += -g
|
||||
|
@ -17,9 +18,12 @@ endif
|
|||
|
||||
CXX := aarch64-linux-gnu-$(CXX)
|
||||
CC := aarch64-linux-gnu-$(CC)
|
||||
OBJCOPY := aarch64-linux-gnu-objcopy -O elf64-littleaarch64
|
||||
CFLAGS += -I/usr/include
|
||||
|
||||
all: pinetab2_framework
|
||||
OBJS += $(patsubst %,sprite_%.o,$(SPRITES))
|
||||
|
||||
all: pinetab2_framework $(patsubst %,%.png,$(SPRITES))
|
||||
|
||||
pinetab2_framework: $(OBJS)
|
||||
$(CXX) $(LDFLAGS) -o $@ $^
|
||||
|
@ -33,3 +37,25 @@ pinetab2_framework: $(OBJS)
|
|||
|
||||
clean:
|
||||
rm -fr pinetab2_framework $(OBJS) $(EXTRA_CLEAN)
|
||||
|
||||
sprite_%.o: $(PROJECT_ROOT)sprites/%.xcf
|
||||
gimp -in -b '(let ((image (car (gimp-xcf-load 0 "$<" "$(notdir $<)")))) ;\
|
||||
(gimp-image-scale image 1280 800) ;\
|
||||
(gimp-image-rotate image ROTATE-90) ;\
|
||||
(let ((layer (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))) ;\
|
||||
(plug-in-colors-channel-mixer RUN-NONINTERACTIVE image layer 0 0 0 1 0 1 0 1 0 0) ; swap red and blue channels \
|
||||
(file-raw-save 1 image layer "$(patsubst %.o,%.raw,$@)" "$(patsubst %.o,%.raw,$@)") ;\
|
||||
) ;\
|
||||
)' -b '(gimp-quit 0)'
|
||||
$(OBJCOPY) -I binary --rename-section .data=.rodata,alloc,load,readonly,data,contents $(patsubst %.o,%.raw,$@) $@
|
||||
rm $(patsubst %.o,%.raw,$@)
|
||||
@# symbols are _binary_sprite_%_raw_start / _end / _size
|
||||
|
||||
%.png: $(PROJECT_ROOT)sprites/%.xcf
|
||||
gimp -in -b '(let ((image (car (gimp-xcf-load 0 "$<" "$(notdir $<)")))) ;\
|
||||
(gimp-image-scale image 1280 800) ;\
|
||||
(let ((layer (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))) ;\
|
||||
(file-png-save 1 image layer "$@" "$@" 0 9 0 0 0 0 0) ;\
|
||||
) ;\
|
||||
)' -b '(gimp-quit 0)'
|
||||
|
220365
basement.raw.h
220365
basement.raw.h
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
|||
#define MAX_GAME_OBJECTS 300
|
||||
extern struct object {
|
||||
int id; // 0 if slot unused
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
const char *pixels;
|
||||
} objects[MAX_GAME_OBJECTS];
|
||||
|
||||
// engine
|
||||
void transition_scene(int scene); // Immediately stops all event processing for current scene, which is unloaded before this function returns
|
||||
void scene_add_clickrect(int id, int x, int y, int width, int height);
|
||||
void scene_add_object(int id, int x, int y, int width, int height, const char *pixels);
|
||||
|
||||
// game-specific constants
|
||||
#define SCENE_LOBBY 1
|
||||
#define SCENE_MANAGERS_OFFICE 2
|
||||
|
||||
// game.cpp
|
||||
void scene_setup(int scene);
|
||||
void onclick(int curscene, int objid);
|
|
@ -0,0 +1,43 @@
|
|||
#include "engine.h"
|
||||
|
||||
#define BGWIDTH 1280
|
||||
#define BGHEIGHT 800
|
||||
|
||||
extern const char _binary_sprite_lobby_raw_start[];
|
||||
extern const char _binary_sprite_managers_office_raw_start[];
|
||||
|
||||
#define OBJID_BACKGROUND 1
|
||||
#define OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY 2
|
||||
#define OBJID_DOOR_TO_LOBBY_FROM_MANAGERS_OFFICE 3
|
||||
|
||||
void scene_setup(int scene) {
|
||||
switch(scene) {
|
||||
case SCENE_LOBBY:
|
||||
scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_lobby_raw_start);
|
||||
scene_add_object(OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY, 273, 313, 76, 128, nullptr);
|
||||
break;
|
||||
case SCENE_MANAGERS_OFFICE:
|
||||
scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_managers_office_raw_start);
|
||||
scene_add_object(OBJID_DOOR_TO_LOBBY_FROM_MANAGERS_OFFICE, 273, 313, 76, 128, nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void onclick(int curscene, int objid) {
|
||||
switch(curscene) {
|
||||
case SCENE_LOBBY:
|
||||
switch(objid) {
|
||||
case OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY:
|
||||
transition_scene(SCENE_MANAGERS_OFFICE);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case SCENE_MANAGERS_OFFICE:
|
||||
switch(objid) {
|
||||
case OBJID_DOOR_TO_LOBBY_FROM_MANAGERS_OFFICE:
|
||||
transition_scene(SCENE_LOBBY);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -15,24 +15,24 @@
|
|||
#include <math.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <sys/poll.h>
|
||||
#include <inttypes.h>
|
||||
#include "engine.h"
|
||||
|
||||
|
||||
#include "basement.raw.h"
|
||||
|
||||
#define PLAYFIELD_WIDTH 1280
|
||||
#define PLAYFIELD_HEIGHT 800
|
||||
|
||||
|
||||
|
||||
static int touchscreen_fd;
|
||||
|
||||
static uint32_t *fb;
|
||||
|
||||
#define MAX_MT_SLOTS 16
|
||||
static struct mtslot {
|
||||
int x, y, touch;
|
||||
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;
|
||||
|
@ -58,7 +58,7 @@ retry_read:
|
|||
struct mtslot *s = &mtslots[j];
|
||||
if(s->touch) {
|
||||
//printf("slot %d x=%d y=%d\n", j, s->x, s->y);
|
||||
uint32_t col;
|
||||
/*uint32_t col;
|
||||
switch(j & 3) {
|
||||
case 0: col = 0xFFFFFFFF; break;
|
||||
case 1: col = 0xFF0000FF; break;
|
||||
|
@ -87,10 +87,14 @@ retry_read:
|
|||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
if(!s->lasttouch) {
|
||||
handle_tap(s->y, 799-s->x);
|
||||
}
|
||||
s->lastx = s->x;
|
||||
s->lasty = s->y;
|
||||
}
|
||||
s->lasttouch = s->touch;
|
||||
s->lastx = s->x;
|
||||
s->lasty = s->y;
|
||||
}
|
||||
}
|
||||
if(evt->type == EV_ABS && evt->code == ABS_MT_SLOT) {
|
||||
|
@ -114,8 +118,79 @@ retry_read:
|
|||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
|
@ -125,47 +200,52 @@ int main(int argc, char **argv) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
int fd = open("/dev/dri/card0", O_RDWR);
|
||||
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");
|
||||
printf("opened\n");
|
||||
|
||||
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
|
||||
|
||||
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");
|
||||
// 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");
|
||||
|
||||
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");
|
||||
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_map_dumb mapdumb = {
|
||||
.handle = createdumb.handle
|
||||
};
|
||||
if(ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mapdumb) < 0) error(1, errno, "map dumb buffer");
|
||||
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");
|
||||
|
||||
fb = (uint32_t*)mmap(NULL, createdumb.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mapdumb.offset);
|
||||
if(!fb) error(1, errno, "mmap dumb buffer");
|
||||
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");
|
||||
|
||||
//getrandom(fb, createdumb.size, 0);
|
||||
memset(fb, 0, createdumb.size);
|
||||
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;
|
||||
|
||||
memcpy(fb, basement.pixel_data, createdumb.size);
|
||||
memset(fbs[i], 0, createdumb.size);
|
||||
}
|
||||
|
||||
uint32_t connectors[1] = {56};
|
||||
struct drm_mode_crtc crtc = {.crtc_id = 51};
|
||||
|
@ -173,7 +253,7 @@ int main(int argc, char **argv) {
|
|||
//struct drm_mode_crtc oldcrtc = crtc;
|
||||
crtc.set_connectors_ptr = (uint64_t)connectors;
|
||||
crtc.count_connectors=1;
|
||||
crtc.fb_id = addfb.fb_id;
|
||||
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);
|
||||
|
@ -183,31 +263,92 @@ int main(int argc, char **argv) {
|
|||
if(timerfd_settime(timerfd, 0, &tspec, NULL) < 0) error(1, errno, "timerfd_settime");
|
||||
}
|
||||
|
||||
printf("ready\n");
|
||||
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},
|
||||
};
|
||||
int npoll = poll(polls, sizeof(polls)/sizeof(polls[0]), 0);
|
||||
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
|
||||
if((polls[0].revents | polls[1].revents) & POLLNVAL) error(1, 0, "poll: got POLLNVAL");
|
||||
|
||||
if(polls[0].revents & POLLERR) error(1, 0, "got POLLERR on touchscreen");
|
||||
if(polls[0].revents & POLLHUP) error(1, 0, "got POLLHUP on touchscreen");
|
||||
if(polls[1].revents & POLLERR) error(1, 0, "got POLLERR on animation timer");
|
||||
if(polls[1].revents & POLLHUP) error(1, 0, "got POLLHUP on animation timer");
|
||||
|
||||
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)");
|
||||
printf("animation frame\n");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue