two scenes that exchange on tapping on the door position in the first scene

master
immibis 2025-02-17 20:11:44 +01:00
parent 1ca8e611af
commit 621d92a554
7 changed files with 282 additions and 220415 deletions

View File

@ -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)'

File diff suppressed because it is too large Load Diff

22
engine.h Normal file
View File

@ -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);

43
game.cpp Normal file
View File

@ -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;
}
}

View File

@ -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;
}
}

BIN
sprites/managers_office.xcf Normal file

Binary file not shown.