Created
December 7, 2019 12:15
-
-
Save aolo2/b5ba730278eb04fde24133ef343ff8e9 to your computer and use it in GitHub Desktop.
halcyon with kb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
/* Halcyon ZERO (single-header video-only) -- Linux */ | |
#include <stdint.h> | |
#include <stdlib.h> | |
#include <math.h> | |
#include <float.h> | |
#include <string.h> | |
#include <assert.h> | |
#include <X11/Xlib.h> | |
#include <X11/Xutil.h> | |
#include <X11/extensions/XShm.h> | |
#include <X11/Xlibint.h> | |
#include <sys/ipc.h> | |
#include <sys/shm.h> | |
#include <sys/timerfd.h> | |
#include <errno.h> | |
#include <poll.h> | |
#include <unistd.h> | |
/* user configuation */ | |
#ifndef WIN_W | |
#define WIN_W 640 | |
#endif | |
#ifndef WIN_H | |
#define WIN_H 360 | |
#endif | |
typedef struct hc_v2 { int_fast16_t x, y; } hc_v2; | |
typedef struct hc_vx { int_fast16_t x, y; float tx, ty; } hc_vx; | |
typedef struct hc_tex { int_fast16_t w, h; void* data; } hc_tex; | |
void hc_init (const char* name); | |
void hc_point (hc_v2 a, uint32_t rgb); | |
void hc_line (hc_v2 a, hc_v2 b, uint32_t rgb); | |
void hc_tri (hc_v2 a, hc_v2 b, hc_v2 c, uint32_t fill, uint32_t border); | |
void hc_tri_tex (hc_vx verts[static 3], const hc_tex* tex); | |
int hc_scanout (int n); | |
void hc_scroll (hc_v2 p); | |
void hc_finish (uint8_t flags); // TODO: hc_v2* [2] param for redraw rect | |
void* hc_vram; | |
enum { HC_CLEAR=1, HC_NODRAW=2, HC_NOESC=4 }; | |
char hc_keys[0xFFFFF]; | |
// begin private-ish stuff | |
#define HC_VERSION "0.1 ZERO" | |
#define HC_FPS 60UL | |
__attribute__((always_inline)) | |
static inline void asmemset(register void* _dest, uint32_t _num, size_t _count){ | |
register void* dest asm("rdi") = _dest; | |
register uint32_t num asm("eax") = _num; | |
register size_t count asm("rcx") = _count; | |
asm("rep stosl" :: "r" (num), "r" (count), "r" (dest)); | |
} | |
static struct { | |
int pres_op; | |
int shm_ev_base; | |
XShmSegmentInfo shm_info; | |
XImage* img; | |
Pixmap pix; | |
Window win; | |
XVisualInfo xvi; | |
Display* dpy; | |
void* imgmem; | |
Atom close_atom; | |
int scan_ctr; | |
hc_v2 scroll; | |
struct pollfd pollfds[2]; | |
size_t npollfds; | |
int timer_started; | |
} _hc_priv; | |
void hc_init(const char* name){ | |
hc_vram = calloc(WIN_W*WIN_H, sizeof(uint32_t)); | |
int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); | |
_hc_priv.pollfds[0] = (struct pollfd){ | |
.fd = timer_fd, | |
.events = POLLIN, | |
}; | |
int w = WIN_W << 1, h = WIN_H << 1; | |
_hc_priv.dpy = XOpenDisplay(NULL); | |
assert(_hc_priv.dpy); | |
assert(XShmQueryExtension(_hc_priv.dpy)); | |
assert(XMatchVisualInfo(_hc_priv.dpy, DefaultScreen(_hc_priv.dpy), 24, TrueColor, &_hc_priv.xvi)); | |
_hc_priv.img = XShmCreateImage( | |
_hc_priv.dpy, | |
_hc_priv.xvi.visual, | |
_hc_priv.xvi.depth, | |
ZPixmap, | |
NULL, | |
&_hc_priv.shm_info, | |
w, h | |
); | |
_hc_priv.shm_info.shmid = shmget( | |
IPC_PRIVATE, | |
_hc_priv.img->bytes_per_line * _hc_priv.img->height, | |
IPC_CREAT | 0600 | |
); | |
_hc_priv.imgmem = _hc_priv.shm_info.shmaddr = _hc_priv.img->data = shmat(_hc_priv.shm_info.shmid, 0, 0); | |
XShmAttach(_hc_priv.dpy, &_hc_priv.shm_info); | |
shmctl(_hc_priv.shm_info.shmid, IPC_RMID, NULL); | |
XSetWindowAttributes swa = { | |
.background_pixel = BlackPixel(_hc_priv.dpy, _hc_priv.xvi.screen), | |
.colormap = XCreateColormap(_hc_priv.dpy, RootWindow(_hc_priv.dpy, _hc_priv.xvi.screen), _hc_priv.xvi.visual, AllocNone), | |
.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask, | |
}; | |
_hc_priv.win = XCreateWindow( | |
_hc_priv.dpy, RootWindow(_hc_priv.dpy, _hc_priv.xvi.screen), | |
0, 0, w, h, | |
0, _hc_priv.xvi.depth, InputOutput, _hc_priv.xvi.visual, | |
CWBackPixel | CWEventMask, &swa | |
); | |
XStoreName(_hc_priv.dpy, _hc_priv.win, name ? "" : "HALCYON ZERO"); | |
XMapRaised(_hc_priv.dpy, _hc_priv.win); | |
XSizeHints* xsh = XAllocSizeHints(); | |
xsh->flags = PMinSize | PMaxSize; | |
xsh->min_width = xsh->max_width = w; | |
xsh->min_height = xsh->max_height = h; | |
XSetWMNormalHints(_hc_priv.dpy, _hc_priv.win, xsh); | |
XFree(xsh); | |
_hc_priv.close_atom = XInternAtom(_hc_priv.dpy, "WM_DELETE_WINDOW", 0); | |
XSetWMProtocols(_hc_priv.dpy, _hc_priv.win, &_hc_priv.close_atom, 1); | |
int ev, err; | |
XQueryExtension(_hc_priv.dpy, "Present", &_hc_priv.pres_op, &ev, &err); | |
_hc_priv.pres_op = XInitExtension(_hc_priv.dpy, "Present")->major_opcode; | |
_hc_priv.pix = XCreatePixmap(_hc_priv.dpy, _hc_priv.win, w, h, _hc_priv.xvi.depth); | |
_hc_priv.shm_ev_base = XShmGetEventBase(_hc_priv.dpy); | |
_hc_priv.pollfds[1] = (struct pollfd){ | |
.fd = ConnectionNumber(_hc_priv.dpy), | |
.events = POLLIN, | |
}; | |
} | |
static void _hc_video_x11_frame(void){ | |
XShmPutImage( | |
_hc_priv.dpy, | |
_hc_priv.pix, | |
DefaultGC(_hc_priv.dpy, _hc_priv.xvi.screen), | |
_hc_priv.img, | |
0, 0, 0, 0, | |
_hc_priv.img->width, | |
_hc_priv.img->height, | |
1 | |
); | |
XFlush(_hc_priv.dpy); | |
} | |
static void _hc_video_x11_event(uint8_t flags){ | |
if(!(_hc_priv.pollfds[1].revents & POLLIN)) | |
return; | |
struct pres_req { | |
uint8_t type, subtype; | |
uint16_t length; | |
uint32_t window, pixmap, crap[11]; | |
uint64_t divisor, remainder; | |
}; | |
for(int n = XEventsQueued(_hc_priv.dpy, QueuedAfterReading); n; --n){ | |
XEvent ev; | |
XNextEvent(_hc_priv.dpy, &ev); | |
if(ev.type == ClientMessage && ev.xclient.data.l[0] == (long int) _hc_priv.close_atom){ | |
exit(0); | |
} | |
else if(ev.type == KeyPress && !(flags & HC_NOESC)){ | |
KeySym sym = XLookupKeysym(&ev.xkey, 0); | |
if(sym == XK_Escape){ | |
exit(0); | |
} | |
} | |
else if(ev.type == _hc_priv.shm_ev_base + ShmCompletion) { | |
struct pres_req* req = _XGetRequest(_hc_priv.dpy, _hc_priv.pres_op, sizeof(*req)); | |
memset(req->crap, 0, sizeof(req->crap)); | |
req->subtype = 1; | |
req->window = _hc_priv.win; | |
req->pixmap = _hc_priv.pix; | |
req->divisor = 1; | |
req->remainder = 0; | |
XFlush(_hc_priv.dpy); | |
if(flags & HC_CLEAR){ | |
memset(_hc_priv.imgmem, 0, _hc_priv.img->width * _hc_priv.img->height * 4); | |
} | |
} | |
if(ev.type == KeyPress){ | |
KeySym sym = XLookupKeysym(&ev.xkey, 0); | |
//printf("+%ld\n", sym); | |
hc_keys[sym] = 1; | |
} | |
if(ev.type == KeyRelease){ | |
KeySym sym = XLookupKeysym(&ev.xkey, 0); | |
//printf("-%ld\n", sym); | |
hc_keys[sym] = 0; | |
} | |
} | |
} | |
void hc_finish(uint8_t flags){ | |
if(!(flags & HC_NODRAW)){ | |
hc_scanout(WIN_H - _hc_priv.scan_ctr); | |
} | |
_hc_priv.scan_ctr = 0; | |
if(!(flags & HC_NODRAW)) | |
_hc_video_x11_frame(); | |
if(!_hc_priv.timer_started){ | |
_hc_priv.timer_started = 1; | |
static const struct itimerspec spec = { | |
.it_value = { | |
.tv_sec = 0, | |
.tv_nsec = 1UL * 1000UL * 1000UL, | |
}, | |
.it_interval = { | |
.tv_sec = 0, | |
.tv_nsec = (1000000000UL / HC_FPS), | |
} | |
}; | |
timerfd_settime(_hc_priv.pollfds[0].fd, 0, &spec, NULL); | |
} | |
for (uint32_t i = 0; i < 0xFFFFF; ++i) { | |
if (i != XK_Shift_L) { | |
hc_keys[i] = 0; | |
} | |
} | |
//memset(hc_keys, 0, 0xFFFFF); | |
for(;;){ | |
if(poll(_hc_priv.pollfds, 2, -1) == -1) | |
continue; | |
if(_hc_priv.pollfds[0].revents & POLLIN){ | |
uint64_t tmp; | |
ssize_t ret; | |
do { | |
ret = read(_hc_priv.pollfds[0].fd, &tmp, sizeof(tmp)); | |
} while(ret == -1 && errno != EAGAIN); | |
break; | |
} | |
_hc_video_x11_event(flags); | |
} | |
} | |
static void _hc_backend_scanline(unsigned scan_ctr, uint32_t* linebuf, int off){ | |
scan_ctr <<= 1; | |
uint32_t (*wr)[_hc_priv.img->width] = _hc_priv.imgmem; | |
for(size_t x = 0; x < (size_t) _hc_priv.img->width - off; x+=2){ | |
wr[scan_ctr][x+off] = wr[scan_ctr][x+off+1] = linebuf[x>>1]; | |
} | |
memcpy(wr[scan_ctr+1], wr[scan_ctr], _hc_priv.img->width*4); | |
} | |
int hc_scanout(int n){ | |
uint32_t (*p)[WIN_W] = hc_vram; | |
for(int i = 0; i < n; ++i){ | |
if(_hc_priv.scan_ctr+i >= WIN_H) | |
break; | |
_hc_backend_scanline(_hc_priv.scan_ctr+i, p[_hc_priv.scan_ctr+i], _hc_priv.scroll.x); | |
} | |
_hc_priv.scan_ctr += n; | |
return WIN_H - _hc_priv.scan_ctr; | |
} | |
void hc_scroll(hc_v2 p){ | |
_hc_priv.scroll = p; | |
} | |
static void _hc_plot(int x, int y, uint32_t c){ | |
uint32_t (*p)[WIN_W] = hc_vram; | |
if(x >= 0 && y >= 0 && y < WIN_H && x < WIN_W){ | |
p[y][x] = c; | |
} | |
} | |
static inline void _hc_swaphc_v2(hc_v2* restrict a, hc_v2* restrict b){ | |
hc_v2 tmp = *a; *a = *b; *b = tmp; | |
} | |
static inline void _hc_swaphc_vx(hc_vx* restrict a, hc_vx* restrict b){ | |
hc_vx tmp = *a; *a = *b; *b = tmp; | |
} | |
static inline void _hc_swaphc_vx2(hc_v2* restrict a, hc_v2* restrict b){ | |
_hc_swaphc_vx((hc_vx*)a, (hc_vx*)b); | |
} | |
// POINT | |
void hc_point(hc_v2 xy, uint32_t rgb){ | |
_hc_plot(xy.x, xy.y, rgb | 0xff000000); | |
} | |
// LINE | |
typedef struct { | |
uint_fast16_t ax2, ay2; | |
int_fast16_t err, inc; | |
} _hc_ln_state; | |
typedef void (*_hc_plot_fn)(int, int, uint32_t); | |
typedef void (*_hc_swap_fn)(hc_v2* restrict a, hc_v2* restrict b); | |
typedef void (*_hc_line_fn)(hc_v2* restrict v[static restrict 4], intptr_t fill); | |
__attribute__((always_inline, flatten)) | |
static inline void _hc_line_setup(hc_v2* restrict a, hc_v2* restrict b, _hc_ln_state* restrict s, _hc_swap_fn swap){ | |
int dx = b->x - a->x, dy = b->y - a->y; | |
uint_fast16_t ax = __builtin_abs(dx) << 1, ay = __builtin_abs(dy) << 1; | |
*s = (_hc_ln_state){ | |
.ax2 = ax, .ay2 = ay, | |
.inc = (((dx >= 0) ^ (dy < 0)) << 1) - 1, | |
}; | |
if(dy < 0){ | |
swap(a, b); | |
} | |
} | |
__attribute__((always_inline, flatten)) | |
static inline _Bool _hc_line_step_x(hc_v2* restrict a, hc_v2* restrict b, _hc_ln_state* restrict s, _hc_plot_fn plot, uint32_t color){ | |
while(a->x != b->x){ | |
plot(a->x, a->y, color); | |
a->x += s->inc; | |
if(s->err > 0){ | |
++a->y; | |
s->err -= s->ax2; | |
s->err += s->ay2; | |
return 1; | |
} | |
s->err += s->ay2; | |
} | |
plot(a->x, a->y, color); | |
return 0; | |
} | |
__attribute__((always_inline, flatten)) | |
static inline _Bool _hc_line_step_y(hc_v2* restrict a, hc_v2* restrict b, _hc_ln_state* restrict s, _hc_plot_fn plot, uint32_t color){ | |
if(a->y != b->y){ | |
plot(a->x, a->y, color); | |
++a->y; | |
if(s->err > 0){ | |
a->x += s->inc; | |
s->err -= s->ay2; | |
} | |
s->err += s->ax2; | |
return 1; | |
} | |
plot(a->x, a->y, color); | |
return 0; | |
} | |
__attribute__((flatten)) | |
void hc_line(hc_v2 a, hc_v2 b, uint32_t rgb){ | |
_hc_ln_state s; | |
_hc_line_setup(&a, &b, &s, &_hc_swaphc_v2); | |
if(s.ax2 > s.ay2){ | |
s.err = s.ay2 - (s.ax2 >> 1); | |
while(_hc_line_step_x(&a, &b, &s, &_hc_plot, rgb)); | |
} else { | |
s.err = s.ax2 - (s.ay2 >> 1); | |
while(_hc_line_step_y(&a, &b, &s, &_hc_plot, rgb)); | |
} | |
} | |
// TRIANGLE | |
__attribute__((always_inline, flatten)) | |
static inline void _hc_half_tri(hc_v2* restrict p[static restrict 4], intptr_t fill, uint32_t border, _hc_plot_fn plot, _hc_line_fn line, _hc_swap_fn swap){ | |
_hc_ln_state s0, s1; | |
_hc_line_setup(p[0], p[1], &s0, swap); | |
_hc_line_setup(p[3], p[2], &s1, swap); | |
if(s0.ax2 > s0.ay2){ | |
s0.err = s0.ay2 - (s0.ax2 >> 1); | |
if(s1.ax2 > s1.ay2){ | |
s1.err = s1.ay2 - (s1.ax2 >> 1); | |
for(;;){ | |
if(!_hc_line_step_x(p[0], p[1], &s0, plot, border) || !_hc_line_step_x(p[3], p[2], &s1, plot, border)) | |
return; | |
line(p, fill); | |
} | |
} else { | |
s1.err = s1.ax2 - (s1.ay2 >> 1); | |
for(;;){ | |
if(!_hc_line_step_x(p[0], p[1], &s0, plot, border) || !_hc_line_step_y(p[3], p[2], &s1, plot, border)) | |
return; | |
line(p, fill); | |
} | |
} | |
} else { | |
s0.err = s0.ax2 - (s0.ay2 >> 1); | |
if(s1.ax2 > s1.ay2){ | |
s1.err = s1.ay2 - (s1.ax2 >> 1); | |
for(;;){ | |
if(!_hc_line_step_y(p[0], p[1], &s0, plot, border) || !_hc_line_step_x(p[3], p[2], &s1, plot, border)) | |
return; | |
line(p, fill); | |
} | |
} else { | |
s1.err = s1.ax2 - (s1.ay2 >> 1); | |
for(;;){ | |
if(!_hc_line_step_y(p[0], p[1], &s0, plot, border) || !_hc_line_step_y(p[3], p[2], &s1, plot, border)) | |
return; | |
line(p, fill); | |
} | |
} | |
} | |
} | |
__attribute__((always_inline)) | |
static inline void _hc_fastline(hc_v2* restrict v[static restrict 4], intptr_t fill){ | |
int_fast16_t x0 = v[0]->x, x1 = v[3]->x, y = v[0]->y; | |
if(x0 == x1){ | |
_hc_plot(x0, y, fill); | |
return; | |
} | |
if(x0 > x1){ | |
int tmp = x0; | |
x0 = x1; | |
x1 = tmp; | |
} | |
if(y > 0 && y < WIN_H && x0 > 0 && x1 > 0 && x0 < WIN_W && x1 < WIN_W){ | |
uint32_t (*p)[WIN_W] = hc_vram; | |
int max = WIN_W - x0; | |
int diff = x1 - x0; | |
if(diff > max) | |
diff = max; | |
asmemset(&p[y][x0], fill, diff); | |
} | |
} | |
static void _hc_fill_half_tri(hc_v2 p0, hc_v2 p1, hc_v2 p2, uint32_t fill, uint32_t border){ | |
hc_v2 p3 = p0; | |
hc_v2* restrict arr[] = { &p0, &p1, &p2, &p3 }; | |
_hc_half_tri(arr, fill, border, &_hc_plot, &_hc_fastline, &_hc_swaphc_v2); | |
} | |
void hc_tri(hc_v2 a, hc_v2 b, hc_v2 c, uint32_t fill, uint32_t border){ | |
hc_v2 p[3] = { a, b, c }; | |
if(p[0].y > p[1].y) _hc_swaphc_v2(p+0, p+1); | |
if(p[1].y > p[2].y) _hc_swaphc_v2(p+1, p+2); | |
if(p[0].y > p[1].y) _hc_swaphc_v2(p+0, p+1); | |
int nx = p[0].x + (p[1].y - p[0].y) / (float)(p[2].y - p[0].y) * (p[2].x - p[0].x); | |
hc_v2 p4 = { nx, p[1].y }; | |
_hc_fill_half_tri(p[0], p[1], p4, fill, border); | |
_hc_fill_half_tri(p[2], p[1], p4, fill, border); | |
} | |
// TEXTURED TRIANGLE | |
typedef struct { | |
const hc_tex* tex; | |
float height; | |
} _hc_tex_info; | |
__attribute__((always_inline)) | |
static inline void _hc_texline(hc_v2* restrict _v[static restrict 4], intptr_t arg){ | |
typedef float v2f __attribute__((vector_size(8))); | |
const hc_vx** v = (const hc_vx**)_v; | |
const _hc_tex_info* info = (_hc_tex_info*)arg; | |
const hc_tex* tex = info->tex; | |
const uint32_t (*txp)[tex->w] = tex->data; | |
const float t = 1.0f - ((v[1]->y - v[0]->y) / info->height); | |
v2f tex0 = (v2f){ v[0]->tx, v[0]->ty } + t * ((v2f){ v[1]->tx, v[1]->ty } - (v2f){ v[0]->tx, v[0]->ty }); | |
v2f tex1 = (v2f){ v[3]->tx, v[3]->ty } + t * ((v2f){ v[2]->tx, v[2]->ty } - (v2f){ v[3]->tx, v[3]->ty }); | |
int_fast16_t x0 = v[0]->x, x1 = v[3]->x; | |
if(x0 > x1){ | |
int_fast16_t tmp = x0; | |
x0 = x1; | |
x1 = tmp; | |
v2f vtmp; | |
memcpy(&vtmp, &tex0, sizeof(tex0)); | |
memcpy(&tex0, &tex1, sizeof(tex0)); | |
memcpy(&tex1, &vtmp, sizeof(tex0)); | |
} | |
int w = x1 - x0; | |
float w1 = 1.0f / (float)w; | |
const v2f sz = { tex->w, tex->h }; | |
for(int i = 0; i < w; ++i){ | |
v2f p = (tex0 + (i * w1) * (tex1 - tex0)) * sz; | |
uint32_t c = txp[(int)p[1] % tex->h][(int)p[0] % tex->w]; | |
if(!(c >> 24)) | |
continue; | |
uint8_t r = (c >> 0 ) & 0xff; | |
uint8_t g = (c >> 8 ) & 0xff; | |
uint8_t b = (c >> 16) & 0xff; | |
c = 0xff000000 | (r << 16) | (g << 8) | b; | |
_hc_plot(x0 + i, v[0]->y, c); | |
} | |
} | |
#pragma GCC diagnostic ignored "-Wunused-parameter" | |
static void _hc_plot_nop(int x, int y, uint32_t c){} | |
#pragma GCC diagnostic pop | |
static void _hc_fill_half_tri_tex(hc_vx v[static 3], const hc_tex* restrict tex){ | |
hc_vx p3 = v[0]; | |
hc_v2* arr[] = { (hc_v2*)(v+0), (hc_v2*)(v+1), (hc_v2*)(v+2), (hc_v2*)&p3 }; | |
_hc_tex_info info = { | |
.tex = tex, | |
.height = fabsf((float)v[1].y - (float)v[0].y), | |
}; | |
_hc_half_tri(arr, (intptr_t)&info, 0, &_hc_plot_nop, &_hc_texline, &_hc_swaphc_vx2); | |
} | |
void hc_tri_tex(hc_vx verts[static 3], const hc_tex* tex){ | |
if(verts[0].y > verts[1].y) _hc_swaphc_vx(verts+0, verts+1); | |
if(verts[1].y > verts[2].y) _hc_swaphc_vx(verts+1, verts+2); | |
if(verts[0].y > verts[1].y) _hc_swaphc_vx(verts+0, verts+1); | |
float ty = (verts[1].y - verts[0].y) / (float)(verts[2].y - verts[0].y); | |
int nx = verts[0].x + ty * (verts[2].x - verts[0].x); | |
float d = (float)(verts[2].x - verts[0].x); | |
float tx = fabsf(d) < FLT_EPSILON ? 0.0f : (nx - verts[0].x) / d; | |
hc_vx v4 = { | |
.x = nx, | |
.y = verts[1].y, | |
.tx = verts[0].tx + tx * (verts[2].tx - verts[0].tx), | |
.ty = verts[0].ty + ty * (verts[2].ty - verts[0].ty), | |
}; | |
_hc_fill_half_tri_tex((hc_vx[]){ verts[0], verts[1], v4 }, tex); | |
_hc_fill_half_tri_tex((hc_vx[]){ verts[2], verts[1], v4 }, tex); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment