#include
#include
#include
#include
#include
EM_JS(void, peak_web_dom_open, (const char *id, int w, int h), {
var name = UTF8ToString(id);
var c = document.getElementById(name);
if (!c) {
c = document.createElement('canvas');
c.id = name;
document.body.appendChild(c);
}
c.width = w;
c.height = h;
c.tabIndex = 0;
c.focus();
});
EM_JS(void, peak_web_dom_present, (const char *id, int w, int h, uintptr_t pixels), {
var c = document.getElementById(UTF8ToString(id));
if (!c) return;
var ctx = c.getContext('2d');
if (c.width !== w || c.height !== h) {
c.width = w;
c.height = h;
}
var img = ctx.createImageData(w, h);
img.data.set(HEAPU8.subarray(pixels, pixels + w * h * 4));
ctx.putImageData(img, 0, 0);
});
struct peak_web_win {
char name[64];
uint32_t width, height;
PeakQ q;
uint32_t buffer[];
};
struct peak_window_internal_t {
struct peak_web_win *w;
};
static void
peak_web_sel(const char *name, char *out, size_t n)
{
out[0] = '#';
strncpy(out + 1, name, n - 2);
out[n - 1] = 0;
}
static PeakKeyCode
peak_web_key_map(const char *code)
{
if (code[0] == 'K' && code[1] == 'e' && code[2] == 'y' && code[3] >= 'A' && code[3] <= 'Z' && code[4] == 0)
return (PeakKeyCode)(PEAK_KEY_A + (code[3] - 'A'));
if (!strcmp(code, "ArrowUp")) return PEAK_KEY_UP;
if (!strcmp(code, "ArrowDown")) return PEAK_KEY_DOWN;
if (!strcmp(code, "ArrowLeft")) return PEAK_KEY_LEFT;
if (!strcmp(code, "ArrowRight")) return PEAK_KEY_RIGHT;
if (!strcmp(code, "Space")) return PEAK_KEY_SPACE;
if (!strcmp(code, "Escape")) return PEAK_KEY_ESCAPE;
if (!strcmp(code, "Enter")) return PEAK_KEY_ENTER;
return PEAK_KEY_UNKNOWN;
}
static EM_BOOL
peak_web_key(int type, const EmscriptenKeyboardEvent *e, void *ud)
{
PeakEvent ev = {0};
ev.type = (type == EMSCRIPTEN_EVENT_KEYDOWN) ? PEAK_EVENT_KEY_DOWN : PEAK_EVENT_KEY_UP;
ev.key.key = peak_web_key_map(e->code);
ev.key.mod = e->ctrlKey ? PEAK_KEYMOD_CTRL : e->altKey ? PEAK_KEYMOD_ALT : e->shiftKey ? PEAK_KEYMOD_SHIFT : 0;
peak_q_push(&((struct peak_web_win *)ud)->q, ev);
return EM_TRUE;
}
static EM_BOOL
peak_web_mouse(int type, const EmscriptenMouseEvent *e, void *ud)
{
PeakEvent ev = {0};
ev.type = PEAK_EVENT_POINTER;
ev.pointer.x = (float)e->targetX;
ev.pointer.y = (float)e->targetY;
if (type == EMSCRIPTEN_EVENT_MOUSEDOWN)
ev.pointer.state = PEAK_POINTER_PRESSED;
else if (type == EMSCRIPTEN_EVENT_MOUSEUP)
ev.pointer.state = PEAK_POINTER_RELEASED;
else
ev.pointer.state = PEAK_POINTER_MOVED;
if (type == EMSCRIPTEN_EVENT_MOUSEMOVE) {
ev.pointer.type = (e->buttons & 4) ? PEAK_POINTER_MIDDLE :
(e->buttons & 2) ? PEAK_POINTER_RIGHT : PEAK_POINTER_LEFT;
} else {
ev.pointer.type = (e->button == 1) ? PEAK_POINTER_MIDDLE :
(e->button == 2) ? PEAK_POINTER_RIGHT : PEAK_POINTER_LEFT;
}
peak_q_push(&((struct peak_web_win *)ud)->q, ev);
return EM_TRUE;
}
static void
peak_web_listen(struct peak_web_win *w, int on)
{
char sel[66];
peak_web_sel(w->name, sel, sizeof sel);
emscripten_set_keydown_callback(sel, w, EM_TRUE, on ? peak_web_key : NULL);
emscripten_set_keyup_callback(sel, w, EM_TRUE, on ? peak_web_key : NULL);
emscripten_set_mousedown_callback(sel, w, EM_TRUE, on ? peak_web_mouse : NULL);
emscripten_set_mouseup_callback(sel, w, EM_TRUE, on ? peak_web_mouse : NULL);
emscripten_set_mousemove_callback(sel, w, EM_TRUE, on ? peak_web_mouse : NULL);
}
static int
peak_platform_init(void)
{
return 1;
}
static void
peak_platform_quit(void)
{
}
static PeakWindowInternal
peak_platform_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags)
{
PeakWindowInternal intern = {0};
struct peak_web_win *w;
(void)flags;
w = calloc(1, sizeof *w + (size_t)width * height * sizeof *w->buffer);
if (!w)
return intern;
strncpy(w->name, name, sizeof w->name - 1);
w->width = width;
w->height = height;
peak_web_dom_open(w->name, (int)width, (int)height);
peak_web_listen(w, 1);
intern.w = w;
return intern;
}
static void
peak_platform_window_close(PeakWindowInternal *intern)
{
struct peak_web_win *w;
if (!intern || !intern->w)
return;
w = intern->w;
peak_web_listen(w, 0);
free(w);
intern->w = NULL;
}
static uint32_t *
peak_platform_window_buffer(PeakWindowInternal *intern, size_t *width, size_t *height)
{
struct peak_web_win *w = intern ? intern->w : NULL;
if (!w) {
*width = 0;
*height = 0;
return NULL;
}
*width = w->width;
*height = w->height;
return w->buffer;
}
static void
peak_platform_window_present(PeakWindowInternal *intern)
{
struct peak_web_win *w = intern ? intern->w : NULL;
if (!w)
return;
peak_web_dom_present(w->name, (int)w->width, (int)w->height, (uintptr_t)w->buffer);
}
static bool
peak_platform_epoll(PeakWindowInternal *intern, PeakEvent *ev)
{
struct peak_web_win *w = intern ? intern->w : NULL;
return w ? peak_q_pop(&w->q, ev) : 0;
}
#define PEAK_AUDIO_FRAMES 1024
static struct {
int run;
uint32_t channels;
int16_t *buf;
void (*fill)(int16_t *out, size_t frames, void *userdata);
void *userdata;
} peak_web_audio;
void EMSCRIPTEN_KEEPALIVE
peak_internal_web_audio_fill(int16_t *out, int frames)
{
size_t n;
if (!peak_web_audio.run || !out || frames <= 0)
return;
n = (size_t)frames * peak_web_audio.channels;
memset(out, 0, n * sizeof(int16_t));
if (peak_web_audio.fill)
peak_web_audio.fill(out, (size_t)frames, peak_web_audio.userdata);
}
EM_JS(int, peak_web_audio_dom_start, (int channels, int rate, int frames, uintptr_t ptr), {
var AC = window.AudioContext || window.webkitAudioContext;
var ctx, proc, i, c, heap, off, ch;
if (!AC || Module._peak_web_audio)
return 0;
ctx = new AC({ sampleRate: rate });
if (ctx.sampleRate !== rate) {
ctx.close();
return 0;
}
proc = ctx.createScriptProcessor(frames, 0, channels);
proc.onaudioprocess = function(e) {
Module._peak_internal_web_audio_fill(ptr, frames);
heap = Module.HEAP16;
off = ptr >> 1;
for (c = 0; c < channels; c++) {
ch = e.outputBuffer.getChannelData(c);
for (i = 0; i < frames; i++)
ch[i] = heap[off + i * channels + c] / 32768.0;
}
};
proc.connect(ctx.destination);
ctx.resume();
Module._peak_web_audio = { ctx: ctx, proc: proc };
return 1;
});
EM_JS(void, peak_web_audio_dom_stop, (void), {
var a = Module._peak_web_audio;
if (!a)
return;
a.proc.disconnect();
a.ctx.close();
Module._peak_web_audio = null;
});
static int
peak_platform_audio_start(uint32_t channels, uint32_t rate, void (*fill)(int16_t *out, size_t frames, void *userdata), void *userdata)
{
if (channels > 32)
return 0;
if (!(peak_web_audio.buf = calloc((size_t)channels * PEAK_AUDIO_FRAMES, sizeof(int16_t))))
return 0;
peak_web_audio.fill = fill;
peak_web_audio.userdata = userdata;
peak_web_audio.channels = channels;
peak_web_audio.run = 1;
if (!peak_web_audio_dom_start((int)channels, (int)rate, PEAK_AUDIO_FRAMES, (uintptr_t)peak_web_audio.buf)) {
free(peak_web_audio.buf);
peak_web_audio.buf = NULL;
peak_web_audio.run = 0;
peak_web_audio.fill = NULL;
return 0;
}
return 1;
}
static uint64_t
peak_platform_get_time(void)
{
return (uint64_t)(emscripten_get_now() * 1000000.0);
}
static void
peak_platform_sleep_ns(int64_t ns)
{
if (ns <= 0) return;
emscripten_sleep((unsigned)(ns / 1000000));
}
static const char **
peak_platform_vulkan_get_extensions(uint32_t *count)
{
if (count) *count = 0;
return NULL;
}
static int
peak_platform_vulkan_create_surface(PeakWindowInternal *w, void *instance, const void *allocator, void *out_surface)
{
(void)w; (void)instance; (void)allocator; (void)out_surface;
return 0;
}
static void
peak_platform_audio_stop(void)
{
peak_web_audio.run = 0;
peak_web_audio_dom_stop();
free(peak_web_audio.buf);
peak_web_audio.buf = NULL;
peak_web_audio.fill = NULL;
peak_web_audio.userdata = NULL;
}