From 5dc2716abd43166cd65133625f408ad36a6623b3 Mon Sep 17 00:00:00 2001 From: Vasco Date: Mon, 24 Aug 2026 00:56:24 +0100 Subject: Peak 0.4.0 and Draw --- Peak/build.c | 39 + Peak/demos/demo | Bin 0 -> 43544 bytes Peak/demos/demo.c | 38 +- Peak/demos/demo.html | 19 + Peak/demos/demo.js | 2370 ++++++++++++++++++++++++++++++++++++++++ Peak/demos/demo.wasm | Bin 0 -> 30983 bytes Peak/demos/demo_replace_main.c | 67 -- Peak/demos/demo_run | Bin 0 -> 43912 bytes Peak/demos/demo_run.c | 76 ++ Peak/peak.h | 1556 ++++++++++++++++---------- Peak/src/p_emscripten.c | 194 +++- Peak/src/p_linux.c | 515 ++++----- Peak/src/p_win32.c | 399 ++++++- Peak/src/peak.c | 300 ++--- Peak/src/peak_header.h | 164 ++- Peak/src/peak_header.h.out | 1556 ++++++++++++++++---------- 16 files changed, 5398 insertions(+), 1895 deletions(-) create mode 100644 Peak/build.c create mode 100755 Peak/demos/demo create mode 100644 Peak/demos/demo.html create mode 100644 Peak/demos/demo.js create mode 100755 Peak/demos/demo.wasm delete mode 100644 Peak/demos/demo_replace_main.c create mode 100755 Peak/demos/demo_run create mode 100644 Peak/demos/demo_run.c (limited to 'Peak') diff --git a/Peak/build.c b/Peak/build.c new file mode 100644 index 0000000..c3b20b9 --- /dev/null +++ b/Peak/build.c @@ -0,0 +1,39 @@ +#define POOF_IMPLEMENTATION +#include "../Poof/poof.h" + +int main(int argc, char **argv) { + POOF_GO_REBUILD_URSELF(argc, argv); + + Poof_Cmd cmd = {0}; + poof_cmd_append(&cmd, "python", "../bundle_header.py", "src/peak_header.h"); + if (!poof_cmd_run(&cmd)) return 1; + + poof_copy_file("src/peak_header.h.out", "peak.h"); + + Poof_CC cc = {0}; + poof_cc_init(&cc, POOF_CC_GCC | POOF_CC_CLANG, POOF_TARGET_HOST); + cc.debug_mode = true; + cc.optimization = POOF_O0; + cc.output = "demos/demo"; + poof_cc_append(&cc.inputs, "demos/demo.c"); + poof_cc_append(&cc.extra_flags, "-std=c99", "-Wall", "-Wno-deprecated-declarations"); + poof_cc_append(&cc.libs, "m"); + if (!poof_cc_run(&cc)) return 1; + + cc.debug_mode = true; + cc.optimization = POOF_O0; + cc.output = "demos/demo_run"; + poof_cc_append(&cc.inputs, "demos/demo_run.c"); + poof_cc_append(&cc.extra_flags, "-std=c99", "-Wall", "-Wno-deprecated-declarations"); + poof_cc_append(&cc.libs, "m"); + if (!poof_cc_run(&cc)) return 1; + + poof_cmd_clear(&cmd); + poof_cmd_append(&cmd, "emcc", "demos/demo_run.c", "-o", "demos/demo.js", + "-std=c99", "-Wall", "-Wno-deprecated-declarations", + "-sALLOW_MEMORY_GROWTH=1", "-sENVIRONMENT=web"); + if (!poof_cmd_run(&cmd)) return 1; + + return 0; +} + diff --git a/Peak/demos/demo b/Peak/demos/demo new file mode 100755 index 0000000..8f72b0a Binary files /dev/null and b/Peak/demos/demo differ diff --git a/Peak/demos/demo.c b/Peak/demos/demo.c index 7aec9b3..cb5212d 100644 --- a/Peak/demos/demo.c +++ b/Peak/demos/demo.c @@ -1,16 +1,14 @@ #include -#include -#include #define PEAK_IMPLEMENTATION #include "../peak.h" - void print_window_resize(PeakEvent ev) { printf("window: %u %u\n", ev.resize.width, ev.resize.height); } void print_window_close(PeakEvent ev) { + (void)ev; printf("WINDOW CLOSE!!!!!!\nWOWWWOWAAAAAAAAAAAAAAAAAAAAAAAAh\n"); } @@ -34,32 +32,30 @@ print_event_func print_event[PEAK_EVENT_LAST] = { [PEAK_EVENT_KEY_UP] = print_key, }; +int main(int argc, char **argv) { + (void)argc; + (void)argv; -typedef struct { - char *msg; -} AppData; + if (!peak_init()) return 1; -static AppData data; + PeakWindow win = peak_window_open("Demo", 800, 600, 0); + peak_window_clear(&win, 0.5, 0.5, 0, 1); -int main(int argc, char**argv) { - peak_init(); - int running = 1000000; PeakEvent ev; - data.msg = "hello world"; - while (running--) { - while (peak_poll_events(&ev)) { - if (ev.type == PEAK_EVENT_NONE) - continue; + while (win.running) { + while (peak_window_epoll(&win, &ev)) { + if (ev.type == PEAK_EVENT_NONE) continue; print_event[ev.type](ev); - switch (ev.type) { - case PEAK_EVENT_WINDOW_CLOSE: - printf("STOPPPPEEED\n"); - return 1; + if (ev.type == PEAK_EVENT_WINDOW_CLOSE) { + win.running = 0; + break; } } - // printf("frame: %d - %s\n", running, data.msg); + if (!win.running) break; + } + + peak_window_close(&win); peak_quit(); return 0; } - diff --git a/Peak/demos/demo.html b/Peak/demos/demo.html new file mode 100644 index 0000000..a10e85e --- /dev/null +++ b/Peak/demos/demo.html @@ -0,0 +1,19 @@ + + + + + + Peak + + + +
+ +
+ + + diff --git a/Peak/demos/demo.js b/Peak/demos/demo.js new file mode 100644 index 0000000..6ebf1b1 --- /dev/null +++ b/Peak/demos/demo.js @@ -0,0 +1,2370 @@ +// include: shell.js +// include: minimum_runtime_check.js +(function() { + // "30.0.0" -> 300000 + function humanReadableVersionToPacked(str) { + str = str.split('-')[0]; // Remove any trailing part from e.g. "12.53.3-alpha" + var vers = str.split('.').slice(0, 3); + while(vers.length < 3) vers.push('00'); + vers = vers.map((n, i, arr) => n.padStart(2, '0')); + return vers.join(''); + } + // 300000 -> "30.0.0" + var packedVersionToHumanReadable = n => [n / 10000 | 0, (n / 100 | 0) % 100, n % 100].join('.'); + + var TARGET_NOT_SUPPORTED = 2147483647; + + // Note: We use a typeof check here instead of optional chaining using + // globalThis because older browsers might not have globalThis defined. + + // We skip the node version checking when running on Bun/Deno since the node + // version they report doesn't seem to be useful. + if (typeof process !== 'undefined' && !process.versions?.bun && typeof Deno == "undefined") { + var currentNodeVersion = process.versions?.node ? humanReadableVersionToPacked(process.versions.node) : TARGET_NOT_SUPPORTED; + if (currentNodeVersion < TARGET_NOT_SUPPORTED) { + throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + } + if (currentNodeVersion < 2147483647) { + throw new Error(`This emscripten-generated code requires node v${ packedVersionToHumanReadable(2147483647) } (detected v${packedVersionToHumanReadable(currentNodeVersion)})`); + } + } + + var userAgent = typeof navigator !== 'undefined' && navigator.userAgent; + if (!userAgent) { + return; + } + + var currentSafariVersion = userAgent.includes("Safari/") && !userAgent.includes("Chrome/") && userAgent.match(/Version\/(\d+\.?\d*\.?\d*)/) ? humanReadableVersionToPacked(userAgent.match(/Version\/(\d+\.?\d*\.?\d*)/)[1]) : TARGET_NOT_SUPPORTED; + if (currentSafariVersion < 150000) { + throw new Error(`This emscripten-generated code requires Safari v${ packedVersionToHumanReadable(150000) } (detected v${currentSafariVersion})`); + } + + var currentFirefoxVersion = userAgent.match(/Firefox\/(\d+(?:\.\d+)?)/) ? parseFloat(userAgent.match(/Firefox\/(\d+(?:\.\d+)?)/)[1]) : TARGET_NOT_SUPPORTED; + if (currentFirefoxVersion < 79) { + throw new Error(`This emscripten-generated code requires Firefox v79 (detected v${currentFirefoxVersion})`); + } + + var currentChromeVersion = userAgent.match(/Chrome\/(\d+(?:\.\d+)?)/) ? parseFloat(userAgent.match(/Chrome\/(\d+(?:\.\d+)?)/)[1]) : TARGET_NOT_SUPPORTED; + if (currentChromeVersion < 85) { + throw new Error(`This emscripten-generated code requires Chrome v85 (detected v${currentChromeVersion})`); + } +})(); + +// end include: minimum_runtime_check.js +// The Module object: Our interface to the outside world. We import +// and export values on it. There are various ways Module can be used: +// 1. Not defined. We create it here +// 2. A function parameter, function(moduleArg) => Promise +// 3. pre-run appended it, var Module = {}; ..generated code.. +// 4. External script tag defines var Module. +// We need to check if Module already exists (e.g. case 3 above). +// Substitution will be replaced with actual code on later stage of the build, +// this way Closure Compiler will not mangle it (e.g. case 4. above). +// Note that if you want to run closure, and also to use Module +// after the generated code, you will need to define var Module = {}; +// before the code. Then that object will be used in the code, and you +// can continue to use Module afterwards as well. +var Module = typeof Module != 'undefined' ? Module : {}; + +// Determine the runtime environment we are in. You can customize this by +// setting the ENVIRONMENT setting at compile time (see settings.js). + +// Attempt to auto-detect the environment +var ENVIRONMENT_IS_WEB = !!globalThis.window; +var ENVIRONMENT_IS_WORKER = !!globalThis.WorkerGlobalScope; +// N.b. Electron.js environment is simultaneously a NODE-environment, but +// also a web environment. +var ENVIRONMENT_IS_NODE = globalThis.process?.versions?.node && globalThis.process?.type != 'renderer'; +var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; + +// --pre-jses are emitted after the Module integration code, so that they can +// refer to Module (if they choose; they can also define Module) + + +var programArgs = []; +var thisProgram = './this.program'; +var quit_ = (status, toThrow) => { + throw toThrow; +}; + +// In MODULARIZE mode _scriptName needs to be captured already at the very top of the page immediately when the page is parsed, so it is generated there +// before the page load. In non-MODULARIZE modes generate it here. +var _scriptName = globalThis.document?.currentScript?.src; + +// `/` should be present at the end if `scriptDirectory` is not empty +var scriptDirectory = ''; +function locateFile(path) { + if (Module['locateFile']) { + return Module['locateFile'](path, scriptDirectory); + } + return scriptDirectory + path; +} + +// Hooks that are implemented differently in different runtime environments. +var readAsync, readBinary; + +if (ENVIRONMENT_IS_SHELL) { + +} else + +// Note that this includes Node.js workers when relevant (pthreads is enabled). +// Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and +// ENVIRONMENT_IS_NODE. +if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { + try { + scriptDirectory = new URL('.', _scriptName).href; // includes trailing slash + } catch { + // Must be a `blob:` or `data:` URL (e.g. `blob:http://site.com/etc/etc`), we cannot + // infer anything from them. + } + + if (!(globalThis.window || globalThis.WorkerGlobalScope)) throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); + + { +// include: web_or_worker_shell_read.js +readAsync = async (url) => { + assert(!isFileURI(url), "readAsync does not work with file:// URLs"); + var response = await fetch(url, { credentials: 'same-origin' }); + if (response.ok) { + return response.arrayBuffer(); + } + throw new Error(response.status + ' : ' + response.url); + }; +// end include: web_or_worker_shell_read.js + } +} else +{ + throw new Error('environment detection error'); +} + +var out = console.log.bind(console); +var err = console.error.bind(console); + +var IDBFS = 'IDBFS is no longer included by default; build with -lidbfs.js'; +var PROXYFS = 'PROXYFS is no longer included by default; build with -lproxyfs.js'; +var WORKERFS = 'WORKERFS is no longer included by default; build with -lworkerfs.js'; +var FETCHFS = 'FETCHFS is no longer included by default; build with -lfetchfs.js'; +var ICASEFS = 'ICASEFS is no longer included by default; build with -licasefs.js'; +var JSFILEFS = 'JSFILEFS is no longer included by default; build with -ljsfilefs.js'; +var OPFS = 'OPFS is no longer included by default; build with -lopfs.js'; + +var NODEFS = 'NODEFS is no longer included by default; build with -lnodefs.js'; + +// perform assertions in shell.js after we set up out() and err(), as otherwise +// if an assertion fails it cannot print the message + +assert(!ENVIRONMENT_IS_WORKER, 'worker environment detected but not enabled at build time (add `worker` to `-sENVIRONMENT` to enable)'); + +assert(!ENVIRONMENT_IS_NODE, 'node environment detected but not enabled at build time (add `node` to `-sENVIRONMENT` to enable)'); + +assert(!ENVIRONMENT_IS_SHELL, 'shell environment detected but not enabled at build time (add `shell` to `-sENVIRONMENT` to enable)'); + +// end include: shell.js + +// include: preamble.js +// === Preamble library stuff === + +// Documentation for the public APIs defined in this file must be updated in: +// site/source/docs/api_reference/preamble.js.rst +// A prebuilt local version of the documentation is available at: +// site/build/text/docs/api_reference/preamble.js.txt +// You can also build docs locally as HTML or other formats in site/ +// An online HTML version (which may be of a different version of Emscripten) +// is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html + +var wasmBinary; + +if (!globalThis.WebAssembly) { + err('no native wasm support detected'); +} + +// Wasm globals + +//======================================== +// Runtime essentials +//======================================== + +// whether we are quitting the application. no code should run after this. +// set in exit() and abort() +var ABORT = false; + +// set by exit() and abort(). Passed to 'onExit' handler. +// NOTE: This is also used as the process return code in shell environments +// but only when noExitRuntime is false. +var EXITSTATUS; + +// In STRICT mode, we only define assert() when ASSERTIONS is set. i.e. we +// don't define it at all in release modes. This matches the behaviour of +// MINIMAL_RUNTIME. +// TODO(sbc): Make this the default even without STRICT enabled. +/** @type {function(*, string=)} */ +function assert(condition, text) { + if (!condition) { + abort('Assertion failed' + (text ? ': ' + text : '')); + } +} + +// We used to include malloc/free by default in the past. Show a helpful error in +// builds with assertions. +function _free() { + // Show a helpful error since we used to include free by default in the past. + abort('free() called but not included in the build - add `_free` to EXPORTED_FUNCTIONS'); +} + +/** + * Indicates whether filename is delivered via file protocol (as opposed to http/https) + * @noinline + */ +var isFileURI = (filename) => filename.startsWith('file://'); + +// include: runtime_common.js +// include: runtime_exceptions.js +// Base Emscripten EH error class +class EmscriptenEH {} + +class EmscriptenSjLj extends EmscriptenEH {} + +// end include: runtime_exceptions.js +// include: runtime_debug.js +var runtimeDebug = true; // Switch to false at runtime to disable logging at the right times + +// Used by XXXXX_DEBUG settings to output debug messages. +function dbg(...args) { + if (!runtimeDebug && typeof runtimeDebug != 'undefined') return; + // TODO(sbc): Make this configurable somehow. Its not always convenient for + // logging to show up as warnings. + console.warn(...args); +} + +// Endianness check +(() => { + var h16 = new Int16Array(1); + var h8 = new Int8Array(h16.buffer); + h16[0] = 0x6373; + if (h8[0] !== 0x73 || h8[1] !== 0x63) abort('Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)'); +})(); + +function consumedModuleProp(prop) { + var value = Module[prop]; + var msg = `Attempt to modify \`Module.${prop}\` after it has already been processed. This can happen, for example, when code is injected via '--post-js' rather than '--pre-js'`; + if (Array.isArray(value)) { + value = new Proxy(value, { + set(target, key, val) { + abort(msg); + return false; + }, + defineProperty(target, key, descriptor) { + abort(msg); + return false; + }, + deleteProperty(target, key) { + abort(msg); + return false; + } + }); + } + Object.defineProperty(Module, prop, { + configurable: true, + get() { return value; }, + set() { + abort(msg); + } + }); +} + +function makeInvalidEarlyAccess(name) { + return () => assert(false, `call to '${name}' via reference taken before Wasm module initialization`); + +} + +function ignoredModuleProp(prop) { + if (Object.getOwnPropertyDescriptor(Module, prop)) { + abort(`\`Module.${prop}\` was supplied but \`${prop}\` not included in INCOMING_MODULE_JS_API`); + } +} + +// forcing the filesystem exports a few things by default +function isExportedByForceFilesystem(name) { + return name === 'FS_createPath' || + name === 'FS_createDataFile' || + name === 'FS_createPreloadedFile' || + name === 'FS_preloadFile' || + name === 'FS_unlink' || + name === 'addRunDependency' || + // The old FS has some functionality that WasmFS lacks. + name === 'FS_createLazyFile' || + name === 'FS_createDevice' || + name === 'removeRunDependency'; +} + +/** + * Intercept access to a symbols in the global symbol. This enables us to give + * informative warnings/errors when folks attempt to use symbols they did not + * include in their build, or no symbols that no longer exist. + * + * We don't define this in MODULARIZE mode since in that mode emscripten symbols + * are never placed in the global scope. + */ +function hookGlobalSymbolAccess(sym, func) { + if (!Object.getOwnPropertyDescriptor(globalThis, sym)) { + Object.defineProperty(globalThis, sym, { + configurable: true, + get() { + func(); + return undefined; + } + }); + } +} + +function missingGlobal(sym, msg) { + hookGlobalSymbolAccess(sym, () => { + warnOnce(`\`${sym}\` is no longer defined by emscripten. ${msg}`); + }); +} + +missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer'); +missingGlobal('asm', 'Please use wasmExports instead'); + +function missingLibrarySymbol(sym) { + hookGlobalSymbolAccess(sym, () => { + // Can't `abort()` here because it would break code that does runtime + // checks. e.g. `if (typeof SDL === 'undefined')`. + var msg = `\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`; + // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in + // library.js, which means $name for a JS name with no prefix, or name + // for a JS name like _name. + var librarySymbol = sym; + if (!librarySymbol.startsWith('_')) { + librarySymbol = '$' + sym; + } + msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + warnOnce(msg); + }); + + // Any symbol that is not included from the JS library is also (by definition) + // not exported on the Module object. + unexportedRuntimeSymbol(sym); +} + +function unexportedRuntimeSymbol(sym) { + if (!Object.getOwnPropertyDescriptor(Module, sym)) { + Object.defineProperty(Module, sym, { + configurable: true, + get() { + var msg = `'${sym}' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the Emscripten FAQ)`; + if (isExportedByForceFilesystem(sym)) { + msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; + } + abort(msg); + }, + }); + } +} + +// end include: runtime_debug.js +// include: runtime_stack_check.js +const stackCookie1 = 0x02135467; +const stackCookie2 = 0x89BACDFE; + +// Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode. +function writeStackCookie() { + var max = _emscripten_stack_get_end(); + assert((max & 3) == 0); + // If the stack ends at address zero we write our cookies 4 bytes into the + // stack. This prevents interference with SAFE_HEAP and ASAN which also + // monitor writes to address zero. + if (max == 0) { + max += 4; + } + // The stack grow downwards towards _emscripten_stack_get_end. + // We write cookies to the final two words in the stack and detect if they are + // ever overwritten. + HEAPU32[((max)>>2)] = stackCookie1; + HEAPU32[(((max)+(4))>>2)] = stackCookie2; + // Also test the global address 0 for integrity. + HEAPU32[((0)>>2)] = 1668509029; +} + +function u32ToHexString(num) { + return '0x' + (num >>> 0).toString(16).padStart(8, '0'); +} + +function checkStackCookie() { + if (ABORT) return; + var max = _emscripten_stack_get_end(); + // See writeStackCookie(). + if (max == 0) { + max += 4; + } + var val1 = HEAPU32[((max)>>2)]; + var val2 = HEAPU32[(((max)+(4))>>2)]; + if (val1 != stackCookie1 || val2 != stackCookie2) { + abort(`Stack overflow! Stack cookie has been overwritten at ${ptrToString(max)}, expected hex dwords ${u32ToHexString(stackCookie2)} and ${u32ToHexString(stackCookie1)}, but received ${u32ToHexString(val2)} ${u32ToHexString(val1)}`); + } + // Also test the global address 0 for integrity. + if (HEAPU32[((0)>>2)] != 0x63736d65 /* 'emsc' */) { + abort('Runtime error: The application has corrupted its heap memory area (address zero)!'); + } +} +// end include: runtime_stack_check.js +// Memory management + +var runtimeInitialized = false; + + + +// When ALLOW_MEMORY_GROWTH is enabled, the conversion from Wasm +// memory to ArrayBuffer requires some additional logic. +function getMemoryBuffer() { + return wasmMemory.buffer; +} + +function updateMemoryViews() { + // If we already have a heap that is resizeable/growable buffer we don't + // need to do anything in updateMemoryViews. + if (HEAP8?.buffer?.resizable) return; + var b = getMemoryBuffer(); + HEAP8 = new Int8Array(b); + HEAP16 = new Int16Array(b); + HEAPU8 = new Uint8Array(b); + + HEAP32 = new Int32Array(b); + HEAPU32 = new Uint32Array(b); + + HEAPF64 = new Float64Array(b); + HEAP64 = new BigInt64Array(b); + +} + +// include: memoryprofiler.js +// end include: memoryprofiler.js +// end include: runtime_common.js +assert(globalThis.Int32Array && globalThis.Float64Array && Int32Array.prototype.subarray && Int32Array.prototype.set, + 'JS engine does not provide full typed array support'); + +function preRun() { + var preRun = Module['preRun']; + if (preRun) { + if (typeof preRun == 'function') preRun = [preRun]; + onPreRuns.push(...preRun); + } + consumedModuleProp('preRun'); + // Begin ATPRERUNS hooks + callRuntimeCallbacks(onPreRuns); + // End ATPRERUNS hooks +} + +function initRuntime() { + assert(!runtimeInitialized); + runtimeInitialized = true; + + checkStackCookie(); + + // No ATINITS hooks + + wasmExports['__wasm_call_ctors'](); + + // No ATPOSTCTORS hooks + + checkStackCookie(); +} + +function postRun() { + checkStackCookie(); + + var postRun = Module['postRun']; + if (postRun) { + if (typeof postRun == 'function') postRun = [postRun]; + onPostRuns.push(...postRun); + } + consumedModuleProp('postRun'); + + // Begin ATPOSTRUNS hooks + callRuntimeCallbacks(onPostRuns); + // End ATPOSTRUNS hooks +} + +/** + * @param {string|number=} what + */ +function abort(what) { + Module['onAbort']?.(what); + + what = `Aborted(${what})`; + // TODO(sbc): Should we remove printing and leave it up to whoever + // catches the exception? + err(what); + + ABORT = true; + + // Use a wasm runtime error, because a JS error might be seen as a foreign + // exception, which means we'd run destructors on it. We need the error to + // simply make the program stop. + // FIXME This approach does not work in Wasm EH because it currently does not assume + // all RuntimeErrors are from traps; it decides whether a RuntimeError is from + // a trap or not based on a hidden field within the object. So at the moment + // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that + // allows this in the wasm spec. + + // Suppress closure compiler warning here. Closure compiler's builtin extern + // definition for WebAssembly.RuntimeError claims it takes no arguments even + // though it can. + // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. + /** @suppress {checkTypes} */ + var e = new WebAssembly.RuntimeError(what); + + // Throw the error whether or not MODULARIZE is set because abort is used + // in code paths apart from instantiation where an exception is expected + // to be thrown when abort is called. + throw e; +} + +// show errors on likely calls to FS when it was not included +function fsMissing() { + abort('Filesystem support (FS) was not included. The problem is that you are using files from JS, but files were not used from C/C++, so filesystem support was not auto-included. You can force-include filesystem support with -sFORCE_FILESYSTEM'); +} +var FS = { + init: fsMissing, + createDataFile: fsMissing, + createPreloadedFile: fsMissing, + createLazyFile: fsMissing, + open: fsMissing, + mkdev: fsMissing, + registerDevice: fsMissing, + analyzePath: fsMissing, + ErrnoError: fsMissing, +}; + + +function createExportWrapper(name, func, nargs) { + assert(func); + return (...args) => { + assert(runtimeInitialized, `native function \`${name}\` called before runtime initialization`); + // Only assert for too many arguments. Too few can be valid since the missing arguments will be zero filled. + assert(args.length <= nargs, `native function \`${name}\` called with ${args.length} args but expects ${nargs}`); + return func(...args); + }; +} + +var wasmBinaryFile; + +function findWasmBinary() { + return locateFile('demo.wasm'); +} + +function getBinarySync(file) { + if (readBinary) { + return readBinary(file); + } + // Throwing a plain string here, even though it not normally advisable since + // this gets turning into an `abort` in instantiateArrayBuffer. + throw 'both async and sync fetching of the wasm failed'; +} + +async function getWasmBinary(binaryFile) { + // If we don't have the binary yet, load it asynchronously using readAsync. + if (!wasmBinary) { + // Fetch the binary using readAsync + try { + var response = await readAsync(binaryFile); + return new Uint8Array(response); + } catch { + // Fall back to getBinarySync below; + } + } + + // Otherwise, getBinarySync should be able to get it synchronously + return getBinarySync(binaryFile); +} + +async function instantiateArrayBuffer(binaryFile, imports) { + try { + var binary = await getWasmBinary(binaryFile); + var instance = await WebAssembly.instantiate(binary, imports); + return instance; + } catch (reason) { + err(`failed to asynchronously prepare wasm: ${reason}`); + + // Warn on some common problems. + if (isFileURI(binaryFile)) { + err(`warning: Loading from a file URI (${binaryFile}) is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing`); + } + abort(reason); + } +} + +async function instantiateAsync(binary, binaryFile, imports) { + if (!binary + ) { + try { + var response = fetch(binaryFile, { credentials: 'same-origin' }); + var instantiationResult = await WebAssembly.instantiateStreaming(response, imports); + return instantiationResult; + } catch (reason) { + // We expect the most common failure cause to be a bad MIME type for the binary, + // in which case falling back to ArrayBuffer instantiation should work. + err(`wasm streaming compile failed: ${reason}`); + err('falling back to ArrayBuffer instantiation'); + // fall back of instantiateArrayBuffer below + }; + } + return instantiateArrayBuffer(binaryFile, imports); +} + +function getWasmImports() { + // prepare imports + var imports = { + 'env': wasmImports, + 'wasi_snapshot_preview1': wasmImports, + }; + return imports; +} + +// Create the wasm instance. +// Receives the wasm imports, returns the exports. +async function createWasm() { + // Load the wasm module and create an instance of using native support in the JS engine. + // handle a generated wasm instance, receiving its exports and + // performing other necessary setup + function receiveInstance(instance) { + wasmExports = instance.exports; + + assignWasmExports(wasmExports); + + updateMemoryViews(); + + return wasmExports; + } + + // Prefer streaming instantiation if available. + // Async compilation can be confusing when an error on the page overwrites Module + // (for example, if the order of elements is wrong, and the one defining Module is + // later), so we save Module and check it later. + var trueModule = Module; + function receiveInstantiationResult(result) { + // 'result' is a ResultObject object which has both the module and instance. + // receiveInstance() will swap in the exports (to Module.asm) so they can be called + assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?'); + trueModule = null; + // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. + // When the regression is fixed, can restore the above PTHREADS-enabled path. + return receiveInstance(result['instance']); + } + + var info = getWasmImports(); + + // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback + // to manually instantiate the Wasm module themselves. This allows pages to + // run the instantiation parallel to any other async startup actions they are + // performing. + // Also pthreads and wasm workers initialize the wasm instance through this + // path. + var instantiateWasm = Module['instantiateWasm']; + if (instantiateWasm) { + return new Promise((resolve) => { + try { + instantiateWasm(info, (inst) => resolve(receiveInstance(inst))); + } catch(e) { + err(`Module.instantiateWasm callback failed with error: ${e}`); + throw e; + } + }); + } + + wasmBinaryFile ??= findWasmBinary(); + var result = await instantiateAsync(wasmBinary, wasmBinaryFile, info); + var exports = receiveInstantiationResult(result); + return exports; +} + +// end include: preamble.js + +// Begin JS library code + + + class ExitStatus { + name = 'ExitStatus'; + constructor(status) { + this.message = `Program terminated with exit(${status})`; + this.status = status; + } + } + + /** @type {!Int32Array} */ + var HEAP32; + + /** @type {!Int8Array} */ + var HEAP8; + + /** @type {!Uint32Array} */ + var HEAPU32; + + var callRuntimeCallbacks = (callbacks) => { + while (callbacks.length > 0) { + // Pass the module as the first argument. + callbacks.shift()(Module); + } + }; + var onPostRuns = []; + var addOnPostRun = (cb) => onPostRuns.push(cb); + + var onPreRuns = []; + var addOnPreRun = (cb) => onPreRuns.push(cb); + + + var noExitRuntime = true; + + function ptrToString(ptr) { + assert(typeof ptr === 'number', `ptrToString expects a number, got ${typeof ptr}`); + // Convert to 32-bit unsigned value + ptr >>>= 0; + return '0x' + ptr.toString(16).padStart(8, '0'); + } + + var stackRestore = (val) => __emscripten_stack_restore(val); + + var stackSave = () => _emscripten_stack_get_current(); + + var warnOnce = (text) => { + warnOnce.shown ||= {}; + if (!warnOnce.shown[text]) { + warnOnce.shown[text] = 1; + err(text); + } + }; + + + + var UTF8Decoder = globalThis.TextDecoder && new TextDecoder(); + + + /** + * heapOrArray is either a regular array, or a JavaScript typed array view. + * @param {number} idx + * @param {number=} maxBytesToRead + * @param {boolean=} ignoreNul + * @return {number} + */ + var findStringEnd = (heapOrArray, idx, maxBytesToRead, ignoreNul) => { + var maxIdx = idx + maxBytesToRead; + if (ignoreNul) return maxIdx; + // TextDecoder needs to know the byte length in advance, it doesn't stop on + // null terminator by itself. + // As a tiny code save trick, compare idx against maxIdx using a negation, + // so that maxBytesToRead=undefined/NaN means Infinity. + while (heapOrArray[idx] && !(idx >= maxIdx)) ++idx; + return idx; + }; + + + /** + * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given + * array that contains uint8 values, returns a copy of that string as a + * Javascript String object. + * heapOrArray is either a regular array, or a JavaScript typed array view. + * @param {number=} idx + * @param {number=} maxBytesToRead + * @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character. + * @return {string} + */ + var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead, ignoreNul) => { + + var endPtr = findStringEnd(heapOrArray, idx, maxBytesToRead, ignoreNul); + + // When using conditional TextDecoder, skip it for short strings as the overhead of the native call is not worth it. + if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { + return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); + } + var str = ''; + while (idx < endPtr) { + // For UTF8 byte structure, see: + // http://en.wikipedia.org/wiki/UTF-8#Description + // https://www.ietf.org/rfc/rfc2279.txt + // https://tools.ietf.org/html/rfc3629 + var u0 = heapOrArray[idx++]; + if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } + var u1 = heapOrArray[idx++] & 63; + if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } + var u2 = heapOrArray[idx++] & 63; + if ((u0 & 0xF0) == 0xE0) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + if ((u0 & 0xF8) != 0xF0) warnOnce(`Invalid UTF-8 leading byte ${ptrToString(u0)} encountered when deserializing a UTF-8 string in wasm memory to a JS string!`); + u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); + } + + if (u0 < 0x10000) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 0x10000; + str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); + } + } + return str; + }; + + /** @type {!Uint8Array} */ + var HEAPU8; + + /** + * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the + * emscripten HEAP, returns a copy of that string as a Javascript String object. + * + * @param {number} ptr + * @param {number=} maxBytesToRead - An optional length that specifies the + * maximum number of bytes to read. You can omit this parameter to scan the + * string until the first 0 byte. If maxBytesToRead is passed, and the string + * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the + * string will cut short at that byte index. + * @param {boolean=} ignoreNul - If true, the function will not stop on a NUL character. + * @return {string} + */ + var UTF8ToString = (ptr, maxBytesToRead, ignoreNul) => { + assert(typeof ptr == 'number', `UTF8ToString expects a number (got ${typeof ptr})`); + return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead, ignoreNul) : ''; + }; + var ___assert_fail = (condition, filename, line, func) => + abort(`Assertion failed: ${UTF8ToString(condition)}, at: ` + [filename ? UTF8ToString(filename) : 'unknown filename', line, func ? UTF8ToString(func) : 'unknown function']); + + var __abort_js = () => + abort('native code called abort()'); + + var _emscripten_get_now = () => performance.now(); + + var _emscripten_date_now = () => Date.now(); + + var nowIsMonotonic = 1; + + var checkWasiClock = (clock_id) => clock_id >= 0 && clock_id <= 3; + + var INT53_MAX = 9007199254740992; + + var INT53_MIN = -9007199254740992; + var bigintToI53Checked = (num) => (num < INT53_MIN || num > INT53_MAX) ? NaN : Number(num); + + /** not-@type {!BigInt64Array} */ + var HEAP64; + function _clock_time_get(clk_id, ignored_precision, ptime) { + ignored_precision = bigintToI53Checked(ignored_precision); + + + if (!checkWasiClock(clk_id)) { + return 28; + } + var now; + // all wasi clocks but realtime are monotonic + if (clk_id === 0) { + now = _emscripten_date_now(); + } else if (nowIsMonotonic) { + now = _emscripten_get_now(); + } else { + return 52; + } + // "now" is in ms, and wasi times are in ns. + var nsec = Math.round(now * 1000 * 1000); + HEAP64[((ptime)>>3)] = BigInt(nsec); + return 0; + ; + } + + + var _emscripten_set_main_loop_timing = (mode, value) => { + MainLoop.timingMode = mode; + MainLoop.timingValue = value; + + if (!MainLoop.func) { + err('emscripten_set_main_loop_timing: Cannot set timing mode for main loop since a main loop does not exist! Call emscripten_set_main_loop first to set one up.'); + return 1; // Return non-zero on failure, can't set timing mode when there is no main loop. + } + + if (mode == 0) { + MainLoop.scheduler = function MainLoop_scheduler_setTimeout() { + var timeUntilNextTick = Math.max(0, MainLoop.tickStartTime + value - _emscripten_get_now())|0; + setTimeout(MainLoop.runner, timeUntilNextTick); // doing this each time means that on exception, we stop + }; + } else if (mode == 1) { + MainLoop.scheduler = function MainLoop_scheduler_rAF() { + MainLoop.requestAnimationFrame(MainLoop.runner); + }; + } else { + assert(mode == 2); + if (!MainLoop.setImmediate) { + if (globalThis.scheduler) { + // Some modern browsers implement scheduler.postTask, but not all. + MainLoop.setImmediate = scheduler.postTask.bind(scheduler); + } else { + // Emulate setImmediate. (note: not a complete polyfill, we don't emulate clearImmediate() to keep code size to minimum, since not needed) + var setImmediates = []; + var emscriptenMainLoopMessageId = 'setimmediate'; + /** @param {Event} event */ + var MainLoop_setImmediate_messageHandler = (event) => { + if (event.data === emscriptenMainLoopMessageId) { + event.stopPropagation(); + setImmediates.shift()(); + } + }; + addEventListener('message', MainLoop_setImmediate_messageHandler, true); + MainLoop.setImmediate = /** @type{function(function(): ?, ...?): number} */((func) => { + setImmediates.push(func); + if (ENVIRONMENT_IS_WORKER) { + // The postMessge API in a Worker, sends message to the main + // thread and does not support the `targetOrigin` (*) argument. + postMessage(emscriptenMainLoopMessageId); + } else { + postMessage(emscriptenMainLoopMessageId, '*'); + } + }); + } + } + MainLoop.scheduler = function MainLoop_scheduler_setImmediate() { + MainLoop.setImmediate(MainLoop.runner); + }; + } + return 0; + }; + + + + var runtimeKeepaliveCounter = 0; + var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0; + var _proc_exit = (code) => { + EXITSTATUS = code; + if (!keepRuntimeAlive()) { + Module['onExit']?.(code); + ABORT = true; + } + quit_(code, new ExitStatus(code)); + }; + + + /** @param {boolean|number=} implicit */ + var exitJS = (status, implicit) => { + EXITSTATUS = status; + + checkUnflushedContent(); + + // if exit() was called explicitly, warn the user if the runtime isn't actually being shut down + if (keepRuntimeAlive() && !implicit) { + var msg = `program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`; + err(msg); + } + + _proc_exit(status); + }; + var _exit = exitJS; + + var handleException = (e) => { + // Certain exception types we do not treat as errors since they are used for + // internal control flow. + // 1. ExitStatus, which is thrown by exit() + // 2. "unwind", which is thrown by emscripten_unwind_to_js_event_loop() and others + // that wish to return to JS event loop. + if (e instanceof ExitStatus || e == 'unwind') { + return EXITSTATUS; + } + checkStackCookie(); + if (e instanceof WebAssembly.RuntimeError) { + if (_emscripten_stack_get_current() <= 0) { + err('Stack overflow detected. You can try increasing -sSTACK_SIZE (currently set to 65536)'); + } + } + quit_(1, e); + }; + + var maybeExit = () => { + if (!keepRuntimeAlive()) { + try { + _exit(EXITSTATUS); + } catch (e) { + handleException(e); + } + } + }; + + /** + * @param {number=} arg + * @param {boolean=} noSetTiming + */ + var setMainLoop = (iterFunc, fps, simulateInfiniteLoop, arg, noSetTiming) => { + assert(!MainLoop.func, 'emscripten_set_main_loop: there can only be one main loop function at once') + MainLoop.func = iterFunc; + MainLoop.arg = arg; + + var thisMainLoopId = MainLoop.currentlyRunningMainloop; + function checkIsRunning() { + if (thisMainLoopId < MainLoop.currentlyRunningMainloop) { + maybeExit(); + return false; + } + return true; + } + + // We create the loop runner here but it is not actually running until + // _emscripten_set_main_loop_timing is called (which might happen at a + // later time). + MainLoop.runner = function MainLoop_runner() { + if (ABORT) return; + if (MainLoop.queue.length > 0) { + var start = Date.now(); + var blocker = MainLoop.queue.shift(); + blocker.func(blocker.arg); + if (MainLoop.remainingBlockers) { + var remaining = MainLoop.remainingBlockers; + var next = remaining%1 == 0 ? remaining-1 : Math.floor(remaining); + if (blocker.counted) { + MainLoop.remainingBlockers = next; + } else { + // not counted, but move the progress along a tiny bit + next = next + 0.5; // do not steal all the next one's progress + MainLoop.remainingBlockers = (8*remaining + next)/9; + } + } + MainLoop.updateStatus(); + + // catches pause/resume main loop from blocker execution + if (!checkIsRunning()) return; + + setTimeout(MainLoop.runner, 0); + return; + } + + // catch pauses from non-main loop sources + if (!checkIsRunning()) return; + + // Implement very basic swap interval control + MainLoop.currentFrameNumber = MainLoop.currentFrameNumber + 1 | 0; + if (MainLoop.timingMode == 1 && MainLoop.timingValue > 1 && MainLoop.currentFrameNumber % MainLoop.timingValue != 0) { + // Not the scheduled time to render this frame - skip. + MainLoop.scheduler(); + return; + } else if (MainLoop.timingMode == 0) { + MainLoop.tickStartTime = _emscripten_get_now(); + if (Module['ctx']) { + warnOnce('Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!'); + } + } + + MainLoop.runIter(iterFunc); + + // catch pauses from the main loop itself + if (!checkIsRunning()) return; + + MainLoop.scheduler(); + } + + if (!noSetTiming) { + if (fps > 0) { + _emscripten_set_main_loop_timing(0, 1000.0 / fps); + } else { + // Do rAF by rendering each frame (no decimating) + _emscripten_set_main_loop_timing(1, 1); + } + + MainLoop.scheduler(); + } + + if (simulateInfiniteLoop) { + throw 'unwind'; + } + }; + + + var callUserCallback = (func) => { + if (ABORT) { + err('user callback triggered after runtime exited or application aborted. Ignoring.'); + return; + } + try { + return func(); + } catch (e) { + handleException(e); + } finally { + maybeExit(); + } + }; + + var MainLoop = { + func:null, + scheduler:null, + currentlyRunningMainloop:0, + arg:0, + timingMode:0, + timingValue:0, + currentFrameNumber:0, + queue:[], + preMainLoop:[], + postMainLoop:[], + pause() { + if (MainLoop.scheduler) { + MainLoop.scheduler = null; + // Incrementing this signals the previous main loop that it's now become old, and it must return. + MainLoop.currentlyRunningMainloop++; + + } + }, + resume() { + MainLoop.currentlyRunningMainloop++; + var timingMode = MainLoop.timingMode; + var timingValue = MainLoop.timingValue; + var func = MainLoop.func; + MainLoop.func = null; + // do not set timing and call scheduler, we will do it on the next lines + setMainLoop(func, 0, false, MainLoop.arg, true); + _emscripten_set_main_loop_timing(timingMode, timingValue); + MainLoop.scheduler(); + }, + updateStatus() { + if (Module['setStatus']) { + var message = Module['statusMessage'] || 'Please wait...'; + var remaining = MainLoop.remainingBlockers ?? 0; + var expected = MainLoop.expectedBlockers ?? 0; + if (remaining) { + if (remaining < expected) { + Module['setStatus'](`{message} ({expected - remaining}/{expected})`); + } else { + Module['setStatus'](message); + } + } else { + Module['setStatus'](''); + } + } + }, + init() { + }, + runIter(func) { + if (ABORT) return; + for (var pre of MainLoop.preMainLoop) { + if (pre() === false) { + return; // |return false| skips a frame + } + } + callUserCallback(func); + for (var post of MainLoop.postMainLoop) { + post(); + } + checkStackCookie(); + }, + nextRAF:0, + fakeRequestAnimationFrame(func) { + // try to keep 60fps between calls to here + var now = Date.now(); + if (!MainLoop.nextRAF) { + MainLoop.nextRAF = now + 1000/60; + } else { + while (now + 2 >= MainLoop.nextRAF) { // fudge a little, to avoid timer jitter causing us to do lots of delay:0 + MainLoop.nextRAF += 1000/60; + } + } + var delay = Math.max(MainLoop.nextRAF - now, 0); + setTimeout(func, delay); + }, + requestAnimationFrame(func) { + if (globalThis.requestAnimationFrame) { + requestAnimationFrame(func); + } else { + MainLoop.fakeRequestAnimationFrame(func); + } + }, + }; + var _emscripten_cancel_main_loop = () => { + MainLoop.pause(); + MainLoop.func = null; + }; + + + var getHeapMax = () => + // Stay one Wasm page short of 4GB: while e.g. Chrome is able to allocate + // full 4GB Wasm memories, the size will wrap back to 0 bytes in Wasm side + // for any code that deals with heap sizes, which would require special + // casing all heap size related code to treat 0 specially. + 2147483648; + + var alignMemory = (size, alignment) => { + assert(alignment, 'alignment argument is required'); + return Math.ceil(size / alignment) * alignment; + }; + + var growMemory = (size) => { + var oldHeapSize = wasmMemory.buffer.byteLength; + var pages = ((size - oldHeapSize + 65535) / 65536) | 0; + try { + // round size grow request up to wasm page size (fixed 64KB per spec) + wasmMemory.grow(pages); // .grow() takes a delta compared to the previous size + updateMemoryViews(); + return 1 /*success*/; + } catch(e) { + err(`growMemory: Attempted to grow heap from ${oldHeapSize} bytes to ${size} bytes, but got error: ${e}`); + } + // implicit 0 return to save code size (caller will cast 'undefined' into 0 + // anyhow) + }; + + var _emscripten_resize_heap = (requestedSize) => { + var oldSize = HEAPU8.length; + // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned. + requestedSize >>>= 0; + // With multithreaded builds, races can happen (another thread might increase the size + // in between), so return a failure, and let the caller retry. + assert(requestedSize > oldSize); + + // Memory resize rules: + // 1. Always increase heap size to at least the requested size, rounded up + // to next page multiple. + // 2a. If MEMORY_GROWTH_LINEAR_STEP == -1, excessively resize the heap + // geometrically: increase the heap size according to + // MEMORY_GROWTH_GEOMETRIC_STEP factor (default +20%), At most + // overreserve by MEMORY_GROWTH_GEOMETRIC_CAP bytes (default 96MB). + // 2b. If MEMORY_GROWTH_LINEAR_STEP != -1, excessively resize the heap + // linearly: increase the heap size by at least + // MEMORY_GROWTH_LINEAR_STEP bytes. + // 3. Max size for the heap is capped at 2048MB-WASM_PAGE_SIZE, or by + // MAXIMUM_MEMORY, or by ASAN limit, depending on which is smallest + // 4. If we were unable to allocate as much memory, it may be due to + // over-eager decision to excessively reserve due to (3) above. + // Hence if an allocation fails, cut down on the amount of excess + // growth, in an attempt to succeed to perform a smaller allocation. + + // A limit is set for how much we can grow. We should not exceed that + // (the wasm binary specifies it, so if we tried, we'd fail anyhow). + var maxHeapSize = getHeapMax(); + if (requestedSize > maxHeapSize) { + err(`Cannot enlarge memory, requested ${requestedSize} bytes, but the limit is ${maxHeapSize} bytes!`); + return false; + } + + // Loop through potential heap size increases. If we attempt a too eager + // reservation that fails, cut down on the attempted size and reserve a + // smaller bump instead. (max 3 times, chosen somewhat arbitrarily) + for (var cutDown = 1; cutDown <= 4; cutDown *= 2) { + var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown); // ensure geometric growth + // but limit overreserving (default to capping at +96MB overgrowth at most) + overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296 ); + + var newSize = Math.min(maxHeapSize, alignMemory(Math.max(requestedSize, overGrownHeapSize), 65536)); + + var replacement = growMemory(newSize); + if (replacement) { + + return true; + } + } + err(`Failed to grow the heap from ${oldSize} bytes to ${newSize} bytes, not enough memory!`); + return false; + }; + + var onExits = []; + var addOnExit = (cb) => onExits.push(cb); + var JSEvents = { + removeAllEventListeners() { + while (JSEvents.eventHandlers.length) { + JSEvents._removeHandler(JSEvents.eventHandlers.length - 1); + } + JSEvents.deferredCalls = []; + }, + inEventHandler:0, + deferredCalls:[], + deferCall(targetFunction, precedence, argsList) { + function arraysHaveEqualContent(arrA, arrB) { + if (arrA.length != arrB.length) return false; + + for (var i = 0; i < arrA.length; i++) { + if (arrA[i] != arrB[i]) return false; + } + return true; + } + // Test if the given call was already queued, and if so, don't add it again. + for (var call of JSEvents.deferredCalls) { + if (call.targetFunction == targetFunction && arraysHaveEqualContent(call.argsList, argsList)) { + return; + } + } + JSEvents.deferredCalls.push({ + targetFunction, + precedence, + argsList + }); + + JSEvents.deferredCalls.sort((x,y) => x.precedence - y.precedence); + }, + removeDeferredCalls(targetFunction) { + JSEvents.deferredCalls = JSEvents.deferredCalls.filter((call) => call.targetFunction != targetFunction); + }, + canPerformEventHandlerRequests() { + // Browsers that support navigator.userActivation.isActive: https://developer.mozilla.org/en-US/docs/Web/API/UserActivation/isActive + if (navigator.userActivation) { + // Verify against transient activation status from UserActivation API + // whether it is possible to perform a request here without needing to defer. See + // https://developer.mozilla.org/en-US/docs/Web/Security/User_activation#transient_activation + // and https://caniuse.com/mdn-api_useractivation + return navigator.userActivation.isActive; + } + + return JSEvents.inEventHandler && JSEvents.currentEventHandler.allowsDeferredCalls; + }, + runDeferredCalls() { + if (!JSEvents.canPerformEventHandlerRequests()) { + return; + } + var deferredCalls = JSEvents.deferredCalls; + JSEvents.deferredCalls = []; + for (var call of deferredCalls) { + call.targetFunction(...call.argsList); + } + }, + eventHandlers:[], + removeAllHandlersOnTarget:(target, eventTypeString) => { + for (var i = 0; i < JSEvents.eventHandlers.length; ++i) { + if (JSEvents.eventHandlers[i].target == target && + (!eventTypeString || eventTypeString == JSEvents.eventHandlers[i].eventTypeString)) { + JSEvents._removeHandler(i--); + } + } + }, + _removeHandler(i) { + var h = JSEvents.eventHandlers[i]; + h.target.removeEventListener(h.eventTypeString, h.eventListenerFunc, h.useCapture); + JSEvents.eventHandlers.splice(i, 1); + }, + registerOrRemoveHandler(eventHandler) { + if (!eventHandler.target) { + err('registerOrRemoveHandler: the target element for event handler registration does not exist, when processing the following event handler registration:'); + console.dir(eventHandler); + return -4; + } + if (eventHandler.callbackfunc) { + eventHandler.eventListenerFunc = function(event) { + // Increment nesting count for the event handler. + ++JSEvents.inEventHandler; + JSEvents.currentEventHandler = eventHandler; + // Process any old deferred calls the user has placed. + JSEvents.runDeferredCalls(); + // Process the actual event, calls back to user C code handler. + eventHandler.handlerFunc(event); + // Process any new deferred calls that were placed right now from this event handler. + JSEvents.runDeferredCalls(); + // Out of event handler - restore nesting count. + --JSEvents.inEventHandler; + }; + + eventHandler.target.addEventListener(eventHandler.eventTypeString, + eventHandler.eventListenerFunc, + eventHandler.useCapture); + JSEvents.eventHandlers.push(eventHandler); + } else { + for (var i = 0; i < JSEvents.eventHandlers.length; ++i) { + if (JSEvents.eventHandlers[i].target == eventHandler.target + && JSEvents.eventHandlers[i].eventTypeString == eventHandler.eventTypeString) { + JSEvents._removeHandler(i--); + } + } + } + return 0; + }, + removeSingleHandler(eventHandler) { + let success = false; + for (let i = 0; i < JSEvents.eventHandlers.length; ++i) { + const handler = JSEvents.eventHandlers[i]; + if (handler.target === eventHandler.target + && handler.eventTypeId === eventHandler.eventTypeId + && handler.callbackfunc === eventHandler.callbackfunc + && handler.userData === eventHandler.userData) { + // in some very rare cases (ex: Safari / fullscreen events), there is more than 1 handler (eventTypeString is different) + JSEvents._removeHandler(i--); + success = true; + } + } + return success ? 0 : -5; + }, + getNodeNameForTarget(target) { + if (target == window) return '#window'; + if (target == screen) return '#screen'; + return target?.nodeName ?? ''; + }, + fullscreenEnabled() { + return document.fullscreenEnabled + // Safari 13.0.3 on macOS Catalina 10.15.1 still ships with prefixed webkitFullscreenEnabled. + // TODO: If Safari at some point ships with unprefixed version, update the version check above. + ?? document.webkitFullscreenEnabled + ; + }, + }; + + var maybeCStringToJsString = (cString) => { + // 'cString > 2' checks if the input is a number, and isn't of the special + // values we accept here, EMSCRIPTEN_EVENT_TARGET_* (which map to 0, 1, 2). + // In other words, if cString > 2 then it's a pointer to a valid place in + // memory, and points to a C string. + return cString > 2 ? UTF8ToString(cString) : cString; + }; + + /** @type {Object} */ + var specialHTMLTargets = [0, document, window]; + var findEventTarget = (target) => { + target = maybeCStringToJsString(target); + var domElement = specialHTMLTargets[target] || document.querySelector(target); + return domElement; + }; + + var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => { + assert(typeof str === 'string', `stringToUTF8Array expects a string (got ${typeof str})`); + // Parameter maxBytesToWrite is not optional. Negative values, 0, null, + // undefined and false each don't write out any bytes. + if (!(maxBytesToWrite > 0)) + return 0; + + var startIdx = outIdx; + var endIdx = outIdx + maxBytesToWrite - 1; // -1 for string null terminator. + for (var i = 0; i < str.length; ++i) { + // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description + // and https://www.ietf.org/rfc/rfc2279.txt + // and https://tools.ietf.org/html/rfc3629 + var u = str.codePointAt(i); + if (u <= 0x7F) { + if (outIdx >= endIdx) break; + heap[outIdx++] = u; + } else if (u <= 0x7FF) { + if (outIdx + 1 >= endIdx) break; + heap[outIdx++] = 0xC0 | (u >> 6); + heap[outIdx++] = 0x80 | (u & 63); + } else if (u <= 0xFFFF) { + if (outIdx + 2 >= endIdx) break; + heap[outIdx++] = 0xE0 | (u >> 12); + heap[outIdx++] = 0x80 | ((u >> 6) & 63); + heap[outIdx++] = 0x80 | (u & 63); + } else { + if (outIdx + 3 >= endIdx) break; + if (u > 0x10FFFF) warnOnce(`Invalid Unicode code point ${ptrToString(u)} encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF).`); + heap[outIdx++] = 0xF0 | (u >> 18); + heap[outIdx++] = 0x80 | ((u >> 12) & 63); + heap[outIdx++] = 0x80 | ((u >> 6) & 63); + heap[outIdx++] = 0x80 | (u & 63); + // Gotcha: if codePoint is over 0xFFFF, it is represented as a surrogate pair in UTF-16. + // We need to manually skip over the second code unit for correct iteration. + i++; + } + } + // Null-terminate the pointer to the buffer. + heap[outIdx] = 0; + return outIdx - startIdx; + }; + + var stringToUTF8 = (str, outPtr, maxBytesToWrite) => { + assert(typeof maxBytesToWrite == 'number', 'stringToUTF8 requires a third parameter that specifies the length of the output buffer'); + return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); + }; + + + var wasmTableMirror = []; + + + var getWasmTableEntry = (funcPtr) => { + var func = wasmTableMirror[funcPtr]; + if (!func) { + /** @suppress {checkTypes} */ + wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr); + } + /** @suppress {checkTypes} */ + assert(wasmTable.get(funcPtr) == func, 'table mirror is out of date'); + return func; + }; + + + + /** @type {!Float64Array} */ + var HEAPF64; + var registerKeyEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { + var eventSize = 160; + JSEvents.keyEvent ||= _malloc(eventSize); + + var keyEventHandlerFunc = (e) => { + assert(e); + + var keyEventData = JSEvents.keyEvent; + HEAPF64[((keyEventData)>>3)] = e.timeStamp; + + var idx = ((keyEventData)>>2); + + HEAP32[idx + 2] = e.location; + HEAP8[keyEventData + 12] = e.ctrlKey; + HEAP8[keyEventData + 13] = e.shiftKey; + HEAP8[keyEventData + 14] = e.altKey; + HEAP8[keyEventData + 15] = e.metaKey; + HEAP8[keyEventData + 16] = e.repeat; + HEAP32[idx + 5] = e.charCode; + HEAP32[idx + 6] = e.keyCode; + HEAP32[idx + 7] = e.which; + stringToUTF8(e.key ?? '', keyEventData + 32, 32); + stringToUTF8(e.code ?? '', keyEventData + 64, 32); + stringToUTF8(e.char ?? '', keyEventData + 96, 32); + stringToUTF8(e.locale ?? '', keyEventData + 128, 32); + + if (getWasmTableEntry(callbackfunc)(eventTypeId, keyEventData, userData)) e.preventDefault(); + }; + + var eventHandler = { + target: findEventTarget(target), + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: keyEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + var _emscripten_set_keydown_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => + registerKeyEventCallback(target, userData, useCapture, callbackfunc, 2, 'keydown', targetThread); + + var _emscripten_set_keyup_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => + registerKeyEventCallback(target, userData, useCapture, callbackfunc, 3, 'keyup', targetThread); + + + var _emscripten_set_main_loop_arg = (func, arg, fps, simulateInfiniteLoop) => { + var iterFunc = () => getWasmTableEntry(func)(arg); + setMainLoop(iterFunc, fps, simulateInfiniteLoop, arg); + }; + + + var getBoundingClientRect = (e) => specialHTMLTargets.indexOf(e) < 0 ? e.getBoundingClientRect() : {'left':0,'top':0}; + + + /** @type {!Int16Array} */ + var HEAP16; + + + var fillMouseEventData = (eventStruct, e, target) => { + assert(eventStruct % 4 == 0); + HEAPF64[((eventStruct)>>3)] = e.timeStamp; + var idx = ((eventStruct)>>2); + HEAP32[idx + 2] = e.screenX; + HEAP32[idx + 3] = e.screenY; + HEAP32[idx + 4] = e.clientX; + HEAP32[idx + 5] = e.clientY; + HEAP8[eventStruct + 24] = e.ctrlKey; + HEAP8[eventStruct + 25] = e.shiftKey; + HEAP8[eventStruct + 26] = e.altKey; + HEAP8[eventStruct + 27] = e.metaKey; + HEAP16[idx*2 + 14] = e.button; + HEAP16[idx*2 + 15] = e.buttons; + HEAP32[idx + 8] = e.movementX; + HEAP32[idx + 9] = e.movementY; + + // Note: rect contains doubles (truncated to placate SAFE_HEAP, which is the same behaviour when writing to HEAP32 anyway) + var rect = getBoundingClientRect(target); + HEAP32[idx + 10] = e.clientX - (rect.left | 0); + HEAP32[idx + 11] = e.clientY - (rect.top | 0); + }; + + + + var registerMouseEventCallback = (target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString, targetThread) => { + var eventSize = 64; + JSEvents.mouseEvent ||= _malloc(eventSize); + target = findEventTarget(target); + + var mouseEventHandlerFunc = (e) => { + // TODO: Make this access thread safe, or this could update live while app is reading it. + fillMouseEventData(JSEvents.mouseEvent, e, target); + + if (getWasmTableEntry(callbackfunc)(eventTypeId, JSEvents.mouseEvent, userData)) e.preventDefault(); + }; + + var eventHandler = { + target, + allowsDeferredCalls: eventTypeString != 'mousemove' && eventTypeString != 'mouseenter' && eventTypeString != 'mouseleave', // Mouse move events do not allow fullscreen/pointer lock requests to be handled in them! + eventTypeString, + eventTypeId, + userData, + callbackfunc, + handlerFunc: mouseEventHandlerFunc, + useCapture + }; + return JSEvents.registerOrRemoveHandler(eventHandler); + }; + var _emscripten_set_mousedown_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, 5, 'mousedown', targetThread); + + var _emscripten_set_mousemove_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, 8, 'mousemove', targetThread); + + var _emscripten_set_mouseup_callback_on_thread = (target, userData, useCapture, callbackfunc, targetThread) => + registerMouseEventCallback(target, userData, useCapture, callbackfunc, 6, 'mouseup', targetThread); + + var SYSCALLS = { + varargs:undefined, + getStr(ptr) { + var ret = UTF8ToString(ptr); + return ret; + }, + }; + var _fd_close = (fd) => { + abort('fd_close called without SYSCALLS_REQUIRE_FILESYSTEM'); + }; + + function _fd_seek(fd, offset, whence, newOffset) { + offset = bigintToI53Checked(offset); + + + return 70; + ; + } + + var printCharBuffers = [null,[],[]]; + + var printChar = (stream, curr) => { + var buffer = printCharBuffers[stream]; + assert(buffer); + if (!curr || curr === 10) { + (stream === 1 ? out : err)(UTF8ArrayToString(buffer)); + buffer.length = 0; + } else { + buffer.push(curr); + } + }; + + var flush_NO_FILESYSTEM = () => { + // flush anything remaining in the buffers during shutdown + _fflush(0); + if (printCharBuffers[1].length) printChar(1, 10); + if (printCharBuffers[2].length) printChar(2, 10); + }; + + + + + var _fd_write = (fd, iov, iovcnt, pnum) => { + // hack to support printf in SYSCALLS_REQUIRE_FILESYSTEM=0 + var num = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAPU32[((iov)>>2)]; + var len = HEAPU32[(((iov)+(4))>>2)]; + iov += 8; + for (var j = 0; j < len; j++) { + printChar(fd, HEAPU8[ptr+j]); + } + num += len; + } + HEAPU32[((pnum)>>2)] = num; + return 0; + }; + + + + var lengthBytesUTF8 = (str) => { + var len = 0; + for (var i = 0; i < str.length; ++i) { + // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code + // unit, not a Unicode code point of the character! So decode + // UTF16->UTF32->UTF8. + // See http://unicode.org/faq/utf_bom.html#utf16-3 + var c = str.charCodeAt(i); // possibly a lead surrogate + if (c <= 0x7F) { + len++; + } else if (c <= 0x7FF) { + len += 2; + } else if (c >= 0xD800 && c <= 0xDFFF) { + len += 4; ++i; + } else { + len += 3; + } + } + return len; + }; + + + var stackAlloc = (sz) => __emscripten_stack_alloc(sz); + var stringToUTF8OnStack = (str) => { + var size = lengthBytesUTF8(str) + 1; + var ret = stackAlloc(size); + stringToUTF8(str, ret, size); + return ret; + }; + + + + + + Module['requestAnimationFrame'] = MainLoop.requestAnimationFrame; + Module['pauseMainLoop'] = MainLoop.pause; + Module['resumeMainLoop'] = MainLoop.resume; + MainLoop.init();; +// End JS library code + +// include: postlibrary.js +// This file is included after the automatically-generated JS library code +// but before the wasm module is created. + +{ + + // Begin ATMODULES hooks + if (Module['noExitRuntime']) noExitRuntime = Module['noExitRuntime']; +if (Module['print']) out = Module['print']; +if (Module['printErr']) err = Module['printErr']; + +Module['FS_createDataFile'] = FS.createDataFile; +Module['FS_createPreloadedFile'] = FS.createPreloadedFile; + + // End ATMODULES hooks + + checkIncomingModuleAPI(); + + if (Module['arguments']) programArgs = Module['arguments']; + if (Module['thisProgram']) thisProgram = Module['thisProgram']; + + // Assertions on removed incoming Module JS APIs. + assert(typeof Module['memoryInitializerPrefixURL'] == 'undefined', 'Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['pthreadMainPrefixURL'] == 'undefined', 'Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['cdInitializerPrefixURL'] == 'undefined', 'Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['filePackagePrefixURL'] == 'undefined', 'Module.filePackagePrefixURL option was removed, use Module.locateFile instead'); + assert(typeof Module['read'] == 'undefined', 'Module.read option was removed'); + assert(typeof Module['readAsync'] == 'undefined', 'Module.readAsync option was removed (modify readAsync in JS)'); + assert(typeof Module['readBinary'] == 'undefined', 'Module.readBinary option was removed (modify readBinary in JS)'); + assert(typeof Module['setWindowTitle'] == 'undefined', 'Module.setWindowTitle option was removed (modify emscripten_set_window_title in JS)'); + assert(typeof Module['TOTAL_MEMORY'] == 'undefined', 'Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY'); + assert(typeof Module['ENVIRONMENT'] == 'undefined', 'Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)'); + assert(typeof Module['STACK_SIZE'] == 'undefined', 'STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time') + // If memory is defined in wasm, the user can't provide it, or set INITIAL_MEMORY + assert(typeof Module['wasmMemory'] == 'undefined', 'Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally'); + assert(typeof Module['INITIAL_MEMORY'] == 'undefined', 'Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically'); + + var preInit = Module['preInit']; + if (preInit) { + if (typeof preInit == 'function') Module['preInit'] = preInit = [preInit]; + // Written as a loop so that preInit functions that themselves add more + // preInit functions. Is this actually needed? + while (preInit.length > 0) { + preInit.shift()(); + } + } + consumedModuleProp('preInit'); +} + +// Begin runtime exports + var missingLibrarySymbols = [ + 'writeI53ToI64', + 'writeI53ToI64Clamped', + 'writeI53ToI64Signaling', + 'writeI53ToU64Clamped', + 'writeI53ToU64Signaling', + 'readI53FromI64', + 'readI53FromU64', + 'convertI32PairToI53', + 'convertI32PairToI53Checked', + 'convertU32PairToI53', + 'getTempRet0', + 'setTempRet0', + 'createNamedFunction', + 'zeroMemory', + 'withStackSave', + 'strError', + 'inetPton4', + 'inetNtop4', + 'inetPton6', + 'inetNtop6', + 'readSockaddr', + 'writeSockaddr', + 'readEmAsmArgs', + 'jstoi_q', + 'getExecutableName', + 'autoResumeAudioContext', + 'getDynCaller', + 'dynCall', + 'runtimeKeepalivePush', + 'runtimeKeepalivePop', + 'asyncLoad', + 'asmjsMangle', + 'mmapAlloc', + 'HandleAllocator', + 'getUniqueRunDependency', + 'addRunDependency', + 'removeRunDependency', + 'addOnInit', + 'addOnPostCtor', + 'addOnPreMain', + 'STACK_SIZE', + 'STACK_ALIGN', + 'POINTER_SIZE', + 'ASSERTIONS', + 'ccall', + 'cwrap', + 'convertJsFunctionToWasm', + 'getEmptyTableSlot', + 'updateTableMap', + 'getFunctionAddress', + 'addFunction', + 'removeFunction', + 'setValue', + 'getValue', + 'intArrayFromString', + 'intArrayToString', + 'AsciiToString', + 'stringToAscii', + 'UTF16ToString', + 'stringToUTF16', + 'lengthBytesUTF16', + 'UTF32ToString', + 'stringToUTF32', + 'lengthBytesUTF32', + 'stringToNewUTF8', + 'writeArrayToMemory', + 'registerWheelEventCallback', + 'registerUiEventCallback', + 'registerFocusEventCallback', + 'fillDeviceOrientationEventData', + 'registerDeviceOrientationEventCallback', + 'fillDeviceMotionEventData', + 'registerDeviceMotionEventCallback', + 'screenOrientation', + 'fillOrientationChangeEventData', + 'registerOrientationChangeEventCallback', + 'fillFullscreenChangeEventData', + 'registerFullscreenChangeEventCallback', + 'callCanvasResizedCallback', + 'JSEvents_requestFullscreen', + 'JSEvents_resizeCanvasForFullscreen', + 'registerRestoreOldStyle', + 'hideEverythingExceptGivenElement', + 'restoreHiddenElements', + 'setLetterbox', + 'currentFullscreenStrategy', + 'softFullscreenResizeWebGLRenderTarget', + 'doRequestFullscreen', + 'fillPointerlockChangeEventData', + 'registerPointerlockChangeEventCallback', + 'registerPointerlockErrorEventCallback', + 'requestPointerLock', + 'fillVisibilityChangeEventData', + 'registerVisibilityChangeEventCallback', + 'registerTouchEventCallback', + 'fillGamepadEventData', + 'registerGamepadEventCallback', + 'registerBeforeUnloadEventCallback', + 'fillBatteryEventData', + 'registerBatteryEventCallback', + 'setCanvasElementSize', + 'getCanvasElementSize', + 'jsStackTrace', + 'getCallstack', + 'convertPCtoSourceLocation', + 'getEnvStrings', + 'wasiRightsToMuslOFlags', + 'wasiOFlagsToMuslOFlags', + 'initRandomFill', + 'randomFill', + 'safeSetTimeout', + 'setImmediateWrapped', + 'safeRequestAnimationFrame', + 'clearImmediateWrapped', + 'registerPostMainLoop', + 'registerPreMainLoop', + 'getPromise', + 'makePromise', + 'addPromise', + 'idsToPromises', + 'makePromiseCallback', + 'Browser_asyncPrepareDataCounter', + 'isLeapYear', + 'ydayFromDate', + 'arraySum', + 'addDays', + 'getSocketFromFD', + 'getSocketAddress', + 'FS_createPreloadedFile', + 'FS_preloadFile', + 'FS_modeStringToFlags', + 'FS_getMode', + 'FS_fileDataToTypedArray', + 'FS_stdin_getChar', + 'FS_mkdirTree', + '_setNetworkCallback', + 'heapObjectForWebGLType', + 'toTypedArrayIndex', + 'webgl_enable_ANGLE_instanced_arrays', + 'webgl_enable_OES_vertex_array_object', + 'webgl_enable_WEBGL_draw_buffers', + 'webgl_enable_WEBGL_multi_draw', + 'webgl_enable_EXT_polygon_offset_clamp', + 'webgl_enable_EXT_clip_control', + 'webgl_enable_WEBGL_polygon_mode', + 'emscriptenWebGLGet', + 'computeUnpackAlignedImageSize', + 'colorChannelsInGlTextureFormat', + 'emscriptenWebGLGetTexPixelData', + 'emscriptenWebGLGetUniform', + 'webglGetProgramUniformLocation', + 'webglGetUniformLocation', + 'webglPrepareUniformLocationsBeforeFirstUse', + 'webglGetLeftBracePos', + 'emscriptenWebGLGetVertexAttrib', + '__glGetActiveAttribOrUniform', + 'writeGLArray', + 'registerWebGlEventCallback', + 'runAndAbortIfError', + 'writeStringToMemory', + 'writeAsciiToMemory', + 'allocateUTF8', + 'allocateUTF8OnStack', + 'stackTrace', + 'getNativeTypeSize', +]; +missingLibrarySymbols.forEach(missingLibrarySymbol) + + var unexportedSymbols = [ + 'run', + 'out', + 'err', + 'callMain', + 'abort', + 'wasmExports', + 'writeStackCookie', + 'checkStackCookie', + 'INT53_MAX', + 'INT53_MIN', + 'bigintToI53Checked', + 'HEAP8', + 'HEAPU8', + 'HEAP16', + 'HEAPU16', + 'HEAP32', + 'HEAPU32', + 'HEAPF32', + 'HEAPF64', + 'HEAP64', + 'HEAPU64', + 'stackSave', + 'stackRestore', + 'stackAlloc', + 'ptrToString', + 'exitJS', + 'getHeapMax', + 'growMemory', + 'ENV', + 'ERRNO_CODES', + 'DNS', + 'Protocols', + 'Sockets', + 'timers', + 'warnOnce', + 'readEmAsmArgsArray', + 'handleException', + 'keepRuntimeAlive', + 'callUserCallback', + 'maybeExit', + 'alignMemory', + 'wasmTable', + 'wasmMemory', + 'noExitRuntime', + 'addOnPreRun', + 'addOnExit', + 'addOnPostRun', + 'freeTableIndexes', + 'functionsInTableMap', + 'PATH', + 'PATH_FS', + 'UTF8Decoder', + 'UTF8ArrayToString', + 'UTF8ToString', + 'stringToUTF8Array', + 'stringToUTF8', + 'lengthBytesUTF8', + 'UTF16Decoder', + 'stringToUTF8OnStack', + 'JSEvents', + 'registerKeyEventCallback', + 'specialHTMLTargets', + 'maybeCStringToJsString', + 'findEventTarget', + 'findCanvasEventTarget', + 'getBoundingClientRect', + 'fillMouseEventData', + 'registerMouseEventCallback', + 'restoreOldWindowedStyle', + 'UNWIND_CACHE', + 'ExitStatus', + 'checkWasiClock', + 'flush_NO_FILESYSTEM', + 'emSetImmediate', + 'emClearImmediate_deps', + 'emClearImmediate', + 'promiseMap', + 'Browser', + 'requestFullscreen', + 'setCanvasSize', + 'getUserMedia', + 'createContext', + 'getPreloadedImageData__data', + 'wget', + 'MONTH_DAYS_REGULAR', + 'MONTH_DAYS_LEAP', + 'MONTH_DAYS_REGULAR_CUMULATIVE', + 'MONTH_DAYS_LEAP_CUMULATIVE', + 'SYSCALLS', + 'preloadPlugins', + 'FS_stdin_getChar_buffer', + 'FS_unlink', + 'FS_createPath', + 'FS_createDevice', + 'FS_readFile', + 'FS', + 'FS_root', + 'FS_mounts', + 'FS_devices', + 'FS_streams', + 'FS_nextInode', + 'FS_nameTable', + 'FS_currentPath', + 'FS_initialized', + 'FS_ignorePermissions', + 'FS_filesystems', + 'FS_syncFSRequests', + 'FS_lookupPath', + 'FS_getPath', + 'FS_hashName', + 'FS_hashAddNode', + 'FS_hashRemoveNode', + 'FS_lookupNode', + 'FS_createNode', + 'FS_destroyNode', + 'FS_isRoot', + 'FS_isMountpoint', + 'FS_isFile', + 'FS_isDir', + 'FS_isLink', + 'FS_isChrdev', + 'FS_isBlkdev', + 'FS_isFIFO', + 'FS_isSocket', + 'FS_flagsToPermissionString', + 'FS_nodePermissions', + 'FS_mayLookup', + 'FS_mayCreate', + 'FS_mayDelete', + 'FS_mayOpen', + 'FS_checkOpExists', + 'FS_nextfd', + 'FS_getStreamChecked', + 'FS_getStream', + 'FS_createStream', + 'FS_closeStream', + 'FS_dupStream', + 'FS_doSetAttr', + 'FS_chrdev_stream_ops', + 'FS_major', + 'FS_minor', + 'FS_makedev', + 'FS_registerDevice', + 'FS_getDevice', + 'FS_getMounts', + 'FS_syncfs', + 'FS_mount', + 'FS_unmount', + 'FS_lookup', + 'FS_mknod', + 'FS_statfs', + 'FS_statfsStream', + 'FS_statfsNode', + 'FS_create', + 'FS_mkdir', + 'FS_mkdev', + 'FS_symlink', + 'FS_link', + 'FS_rename', + 'FS_rmdir', + 'FS_readdir', + 'FS_readlink', + 'FS_stat', + 'FS_fstat', + 'FS_lstat', + 'FS_doChmod', + 'FS_chmod', + 'FS_lchmod', + 'FS_fchmod', + 'FS_doChown', + 'FS_chown', + 'FS_lchown', + 'FS_fchown', + 'FS_doTruncate', + 'FS_truncate', + 'FS_ftruncate', + 'FS_utime', + 'FS_open', + 'FS_close', + 'FS_isClosed', + 'FS_llseek', + 'FS_read', + 'FS_write', + 'FS_mmap', + 'FS_msync', + 'FS_ioctl', + 'FS_writeFile', + 'FS_cwd', + 'FS_chdir', + 'FS_createDefaultDirectories', + 'FS_createDefaultDevices', + 'FS_createSpecialDirectories', + 'FS_createStandardStreams', + 'FS_staticInit', + 'FS_init', + 'FS_quit', + 'FS_findObject', + 'FS_analyzePath', + 'FS_createFile', + 'FS_createDataFile', + 'FS_forceLoadFile', + 'FS_createLazyFile', + 'MEMFS', + 'TTY', + 'PIPEFS', + 'SOCKFS', + 'tempFixedLengthArray', + 'miniTempWebGLFloatBuffers', + 'miniTempWebGLIntBuffers', + 'GL', + 'AL', + 'GLUT', + 'EGL', + 'GLEW', + 'IDBStore', + 'SDL', + 'SDL_gfx', + 'print', + 'printErr', + 'jstoi_s', +]; +unexportedSymbols.forEach(unexportedRuntimeSymbol); + + // End runtime exports + // Begin JS library exports + // End JS library exports + +// end include: postlibrary.js + +function checkIncomingModuleAPI() { + ignoredModuleProp('fetchSettings'); + ignoredModuleProp('logReadFiles'); + ignoredModuleProp('loadSplitModule'); + ignoredModuleProp('onMalloc'); + ignoredModuleProp('onRealloc'); + ignoredModuleProp('onFree'); + ignoredModuleProp('onSbrkGrow'); + ignoredModuleProp('onCOSCacheHit'); + ignoredModuleProp('onCOSCacheMiss'); + ignoredModuleProp('onCOSStore'); + ignoredModuleProp('GL_MAX_TEXTURE_IMAGE_UNITS'); + ignoredModuleProp('SDL_canPlayWithWebAudio'); + ignoredModuleProp('SDL_numSimultaneouslyQueuedBuffers'); + ignoredModuleProp('freePreloadedMediaOnUse'); + ignoredModuleProp('preinitializedWebGLContext'); + ignoredModuleProp('keyboardListeningElement'); + ignoredModuleProp('doNotCaptureKeyboard'); + ignoredModuleProp('extraStackTrace'); + ignoredModuleProp('preloadPlugins'); + ignoredModuleProp('preMainLoop'); + ignoredModuleProp('postMainLoop'); + ignoredModuleProp('forcedAspectRatio'); + ignoredModuleProp('mainScriptUrlOrBlob'); + ignoredModuleProp('onFullScreen'); + ignoredModuleProp('INITIAL_MEMORY'); + ignoredModuleProp('wasmMemory'); + ignoredModuleProp('wasmBinary'); +} +function peak_web_dom_open(id,w,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(); } +function peak_web_dom_present(id,w,h,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); } + +// Imports from the Wasm binary. +var _main = Module['_main'] = makeInvalidEarlyAccess('_main'); +var _fflush = makeInvalidEarlyAccess('_fflush'); +var _emscripten_stack_get_end = makeInvalidEarlyAccess('_emscripten_stack_get_end'); +var _emscripten_stack_get_base = makeInvalidEarlyAccess('_emscripten_stack_get_base'); +var _strerror = makeInvalidEarlyAccess('_strerror'); +var _malloc = makeInvalidEarlyAccess('_malloc'); +var _emscripten_stack_init = makeInvalidEarlyAccess('_emscripten_stack_init'); +var _emscripten_stack_get_free = makeInvalidEarlyAccess('_emscripten_stack_get_free'); +var __emscripten_stack_restore = makeInvalidEarlyAccess('__emscripten_stack_restore'); +var __emscripten_stack_alloc = makeInvalidEarlyAccess('__emscripten_stack_alloc'); +var _emscripten_stack_get_current = makeInvalidEarlyAccess('_emscripten_stack_get_current'); +var memory = makeInvalidEarlyAccess('memory'); +var __indirect_function_table = makeInvalidEarlyAccess('__indirect_function_table'); +var wasmMemory = makeInvalidEarlyAccess('wasmMemory'); +var wasmTable = makeInvalidEarlyAccess('wasmTable'); + +function assignWasmExports(wasmExports) { + assert(typeof wasmExports['__main_argc_argv'] != 'undefined', 'missing Wasm export: __main_argc_argv'); + assert(typeof wasmExports['fflush'] != 'undefined', 'missing Wasm export: fflush'); + assert(typeof wasmExports['emscripten_stack_get_end'] != 'undefined', 'missing Wasm export: emscripten_stack_get_end'); + assert(typeof wasmExports['emscripten_stack_get_base'] != 'undefined', 'missing Wasm export: emscripten_stack_get_base'); + assert(typeof wasmExports['strerror'] != 'undefined', 'missing Wasm export: strerror'); + assert(typeof wasmExports['malloc'] != 'undefined', 'missing Wasm export: malloc'); + assert(typeof wasmExports['emscripten_stack_init'] != 'undefined', 'missing Wasm export: emscripten_stack_init'); + assert(typeof wasmExports['emscripten_stack_get_free'] != 'undefined', 'missing Wasm export: emscripten_stack_get_free'); + assert(typeof wasmExports['_emscripten_stack_restore'] != 'undefined', 'missing Wasm export: _emscripten_stack_restore'); + assert(typeof wasmExports['_emscripten_stack_alloc'] != 'undefined', 'missing Wasm export: _emscripten_stack_alloc'); + assert(typeof wasmExports['emscripten_stack_get_current'] != 'undefined', 'missing Wasm export: emscripten_stack_get_current'); + assert(typeof wasmExports['memory'] != 'undefined', 'missing Wasm export: memory'); + assert(typeof wasmExports['__indirect_function_table'] != 'undefined', 'missing Wasm export: __indirect_function_table'); + _main = Module['_main'] = createExportWrapper('__main_argc_argv', wasmExports['__main_argc_argv'], 2); + _fflush = createExportWrapper('fflush', wasmExports['fflush'], 1); + _emscripten_stack_get_end = wasmExports['emscripten_stack_get_end']; + _emscripten_stack_get_base = wasmExports['emscripten_stack_get_base']; + _strerror = createExportWrapper('strerror', wasmExports['strerror'], 1); + _malloc = createExportWrapper('malloc', wasmExports['malloc'], 1); + _emscripten_stack_init = wasmExports['emscripten_stack_init']; + _emscripten_stack_get_free = wasmExports['emscripten_stack_get_free']; + __emscripten_stack_restore = wasmExports['_emscripten_stack_restore']; + __emscripten_stack_alloc = wasmExports['_emscripten_stack_alloc']; + _emscripten_stack_get_current = wasmExports['emscripten_stack_get_current']; + memory = wasmMemory = wasmExports['memory']; + __indirect_function_table = wasmTable = wasmExports['__indirect_function_table']; +} + +var wasmImports = { + /** @export */ + __assert_fail: ___assert_fail, + /** @export */ + _abort_js: __abort_js, + /** @export */ + clock_time_get: _clock_time_get, + /** @export */ + emscripten_cancel_main_loop: _emscripten_cancel_main_loop, + /** @export */ + emscripten_get_now: _emscripten_get_now, + /** @export */ + emscripten_resize_heap: _emscripten_resize_heap, + /** @export */ + emscripten_set_keydown_callback_on_thread: _emscripten_set_keydown_callback_on_thread, + /** @export */ + emscripten_set_keyup_callback_on_thread: _emscripten_set_keyup_callback_on_thread, + /** @export */ + emscripten_set_main_loop_arg: _emscripten_set_main_loop_arg, + /** @export */ + emscripten_set_mousedown_callback_on_thread: _emscripten_set_mousedown_callback_on_thread, + /** @export */ + emscripten_set_mousemove_callback_on_thread: _emscripten_set_mousemove_callback_on_thread, + /** @export */ + emscripten_set_mouseup_callback_on_thread: _emscripten_set_mouseup_callback_on_thread, + /** @export */ + fd_close: _fd_close, + /** @export */ + fd_seek: _fd_seek, + /** @export */ + fd_write: _fd_write, + /** @export */ + peak_web_dom_open +}; + + +// include: postamble.js +// === Auto-generated postamble setup entry stuff === + +var calledRun; + +function callMain(args = []) { + assert(typeof onPreRuns === 'undefined' || onPreRuns.length == 0, 'cannot call main when preRun functions remain to be called'); + + var entryFunction = _main; + + args.unshift(thisProgram); + + var argc = args.length; + var argv = stackAlloc((argc + 1) * 4); + var argv_ptr = argv; + for (var arg of args) { + HEAPU32[((argv_ptr)>>2)] = stringToUTF8OnStack(arg); + argv_ptr += 4; + } + HEAPU32[((argv_ptr)>>2)] = 0; + + try { + + var ret = entryFunction(argc, argv); + + // if we're not running an evented main loop, it's time to exit + exitJS(ret, /* implicit = */ true); + return ret; + } catch (e) { + return handleException(e); + } +} + +function stackCheckInit() { + // This is normally called automatically during __wasm_call_ctors but need to + // get these values before even running any of the ctors so we call it redundantly + // here. + _emscripten_stack_init(); + // TODO(sbc): Move writeStackCookie to native to to avoid this. + writeStackCookie(); +} + +async function run(args = programArgs) { + assert(!calledRun); + calledRun = true; + + stackCheckInit(); + + preRun(); + + var setStatus = Module['setStatus']; + if (setStatus) { + setStatus('Running...'); + // Yield to the event loop to allow the browser to paint "Running..." + await new Promise((resolve) => setTimeout(resolve, 1)); + // Then we want to clear the status text, but only after the rest of this function runs. + setTimeout(setStatus, 1, ''); + } + + if (ABORT) return; + + initRuntime(); + + // No ATMAINS hooks + + Module['onRuntimeInitialized']?.(); + consumedModuleProp('onRuntimeInitialized'); + + var noInitialRun = Module['noInitialRun'] || false; + if (!noInitialRun) callMain(args); + + postRun(); +} + +function checkUnflushedContent() { + // Compiler settings do not allow exiting the runtime, so flushing + // the streams is not possible. but in ASSERTIONS mode we check + // if there was something to flush, and if so tell the user they + // should request that the runtime be exitable. + // Normally we would not even include flush() at all, but in ASSERTIONS + // builds we do so just for this check, and here we see if there is any + // content to flush, that is, we check if there would have been + // something a non-ASSERTIONS build would have not seen. + // How we flush the streams depends on whether we are in SYSCALLS_REQUIRE_FILESYSTEM=0 + // mode (which has its own special function for this; otherwise, all + // the code is inside libc) + var oldOut = out; + var oldErr = err; + var has = false; + out = err = (x) => { + has = true; + } + try { // it doesn't matter if it fails + flush_NO_FILESYSTEM(); + } catch(e) {} + out = oldOut; + err = oldErr; + if (has) { + warnOnce('stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the Emscripten FAQ), or make sure to emit a newline when you printf etc.'); + warnOnce('(this may also be due to not including full filesystem support - try building with -sFORCE_FILESYSTEM)'); + } +} + +var wasmExports; + +// With async instantation wasmExports is assigned asynchronously when the +// instance is received. +createWasm().then(() => run()); + +// end include: postamble.js + diff --git a/Peak/demos/demo.wasm b/Peak/demos/demo.wasm new file mode 100755 index 0000000..e7e428b Binary files /dev/null and b/Peak/demos/demo.wasm differ diff --git a/Peak/demos/demo_replace_main.c b/Peak/demos/demo_replace_main.c deleted file mode 100644 index 3aedfaa..0000000 --- a/Peak/demos/demo_replace_main.c +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include -#include - -#define PEAK_REPLACE_MAIN -#define PEAK_IMPLEMENTATION -#include "../peak.h" - - -void print_window_resize(PeakEvent ev) { - printf("window: %u %u\n", ev.resize.width, ev.resize.height); -} - -void print_window_close(PeakEvent ev) { - printf("WINDOW CLOSE!!!!!!\nWOWWWOWAAAAAAAAAAAAAAAAAAAAAAAAh\n"); -} - -void print_mouse(PeakEvent ev) { - printf("mouse: state=%d type=%d pos=(%f, %f)\n", ev.pointer.state, ev.pointer.type, ev.pointer.x, ev.pointer.y); -} - -void print_key(PeakEvent ev) { - printf("key: key=%d mod=%d\n", ev.key.key, ev.key.mod); -} - -typedef void (*print_event_func)(PeakEvent ev); -print_event_func print_event[PEAK_EVENT_LAST] = { - [PEAK_EVENT_NONE] = print_mouse, - [PEAK_EVENT_WINDOW_RESIZE] = print_window_resize, - [PEAK_EVENT_WINDOW_CLOSE] = print_window_close, - [PEAK_EVENT_POINTER] = print_mouse, - [PEAK_EVENT_POINTER_CONNECTED] = print_mouse, - [PEAK_EVENT_POINTER_DISCONNECTED] = print_mouse, - [PEAK_EVENT_KEY_DOWN] = print_key, - [PEAK_EVENT_KEY_UP] = print_key, -}; - - -typedef struct { - char *msg; -} AppData; - -static AppData data; - -int main(int argc, char**argv) { - data.msg = "Hello World"; - return PEAK_CONTINUE; -} - -void -peak_events(PeakEvent ev) -{ - print_event[ev.type](ev); - switch (ev.type) { - case PEAK_EVENT_WINDOW_CLOSE: - printf("stoppppeeed\n"); - peak_stop(); - break; - } -} - -void -peak_tick() -{ - printf("%s\n",data.msg); - sleep(1); -} diff --git a/Peak/demos/demo_run b/Peak/demos/demo_run new file mode 100755 index 0000000..7a526c1 Binary files /dev/null and b/Peak/demos/demo_run differ diff --git a/Peak/demos/demo_run.c b/Peak/demos/demo_run.c new file mode 100644 index 0000000..f5c2f7f --- /dev/null +++ b/Peak/demos/demo_run.c @@ -0,0 +1,76 @@ +#include + +#define PEAK_IMPLEMENTATION +#include "../peak.h" + + +void print_window_resize(PeakEvent ev) { + printf("window: %u %u\n", ev.resize.width, ev.resize.height); +} + +void print_window_close(PeakEvent ev) { + printf("WINDOW CLOSE!!!!!!\nWOWWWOWAAAAAAAAAAAAAAAAAAAAAAAAh\n"); +} + +void print_mouse(PeakEvent ev) { + printf("mouse: state=%d type=%d pos=(%f, %f)\n", ev.pointer.state, ev.pointer.type, ev.pointer.x, ev.pointer.y); +} + +void print_key(PeakEvent ev) { + printf("key: key=%d mod=%d\n", ev.key.key, ev.key.mod); +} + +typedef void (*print_event_func)(PeakEvent ev); +print_event_func print_event[PEAK_EVENT_LAST] = { + [PEAK_EVENT_NONE] = print_mouse, + [PEAK_EVENT_WINDOW_RESIZE] = print_window_resize, + [PEAK_EVENT_WINDOW_CLOSE] = print_window_close, + [PEAK_EVENT_POINTER] = print_mouse, + [PEAK_EVENT_POINTER_CONNECTED] = print_mouse, + [PEAK_EVENT_POINTER_DISCONNECTED] = print_mouse, + [PEAK_EVENT_KEY_DOWN] = print_key, + [PEAK_EVENT_KEY_UP] = print_key, +}; + + +typedef struct { + char *msg; +} AppData; + +static AppData data; + +static int +peak_run_func(PeakWindow *win, void *userdata) +{ + AppData d = *(AppData*) userdata; + static PeakEvent ev; + while (peak_window_epoll(win, &ev)) { + switch (ev.type) { + case PEAK_EVENT_NONE: + continue; + case PEAK_EVENT_WINDOW_CLOSE: + return 0; + default: + print_event[ev.type](ev); + continue; + } + } + printf("%s\n", d.msg); + return 1; +} + +int +main(int argc, char**argv) +{ + data.msg = "Hello World"; + + if (!peak_init()) { + return 1; + } + + PeakWindow win = peak_window_open("demo", 400, 400, 0); + peak_window_run(&win, peak_run_func, &data); + peak_window_close(&win); + peak_quit(); + return 0; +} diff --git a/Peak/peak.h b/Peak/peak.h index 581aa74..97bb0a3 100644 --- a/Peak/peak.h +++ b/Peak/peak.h @@ -17,12 +17,11 @@ * 0.0.0 - @vasco - prototyping * 0.1.0 - @vasco - linux x11 that automagically loads X11 DLL * 0.1.1 - @vasco - fixed event handling on linux - * - * TODO: demo that stress tests the whole API + * 0.1.2 - @vasco - better handling of windows, if multiple windows becomes necessary in the future + * 0.2.0 - @vasco - win32 */ #include -#include #include #include #include @@ -52,15 +51,13 @@ #else #define PEAK_MACOS #endif -#elif defined(__linux__) - #define PEAK_LINUX -#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) - #define PEAK_BSD #elif defined(__ANDROID__) #define PEAK_ANDROID #define PEAK_LINUX #elif defined(__linux__) #define PEAK_LINUX +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) + #define PEAK_BSD #endif #if defined(PEAK_LINUX) || defined(PEAK_BSD) || defined(PEAK_APPLE) @@ -77,7 +74,8 @@ #endif typedef enum { - PEAK_KEYMOD_ALT = 0, + PEAK_KEYMOD_NONE = 0, + PEAK_KEYMOD_ALT, PEAK_KEYMOD_SHIFT, PEAK_KEYMOD_CTRL, PEAK_KEYMOD_CAPS, @@ -102,7 +100,7 @@ typedef enum { PEAK_EVENT_POINTER_CONNECTED, PEAK_EVENT_POINTER_DISCONNECTED, PEAK_EVENT_LAST -} PeakEvenType; +} PeakEventType; typedef enum { PEAK_POINTER_MOVED = 0, @@ -118,7 +116,7 @@ typedef enum { } PeakPointerType; typedef struct { - PeakEvenType type; + PeakEventType type; union { struct { PeakKeyCode key; PeakKeyMod mod; } key; struct { uint32_t width, height; } resize; @@ -126,727 +124,1085 @@ typedef struct { }; } PeakEvent; +/* Implemented by the platform */ +typedef struct peak_window_internal_t PeakWindowInternal; +typedef struct PeakWindow PeakWindow; + /* Public API */ -PEAK void peak_init(void); // open a window and initialize graphics. called automatically if using peak_setup(). -PEAK void peak_quit(void); // close the window and release resources. called automatically if using peak_setup(). -PEAK bool peak_poll_events(PeakEvent *ev); // Poll the next event from the window queue. Returns true if an event was retrieved. -PEAK void peak_extensions(void); // retrieve gpu window surface extensions for native api access (e.g., vulkan/opengl). -PEAK void peak_blit(int offset_x, int offset_y, const uint32_t *rgba, size_t width, size_t height); // blit rgba pixels to the screen. -PEAK void peak_clip(int x, int y, size_t w, size_t h); // restrict all subsequent rendering operations to this bounding rectangle. -PEAK void peak_clip_reset(void); // reset clipping plane to window size. -PEAK void peak_draw_rectangle(int x, int y, size_t w, size_t h, uint32_t color); // draw a solid-color filled rectangle. -PEAK void peak_draw_rectangle_gradient(int x, int y, size_t w, size_t h, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3); // fill rectangle interpolating four corner colors in clock-wise order. -PEAK void peak_draw_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color); // draw a solid-color filled triangle. -PEAK void peak_draw_triangle_gradient(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t c0, uint32_t c1, uint32_t c2); // fill triangle with color interpolation clock-wise across its three vertices. -PEAK void peak_draw_line(int x0, int y0, int x1, int y1, size_t width, uint32_t color); // draw a line between two points. -PEAK void peak_draw_line_gradient(int x0, int y0, int x1, int y1, size_t width, uint32_t c0, uint32_t c1); // draw a line with a color gradient. +PEAK int peak_init(void); // Initialize platform context and load necessary DLLs. +PEAK void peak_quit(void); // Close platform context. +PEAK PeakWindow peak_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags); // Open a window. +PEAK void peak_window_close(PeakWindow *window); // Close a window. +PEAK void peak_window_run(PeakWindow *win, int (*peak_tick)(PeakWindow *win, void *userdata), void *userdata); // Hijacking the main loop makes life easier on platforms like web +PEAK int peak_window_epoll(PeakWindow *win, PeakEvent *ev); // Poll a window for events. +PEAK uint32_t* peak_window_backbuffer(PeakWindow *win, size_t *width, size_t *height); // Get the windows backbuffer. +PEAK void peak_window_clear(PeakWindow *win, float r, float g, float b, float a); +PEAK void peak_window_present(PeakWindow *win); -/* Implemented by the platform */ -PEAK void peak_platform_window_open(); -PEAK void peak_platform_window_close(); -PEAK uint32_t *peak_platform_window_buffer(size_t *width, size_t *height); -PEAK bool peak_platform_epoll(PeakEvent *ev); +#endif // PEAK_H -/* - * He was wipping up amazing foods, like making happiness. - * Actual happiness? - * Actual happiness and joy. - * Oh the smell, it was so divine. - * He was making happiness!? - * Happiness in the kettle! - */ -#ifdef PEAK_REPLACE_MAIN -enum PeakReturn { PEAK_CONTINUE = 0, PEAK_STOP = 1 }; -extern int peak__main(int argc, char**argv); -extern void peak_events(PeakEvent ev); -extern void peak_tick(); -static bool running = true; -static inline void peak_stop() { running = false; } -#ifndef PEAK_WEB -#define main(...)\ -main(int argc, char **argv) {\ - assert(peak_events != 0 && "Declare peak_events() to handle window events.");\ - assert(peak_tick != 0 && "Declare peak_tick() to handle each frame ticks.");\ - peak_init();\ - int ret = peak__main(argc, argv);\ - if (ret != PEAK_CONTINUE) return ret;\ - PeakEvent ev;\ - while (running) {\ - while (running && peak_poll_events(&ev)) {\ - peak_events(ev);\ - }\ - peak_tick();\ - }\ - peak_quit();\ -}\ -int peak__main(__VA_ARGS__) -#else -#include -/* - * He was wipping up agony in the kettle. - * Actual pain? - * Boiling hatred in the kettle. - * The smell, it's pain. - * Boiling anger!? - * Yes, boiling anger. - */ +#ifdef PEAK_IMPLEMENTATION +#undef PEAK_IMPLEMENTATION -static inline void -peak__emscripten_loop_step(void *arg) +#if defined(PEAK_WIN32) || defined(PEAK_WEB) +#define PEAK_Q 64 + +typedef struct { + unsigned h, n; + PeakEvent e[PEAK_Q]; +} PeakQ; + +static void +peak_q_push(PeakQ *q, PeakEvent ev) { - (void)arg; - PeakEvent ev; - while (peak_poll_events(&ev)) { - peak_events(ev); - } - - if (running) { - peak_tick(); - } else { - emscripten_cancel_main_loop(); - peak_quit(); - } + if (!q || q->n == PEAK_Q) + return; + q->e[(q->h + q->n++) % PEAK_Q] = ev; } -#define main(...)\ - main(int argc, char **argv) {\ - _Static_assert(peak_events != 0, "When using peak_setup, declare peak_events() to handle window events.");\ - _Static_assert(peak_tick != 0, "When using peak_setup, declare peak_tick() to handle frame ticks.");\ - peak_init();\ - int ret = peak__main(argc, argv);\ - if (ret != PEAK_CONTINUE) return ret;\ - emscripten_set_main_loop_arg(peak__emscripten_loop_step, NULL, 0, 1);\ - return 0;\ - }\ - int peak__main(__VA_ARGS__) +static int +peak_q_pop(PeakQ *q, PeakEvent *ev) +{ + if (!q || !q->n) + return 0; + *ev = q->e[q->h]; + q->h = (q->h + 1) % PEAK_Q; + q->n--; + return 1; +} #endif -#endif // PEAK_DONT_REPLACE_MAIN - -#endif // PEAK_H - -#ifdef PEAK_IMPLEMENTATION -#undef PEAK_IMPLEMENTATION #if defined(PEAK_WIN32) /* --- Start of p_win32.c --- */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + #include +#include +#include +#include -void -peak_platform_window_open() +#define PEAK_WIN32_USER32 "user32.dll" +#define PEAK_WIN32_GDI32 "gdi32.dll" +#define PEAK_WIN32_CLASS "PeakWindow" +#define PEAK_WIN32_PROP "Peak" + +#define PEAK_USER32_API(X) \ + X(RegisterClassExA, ATOM, WINAPI, (const WNDCLASSEXA *)) \ + X(UnregisterClassA, BOOL, WINAPI, (LPCSTR, HINSTANCE)) \ + X(CreateWindowExA, HWND, WINAPI, (DWORD, LPCSTR, LPCSTR, DWORD, int, int, int, int, HWND, HMENU, HINSTANCE, LPVOID)) \ + X(DestroyWindow, BOOL, WINAPI, (HWND)) \ + X(ShowWindow, BOOL, WINAPI, (HWND, int)) \ + X(GetDC, HDC, WINAPI, (HWND)) \ + X(ReleaseDC, int, WINAPI, (HWND, HDC)) \ + X(PeekMessageA, BOOL, WINAPI, (LPMSG, HWND, UINT, UINT, UINT)) \ + X(TranslateMessage, BOOL, WINAPI, (const MSG *)) \ + X(DispatchMessageA, LRESULT, WINAPI, (const MSG *)) \ + X(DefWindowProcA, LRESULT, WINAPI, (HWND, UINT, WPARAM, LPARAM)) \ + X(SetPropA, BOOL, WINAPI, (HWND, LPCSTR, HANDLE)) \ + X(GetPropA, HANDLE, WINAPI, (HWND, LPCSTR)) \ + X(RemovePropA, HANDLE, WINAPI, (HWND, LPCSTR)) \ + X(BeginPaint, HDC, WINAPI, (HWND, LPPAINTSTRUCT)) \ + X(EndPaint, BOOL, WINAPI, (HWND, const PAINTSTRUCT *)) \ + X(AdjustWindowRectEx, BOOL, WINAPI, (LPRECT, DWORD, BOOL, DWORD)) \ + X(LoadCursorA, HCURSOR, WINAPI, (HINSTANCE, LPCSTR)) \ + X(GetKeyState, SHORT, WINAPI, (int)) + +#define PEAK_GDI32_API(X) \ + X(StretchDIBits, int, WINAPI, (HDC, int, int, int, int, int, int, int, int, const void *, const BITMAPINFO *, UINT, DWORD)) + +typedef struct { +#define X(name, ret, conv, args) ret (conv *name) args; + PEAK_USER32_API(X) +#undef X +} PeakUser32Api; + +typedef struct { +#define X(name, ret, conv, args) ret (conv *name) args; + PEAK_GDI32_API(X) +#undef X +} PeakGdi32Api; + +typedef struct { + HMODULE user32; + HMODULE gdi32; + int class_reg; +} PeakWin32; + +struct peak_win32_win { + HWND hwnd; + HDC hdc; + uint32_t *buffer; + uint32_t width; + uint32_t height; + PeakQ q; +}; + +struct peak_window_internal_t { + struct peak_win32_win *w; +}; + +static PeakWin32 peak_win32; +static PeakUser32Api peak_user32; +static PeakGdi32Api peak_gdi32; + +static int +peak_internal_user32_load(HMODULE handle) { +#define X(name, ret, conv, args) peak_user32.name = (ret (conv *) args)(void *)GetProcAddress(handle, #name); + PEAK_USER32_API(X) +#undef X +#define X(name, ret, conv, args) || !peak_user32.name + if (0 PEAK_USER32_API(X)) + return 0; +#undef X + return 1; } -void -peak_platform_window_close() +static int +peak_internal_gdi32_load(HMODULE handle) { +#define X(name, ret, conv, args) peak_gdi32.name = (ret (conv *) args)(void *)GetProcAddress(handle, #name); + PEAK_GDI32_API(X) +#undef X +#define X(name, ret, conv, args) || !peak_gdi32.name + if (0 PEAK_GDI32_API(X)) + return 0; +#undef X + return 1; } -uint32_t* -peak_platform_window_buffer(size_t *width, size_t *height) +static PeakKeyCode +peak_internal_win32_key_map(WPARAM vk) { + if (vk >= 'A' && vk <= 'Z') + return (PeakKeyCode)(PEAK_KEY_A + (int)(vk - 'A')); + switch (vk) { + case VK_UP: return PEAK_KEY_UP; + case VK_DOWN: return PEAK_KEY_DOWN; + case VK_LEFT: return PEAK_KEY_LEFT; + case VK_RIGHT: return PEAK_KEY_RIGHT; + case VK_SPACE: return PEAK_KEY_SPACE; + case VK_ESCAPE: return PEAK_KEY_ESCAPE; + case VK_RETURN: return PEAK_KEY_ENTER; + default: return PEAK_KEY_UNKNOWN; + } } -bool -peak_platform_epoll(PeakEvent *ev) +static PeakKeyMod +peak_internal_win32_mod_map(void) +{ + if (peak_user32.GetKeyState(VK_CONTROL) & 0x8000) return PEAK_KEYMOD_CTRL; + if (peak_user32.GetKeyState(VK_MENU) & 0x8000) return PEAK_KEYMOD_ALT; + if (peak_user32.GetKeyState(VK_SHIFT) & 0x8000) return PEAK_KEYMOD_SHIFT; + if (peak_user32.GetKeyState(VK_CAPITAL) & 1) return PEAK_KEYMOD_CAPS; + return (PeakKeyMod)0; +} + +static int +peak_win32_buffer(struct peak_win32_win *w, uint32_t width, uint32_t height) +{ + uint32_t *buffer; + + buffer = calloc((size_t)width * height, sizeof *buffer); + if (!buffer) + return 0; + free(w->buffer); + w->buffer = buffer; + w->width = width; + w->height = height; + return 1; +} + +static LRESULT CALLBACK +peak_internal_win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + struct peak_win32_win *w; + PeakEvent ev; + + if (msg == WM_NCCREATE) { + CREATESTRUCTA *cs = (CREATESTRUCTA *)lparam; + peak_user32.SetPropA(hwnd, PEAK_WIN32_PROP, cs->lpCreateParams); + } + + w = (struct peak_win32_win *)peak_user32.GetPropA(hwnd, PEAK_WIN32_PROP); + if (!w) + return peak_user32.DefWindowProcA(hwnd, msg, wparam, lparam); + + switch (msg) { + case WM_CLOSE: + memset(&ev, 0, sizeof ev); + ev.type = PEAK_EVENT_WINDOW_CLOSE; + peak_q_push(&w->q, ev); + return 0; + case WM_SIZE: { + uint32_t width, height; + if (wparam == SIZE_MINIMIZED) + return 0; + width = (uint32_t)LOWORD(lparam); + height = (uint32_t)HIWORD(lparam); + if (!width || !height || (width == w->width && height == w->height)) + return 0; + if (!peak_win32_buffer(w, width, height)) + return 0; + memset(&ev, 0, sizeof ev); + ev.type = PEAK_EVENT_WINDOW_RESIZE; + ev.resize.width = w->width; + ev.resize.height = w->height; + peak_q_push(&w->q, ev); + return 0; + } + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + memset(&ev, 0, sizeof ev); + ev.type = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) ? PEAK_EVENT_KEY_DOWN : PEAK_EVENT_KEY_UP; + ev.key.key = peak_internal_win32_key_map(wparam); + ev.key.mod = peak_internal_win32_mod_map(); + peak_q_push(&w->q, ev); + return 0; + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + memset(&ev, 0, sizeof ev); + ev.type = PEAK_EVENT_POINTER; + ev.pointer.x = (float)(short)LOWORD(lparam); + ev.pointer.y = (float)(short)HIWORD(lparam); + if (msg == WM_MOUSEMOVE) { + ev.pointer.state = PEAK_POINTER_MOVED; + ev.pointer.type = (wparam & MK_RBUTTON) ? PEAK_POINTER_RIGHT : + (wparam & MK_MBUTTON) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + } else if (msg == WM_LBUTTONDOWN || msg == WM_RBUTTONDOWN || msg == WM_MBUTTONDOWN) { + ev.pointer.state = PEAK_POINTER_PRESSED; + ev.pointer.type = (msg == WM_RBUTTONDOWN) ? PEAK_POINTER_RIGHT : + (msg == WM_MBUTTONDOWN) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + } else { + ev.pointer.state = PEAK_POINTER_RELEASED; + ev.pointer.type = (msg == WM_RBUTTONUP) ? PEAK_POINTER_RIGHT : + (msg == WM_MBUTTONUP) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + } + peak_q_push(&w->q, ev); + return 0; + case WM_PAINT: { + PAINTSTRUCT ps; + peak_user32.BeginPaint(hwnd, &ps); + peak_user32.EndPaint(hwnd, &ps); + return 0; + } + case WM_ERASEBKGND: + return 1; + case WM_DESTROY: + peak_user32.RemovePropA(hwnd, PEAK_WIN32_PROP); + return 0; + default: + return peak_user32.DefWindowProcA(hwnd, msg, wparam, lparam); + } +} + +static int +peak_platform_init(void) { + WNDCLASSEXA wc; + + if (!peak_user32.CreateWindowExA) { + if (!(peak_win32.user32 = LoadLibraryA(PEAK_WIN32_USER32))) { + fputs("Failed to load user32.dll. What system are you fucking using and abusing?", stderr); + return 0; + } + if (!peak_internal_user32_load(peak_win32.user32)) { + fputs("Failed to load user32 symbols", stderr); + return 0; + } + } + if (!peak_gdi32.StretchDIBits) { + if (!(peak_win32.gdi32 = LoadLibraryA(PEAK_WIN32_GDI32))) { + fputs("Failed to load gdi32.dll. What system are you fucking using and abusing?", stderr); + return 0; + } + if (!peak_internal_gdi32_load(peak_win32.gdi32)) { + fputs("Failed to load gdi32 symbols", stderr); + return 0; + } + } + if (!peak_win32.class_reg) { + memset(&wc, 0, sizeof wc); + wc.cbSize = sizeof wc; + wc.style = CS_OWNDC; + wc.lpfnWndProc = peak_internal_win32_wndproc; + wc.hInstance = GetModuleHandleA(NULL); + wc.hCursor = peak_user32.LoadCursorA(NULL, IDC_ARROW); + wc.lpszClassName = PEAK_WIN32_CLASS; + if (!peak_user32.RegisterClassExA(&wc) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) { + fputs("Failed to register window class", stderr); + return 0; + } + peak_win32.class_reg = 1; + } + return 1; +} + +static void +peak_platform_quit(void) +{ + if (!peak_win32.class_reg) + return; + peak_user32.UnregisterClassA(PEAK_WIN32_CLASS, GetModuleHandleA(NULL)); + peak_win32.class_reg = 0; +} + +static PeakWindowInternal +peak_platform_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags) +{ + PeakWindowInternal intern = {0}; + struct peak_win32_win *w; + DWORD style, ex; + RECT r; + HINSTANCE inst; + + (void)flags; + if (!peak_user32.CreateWindowExA && !peak_platform_init()) + return intern; + + w = calloc(1, sizeof *w); + if (!w) + return intern; + if (!peak_win32_buffer(w, width, height)) { + free(w); + return intern; + } + + ex = 0; + style = WS_OVERLAPPEDWINDOW; + r.left = 0; + r.top = 0; + r.right = (LONG)width; + r.bottom = (LONG)height; + peak_user32.AdjustWindowRectEx(&r, style, FALSE, ex); + inst = GetModuleHandleA(NULL); + w->hwnd = peak_user32.CreateWindowExA(ex, PEAK_WIN32_CLASS, name, style, + CW_USEDEFAULT, CW_USEDEFAULT, r.right - r.left, r.bottom - r.top, + NULL, NULL, inst, w); + if (!w->hwnd) { + free(w->buffer); + free(w); + return intern; + } + + w->hdc = peak_user32.GetDC(w->hwnd); + if (!w->hdc) { + peak_user32.DestroyWindow(w->hwnd); + free(w->buffer); + free(w); + return intern; + } + + peak_user32.ShowWindow(w->hwnd, SW_SHOWNORMAL); + intern.w = w; + return intern; +} + +static void +peak_platform_window_close(PeakWindowInternal *intern) +{ + struct peak_win32_win *w; + if (!intern || !intern->w) + return; + w = intern->w; + if (w->hdc && w->hwnd) + peak_user32.ReleaseDC(w->hwnd, w->hdc); + if (w->hwnd) { + peak_user32.RemovePropA(w->hwnd, PEAK_WIN32_PROP); + peak_user32.DestroyWindow(w->hwnd); + } + free(w->buffer); + free(w); + intern->w = NULL; +} + +static uint32_t * +peak_platform_window_buffer(PeakWindowInternal *intern, size_t *width, size_t *height) +{ + struct peak_win32_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_win32_win *w = intern ? intern->w : NULL; + BITMAPINFO bmi; + + if (!w || !w->hwnd || !w->hdc || !w->buffer) + return; + + memset(&bmi, 0, sizeof bmi); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = (LONG)w->width; + bmi.bmiHeader.biHeight = -(LONG)w->height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + peak_gdi32.StretchDIBits(w->hdc, + 0, 0, (int)w->width, (int)w->height, + 0, 0, (int)w->width, (int)w->height, + w->buffer, &bmi, DIB_RGB_COLORS, SRCCOPY); +} + +static bool +peak_platform_epoll(PeakWindowInternal *intern, PeakEvent *ev) +{ + MSG msg; + struct peak_win32_win *w = intern ? intern->w : NULL; + + if (!w || !w->hwnd) + return 0; + + while (peak_user32.PeekMessageA(&msg, w->hwnd, 0, 0, PM_REMOVE)) { + peak_user32.TranslateMessage(&msg); + peak_user32.DispatchMessageA(&msg); + if (peak_q_pop(&w->q, ev)) + return 1; + } + return peak_q_pop(&w->q, ev); } /* --- End of p_win32.c --- */ #elif defined(PEAK_LINUX) /* --- Start of p_linux.c --- */ -#include +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif + #include -#include #include +#include #include -#include +#include +#include #include -#include -#define PEAK_X11_LINUX "/usr/lib/libX11.so.6" - -#define PEAK_LINUX_DEFAULT_WIDTH 800 -#define PEAK_LINUX_DEFAULT_HEIGHT 600 - -typedef Display* (*XOpenDisplay_t)(const char*); -typedef int (*XCloseDisplay_t)(Display*); -typedef Window (*XCreateSimpleWindow_t)(Display*, Window, int, int, unsigned int, unsigned int, unsigned int, unsigned long, unsigned long); -typedef int (*XStoreName_t)(Display*, Window, const char*); -typedef Atom (*XInternAtom_t)(Display*, const char*, Bool); -typedef Status (*XSetWMProtocols_t)(Display*, Window, Atom*, int); -typedef int (*XSelectInput_t)(Display*, Window, long); -typedef GC (*XCreateGC_t)(Display*, Window, unsigned long, XGCValues*); -typedef XImage* (*XCreateImage_t)(Display*, Visual*, unsigned int, int, int, char*, unsigned int, unsigned int, int, int); -typedef int (*XMapRaised_t)(Display*, Window); -typedef int (*XFlush_t)(Display*); -typedef int (*XFreeGC_t)(Display*, GC); -typedef int (*XDestroyWindow_t)(Display*, Window); -typedef int (*XPending_t)(Display*); -typedef int (*XNextEvent_t)(Display*, XEvent*); -typedef KeySym (*XLookupKeysym_t)(XKeyEvent*, int); -typedef int (*XPutImage_t)(Display*, Drawable, GC, XImage*, int, int, int, int, unsigned int, unsigned int); -typedef int (*XDefaultScreen_t)(Display*); -typedef Window (*XRootWindow_t)(Display*, int); -typedef unsigned long (*XBlackPixel_t)(Display*, int); -typedef Visual* (*XDefaultVisual_t)(Display*, int); -typedef int (*XDefaultDepth_t)(Display*, int); +#define PEAK_X11_LINUX "libX11.so.6" + +#define PEAK_X11_API(X) \ + X(XOpenDisplay, Display *, (const char *)) \ + X(XCloseDisplay, int, (Display *)) \ + X(XCreateSimpleWindow, Window, (Display *, Window, int, int, unsigned int, unsigned int, unsigned int, unsigned long, unsigned long)) \ + X(XStoreName, int, (Display *, Window, const char *)) \ + X(XInternAtom, Atom, (Display *, const char *, Bool)) \ + X(XSetWMProtocols, Status, (Display *, Window, Atom *, int)) \ + X(XSelectInput, int, (Display *, Window, long)) \ + X(XCreateGC, GC, (Display *, Window, unsigned long, XGCValues *)) \ + X(XCreateImage, XImage *, (Display *, Visual *, unsigned int, int, int, char *, unsigned int, unsigned int, int, int)) \ + X(XMapRaised, int, (Display *, Window)) \ + X(XFlush, int, (Display *)) \ + X(XFreeGC, int, (Display *, GC)) \ + X(XDestroyWindow, int, (Display *, Window)) \ + X(XCheckIfEvent, Bool, (Display *, XEvent *, Bool (*)(Display *, XEvent *, XPointer), XPointer)) \ + X(XLookupKeysym, KeySym, (XKeyEvent *, int)) \ + X(XPutImage, int, (Display *, Drawable, GC, XImage *, int, int, int, int, unsigned int, unsigned int)) typedef struct { - XOpenDisplay_t XOpenDisplay; - XCloseDisplay_t XCloseDisplay; - XCreateSimpleWindow_t XCreateSimpleWindow; - XStoreName_t XStoreName; - XInternAtom_t XInternAtom; - XSetWMProtocols_t XSetWMProtocols; - XSelectInput_t XSelectInput; - XCreateGC_t XCreateGC; - XCreateImage_t XCreateImage; - XMapRaised_t XMapRaised; - XFlush_t XFlush; - XFreeGC_t XFreeGC; - XDestroyWindow_t XDestroyWindow; - XPending_t XPending; - XNextEvent_t XNextEvent; - XLookupKeysym_t XLookupKeysym; - XPutImage_t XPutImage; - XDefaultScreen_t XDefaultScreen; - XRootWindow_t XRootWindow; - XBlackPixel_t XBlackPixel; - XDefaultVisual_t XDefaultVisual; - XDefaultDepth_t XDefaultDepth; +#define X(name, ret, args) ret (*name) args; + PEAK_X11_API(X) +#undef X } PeakX11Api; -static PeakX11Api peak_x11; - -static int peak_load_x11(void *handle) { - peak_x11.XOpenDisplay = (XOpenDisplay_t)dlsym(handle, "XOpenDisplay"); - peak_x11.XCloseDisplay = (XCloseDisplay_t)dlsym(handle, "XCloseDisplay"); - peak_x11.XCreateSimpleWindow = (XCreateSimpleWindow_t)dlsym(handle, "XCreateSimpleWindow"); - peak_x11.XStoreName = (XStoreName_t)dlsym(handle, "XStoreName"); - peak_x11.XInternAtom = (XInternAtom_t)dlsym(handle, "XInternAtom"); - peak_x11.XSetWMProtocols = (XSetWMProtocols_t)dlsym(handle, "XSetWMProtocols"); - peak_x11.XSelectInput = (XSelectInput_t)dlsym(handle, "XSelectInput"); - peak_x11.XCreateGC = (XCreateGC_t)dlsym(handle, "XCreateGC"); - peak_x11.XCreateImage = (XCreateImage_t)dlsym(handle, "XCreateImage"); - peak_x11.XMapRaised = (XMapRaised_t)dlsym(handle, "XMapRaised"); - peak_x11.XFlush = (XFlush_t)dlsym(handle, "XFlush"); - peak_x11.XFreeGC = (XFreeGC_t)dlsym(handle, "XFreeGC"); - peak_x11.XDestroyWindow = (XDestroyWindow_t)dlsym(handle, "XDestroyWindow"); - peak_x11.XPending = (XPending_t)dlsym(handle, "XPending"); - peak_x11.XNextEvent = (XNextEvent_t)dlsym(handle, "XNextEvent"); - peak_x11.XLookupKeysym = (XLookupKeysym_t)dlsym(handle, "XLookupKeysym"); - peak_x11.XPutImage = (XPutImage_t)dlsym(handle, "XPutImage"); - peak_x11.XDefaultScreen = (XDefaultScreen_t)dlsym(handle, "XDefaultScreen"); - peak_x11.XRootWindow = (XRootWindow_t)dlsym(handle, "XRootWindow"); - peak_x11.XBlackPixel = (XBlackPixel_t)dlsym(handle, "XBlackPixel"); - peak_x11.XDefaultVisual = (XDefaultVisual_t)dlsym(handle, "XDefaultVisual"); - peak_x11.XDefaultDepth = (XDefaultDepth_t)dlsym(handle, "XDefaultDepth"); - - if (!peak_x11.XOpenDisplay || !peak_x11.XCloseDisplay || !peak_x11.XCreateSimpleWindow || - !peak_x11.XStoreName || !peak_x11.XInternAtom || !peak_x11.XSetWMProtocols || - !peak_x11.XSelectInput || !peak_x11.XCreateGC || !peak_x11.XCreateImage || - !peak_x11.XMapRaised || !peak_x11.XFlush || - !peak_x11.XFreeGC || !peak_x11.XDestroyWindow || !peak_x11.XPending || - !peak_x11.XNextEvent || !peak_x11.XLookupKeysym || !peak_x11.XPutImage || - !peak_x11.XDefaultScreen || !peak_x11.XRootWindow || !peak_x11.XBlackPixel || - !peak_x11.XDefaultVisual || !peak_x11.XDefaultDepth) { - return 0; - } - return 1; -} - - typedef struct { - Display *display; - Atom wm_delete_window; - bool initialized; + Display *display; + Atom wm_delete_window; } PeakLinux; -typedef struct { - Window window; - GC gfx_ctx; /* GRAPHICS CONTEXT, not garbage collector lol */ -} PeakLinuxWindow; +struct peak_window_internal_t { + Window window; + GC gfx_ctx; + XImage *ximage; + uint32_t *buffer; + uint32_t width; + uint32_t height; +}; -static PeakLinux peak_linux = {0}; -static PeakLinuxWindow peak_linux_window = {0}; -static XImage *peak_linux_ximage = NULL; -static uint32_t *peak_linux_buffer = NULL; -static size_t peak_linux_width = PEAK_LINUX_DEFAULT_WIDTH; -static size_t peak_linux_height = PEAK_LINUX_DEFAULT_HEIGHT; +static PeakLinux peak_linux; +static PeakX11Api peak_x11; + +static int +peak_internal_x11_load(void *handle) +{ +#define X(name, ret, args) peak_x11.name = (ret (*) args)dlsym(handle, #name); + PEAK_X11_API(X) +#undef X +#define X(name, ret, args) || !peak_x11.name + if (0 PEAK_X11_API(X)) + return 0; +#undef X + return 1; +} static PeakKeyCode -peak__x11_map_key(KeySym sym) -{ - if (sym >= XK_a && sym <= XK_z) return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_a)); - if (sym >= XK_A && sym <= XK_Z) return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_A)); - switch (sym) { - case XK_Up: return PEAK_KEY_UP; - case XK_Down: return PEAK_KEY_DOWN; - case XK_Left: return PEAK_KEY_LEFT; - case XK_Right: return PEAK_KEY_RIGHT; - case XK_space: return PEAK_KEY_SPACE; - case XK_Escape: return PEAK_KEY_ESCAPE; - case XK_Return: return PEAK_KEY_ENTER; - default: return PEAK_KEY_UNKNOWN; - } +peak_internal_x11_key_map(KeySym sym) +{ + if (sym >= XK_a && sym <= XK_z) + return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_a)); + if (sym >= XK_A && sym <= XK_Z) + return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_A)); + switch (sym) { + case XK_Up: return PEAK_KEY_UP; + case XK_Down: return PEAK_KEY_DOWN; + case XK_Left: return PEAK_KEY_LEFT; + case XK_Right: return PEAK_KEY_RIGHT; + case XK_space: return PEAK_KEY_SPACE; + case XK_Escape: return PEAK_KEY_ESCAPE; + case XK_Return: return PEAK_KEY_ENTER; + default: return PEAK_KEY_UNKNOWN; + } } static PeakKeyMod -peak__x11_map_mod(unsigned int state) +peak_internal_x11_mod_map(unsigned int state) { - if (state & ControlMask) return PEAK_KEYMOD_CTRL; - if (state & Mod1Mask) return PEAK_KEYMOD_ALT; - if (state & ShiftMask) return PEAK_KEYMOD_SHIFT; - if (state & LockMask) return PEAK_KEYMOD_CAPS; - return (PeakKeyMod)0; + if (state & ControlMask) return PEAK_KEYMOD_CTRL; + if (state & Mod1Mask) return PEAK_KEYMOD_ALT; + if (state & ShiftMask) return PEAK_KEYMOD_SHIFT; + if (state & LockMask) return PEAK_KEYMOD_CAPS; + return (PeakKeyMod)0; } -void -peak_platform_window_open() +static Bool +peak_internal_x11_window_match(Display *dpy, XEvent *ev, XPointer arg) { - void *x11_handle = dlopen(PEAK_X11_LINUX, RTLD_LOCAL | RTLD_NOW); - assert(x11_handle && "Failed to load X11 library. What system are you fucking using and abusing?"); - - int success = peak_load_x11(x11_handle); - assert(success && "Failed to open X11 display"); - - peak_linux.display = peak_x11.XOpenDisplay(NULL); - assert(peak_linux.display && "Failed to open X11 display"); - - /* Who design this API??? */ - int screen = DefaultScreen(peak_linux.display); - peak_linux_window.window = peak_x11.XCreateSimpleWindow(peak_linux.display, RootWindow(peak_linux.display, screen), 0, 0, peak_linux_width, peak_linux_height, 0, BlackPixel(peak_linux.display, screen), BlackPixel(peak_linux.display, screen)); - peak_x11.XStoreName(peak_linux.display, peak_linux_window.window, "Peak"); - peak_linux.wm_delete_window = peak_x11.XInternAtom(peak_linux.display, "WM_DELETE_WINDOW", False); - peak_x11.XSetWMProtocols(peak_linux.display, peak_linux_window.window, &peak_linux.wm_delete_window, 1); - peak_x11.XSelectInput(peak_linux.display, peak_linux_window.window, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask); - peak_linux_window.gfx_ctx = peak_x11.XCreateGC(peak_linux.display, peak_linux_window.window, 0, NULL); - peak_linux_buffer = (uint32_t *)calloc(peak_linux_width * peak_linux_height, sizeof(uint32_t)); - assert(peak_linux_buffer && "Failed to allocate framebuffer"); - - /* Wrap the buffer in an XImage for XPutImage blitting */ - Visual *visual = DefaultVisual(peak_linux.display, screen); - int depth = DefaultDepth(peak_linux.display, screen); - - peak_linux_ximage = peak_x11.XCreateImage(peak_linux.display, visual, depth, ZPixmap, 0, (char *)peak_linux_buffer, peak_linux_width, peak_linux_height, 32, 0); - assert(peak_linux_ximage && "Failed to create XImage"); - - peak_x11.XMapRaised(peak_linux.display, peak_linux_window.window); - peak_x11.XFlush(peak_linux.display); + (void)dpy; + return ev->xany.window == *(Window *)arg; +} - peak_linux.initialized = true; +static int +peak_linux_buffer(PeakWindowInternal *w, uint32_t width, uint32_t height) +{ + int screen; + + if (w->ximage) { + w->ximage->data = NULL; + XDestroyImage(w->ximage); + w->ximage = NULL; + } + free(w->buffer); + w->buffer = calloc((size_t)width * height, sizeof *w->buffer); + if (!w->buffer) + return 0; + + w->width = width; + w->height = height; + screen = DefaultScreen(peak_linux.display); + w->ximage = peak_x11.XCreateImage(peak_linux.display, + DefaultVisual(peak_linux.display, screen), + DefaultDepth(peak_linux.display, screen), + ZPixmap, 0, (char *)w->buffer, width, height, 32, 0); + if (!w->ximage) { + free(w->buffer); + w->buffer = NULL; + return 0; + } + return 1; } -void -peak_platform_window_close() +static int +peak_platform_init(void) { - if (!peak_linux.initialized) return; + void *handle; + + if (!peak_x11.XOpenDisplay) { + if (!(handle = dlopen(PEAK_X11_LINUX, RTLD_LOCAL | RTLD_NOW))) { + fputs("Failed to load X11 library. What system are you fucking using and abusing?", stderr); + return 0; + } + if (!peak_internal_x11_load(handle)) { + fputs("Failed to load X11 symbols", stderr); + return 0; + } + } + if (!peak_linux.display) { + if (!(peak_linux.display = peak_x11.XOpenDisplay(NULL))) { + fputs("Failed to open X11 display", stderr); + return 0; + } + peak_linux.wm_delete_window = peak_x11.XInternAtom(peak_linux.display, "WM_DELETE_WINDOW", False); + } + return 1; +} - if (peak_linux_ximage) { - peak_linux_ximage->data = NULL; - XDestroyImage(peak_linux_ximage); - peak_linux_ximage = NULL; - } +static void +peak_platform_quit(void) +{ + if (!peak_linux.display) + return; + peak_x11.XCloseDisplay(peak_linux.display); + peak_linux.display = 0; +} - free(peak_linux_buffer); - peak_linux_buffer = NULL; +static PeakWindowInternal +peak_platform_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags) +{ + PeakWindowInternal w = {0}; + int screen; + + (void)flags; + if (!peak_linux.display && !peak_platform_init()) + return w; + + screen = DefaultScreen(peak_linux.display); + w.window = peak_x11.XCreateSimpleWindow(peak_linux.display, + RootWindow(peak_linux.display, screen), 0, 0, width, height, 0, + BlackPixel(peak_linux.display, screen), BlackPixel(peak_linux.display, screen)); + peak_x11.XStoreName(peak_linux.display, w.window, name); + peak_x11.XSetWMProtocols(peak_linux.display, w.window, &peak_linux.wm_delete_window, 1); + peak_x11.XSelectInput(peak_linux.display, w.window, + KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | StructureNotifyMask); + w.gfx_ctx = peak_x11.XCreateGC(peak_linux.display, w.window, 0, NULL); + + if (!peak_linux_buffer(&w, width, height)) { + peak_x11.XFreeGC(peak_linux.display, w.gfx_ctx); + peak_x11.XDestroyWindow(peak_linux.display, w.window); + return (PeakWindowInternal){0}; + } + + peak_x11.XMapRaised(peak_linux.display, w.window); + peak_x11.XFlush(peak_linux.display); + return w; +} - peak_x11.XFreeGC(peak_linux.display, peak_linux_window.gfx_ctx); - peak_x11.XDestroyWindow(peak_linux.display, peak_linux_window.window); - peak_x11.XCloseDisplay(peak_linux.display); +static void +peak_platform_window_close(PeakWindowInternal *w) +{ + if (!w || !w->window || !peak_linux.display) + return; + if (w->ximage) { + w->ximage->data = NULL; + XDestroyImage(w->ximage); + } + free(w->buffer); + if (w->gfx_ctx) + peak_x11.XFreeGC(peak_linux.display, w->gfx_ctx); + peak_x11.XDestroyWindow(peak_linux.display, w->window); + *w = (PeakWindowInternal){0}; +} - peak_linux.initialized = false; +static uint32_t * +peak_platform_window_buffer(PeakWindowInternal *w, size_t *width, size_t *height) +{ + if (!w || !w->window) { + *width = 0; + *height = 0; + return NULL; + } + *width = w->width; + *height = w->height; + return w->buffer; } -uint32_t * -peak_platform_window_buffer(size_t *width, size_t *height) -{ - *width = peak_linux_width; - *height = peak_linux_height; - return peak_linux_buffer; -} - -bool -peak_platform_epoll(PeakEvent *ev) -{ - if (!peak_linux.initialized) return false; - - Display *dpy = peak_linux.display; - - while (peak_x11.XPending(dpy) > 0) { - XEvent xev; - peak_x11.XNextEvent(dpy, &xev); - - switch (xev.type) { - - case ClientMessage: - if ((Atom)xev.xclient.data.l[0] == peak_linux.wm_delete_window) { - ev->type = PEAK_EVENT_WINDOW_CLOSE; - printf("CLLOOOOOOOSEEEE"); - return true; - } - continue; - - case KeyPress: - case KeyRelease: { - KeySym sym = peak_x11.XLookupKeysym(&xev.xkey, 0); - ev->type = (xev.type == KeyPress) ? PEAK_EVENT_KEY_DOWN : PEAK_EVENT_KEY_UP; - ev->key.key = peak__x11_map_key(sym); - ev->key.mod = peak__x11_map_mod(xev.xkey.state); - return true; - } - - case ButtonPress: - case ButtonRelease: { - ev->type = PEAK_EVENT_POINTER; - ev->pointer.state = (xev.type == ButtonPress) ? PEAK_POINTER_PRESSED : PEAK_POINTER_RELEASED; - ev->pointer.x = (float)xev.xbutton.x; - ev->pointer.y = (float)xev.xbutton.y; - switch (xev.xbutton.button) { - case Button1: ev->pointer.type = PEAK_POINTER_LEFT; break; - case Button2: ev->pointer.type = PEAK_POINTER_MIDDLE; break; - case Button3: ev->pointer.type = PEAK_POINTER_RIGHT; break; - default: ev->pointer.type = PEAK_POINTER_LEFT; break; - } - return true; - } - - case MotionNotify: - ev->type = PEAK_EVENT_POINTER; - ev->pointer.state = PEAK_POINTER_MOVED; - ev->pointer.x = (float)xev.xmotion.x; - ev->pointer.y = (float)xev.xmotion.y; - if (xev.xmotion.state & Button3Mask) - ev->pointer.type = PEAK_POINTER_RIGHT; - else if (xev.xmotion.state & Button2Mask) - ev->pointer.type = PEAK_POINTER_MIDDLE; - else - ev->pointer.type = PEAK_POINTER_LEFT; - return true; - - case ConfigureNotify: - if ((size_t)xev.xconfigure.width != peak_linux_width || - (size_t)xev.xconfigure.height != peak_linux_height) { - ev->type = PEAK_EVENT_WINDOW_RESIZE; - ev->resize.width = (uint32_t)xev.xconfigure.width; - ev->resize.height = (uint32_t)xev.xconfigure.height; - return true; - } - continue; - - case Expose: - if (xev.xexpose.count == 0) { - peak_x11.XPutImage(dpy, peak_linux_window.window, peak_linux_window.gfx_ctx, peak_linux_ximage, 0, 0, 0, 0, peak_linux_width, peak_linux_height); - } - continue; - - default: - continue; - } - } +static void +peak_platform_window_present(PeakWindowInternal *w) +{ + if (!w || !w->ximage || !peak_linux.display) + return; + peak_x11.XPutImage(peak_linux.display, w->window, w->gfx_ctx, w->ximage, + 0, 0, 0, 0, w->width, w->height); + peak_x11.XFlush(peak_linux.display); +} - peak_x11.XPutImage(dpy, peak_linux_window.window, peak_linux_window.gfx_ctx, peak_linux_ximage, 0, 0, 0, 0, peak_linux_width, peak_linux_height); - peak_x11.XFlush(dpy); - return false; +static bool +peak_platform_epoll(PeakWindowInternal *w, PeakEvent *ev) +{ + XEvent xev; + + if (!w || !w->window || !peak_linux.display) + return 0; + + while (peak_x11.XCheckIfEvent(peak_linux.display, &xev, peak_internal_x11_window_match, (XPointer)&w->window)) { + switch (xev.type) { + case ClientMessage: + if ((Atom)xev.xclient.data.l[0] == peak_linux.wm_delete_window) { + ev->type = PEAK_EVENT_WINDOW_CLOSE; + return 1; + } + continue; + case KeyPress: + case KeyRelease: + ev->type = (xev.type == KeyPress) ? PEAK_EVENT_KEY_DOWN : PEAK_EVENT_KEY_UP; + ev->key.key = peak_internal_x11_key_map(peak_x11.XLookupKeysym(&xev.xkey, 0)); + ev->key.mod = peak_internal_x11_mod_map(xev.xkey.state); + return 1; + case ButtonPress: + case ButtonRelease: + ev->type = PEAK_EVENT_POINTER; + ev->pointer.state = (xev.type == ButtonPress) ? PEAK_POINTER_PRESSED : PEAK_POINTER_RELEASED; + ev->pointer.x = (float)xev.xbutton.x; + ev->pointer.y = (float)xev.xbutton.y; + ev->pointer.type = (xev.xbutton.button == Button2) ? PEAK_POINTER_MIDDLE : + (xev.xbutton.button == Button3) ? PEAK_POINTER_RIGHT : PEAK_POINTER_LEFT; + return 1; + case MotionNotify: + ev->type = PEAK_EVENT_POINTER; + ev->pointer.state = PEAK_POINTER_MOVED; + ev->pointer.x = (float)xev.xmotion.x; + ev->pointer.y = (float)xev.xmotion.y; + ev->pointer.type = (xev.xmotion.state & Button3Mask) ? PEAK_POINTER_RIGHT : + (xev.xmotion.state & Button2Mask) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + return 1; + case ConfigureNotify: { + uint32_t width = (uint32_t)xev.xconfigure.width; + uint32_t height = (uint32_t)xev.xconfigure.height; + if (width == w->width && height == w->height) + continue; + if (!peak_linux_buffer(w, width, height)) + continue; + ev->type = PEAK_EVENT_WINDOW_RESIZE; + ev->resize.width = w->width; + ev->resize.height = w->height; + return 1; + } + default: + continue; + } + } + return 0; } /* --- End of p_linux.c --- */ #elif defined(PEAK_WEB) /* --- Start of p_emscripten.c --- */ +#include +#include #include #include -#include - -#define PEAK_CANVAS "#PeakCanvas" +#include -static PeakKeyCode map_key(const char *code); -static PeakKeyMod map_mod(const EmscriptenKeyboardEvent *keyEvent); -static EM_BOOL key_callback(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData); -static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); -static EM_BOOL resize_callback(int eventType, const EmscriptenUiEvent *uiEvent, void *userData); +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; +} -void -peak_platform_window_open() +static PeakKeyCode +peak_web_key_map(const char *code) { - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_TRUE, key_callback); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_TRUE, key_callback); - emscripten_set_mousedown_callback(PEAK_CANVAS, NULL, EM_TRUE, mouse_callback); - emscripten_set_mouseup_callback(PEAK_CANVAS, NULL, EM_TRUE, mouse_callback); - emscripten_set_mousemove_callback(PEAK_CANVAS, NULL, EM_TRUE, mouse_callback); - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_TRUE, resize_callback); + 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; } -void -peak_platform_window_close() +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; } -uint32_t* -peak_platform_window_buffer(size_t *width, size_t *height) +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; } -bool -peak_platform_epoll(PeakEvent *ev) +static void +peak_web_listen(struct peak_web_win *w, int on) { - return 0; + 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); } -/* --- End of p_emscripten.c --- */ -#endif -/* --- Start of peak.c --- */ -static inline void peak__put_pixel(int x, int y, uint32_t color); -static inline uint32_t peak__lerp_color(uint32_t c0, uint32_t c1, float t); -static inline float peak__edge_cross(float ax, float ay, float bx, float by, float cx, float cy); - -static uint32_t *peak_window_buffer; -static size_t peak_window_width; -static size_t peak_window_height; -static int peak_clip_x; -static int peak_clip_y; -static size_t peak_clip_width; -static size_t peak_clip_height; +static int +peak_platform_init(void) +{ + return 1; +} -void -peak__put_pixel(int x, int y, uint32_t color) +static void +peak_platform_quit(void) { - if (x < peak_clip_x || x >= (int)(peak_clip_x + peak_clip_width) || - y < peak_clip_y || y >= (int)(peak_clip_y + peak_clip_height)) { - return; - } - if (x < 0 || x >= (int)peak_window_width || y < 0 || y >= (int)peak_window_height) { - return; - } - peak_window_buffer[y * peak_window_width + x] = color; } -uint32_t -peak__lerp_color(uint32_t c0, uint32_t c1, float t) +static PeakWindowInternal +peak_platform_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags) { - if (t <= 0.0f) return c0; - if (t >= 1.0f) return c1; + PeakWindowInternal intern = {0}; + struct peak_web_win *w; - uint32_t a0 = (c0 >> 24) & 0xFF, a1 = (c1 >> 24) & 0xFF; - uint32_t r0 = (c0 >> 16) & 0xFF, r1 = (c1 >> 16) & 0xFF; - uint32_t g0 = (c0 >> 8) & 0xFF, g1 = (c1 >> 8) & 0xFF; - uint32_t b0 = c0 & 0xFF, b1 = c1 & 0xFF; + (void)flags; - uint32_t a = (uint32_t)(a0 + t * ((float)a1 - (float)a0)); - uint32_t r = (uint32_t)(r0 + t * ((float)r1 - (float)r0)); - uint32_t g = (uint32_t)(g0 + t * ((float)g1 - (float)g0)); - uint32_t b = (uint32_t)(b0 + t * ((float)b1 - (float)b0)); + 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; - return (a << 24) | (r << 16) | (g << 8) | b; -} + peak_web_dom_open(w->name, (int)width, (int)height); -float -peak__edge_cross(float ax, float ay, float bx, float by, float cx, float cy) -{ - return (cx - ax) * (by - ay) - (cy - ay) * (bx - ax); + peak_web_listen(w, 1); + intern.w = w; + return intern; } -void -peak_init(void) +static void +peak_platform_window_close(PeakWindowInternal *intern) { - peak_platform_window_open(); - peak_window_buffer = peak_platform_window_buffer(&peak_window_width, &peak_window_height); - peak_clip_x = 0; - peak_clip_y = 0; - peak_clip_width = peak_window_width; - peak_clip_height = peak_window_height; + struct peak_web_win *w; + if (!intern || !intern->w) + return; + w = intern->w; + peak_web_listen(w, 0); + free(w); + intern->w = NULL; } -void -peak_quit(void) +static uint32_t * +peak_platform_window_buffer(PeakWindowInternal *intern, size_t *width, size_t *height) { - peak_platform_window_close(); + 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; } -void -peak_extensions(void) +static void +peak_platform_window_present(PeakWindowInternal *intern) { - // TODO + 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); } -bool -peak_poll_events(PeakEvent *ev) +static bool +peak_platform_epoll(PeakWindowInternal *intern, PeakEvent *ev) { - return peak_platform_epoll(ev); + struct peak_web_win *w = intern ? intern->w : NULL; + return w ? peak_q_pop(&w->q, ev) : 0; } +/* --- End of p_emscripten.c --- */ +#endif -void -peak_blit(int offset_x, int offset_y, const uint32_t *rgba, size_t width, size_t height) -{ - for (size_t y = 0; y < height; ++y) { - int target_y = offset_y + (int)y; - if (target_y < peak_clip_y || target_y >= (int)(peak_clip_y + peak_clip_height) || - target_y < 0 || target_y >= (int)peak_window_height) continue; - - int start_x = offset_x < peak_clip_x ? peak_clip_x : offset_x; - int end_x = (offset_x + (int)width) > (int)(peak_clip_x + peak_clip_width) - ? (int)(peak_clip_x + peak_clip_width) - : (offset_x + (int)width); +/* We define PeakWindow only when we know the type of peak_window_internal_t */ +struct PeakWindow { + PeakWindowInternal internal; + int (*tick)(struct PeakWindow *win, void *userdata); + void *userdata; + uint32_t *buffer; + uint32_t width; + uint32_t height; + uint32_t bufsize; + int running; +}; - if (start_x >= end_x) continue; - size_t src_offset_x = (size_t)(start_x - offset_x); - size_t copy_pixels = (size_t)(end_x - start_x); +/* --- Start of peak.c --- */ +static uint32_t * +peak_window_sync(PeakWindow *win, size_t *width, size_t *height) +{ + size_t w = 0, h = 0; + win->buffer = peak_platform_window_buffer(&win->internal, &w, &h); + win->width = (uint32_t)w; + win->height = (uint32_t)h; + win->bufsize = win->width * win->height; + if (width) *width = w; + if (height) *height = h; + return win->buffer; +} - memcpy(&peak_window_buffer[target_y * peak_window_width + start_x], &rgba[y * width + src_offset_x], copy_pixels * sizeof(uint32_t)); - } +int +peak_init(void) +{ + return peak_platform_init(); } void -peak_clip(int x, int y, size_t w, size_t h) +peak_quit(void) { - peak_clip_x = x; - peak_clip_y = y; - peak_clip_width = w; - peak_clip_height = h; + peak_platform_quit(); } -void -peak_clip_reset(void) +PeakWindow +peak_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags) { - peak_clip_x = 0; - peak_clip_y = 0; - peak_clip_width = peak_window_width; - peak_clip_height = peak_window_height; + PeakWindow win = {0}; + if (!name || !name[0]) name = "Peak"; + if (!width) width = 800; + if (!height) height = 600; + win.internal = peak_platform_window_open(name, width, height, flags); + if (!peak_window_sync(&win, NULL, NULL)) return win; + win.running = 1; + return win; } void -peak_draw_rectangle(int x, int y, size_t w, size_t h, uint32_t color) -{ - int start_x = (x < peak_clip_x) ? peak_clip_x : x; - int start_y = (y < peak_clip_y) ? peak_clip_y : y; - int end_x = (x + (int)w > (int)(peak_clip_x + peak_clip_width)) ? (int)(peak_clip_x + peak_clip_width) : x + (int)w; - int end_y = (y + (int)h > (int)(peak_clip_y + peak_clip_height)) ? (int)(peak_clip_y + peak_clip_height) : y + (int)h; - - for (int py = start_y; py < end_y; ++py) { - if (py < 0 || py >= (int)peak_window_height) continue; - for (int px = start_x; px < end_x; ++px) { - if (px < 0 || px >= (int)peak_window_width) continue; - peak_window_buffer[py * peak_window_width + px] = color; - } - } +peak_window_close(PeakWindow *win) +{ + win->running = 0; + peak_platform_window_close(&win->internal); + win->buffer = NULL; + win->width = 0; + win->height = 0; + win->bufsize = 0; } -void -peak_draw_rectangle_gradient(int x, int y, size_t w, size_t h, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) -{ - for (size_t ry = 0; ry < h; ++ry) { - float ty = (h > 1) ? (float)ry / (float)(h - 1) : 0.0f; - uint32_t left_color = peak__lerp_color(c0, c3, ty); - uint32_t right_color = peak__lerp_color(c1, c2, ty); - - for (size_t rx = 0; rx < w; ++rx) { - float tx = (w > 1) ? (float)rx / (float)(w - 1) : 0.0f; - uint32_t final_color = peak__lerp_color(left_color, right_color, tx); - peak__put_pixel(x + (int)rx, y + (int)ry, final_color); - } - } +int +peak_window_epoll(PeakWindow *win, PeakEvent *ev) +{ + int got = peak_platform_epoll(&win->internal, ev); + if (got && ev->type == PEAK_EVENT_WINDOW_RESIZE) + peak_window_sync(win, NULL, NULL); + return got; } -void -peak_draw_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) -{ - int min_x = (x0 < x1) ? ((x0 < x2) ? x0 : x2) : ((x1 < x2) ? x1 : x2); - int max_x = (x0 > x1) ? ((x0 > x2) ? x0 : x2) : ((x1 > x2) ? x1 : x2); - int min_y = (y0 < y1) ? ((y0 < y2) ? y0 : y2) : ((y1 < y2) ? y1 : y2); - int max_y = (y0 > y1) ? ((y0 > y2) ? y0 : y2) : ((y1 > y2) ? y1 : y2); - - float area = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x2, (float)y2); - if (area == 0.0f) return; - - for (int y = min_y; y <= max_y; ++y) { - for (int x = min_x; x <= max_x; ++x) { - float w0 = peak__edge_cross((float)x1, (float)y1, (float)x2, (float)y2, (float)x, (float)y); - float w1 = peak__edge_cross((float)x2, (float)y2, (float)x0, (float)y0, (float)x, (float)y); - float w2 = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x, (float)y); - - if ((w0 >= 0 && w1 >= 0 && w2 >= 0) || (w0 <= 0 && w1 <= 0 && w2 <= 0)) { - peak__put_pixel(x, y, color); - } - } - } +uint32_t * +peak_window_backbuffer(PeakWindow *win, size_t *width, size_t *height) +{ + return peak_window_sync(win, width, height); } void -peak_draw_triangle_gradient(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t c0, uint32_t c1, uint32_t c2) -{ - int min_x = (x0 < x1) ? ((x0 < x2) ? x0 : x2) : ((x1 < x2) ? x1 : x2); - int max_x = (x0 > x1) ? ((x0 > x2) ? x0 : x2) : ((x1 > x2) ? x1 : x2); - int min_y = (y0 < y1) ? ((y0 < y2) ? y0 : y2) : ((y1 < y2) ? y1 : y2); - int max_y = (y0 > y1) ? ((y0 > y2) ? y0 : y2) : ((y1 > y2) ? y1 : y2); - - float area = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x2, (float)y2); - if (area == 0.0f) return; - - for (int y = min_y; y <= max_y; ++y) { - for (int x = min_x; x <= max_x; ++x) { - float w0 = peak__edge_cross((float)x1, (float)y1, (float)x2, (float)y2, (float)x, (float)y) / area; - float w1 = peak__edge_cross((float)x2, (float)y2, (float)x0, (float)y0, (float)x, (float)y) / area; - float w2 = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x, (float)y) / area; - - if (w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f) { - uint32_t col_a = peak__lerp_color(c0, c1, w1 / (w0 + w1 + 1e-6f)); - uint32_t final_color = peak__lerp_color(col_a, c2, w2); - peak__put_pixel(x, y, final_color); - } - } - } +peak_window_clear(PeakWindow *win, float r, float g, float b, float a) +{ + uint32_t c, i; + if (!win || !win->buffer) return; +#if defined(PEAK_WEB) + /* ImageData RGBA bytes = LE 0xAABBGGRR */ + c = (uint32_t)(r * 255.f) + | ((uint32_t)(g * 255.f) << 8) + | ((uint32_t)(b * 255.f) << 16) + | ((uint32_t)(a * 255.f) << 24); +#else + c = ((uint32_t)(a * 255.f) << 24) + | ((uint32_t)(r * 255.f) << 16) + | ((uint32_t)(g * 255.f) << 8) + | (uint32_t)(b * 255.f); +#endif + for (i = 0; i < win->bufsize; i++) + win->buffer[i] = c; } void -peak_draw_line(int x0, int y0, int x1, int y1, size_t width, uint32_t color) -{ - int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; - int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; - int err = dx + dy, e2; - - int half_w = (int)width / 2; - - while (1) { - /* Draw square footprint for line thickness */ - for (int ox = -half_w; ox <= half_w; ++ox) { - for (int oy = -half_w; oy <= half_w; ++oy) { - peak__put_pixel(x0 + ox, y0 + oy, color); - } - } - if (x0 == x1 && y0 == y1) break; - e2 = 2 * err; - if (e2 >= dy) { err += dy; x0 += sx; } - if (e2 <= dx) { err += dx; y0 += sy; } - } +peak_window_present(PeakWindow *win) +{ + if (win) peak_platform_window_present(&win->internal); } -void -peak_draw_line_gradient(int x0, int y0, int x1, int y1, size_t width, uint32_t c0, uint32_t c1) -{ - float total_dist = sqrtf((float)((x1 - x0)*(x1 - x0) + (y1 - y0)*(y1 - y0))); - int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; - int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; - int err = dx + dy, e2; - - int orig_x0 = x0, orig_y0 = y0; - int half_w = (int)width / 2; - - while (1) { - float current_dist = sqrtf((float)((x0 - orig_x0)*(x0 - orig_x0) + (y0 - orig_y0)*(y0 - orig_y0))); - float t = (total_dist > 0.0f) ? (current_dist / total_dist) : 0.0f; - uint32_t color = peak__lerp_color(c0, c1, t); - - for (int ox = -half_w; ox <= half_w; ++ox) { - for (int oy = -half_w; oy <= half_w; ++oy) { - peak__put_pixel(x0 + ox, y0 + oy, color); - } - } - - if (x0 == x1 && y0 == y1) break; - e2 = 2 * err; - if (e2 >= dy) { err += dy; x0 += sx; } - if (e2 <= dx) { err += dx; y0 += sy; } +#ifdef PEAK_WEB +#include +static void +peak_internal_web_step(void *arg) +{ + PeakWindow *win = arg; + if (!win->tick(win, win->userdata) || !win->running) { + win->running = 0; + emscripten_cancel_main_loop(); } } +#endif +void +peak_window_run(PeakWindow *win, int (*peak_tick)(PeakWindow *win, void *userdata), void *userdata) +{ + assert(win && "peak_window_run needs a window"); + assert(peak_tick && "peak_window_run needs tick callback"); + win->tick = peak_tick; + win->userdata = userdata; + win->running = 1; +#ifdef PEAK_WEB + emscripten_set_main_loop_arg(peak_internal_web_step, win, 0, 1); +#else + while (win->running && win->tick(win, win->userdata)); +#endif +} /* --- End of peak.c --- */ #endif // PEAK_IMPLEMENTATION diff --git a/Peak/src/p_emscripten.c b/Peak/src/p_emscripten.c index dc558f5..f79f575 100644 --- a/Peak/src/p_emscripten.c +++ b/Peak/src/p_emscripten.c @@ -1,38 +1,188 @@ +#include +#include #include #include -#include +#include -#define PEAK_CANVAS "#PeakCanvas" +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(); +}); -static PeakKeyCode map_key(const char *code); -static PeakKeyMod map_mod(const EmscriptenKeyboardEvent *keyEvent); -static EM_BOOL key_callback(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData); -static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); -static EM_BOOL resize_callback(int eventType, const EmscriptenUiEvent *uiEvent, void *userData); +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); +}); -void -peak_platform_window_open() +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) { - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_TRUE, key_callback); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_TRUE, key_callback); - emscripten_set_mousedown_callback(PEAK_CANVAS, NULL, EM_TRUE, mouse_callback); - emscripten_set_mouseup_callback(PEAK_CANVAS, NULL, EM_TRUE, mouse_callback); - emscripten_set_mousemove_callback(PEAK_CANVAS, NULL, EM_TRUE, mouse_callback); - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_TRUE, resize_callback); + struct peak_web_win *w; + if (!intern || !intern->w) + return; + w = intern->w; + peak_web_listen(w, 0); + free(w); + intern->w = NULL; } -void -peak_platform_window_close() +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; } -uint32_t* -peak_platform_window_buffer(size_t *width, size_t *height) +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); } -bool -peak_platform_epoll(PeakEvent *ev) +static bool +peak_platform_epoll(PeakWindowInternal *intern, PeakEvent *ev) { - return 0; + struct peak_web_win *w = intern ? intern->w : NULL; + return w ? peak_q_pop(&w->q, ev) : 0; } diff --git a/Peak/src/p_linux.c b/Peak/src/p_linux.c index c3d70bf..0dc5e2f 100644 --- a/Peak/src/p_linux.c +++ b/Peak/src/p_linux.c @@ -1,295 +1,296 @@ -#include +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif + #include -#include #include +#include #include -#include +#include +#include #include -#include - -#define PEAK_X11_LINUX "/usr/lib/libX11.so.6" -#define PEAK_LINUX_DEFAULT_WIDTH 800 -#define PEAK_LINUX_DEFAULT_HEIGHT 600 - -typedef Display* (*XOpenDisplay_t)(const char*); -typedef int (*XCloseDisplay_t)(Display*); -typedef Window (*XCreateSimpleWindow_t)(Display*, Window, int, int, unsigned int, unsigned int, unsigned int, unsigned long, unsigned long); -typedef int (*XStoreName_t)(Display*, Window, const char*); -typedef Atom (*XInternAtom_t)(Display*, const char*, Bool); -typedef Status (*XSetWMProtocols_t)(Display*, Window, Atom*, int); -typedef int (*XSelectInput_t)(Display*, Window, long); -typedef GC (*XCreateGC_t)(Display*, Window, unsigned long, XGCValues*); -typedef XImage* (*XCreateImage_t)(Display*, Visual*, unsigned int, int, int, char*, unsigned int, unsigned int, int, int); -typedef int (*XMapRaised_t)(Display*, Window); -typedef int (*XFlush_t)(Display*); -typedef int (*XFreeGC_t)(Display*, GC); -typedef int (*XDestroyWindow_t)(Display*, Window); -typedef int (*XPending_t)(Display*); -typedef int (*XNextEvent_t)(Display*, XEvent*); -typedef KeySym (*XLookupKeysym_t)(XKeyEvent*, int); -typedef int (*XPutImage_t)(Display*, Drawable, GC, XImage*, int, int, int, int, unsigned int, unsigned int); -typedef int (*XDefaultScreen_t)(Display*); -typedef Window (*XRootWindow_t)(Display*, int); -typedef unsigned long (*XBlackPixel_t)(Display*, int); -typedef Visual* (*XDefaultVisual_t)(Display*, int); -typedef int (*XDefaultDepth_t)(Display*, int); +#define PEAK_X11_LINUX "libX11.so.6" + +#define PEAK_X11_API(X) \ + X(XOpenDisplay, Display *, (const char *)) \ + X(XCloseDisplay, int, (Display *)) \ + X(XCreateSimpleWindow, Window, (Display *, Window, int, int, unsigned int, unsigned int, unsigned int, unsigned long, unsigned long)) \ + X(XStoreName, int, (Display *, Window, const char *)) \ + X(XInternAtom, Atom, (Display *, const char *, Bool)) \ + X(XSetWMProtocols, Status, (Display *, Window, Atom *, int)) \ + X(XSelectInput, int, (Display *, Window, long)) \ + X(XCreateGC, GC, (Display *, Window, unsigned long, XGCValues *)) \ + X(XCreateImage, XImage *, (Display *, Visual *, unsigned int, int, int, char *, unsigned int, unsigned int, int, int)) \ + X(XMapRaised, int, (Display *, Window)) \ + X(XFlush, int, (Display *)) \ + X(XFreeGC, int, (Display *, GC)) \ + X(XDestroyWindow, int, (Display *, Window)) \ + X(XCheckIfEvent, Bool, (Display *, XEvent *, Bool (*)(Display *, XEvent *, XPointer), XPointer)) \ + X(XLookupKeysym, KeySym, (XKeyEvent *, int)) \ + X(XPutImage, int, (Display *, Drawable, GC, XImage *, int, int, int, int, unsigned int, unsigned int)) typedef struct { - XOpenDisplay_t XOpenDisplay; - XCloseDisplay_t XCloseDisplay; - XCreateSimpleWindow_t XCreateSimpleWindow; - XStoreName_t XStoreName; - XInternAtom_t XInternAtom; - XSetWMProtocols_t XSetWMProtocols; - XSelectInput_t XSelectInput; - XCreateGC_t XCreateGC; - XCreateImage_t XCreateImage; - XMapRaised_t XMapRaised; - XFlush_t XFlush; - XFreeGC_t XFreeGC; - XDestroyWindow_t XDestroyWindow; - XPending_t XPending; - XNextEvent_t XNextEvent; - XLookupKeysym_t XLookupKeysym; - XPutImage_t XPutImage; - XDefaultScreen_t XDefaultScreen; - XRootWindow_t XRootWindow; - XBlackPixel_t XBlackPixel; - XDefaultVisual_t XDefaultVisual; - XDefaultDepth_t XDefaultDepth; +#define X(name, ret, args) ret (*name) args; + PEAK_X11_API(X) +#undef X } PeakX11Api; -static PeakX11Api peak_x11; - -static int peak_load_x11(void *handle) { - peak_x11.XOpenDisplay = (XOpenDisplay_t)dlsym(handle, "XOpenDisplay"); - peak_x11.XCloseDisplay = (XCloseDisplay_t)dlsym(handle, "XCloseDisplay"); - peak_x11.XCreateSimpleWindow = (XCreateSimpleWindow_t)dlsym(handle, "XCreateSimpleWindow"); - peak_x11.XStoreName = (XStoreName_t)dlsym(handle, "XStoreName"); - peak_x11.XInternAtom = (XInternAtom_t)dlsym(handle, "XInternAtom"); - peak_x11.XSetWMProtocols = (XSetWMProtocols_t)dlsym(handle, "XSetWMProtocols"); - peak_x11.XSelectInput = (XSelectInput_t)dlsym(handle, "XSelectInput"); - peak_x11.XCreateGC = (XCreateGC_t)dlsym(handle, "XCreateGC"); - peak_x11.XCreateImage = (XCreateImage_t)dlsym(handle, "XCreateImage"); - peak_x11.XMapRaised = (XMapRaised_t)dlsym(handle, "XMapRaised"); - peak_x11.XFlush = (XFlush_t)dlsym(handle, "XFlush"); - peak_x11.XFreeGC = (XFreeGC_t)dlsym(handle, "XFreeGC"); - peak_x11.XDestroyWindow = (XDestroyWindow_t)dlsym(handle, "XDestroyWindow"); - peak_x11.XPending = (XPending_t)dlsym(handle, "XPending"); - peak_x11.XNextEvent = (XNextEvent_t)dlsym(handle, "XNextEvent"); - peak_x11.XLookupKeysym = (XLookupKeysym_t)dlsym(handle, "XLookupKeysym"); - peak_x11.XPutImage = (XPutImage_t)dlsym(handle, "XPutImage"); - peak_x11.XDefaultScreen = (XDefaultScreen_t)dlsym(handle, "XDefaultScreen"); - peak_x11.XRootWindow = (XRootWindow_t)dlsym(handle, "XRootWindow"); - peak_x11.XBlackPixel = (XBlackPixel_t)dlsym(handle, "XBlackPixel"); - peak_x11.XDefaultVisual = (XDefaultVisual_t)dlsym(handle, "XDefaultVisual"); - peak_x11.XDefaultDepth = (XDefaultDepth_t)dlsym(handle, "XDefaultDepth"); - - if (!peak_x11.XOpenDisplay || !peak_x11.XCloseDisplay || !peak_x11.XCreateSimpleWindow || - !peak_x11.XStoreName || !peak_x11.XInternAtom || !peak_x11.XSetWMProtocols || - !peak_x11.XSelectInput || !peak_x11.XCreateGC || !peak_x11.XCreateImage || - !peak_x11.XMapRaised || !peak_x11.XFlush || - !peak_x11.XFreeGC || !peak_x11.XDestroyWindow || !peak_x11.XPending || - !peak_x11.XNextEvent || !peak_x11.XLookupKeysym || !peak_x11.XPutImage || - !peak_x11.XDefaultScreen || !peak_x11.XRootWindow || !peak_x11.XBlackPixel || - !peak_x11.XDefaultVisual || !peak_x11.XDefaultDepth) { - return 0; - } - return 1; -} - - typedef struct { - Display *display; - Atom wm_delete_window; - bool initialized; + Display *display; + Atom wm_delete_window; } PeakLinux; -typedef struct { - Window window; - GC gfx_ctx; /* GRAPHICS CONTEXT, not garbage collector lol */ -} PeakLinuxWindow; +struct peak_window_internal_t { + Window window; + GC gfx_ctx; + XImage *ximage; + uint32_t *buffer; + uint32_t width; + uint32_t height; +}; -static PeakLinux peak_linux = {0}; -static PeakLinuxWindow peak_linux_window = {0}; -static XImage *peak_linux_ximage = NULL; -static uint32_t *peak_linux_buffer = NULL; -static size_t peak_linux_width = PEAK_LINUX_DEFAULT_WIDTH; -static size_t peak_linux_height = PEAK_LINUX_DEFAULT_HEIGHT; +static PeakLinux peak_linux; +static PeakX11Api peak_x11; + +static int +peak_internal_x11_load(void *handle) +{ +#define X(name, ret, args) peak_x11.name = (ret (*) args)dlsym(handle, #name); + PEAK_X11_API(X) +#undef X +#define X(name, ret, args) || !peak_x11.name + if (0 PEAK_X11_API(X)) + return 0; +#undef X + return 1; +} static PeakKeyCode -peak__x11_map_key(KeySym sym) +peak_internal_x11_key_map(KeySym sym) { - if (sym >= XK_a && sym <= XK_z) return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_a)); - if (sym >= XK_A && sym <= XK_Z) return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_A)); - switch (sym) { - case XK_Up: return PEAK_KEY_UP; - case XK_Down: return PEAK_KEY_DOWN; - case XK_Left: return PEAK_KEY_LEFT; - case XK_Right: return PEAK_KEY_RIGHT; - case XK_space: return PEAK_KEY_SPACE; - case XK_Escape: return PEAK_KEY_ESCAPE; - case XK_Return: return PEAK_KEY_ENTER; - default: return PEAK_KEY_UNKNOWN; - } + if (sym >= XK_a && sym <= XK_z) + return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_a)); + if (sym >= XK_A && sym <= XK_Z) + return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_A)); + switch (sym) { + case XK_Up: return PEAK_KEY_UP; + case XK_Down: return PEAK_KEY_DOWN; + case XK_Left: return PEAK_KEY_LEFT; + case XK_Right: return PEAK_KEY_RIGHT; + case XK_space: return PEAK_KEY_SPACE; + case XK_Escape: return PEAK_KEY_ESCAPE; + case XK_Return: return PEAK_KEY_ENTER; + default: return PEAK_KEY_UNKNOWN; + } } static PeakKeyMod -peak__x11_map_mod(unsigned int state) +peak_internal_x11_mod_map(unsigned int state) { - if (state & ControlMask) return PEAK_KEYMOD_CTRL; - if (state & Mod1Mask) return PEAK_KEYMOD_ALT; - if (state & ShiftMask) return PEAK_KEYMOD_SHIFT; - if (state & LockMask) return PEAK_KEYMOD_CAPS; - return (PeakKeyMod)0; + if (state & ControlMask) return PEAK_KEYMOD_CTRL; + if (state & Mod1Mask) return PEAK_KEYMOD_ALT; + if (state & ShiftMask) return PEAK_KEYMOD_SHIFT; + if (state & LockMask) return PEAK_KEYMOD_CAPS; + return (PeakKeyMod)0; } -void -peak_platform_window_open() +static Bool +peak_internal_x11_window_match(Display *dpy, XEvent *ev, XPointer arg) { - void *x11_handle = dlopen(PEAK_X11_LINUX, RTLD_LOCAL | RTLD_NOW); - assert(x11_handle && "Failed to load X11 library. What system are you fucking using and abusing?"); - - int success = peak_load_x11(x11_handle); - assert(success && "Failed to open X11 display"); - - peak_linux.display = peak_x11.XOpenDisplay(NULL); - assert(peak_linux.display && "Failed to open X11 display"); - - /* Who design this API??? */ - int screen = DefaultScreen(peak_linux.display); - peak_linux_window.window = peak_x11.XCreateSimpleWindow(peak_linux.display, RootWindow(peak_linux.display, screen), 0, 0, peak_linux_width, peak_linux_height, 0, BlackPixel(peak_linux.display, screen), BlackPixel(peak_linux.display, screen)); - peak_x11.XStoreName(peak_linux.display, peak_linux_window.window, "Peak"); - peak_linux.wm_delete_window = peak_x11.XInternAtom(peak_linux.display, "WM_DELETE_WINDOW", False); - peak_x11.XSetWMProtocols(peak_linux.display, peak_linux_window.window, &peak_linux.wm_delete_window, 1); - peak_x11.XSelectInput(peak_linux.display, peak_linux_window.window, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask); - peak_linux_window.gfx_ctx = peak_x11.XCreateGC(peak_linux.display, peak_linux_window.window, 0, NULL); - peak_linux_buffer = (uint32_t *)calloc(peak_linux_width * peak_linux_height, sizeof(uint32_t)); - assert(peak_linux_buffer && "Failed to allocate framebuffer"); - - /* Wrap the buffer in an XImage for XPutImage blitting */ - Visual *visual = DefaultVisual(peak_linux.display, screen); - int depth = DefaultDepth(peak_linux.display, screen); - - peak_linux_ximage = peak_x11.XCreateImage(peak_linux.display, visual, depth, ZPixmap, 0, (char *)peak_linux_buffer, peak_linux_width, peak_linux_height, 32, 0); - assert(peak_linux_ximage && "Failed to create XImage"); - - peak_x11.XMapRaised(peak_linux.display, peak_linux_window.window); - peak_x11.XFlush(peak_linux.display); - - peak_linux.initialized = true; + (void)dpy; + return ev->xany.window == *(Window *)arg; } -void -peak_platform_window_close() +static int +peak_linux_buffer(PeakWindowInternal *w, uint32_t width, uint32_t height) { - if (!peak_linux.initialized) return; - - if (peak_linux_ximage) { - peak_linux_ximage->data = NULL; - XDestroyImage(peak_linux_ximage); - peak_linux_ximage = NULL; - } - - free(peak_linux_buffer); - peak_linux_buffer = NULL; - - peak_x11.XFreeGC(peak_linux.display, peak_linux_window.gfx_ctx); - peak_x11.XDestroyWindow(peak_linux.display, peak_linux_window.window); - peak_x11.XCloseDisplay(peak_linux.display); - - peak_linux.initialized = false; + int screen; + + if (w->ximage) { + w->ximage->data = NULL; + XDestroyImage(w->ximage); + w->ximage = NULL; + } + free(w->buffer); + w->buffer = calloc((size_t)width * height, sizeof *w->buffer); + if (!w->buffer) + return 0; + + w->width = width; + w->height = height; + screen = DefaultScreen(peak_linux.display); + w->ximage = peak_x11.XCreateImage(peak_linux.display, + DefaultVisual(peak_linux.display, screen), + DefaultDepth(peak_linux.display, screen), + ZPixmap, 0, (char *)w->buffer, width, height, 32, 0); + if (!w->ximage) { + free(w->buffer); + w->buffer = NULL; + return 0; + } + return 1; } -uint32_t * -peak_platform_window_buffer(size_t *width, size_t *height) +static int +peak_platform_init(void) { - *width = peak_linux_width; - *height = peak_linux_height; - return peak_linux_buffer; + void *handle; + + if (!peak_x11.XOpenDisplay) { + if (!(handle = dlopen(PEAK_X11_LINUX, RTLD_LOCAL | RTLD_NOW))) { + fputs("Failed to load X11 library. What system are you fucking using and abusing?", stderr); + return 0; + } + if (!peak_internal_x11_load(handle)) { + fputs("Failed to load X11 symbols", stderr); + return 0; + } + } + if (!peak_linux.display) { + if (!(peak_linux.display = peak_x11.XOpenDisplay(NULL))) { + fputs("Failed to open X11 display", stderr); + return 0; + } + peak_linux.wm_delete_window = peak_x11.XInternAtom(peak_linux.display, "WM_DELETE_WINDOW", False); + } + return 1; } -bool -peak_platform_epoll(PeakEvent *ev) +static void +peak_platform_quit(void) { - if (!peak_linux.initialized) return false; - - Display *dpy = peak_linux.display; - - while (peak_x11.XPending(dpy) > 0) { - XEvent xev; - peak_x11.XNextEvent(dpy, &xev); - - switch (xev.type) { - - case ClientMessage: - if ((Atom)xev.xclient.data.l[0] == peak_linux.wm_delete_window) { - ev->type = PEAK_EVENT_WINDOW_CLOSE; - printf("CLLOOOOOOOSEEEE"); - return true; - } - continue; - - case KeyPress: - case KeyRelease: { - KeySym sym = peak_x11.XLookupKeysym(&xev.xkey, 0); - ev->type = (xev.type == KeyPress) ? PEAK_EVENT_KEY_DOWN : PEAK_EVENT_KEY_UP; - ev->key.key = peak__x11_map_key(sym); - ev->key.mod = peak__x11_map_mod(xev.xkey.state); - return true; - } - - case ButtonPress: - case ButtonRelease: { - ev->type = PEAK_EVENT_POINTER; - ev->pointer.state = (xev.type == ButtonPress) ? PEAK_POINTER_PRESSED : PEAK_POINTER_RELEASED; - ev->pointer.x = (float)xev.xbutton.x; - ev->pointer.y = (float)xev.xbutton.y; - switch (xev.xbutton.button) { - case Button1: ev->pointer.type = PEAK_POINTER_LEFT; break; - case Button2: ev->pointer.type = PEAK_POINTER_MIDDLE; break; - case Button3: ev->pointer.type = PEAK_POINTER_RIGHT; break; - default: ev->pointer.type = PEAK_POINTER_LEFT; break; - } - return true; - } + if (!peak_linux.display) + return; + peak_x11.XCloseDisplay(peak_linux.display); + peak_linux.display = 0; +} - case MotionNotify: - ev->type = PEAK_EVENT_POINTER; - ev->pointer.state = PEAK_POINTER_MOVED; - ev->pointer.x = (float)xev.xmotion.x; - ev->pointer.y = (float)xev.xmotion.y; - if (xev.xmotion.state & Button3Mask) - ev->pointer.type = PEAK_POINTER_RIGHT; - else if (xev.xmotion.state & Button2Mask) - ev->pointer.type = PEAK_POINTER_MIDDLE; - else - ev->pointer.type = PEAK_POINTER_LEFT; - return true; +static PeakWindowInternal +peak_platform_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags) +{ + PeakWindowInternal w = {0}; + int screen; + + (void)flags; + if (!peak_linux.display && !peak_platform_init()) + return w; + + screen = DefaultScreen(peak_linux.display); + w.window = peak_x11.XCreateSimpleWindow(peak_linux.display, + RootWindow(peak_linux.display, screen), 0, 0, width, height, 0, + BlackPixel(peak_linux.display, screen), BlackPixel(peak_linux.display, screen)); + peak_x11.XStoreName(peak_linux.display, w.window, name); + peak_x11.XSetWMProtocols(peak_linux.display, w.window, &peak_linux.wm_delete_window, 1); + peak_x11.XSelectInput(peak_linux.display, w.window, + KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | StructureNotifyMask); + w.gfx_ctx = peak_x11.XCreateGC(peak_linux.display, w.window, 0, NULL); + + if (!peak_linux_buffer(&w, width, height)) { + peak_x11.XFreeGC(peak_linux.display, w.gfx_ctx); + peak_x11.XDestroyWindow(peak_linux.display, w.window); + return (PeakWindowInternal){0}; + } + + peak_x11.XMapRaised(peak_linux.display, w.window); + peak_x11.XFlush(peak_linux.display); + return w; +} - case ConfigureNotify: - if ((size_t)xev.xconfigure.width != peak_linux_width || - (size_t)xev.xconfigure.height != peak_linux_height) { - ev->type = PEAK_EVENT_WINDOW_RESIZE; - ev->resize.width = (uint32_t)xev.xconfigure.width; - ev->resize.height = (uint32_t)xev.xconfigure.height; - return true; - } - continue; +static void +peak_platform_window_close(PeakWindowInternal *w) +{ + if (!w || !w->window || !peak_linux.display) + return; + if (w->ximage) { + w->ximage->data = NULL; + XDestroyImage(w->ximage); + } + free(w->buffer); + if (w->gfx_ctx) + peak_x11.XFreeGC(peak_linux.display, w->gfx_ctx); + peak_x11.XDestroyWindow(peak_linux.display, w->window); + *w = (PeakWindowInternal){0}; +} - case Expose: - if (xev.xexpose.count == 0) { - peak_x11.XPutImage(dpy, peak_linux_window.window, peak_linux_window.gfx_ctx, peak_linux_ximage, 0, 0, 0, 0, peak_linux_width, peak_linux_height); - } - continue; +static uint32_t * +peak_platform_window_buffer(PeakWindowInternal *w, size_t *width, size_t *height) +{ + if (!w || !w->window) { + *width = 0; + *height = 0; + return NULL; + } + *width = w->width; + *height = w->height; + return w->buffer; +} - default: - continue; - } - } +static void +peak_platform_window_present(PeakWindowInternal *w) +{ + if (!w || !w->ximage || !peak_linux.display) + return; + peak_x11.XPutImage(peak_linux.display, w->window, w->gfx_ctx, w->ximage, + 0, 0, 0, 0, w->width, w->height); + peak_x11.XFlush(peak_linux.display); +} - peak_x11.XPutImage(dpy, peak_linux_window.window, peak_linux_window.gfx_ctx, peak_linux_ximage, 0, 0, 0, 0, peak_linux_width, peak_linux_height); - peak_x11.XFlush(dpy); - return false; +static bool +peak_platform_epoll(PeakWindowInternal *w, PeakEvent *ev) +{ + XEvent xev; + + if (!w || !w->window || !peak_linux.display) + return 0; + + while (peak_x11.XCheckIfEvent(peak_linux.display, &xev, peak_internal_x11_window_match, (XPointer)&w->window)) { + switch (xev.type) { + case ClientMessage: + if ((Atom)xev.xclient.data.l[0] == peak_linux.wm_delete_window) { + ev->type = PEAK_EVENT_WINDOW_CLOSE; + return 1; + } + continue; + case KeyPress: + case KeyRelease: + ev->type = (xev.type == KeyPress) ? PEAK_EVENT_KEY_DOWN : PEAK_EVENT_KEY_UP; + ev->key.key = peak_internal_x11_key_map(peak_x11.XLookupKeysym(&xev.xkey, 0)); + ev->key.mod = peak_internal_x11_mod_map(xev.xkey.state); + return 1; + case ButtonPress: + case ButtonRelease: + ev->type = PEAK_EVENT_POINTER; + ev->pointer.state = (xev.type == ButtonPress) ? PEAK_POINTER_PRESSED : PEAK_POINTER_RELEASED; + ev->pointer.x = (float)xev.xbutton.x; + ev->pointer.y = (float)xev.xbutton.y; + ev->pointer.type = (xev.xbutton.button == Button2) ? PEAK_POINTER_MIDDLE : + (xev.xbutton.button == Button3) ? PEAK_POINTER_RIGHT : PEAK_POINTER_LEFT; + return 1; + case MotionNotify: + ev->type = PEAK_EVENT_POINTER; + ev->pointer.state = PEAK_POINTER_MOVED; + ev->pointer.x = (float)xev.xmotion.x; + ev->pointer.y = (float)xev.xmotion.y; + ev->pointer.type = (xev.xmotion.state & Button3Mask) ? PEAK_POINTER_RIGHT : + (xev.xmotion.state & Button2Mask) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + return 1; + case ConfigureNotify: { + uint32_t width = (uint32_t)xev.xconfigure.width; + uint32_t height = (uint32_t)xev.xconfigure.height; + if (width == w->width && height == w->height) + continue; + if (!peak_linux_buffer(w, width, height)) + continue; + ev->type = PEAK_EVENT_WINDOW_RESIZE; + ev->resize.width = w->width; + ev->resize.height = w->height; + return 1; + } + default: + continue; + } + } + return 0; } diff --git a/Peak/src/p_win32.c b/Peak/src/p_win32.c index 16fab7f..c1a289b 100644 --- a/Peak/src/p_win32.c +++ b/Peak/src/p_win32.c @@ -1,21 +1,404 @@ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + #include +#include +#include +#include + +#define PEAK_WIN32_USER32 "user32.dll" +#define PEAK_WIN32_GDI32 "gdi32.dll" +#define PEAK_WIN32_CLASS "PeakWindow" +#define PEAK_WIN32_PROP "Peak" + +#define PEAK_USER32_API(X) \ + X(RegisterClassExA, ATOM, WINAPI, (const WNDCLASSEXA *)) \ + X(UnregisterClassA, BOOL, WINAPI, (LPCSTR, HINSTANCE)) \ + X(CreateWindowExA, HWND, WINAPI, (DWORD, LPCSTR, LPCSTR, DWORD, int, int, int, int, HWND, HMENU, HINSTANCE, LPVOID)) \ + X(DestroyWindow, BOOL, WINAPI, (HWND)) \ + X(ShowWindow, BOOL, WINAPI, (HWND, int)) \ + X(GetDC, HDC, WINAPI, (HWND)) \ + X(ReleaseDC, int, WINAPI, (HWND, HDC)) \ + X(PeekMessageA, BOOL, WINAPI, (LPMSG, HWND, UINT, UINT, UINT)) \ + X(TranslateMessage, BOOL, WINAPI, (const MSG *)) \ + X(DispatchMessageA, LRESULT, WINAPI, (const MSG *)) \ + X(DefWindowProcA, LRESULT, WINAPI, (HWND, UINT, WPARAM, LPARAM)) \ + X(SetPropA, BOOL, WINAPI, (HWND, LPCSTR, HANDLE)) \ + X(GetPropA, HANDLE, WINAPI, (HWND, LPCSTR)) \ + X(RemovePropA, HANDLE, WINAPI, (HWND, LPCSTR)) \ + X(BeginPaint, HDC, WINAPI, (HWND, LPPAINTSTRUCT)) \ + X(EndPaint, BOOL, WINAPI, (HWND, const PAINTSTRUCT *)) \ + X(AdjustWindowRectEx, BOOL, WINAPI, (LPRECT, DWORD, BOOL, DWORD)) \ + X(LoadCursorA, HCURSOR, WINAPI, (HINSTANCE, LPCSTR)) \ + X(GetKeyState, SHORT, WINAPI, (int)) + +#define PEAK_GDI32_API(X) \ + X(StretchDIBits, int, WINAPI, (HDC, int, int, int, int, int, int, int, int, const void *, const BITMAPINFO *, UINT, DWORD)) + +typedef struct { +#define X(name, ret, conv, args) ret (conv *name) args; + PEAK_USER32_API(X) +#undef X +} PeakUser32Api; + +typedef struct { +#define X(name, ret, conv, args) ret (conv *name) args; + PEAK_GDI32_API(X) +#undef X +} PeakGdi32Api; + +typedef struct { + HMODULE user32; + HMODULE gdi32; + int class_reg; +} PeakWin32; + +struct peak_win32_win { + HWND hwnd; + HDC hdc; + uint32_t *buffer; + uint32_t width; + uint32_t height; + PeakQ q; +}; + +struct peak_window_internal_t { + struct peak_win32_win *w; +}; + +static PeakWin32 peak_win32; +static PeakUser32Api peak_user32; +static PeakGdi32Api peak_gdi32; + +static int +peak_internal_user32_load(HMODULE handle) +{ +#define X(name, ret, conv, args) peak_user32.name = (ret (conv *) args)(void *)GetProcAddress(handle, #name); + PEAK_USER32_API(X) +#undef X +#define X(name, ret, conv, args) || !peak_user32.name + if (0 PEAK_USER32_API(X)) + return 0; +#undef X + return 1; +} -void -peak_platform_window_open() +static int +peak_internal_gdi32_load(HMODULE handle) { +#define X(name, ret, conv, args) peak_gdi32.name = (ret (conv *) args)(void *)GetProcAddress(handle, #name); + PEAK_GDI32_API(X) +#undef X +#define X(name, ret, conv, args) || !peak_gdi32.name + if (0 PEAK_GDI32_API(X)) + return 0; +#undef X + return 1; } -void -peak_platform_window_close() +static PeakKeyCode +peak_internal_win32_key_map(WPARAM vk) { + if (vk >= 'A' && vk <= 'Z') + return (PeakKeyCode)(PEAK_KEY_A + (int)(vk - 'A')); + switch (vk) { + case VK_UP: return PEAK_KEY_UP; + case VK_DOWN: return PEAK_KEY_DOWN; + case VK_LEFT: return PEAK_KEY_LEFT; + case VK_RIGHT: return PEAK_KEY_RIGHT; + case VK_SPACE: return PEAK_KEY_SPACE; + case VK_ESCAPE: return PEAK_KEY_ESCAPE; + case VK_RETURN: return PEAK_KEY_ENTER; + default: return PEAK_KEY_UNKNOWN; + } } -uint32_t* -peak_platform_window_buffer(size_t *width, size_t *height) +static PeakKeyMod +peak_internal_win32_mod_map(void) { + if (peak_user32.GetKeyState(VK_CONTROL) & 0x8000) return PEAK_KEYMOD_CTRL; + if (peak_user32.GetKeyState(VK_MENU) & 0x8000) return PEAK_KEYMOD_ALT; + if (peak_user32.GetKeyState(VK_SHIFT) & 0x8000) return PEAK_KEYMOD_SHIFT; + if (peak_user32.GetKeyState(VK_CAPITAL) & 1) return PEAK_KEYMOD_CAPS; + return (PeakKeyMod)0; } -bool -peak_platform_epoll(PeakEvent *ev) +static int +peak_win32_buffer(struct peak_win32_win *w, uint32_t width, uint32_t height) { + uint32_t *buffer; + + buffer = calloc((size_t)width * height, sizeof *buffer); + if (!buffer) + return 0; + free(w->buffer); + w->buffer = buffer; + w->width = width; + w->height = height; + return 1; +} + +static LRESULT CALLBACK +peak_internal_win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + struct peak_win32_win *w; + PeakEvent ev; + + if (msg == WM_NCCREATE) { + CREATESTRUCTA *cs = (CREATESTRUCTA *)lparam; + peak_user32.SetPropA(hwnd, PEAK_WIN32_PROP, cs->lpCreateParams); + } + + w = (struct peak_win32_win *)peak_user32.GetPropA(hwnd, PEAK_WIN32_PROP); + if (!w) + return peak_user32.DefWindowProcA(hwnd, msg, wparam, lparam); + + switch (msg) { + case WM_CLOSE: + memset(&ev, 0, sizeof ev); + ev.type = PEAK_EVENT_WINDOW_CLOSE; + peak_q_push(&w->q, ev); + return 0; + case WM_SIZE: { + uint32_t width, height; + if (wparam == SIZE_MINIMIZED) + return 0; + width = (uint32_t)LOWORD(lparam); + height = (uint32_t)HIWORD(lparam); + if (!width || !height || (width == w->width && height == w->height)) + return 0; + if (!peak_win32_buffer(w, width, height)) + return 0; + memset(&ev, 0, sizeof ev); + ev.type = PEAK_EVENT_WINDOW_RESIZE; + ev.resize.width = w->width; + ev.resize.height = w->height; + peak_q_push(&w->q, ev); + return 0; + } + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + memset(&ev, 0, sizeof ev); + ev.type = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) ? PEAK_EVENT_KEY_DOWN : PEAK_EVENT_KEY_UP; + ev.key.key = peak_internal_win32_key_map(wparam); + ev.key.mod = peak_internal_win32_mod_map(); + peak_q_push(&w->q, ev); + return 0; + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + memset(&ev, 0, sizeof ev); + ev.type = PEAK_EVENT_POINTER; + ev.pointer.x = (float)(short)LOWORD(lparam); + ev.pointer.y = (float)(short)HIWORD(lparam); + if (msg == WM_MOUSEMOVE) { + ev.pointer.state = PEAK_POINTER_MOVED; + ev.pointer.type = (wparam & MK_RBUTTON) ? PEAK_POINTER_RIGHT : + (wparam & MK_MBUTTON) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + } else if (msg == WM_LBUTTONDOWN || msg == WM_RBUTTONDOWN || msg == WM_MBUTTONDOWN) { + ev.pointer.state = PEAK_POINTER_PRESSED; + ev.pointer.type = (msg == WM_RBUTTONDOWN) ? PEAK_POINTER_RIGHT : + (msg == WM_MBUTTONDOWN) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + } else { + ev.pointer.state = PEAK_POINTER_RELEASED; + ev.pointer.type = (msg == WM_RBUTTONUP) ? PEAK_POINTER_RIGHT : + (msg == WM_MBUTTONUP) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + } + peak_q_push(&w->q, ev); + return 0; + case WM_PAINT: { + PAINTSTRUCT ps; + peak_user32.BeginPaint(hwnd, &ps); + peak_user32.EndPaint(hwnd, &ps); + return 0; + } + case WM_ERASEBKGND: + return 1; + case WM_DESTROY: + peak_user32.RemovePropA(hwnd, PEAK_WIN32_PROP); + return 0; + default: + return peak_user32.DefWindowProcA(hwnd, msg, wparam, lparam); + } +} + +static int +peak_platform_init(void) +{ + WNDCLASSEXA wc; + + if (!peak_user32.CreateWindowExA) { + if (!(peak_win32.user32 = LoadLibraryA(PEAK_WIN32_USER32))) { + fputs("Failed to load user32.dll. What system are you fucking using and abusing?", stderr); + return 0; + } + if (!peak_internal_user32_load(peak_win32.user32)) { + fputs("Failed to load user32 symbols", stderr); + return 0; + } + } + if (!peak_gdi32.StretchDIBits) { + if (!(peak_win32.gdi32 = LoadLibraryA(PEAK_WIN32_GDI32))) { + fputs("Failed to load gdi32.dll. What system are you fucking using and abusing?", stderr); + return 0; + } + if (!peak_internal_gdi32_load(peak_win32.gdi32)) { + fputs("Failed to load gdi32 symbols", stderr); + return 0; + } + } + if (!peak_win32.class_reg) { + memset(&wc, 0, sizeof wc); + wc.cbSize = sizeof wc; + wc.style = CS_OWNDC; + wc.lpfnWndProc = peak_internal_win32_wndproc; + wc.hInstance = GetModuleHandleA(NULL); + wc.hCursor = peak_user32.LoadCursorA(NULL, IDC_ARROW); + wc.lpszClassName = PEAK_WIN32_CLASS; + if (!peak_user32.RegisterClassExA(&wc) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) { + fputs("Failed to register window class", stderr); + return 0; + } + peak_win32.class_reg = 1; + } + return 1; +} + +static void +peak_platform_quit(void) +{ + if (!peak_win32.class_reg) + return; + peak_user32.UnregisterClassA(PEAK_WIN32_CLASS, GetModuleHandleA(NULL)); + peak_win32.class_reg = 0; +} + +static PeakWindowInternal +peak_platform_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags) +{ + PeakWindowInternal intern = {0}; + struct peak_win32_win *w; + DWORD style, ex; + RECT r; + HINSTANCE inst; + + (void)flags; + if (!peak_user32.CreateWindowExA && !peak_platform_init()) + return intern; + + w = calloc(1, sizeof *w); + if (!w) + return intern; + if (!peak_win32_buffer(w, width, height)) { + free(w); + return intern; + } + + ex = 0; + style = WS_OVERLAPPEDWINDOW; + r.left = 0; + r.top = 0; + r.right = (LONG)width; + r.bottom = (LONG)height; + peak_user32.AdjustWindowRectEx(&r, style, FALSE, ex); + inst = GetModuleHandleA(NULL); + w->hwnd = peak_user32.CreateWindowExA(ex, PEAK_WIN32_CLASS, name, style, + CW_USEDEFAULT, CW_USEDEFAULT, r.right - r.left, r.bottom - r.top, + NULL, NULL, inst, w); + if (!w->hwnd) { + free(w->buffer); + free(w); + return intern; + } + + w->hdc = peak_user32.GetDC(w->hwnd); + if (!w->hdc) { + peak_user32.DestroyWindow(w->hwnd); + free(w->buffer); + free(w); + return intern; + } + + peak_user32.ShowWindow(w->hwnd, SW_SHOWNORMAL); + intern.w = w; + return intern; +} + +static void +peak_platform_window_close(PeakWindowInternal *intern) +{ + struct peak_win32_win *w; + if (!intern || !intern->w) + return; + w = intern->w; + if (w->hdc && w->hwnd) + peak_user32.ReleaseDC(w->hwnd, w->hdc); + if (w->hwnd) { + peak_user32.RemovePropA(w->hwnd, PEAK_WIN32_PROP); + peak_user32.DestroyWindow(w->hwnd); + } + free(w->buffer); + free(w); + intern->w = NULL; +} + +static uint32_t * +peak_platform_window_buffer(PeakWindowInternal *intern, size_t *width, size_t *height) +{ + struct peak_win32_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_win32_win *w = intern ? intern->w : NULL; + BITMAPINFO bmi; + + if (!w || !w->hwnd || !w->hdc || !w->buffer) + return; + + memset(&bmi, 0, sizeof bmi); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = (LONG)w->width; + bmi.bmiHeader.biHeight = -(LONG)w->height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + peak_gdi32.StretchDIBits(w->hdc, + 0, 0, (int)w->width, (int)w->height, + 0, 0, (int)w->width, (int)w->height, + w->buffer, &bmi, DIB_RGB_COLORS, SRCCOPY); +} + +static bool +peak_platform_epoll(PeakWindowInternal *intern, PeakEvent *ev) +{ + MSG msg; + struct peak_win32_win *w = intern ? intern->w : NULL; + + if (!w || !w->hwnd) + return 0; + + while (peak_user32.PeekMessageA(&msg, w->hwnd, 0, 0, PM_REMOVE)) { + peak_user32.TranslateMessage(&msg); + peak_user32.DispatchMessageA(&msg); + if (peak_q_pop(&w->q, ev)) + return 1; + } + return peak_q_pop(&w->q, ev); } diff --git a/Peak/src/peak.c b/Peak/src/peak.c index 5e09070..6c4c038 100644 --- a/Peak/src/peak.c +++ b/Peak/src/peak.c @@ -1,254 +1,118 @@ -static inline void peak__put_pixel(int x, int y, uint32_t color); -static inline uint32_t peak__lerp_color(uint32_t c0, uint32_t c1, float t); -static inline float peak__edge_cross(float ax, float ay, float bx, float by, float cx, float cy); - -static uint32_t *peak_window_buffer; -static size_t peak_window_width; -static size_t peak_window_height; -static int peak_clip_x; -static int peak_clip_y; -static size_t peak_clip_width; -static size_t peak_clip_height; - -void -peak__put_pixel(int x, int y, uint32_t color) +static uint32_t * +peak_window_sync(PeakWindow *win, size_t *width, size_t *height) { - if (x < peak_clip_x || x >= (int)(peak_clip_x + peak_clip_width) || - y < peak_clip_y || y >= (int)(peak_clip_y + peak_clip_height)) { - return; - } - if (x < 0 || x >= (int)peak_window_width || y < 0 || y >= (int)peak_window_height) { - return; - } - peak_window_buffer[y * peak_window_width + x] = color; + size_t w = 0, h = 0; + win->buffer = peak_platform_window_buffer(&win->internal, &w, &h); + win->width = (uint32_t)w; + win->height = (uint32_t)h; + win->bufsize = win->width * win->height; + if (width) *width = w; + if (height) *height = h; + return win->buffer; } -uint32_t -peak__lerp_color(uint32_t c0, uint32_t c1, float t) +int +peak_init(void) { - if (t <= 0.0f) return c0; - if (t >= 1.0f) return c1; - - uint32_t a0 = (c0 >> 24) & 0xFF, a1 = (c1 >> 24) & 0xFF; - uint32_t r0 = (c0 >> 16) & 0xFF, r1 = (c1 >> 16) & 0xFF; - uint32_t g0 = (c0 >> 8) & 0xFF, g1 = (c1 >> 8) & 0xFF; - uint32_t b0 = c0 & 0xFF, b1 = c1 & 0xFF; - - uint32_t a = (uint32_t)(a0 + t * ((float)a1 - (float)a0)); - uint32_t r = (uint32_t)(r0 + t * ((float)r1 - (float)r0)); - uint32_t g = (uint32_t)(g0 + t * ((float)g1 - (float)g0)); - uint32_t b = (uint32_t)(b0 + t * ((float)b1 - (float)b0)); - - return (a << 24) | (r << 16) | (g << 8) | b; -} - -float -peak__edge_cross(float ax, float ay, float bx, float by, float cx, float cy) -{ - return (cx - ax) * (by - ay) - (cy - ay) * (bx - ax); + return peak_platform_init(); } void -peak_init(void) +peak_quit(void) { - peak_platform_window_open(); - peak_window_buffer = peak_platform_window_buffer(&peak_window_width, &peak_window_height); - peak_clip_x = 0; - peak_clip_y = 0; - peak_clip_width = peak_window_width; - peak_clip_height = peak_window_height; + peak_platform_quit(); } -void -peak_quit(void) +PeakWindow +peak_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags) { - peak_platform_window_close(); + PeakWindow win = {0}; + if (!name || !name[0]) name = "Peak"; + if (!width) width = 800; + if (!height) height = 600; + win.internal = peak_platform_window_open(name, width, height, flags); + if (!peak_window_sync(&win, NULL, NULL)) return win; + win.running = 1; + return win; } void -peak_extensions(void) +peak_window_close(PeakWindow *win) { - // TODO + win->running = 0; + peak_platform_window_close(&win->internal); + win->buffer = NULL; + win->width = 0; + win->height = 0; + win->bufsize = 0; } -bool -peak_poll_events(PeakEvent *ev) +int +peak_window_epoll(PeakWindow *win, PeakEvent *ev) { - return peak_platform_epoll(ev); + int got = peak_platform_epoll(&win->internal, ev); + if (got && ev->type == PEAK_EVENT_WINDOW_RESIZE) + peak_window_sync(win, NULL, NULL); + return got; } -void -peak_blit(int offset_x, int offset_y, const uint32_t *rgba, size_t width, size_t height) +uint32_t * +peak_window_backbuffer(PeakWindow *win, size_t *width, size_t *height) { - for (size_t y = 0; y < height; ++y) { - int target_y = offset_y + (int)y; - if (target_y < peak_clip_y || target_y >= (int)(peak_clip_y + peak_clip_height) || - target_y < 0 || target_y >= (int)peak_window_height) continue; - - int start_x = offset_x < peak_clip_x ? peak_clip_x : offset_x; - int end_x = (offset_x + (int)width) > (int)(peak_clip_x + peak_clip_width) - ? (int)(peak_clip_x + peak_clip_width) - : (offset_x + (int)width); - - if (start_x >= end_x) continue; - - size_t src_offset_x = (size_t)(start_x - offset_x); - size_t copy_pixels = (size_t)(end_x - start_x); - - memcpy(&peak_window_buffer[target_y * peak_window_width + start_x], &rgba[y * width + src_offset_x], copy_pixels * sizeof(uint32_t)); - } + return peak_window_sync(win, width, height); } void -peak_clip(int x, int y, size_t w, size_t h) -{ - peak_clip_x = x; - peak_clip_y = y; - peak_clip_width = w; - peak_clip_height = h; +peak_window_clear(PeakWindow *win, float r, float g, float b, float a) +{ + uint32_t c, i; + if (!win || !win->buffer) return; +#if defined(PEAK_WEB) + /* ImageData RGBA bytes = LE 0xAABBGGRR */ + c = (uint32_t)(r * 255.f) + | ((uint32_t)(g * 255.f) << 8) + | ((uint32_t)(b * 255.f) << 16) + | ((uint32_t)(a * 255.f) << 24); +#else + c = ((uint32_t)(a * 255.f) << 24) + | ((uint32_t)(r * 255.f) << 16) + | ((uint32_t)(g * 255.f) << 8) + | (uint32_t)(b * 255.f); +#endif + for (i = 0; i < win->bufsize; i++) + win->buffer[i] = c; } void -peak_clip_reset(void) +peak_window_present(PeakWindow *win) { - peak_clip_x = 0; - peak_clip_y = 0; - peak_clip_width = peak_window_width; - peak_clip_height = peak_window_height; + if (win) peak_platform_window_present(&win->internal); } -void -peak_draw_rectangle(int x, int y, size_t w, size_t h, uint32_t color) +#ifdef PEAK_WEB +#include +static void +peak_internal_web_step(void *arg) { - int start_x = (x < peak_clip_x) ? peak_clip_x : x; - int start_y = (y < peak_clip_y) ? peak_clip_y : y; - int end_x = (x + (int)w > (int)(peak_clip_x + peak_clip_width)) ? (int)(peak_clip_x + peak_clip_width) : x + (int)w; - int end_y = (y + (int)h > (int)(peak_clip_y + peak_clip_height)) ? (int)(peak_clip_y + peak_clip_height) : y + (int)h; - - for (int py = start_y; py < end_y; ++py) { - if (py < 0 || py >= (int)peak_window_height) continue; - for (int px = start_x; px < end_x; ++px) { - if (px < 0 || px >= (int)peak_window_width) continue; - peak_window_buffer[py * peak_window_width + px] = color; - } + PeakWindow *win = arg; + if (!win->tick(win, win->userdata) || !win->running) { + win->running = 0; + emscripten_cancel_main_loop(); } } +#endif void -peak_draw_rectangle_gradient(int x, int y, size_t w, size_t h, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) -{ - for (size_t ry = 0; ry < h; ++ry) { - float ty = (h > 1) ? (float)ry / (float)(h - 1) : 0.0f; - uint32_t left_color = peak__lerp_color(c0, c3, ty); - uint32_t right_color = peak__lerp_color(c1, c2, ty); - - for (size_t rx = 0; rx < w; ++rx) { - float tx = (w > 1) ? (float)rx / (float)(w - 1) : 0.0f; - uint32_t final_color = peak__lerp_color(left_color, right_color, tx); - peak__put_pixel(x + (int)rx, y + (int)ry, final_color); - } - } +peak_window_run(PeakWindow *win, int (*peak_tick)(PeakWindow *win, void *userdata), void *userdata) +{ + assert(win && "peak_window_run needs a window"); + assert(peak_tick && "peak_window_run needs tick callback"); + win->tick = peak_tick; + win->userdata = userdata; + win->running = 1; +#ifdef PEAK_WEB + emscripten_set_main_loop_arg(peak_internal_web_step, win, 0, 1); +#else + while (win->running && win->tick(win, win->userdata)); +#endif } - -void -peak_draw_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) -{ - int min_x = (x0 < x1) ? ((x0 < x2) ? x0 : x2) : ((x1 < x2) ? x1 : x2); - int max_x = (x0 > x1) ? ((x0 > x2) ? x0 : x2) : ((x1 > x2) ? x1 : x2); - int min_y = (y0 < y1) ? ((y0 < y2) ? y0 : y2) : ((y1 < y2) ? y1 : y2); - int max_y = (y0 > y1) ? ((y0 > y2) ? y0 : y2) : ((y1 > y2) ? y1 : y2); - - float area = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x2, (float)y2); - if (area == 0.0f) return; - - for (int y = min_y; y <= max_y; ++y) { - for (int x = min_x; x <= max_x; ++x) { - float w0 = peak__edge_cross((float)x1, (float)y1, (float)x2, (float)y2, (float)x, (float)y); - float w1 = peak__edge_cross((float)x2, (float)y2, (float)x0, (float)y0, (float)x, (float)y); - float w2 = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x, (float)y); - - if ((w0 >= 0 && w1 >= 0 && w2 >= 0) || (w0 <= 0 && w1 <= 0 && w2 <= 0)) { - peak__put_pixel(x, y, color); - } - } - } -} - -void -peak_draw_triangle_gradient(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t c0, uint32_t c1, uint32_t c2) -{ - int min_x = (x0 < x1) ? ((x0 < x2) ? x0 : x2) : ((x1 < x2) ? x1 : x2); - int max_x = (x0 > x1) ? ((x0 > x2) ? x0 : x2) : ((x1 > x2) ? x1 : x2); - int min_y = (y0 < y1) ? ((y0 < y2) ? y0 : y2) : ((y1 < y2) ? y1 : y2); - int max_y = (y0 > y1) ? ((y0 > y2) ? y0 : y2) : ((y1 > y2) ? y1 : y2); - - float area = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x2, (float)y2); - if (area == 0.0f) return; - - for (int y = min_y; y <= max_y; ++y) { - for (int x = min_x; x <= max_x; ++x) { - float w0 = peak__edge_cross((float)x1, (float)y1, (float)x2, (float)y2, (float)x, (float)y) / area; - float w1 = peak__edge_cross((float)x2, (float)y2, (float)x0, (float)y0, (float)x, (float)y) / area; - float w2 = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x, (float)y) / area; - - if (w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f) { - uint32_t col_a = peak__lerp_color(c0, c1, w1 / (w0 + w1 + 1e-6f)); - uint32_t final_color = peak__lerp_color(col_a, c2, w2); - peak__put_pixel(x, y, final_color); - } - } - } -} - -void -peak_draw_line(int x0, int y0, int x1, int y1, size_t width, uint32_t color) -{ - int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; - int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; - int err = dx + dy, e2; - - int half_w = (int)width / 2; - - while (1) { - /* Draw square footprint for line thickness */ - for (int ox = -half_w; ox <= half_w; ++ox) { - for (int oy = -half_w; oy <= half_w; ++oy) { - peak__put_pixel(x0 + ox, y0 + oy, color); - } - } - if (x0 == x1 && y0 == y1) break; - e2 = 2 * err; - if (e2 >= dy) { err += dy; x0 += sx; } - if (e2 <= dx) { err += dx; y0 += sy; } - } -} - -void -peak_draw_line_gradient(int x0, int y0, int x1, int y1, size_t width, uint32_t c0, uint32_t c1) -{ - float total_dist = sqrtf((float)((x1 - x0)*(x1 - x0) + (y1 - y0)*(y1 - y0))); - int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; - int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; - int err = dx + dy, e2; - - int orig_x0 = x0, orig_y0 = y0; - int half_w = (int)width / 2; - - while (1) { - float current_dist = sqrtf((float)((x0 - orig_x0)*(x0 - orig_x0) + (y0 - orig_y0)*(y0 - orig_y0))); - float t = (total_dist > 0.0f) ? (current_dist / total_dist) : 0.0f; - uint32_t color = peak__lerp_color(c0, c1, t); - - for (int ox = -half_w; ox <= half_w; ++ox) { - for (int oy = -half_w; oy <= half_w; ++oy) { - peak__put_pixel(x0 + ox, y0 + oy, color); - } - } - - if (x0 == x1 && y0 == y1) break; - e2 = 2 * err; - if (e2 >= dy) { err += dy; x0 += sx; } - if (e2 <= dx) { err += dx; y0 += sy; } - } -} - diff --git a/Peak/src/peak_header.h b/Peak/src/peak_header.h index 9521abb..b0a3023 100644 --- a/Peak/src/peak_header.h +++ b/Peak/src/peak_header.h @@ -17,12 +17,13 @@ * 0.0.0 - @vasco - prototyping * 0.1.0 - @vasco - linux x11 that automagically loads X11 DLL * 0.1.1 - @vasco - fixed event handling on linux - * - * TODO: demo that stress tests the whole API + * 0.1.2 - @vasco - better handling of windows, if multiple windows becomes necessary in the future + * 0.2.0 - @vasco - multiple windows, major-ish API changes. + * 0.3.0 - @vasco - web via emscripten + * 0.4.0 - @vasco - win32 */ #include -#include #include #include #include @@ -52,15 +53,13 @@ #else #define PEAK_MACOS #endif -#elif defined(__linux__) - #define PEAK_LINUX -#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) - #define PEAK_BSD #elif defined(__ANDROID__) #define PEAK_ANDROID #define PEAK_LINUX #elif defined(__linux__) #define PEAK_LINUX +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) + #define PEAK_BSD #endif #if defined(PEAK_LINUX) || defined(PEAK_BSD) || defined(PEAK_APPLE) @@ -77,7 +76,8 @@ #endif typedef enum { - PEAK_KEYMOD_ALT = 0, + PEAK_KEYMOD_NONE = 0, + PEAK_KEYMOD_ALT, PEAK_KEYMOD_SHIFT, PEAK_KEYMOD_CTRL, PEAK_KEYMOD_CAPS, @@ -102,7 +102,7 @@ typedef enum { PEAK_EVENT_POINTER_CONNECTED, PEAK_EVENT_POINTER_DISCONNECTED, PEAK_EVENT_LAST -} PeakEvenType; +} PeakEventType; typedef enum { PEAK_POINTER_MOVED = 0, @@ -118,7 +118,7 @@ typedef enum { } PeakPointerType; typedef struct { - PeakEvenType type; + PeakEventType type; union { struct { PeakKeyCode key; PeakKeyMod mod; } key; struct { uint32_t width, height; } resize; @@ -126,106 +126,53 @@ typedef struct { }; } PeakEvent; +/* Implemented by the platform */ +typedef struct peak_window_internal_t PeakWindowInternal; +typedef struct PeakWindow PeakWindow; + /* Public API */ -PEAK void peak_init(void); // open a window and initialize graphics. called automatically if using peak_setup(). -PEAK void peak_quit(void); // close the window and release resources. called automatically if using peak_setup(). -PEAK bool peak_poll_events(PeakEvent *ev); // Poll the next event from the window queue. Returns true if an event was retrieved. -PEAK void peak_extensions(void); // retrieve gpu window surface extensions for native api access (e.g., vulkan/opengl). -PEAK void peak_blit(int offset_x, int offset_y, const uint32_t *rgba, size_t width, size_t height); // blit rgba pixels to the screen. -PEAK void peak_clip(int x, int y, size_t w, size_t h); // restrict all subsequent rendering operations to this bounding rectangle. -PEAK void peak_clip_reset(void); // reset clipping plane to window size. -PEAK void peak_draw_rectangle(int x, int y, size_t w, size_t h, uint32_t color); // draw a solid-color filled rectangle. -PEAK void peak_draw_rectangle_gradient(int x, int y, size_t w, size_t h, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3); // fill rectangle interpolating four corner colors in clock-wise order. -PEAK void peak_draw_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color); // draw a solid-color filled triangle. -PEAK void peak_draw_triangle_gradient(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t c0, uint32_t c1, uint32_t c2); // fill triangle with color interpolation clock-wise across its three vertices. -PEAK void peak_draw_line(int x0, int y0, int x1, int y1, size_t width, uint32_t color); // draw a line between two points. -PEAK void peak_draw_line_gradient(int x0, int y0, int x1, int y1, size_t width, uint32_t c0, uint32_t c1); // draw a line with a color gradient. +PEAK int peak_init(void); // Initialize platform context and load necessary DLLs. +PEAK void peak_quit(void); // Close platform context. +PEAK PeakWindow peak_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags); // Open a window. +PEAK void peak_window_close(PeakWindow *window); // Close a window. +PEAK void peak_window_run(PeakWindow *win, int (*peak_tick)(PeakWindow *win, void *userdata), void *userdata); // Hijacking the main loop makes life easier on platforms like web +PEAK int peak_window_epoll(PeakWindow *win, PeakEvent *ev); // Poll a window for events. +PEAK uint32_t* peak_window_backbuffer(PeakWindow *win, size_t *width, size_t *height); // Get the windows backbuffer. +PEAK void peak_window_clear(PeakWindow *win, float r, float g, float b, float a); +PEAK void peak_window_present(PeakWindow *win); -/* Implemented by the platform */ -PEAK void peak_platform_window_open(); -PEAK void peak_platform_window_close(); -PEAK uint32_t *peak_platform_window_buffer(size_t *width, size_t *height); -PEAK bool peak_platform_epoll(PeakEvent *ev); +#endif // PEAK_H -/* - * He was wipping up amazing foods, like making happiness. - * Actual happiness? - * Actual happiness and joy. - * Oh the smell, it was so divine. - * He was making happiness!? - * Happiness in the kettle! - */ -#ifdef PEAK_REPLACE_MAIN -enum PeakReturn { PEAK_CONTINUE = 0, PEAK_STOP = 1 }; -extern int peak__main(int argc, char**argv); -extern void peak_events(PeakEvent ev); -extern void peak_tick(); -static bool running = true; -static inline void peak_stop() { running = false; } -#ifndef PEAK_WEB -#define main(...)\ -main(int argc, char **argv) {\ - assert(peak_events != 0 && "Declare peak_events() to handle window events.");\ - assert(peak_tick != 0 && "Declare peak_tick() to handle each frame ticks.");\ - peak_init();\ - int ret = peak__main(argc, argv);\ - if (ret != PEAK_CONTINUE) return ret;\ - PeakEvent ev;\ - while (running) {\ - while (running && peak_poll_events(&ev)) {\ - peak_events(ev);\ - }\ - peak_tick();\ - }\ - peak_quit();\ -}\ -int peak__main(__VA_ARGS__) -#else -#include -/* - * He was wipping up agony in the kettle. - * Actual pain? - * Boiling hatred in the kettle. - * The smell, it's pain. - * Boiling anger!? - * Yes, boiling anger. - */ +#ifdef PEAK_IMPLEMENTATION +#undef PEAK_IMPLEMENTATION + +#if defined(PEAK_WIN32) || defined(PEAK_WEB) +#define PEAK_Q 64 -static inline void -peak__emscripten_loop_step(void *arg) +typedef struct { + unsigned h, n; + PeakEvent e[PEAK_Q]; +} PeakQ; + +static void +peak_q_push(PeakQ *q, PeakEvent ev) { - (void)arg; - PeakEvent ev; - while (peak_poll_events(&ev)) { - peak_events(ev); - } - - if (running) { - peak_tick(); - } else { - emscripten_cancel_main_loop(); - peak_quit(); - } + if (!q || q->n == PEAK_Q) + return; + q->e[(q->h + q->n++) % PEAK_Q] = ev; } -#define main(...)\ - main(int argc, char **argv) {\ - _Static_assert(peak_events != 0, "When using peak_setup, declare peak_events() to handle window events.");\ - _Static_assert(peak_tick != 0, "When using peak_setup, declare peak_tick() to handle frame ticks.");\ - peak_init();\ - int ret = peak__main(argc, argv);\ - if (ret != PEAK_CONTINUE) return ret;\ - emscripten_set_main_loop_arg(peak__emscripten_loop_step, NULL, 0, 1);\ - return 0;\ - }\ - int peak__main(__VA_ARGS__) +static int +peak_q_pop(PeakQ *q, PeakEvent *ev) +{ + if (!q || !q->n) + return 0; + *ev = q->e[q->h]; + q->h = (q->h + 1) % PEAK_Q; + q->n--; + return 1; +} #endif -#endif // PEAK_DONT_REPLACE_MAIN - -#endif // PEAK_H - -#ifdef PEAK_IMPLEMENTATION -#undef PEAK_IMPLEMENTATION #if defined(PEAK_WIN32) #include "p_win32.c" // @@ -235,6 +182,19 @@ peak__emscripten_loop_step(void *arg) #include "p_emscripten.c" // #endif +/* We define PeakWindow only when we know the type of peak_window_internal_t */ +struct PeakWindow { + PeakWindowInternal internal; + int (*tick)(struct PeakWindow *win, void *userdata); + void *userdata; + uint32_t *buffer; + uint32_t width; + uint32_t height; + uint32_t bufsize; + int running; +}; + + #include "peak.c" // #endif // PEAK_IMPLEMENTATION diff --git a/Peak/src/peak_header.h.out b/Peak/src/peak_header.h.out index 581aa74..97bb0a3 100644 --- a/Peak/src/peak_header.h.out +++ b/Peak/src/peak_header.h.out @@ -17,12 +17,11 @@ * 0.0.0 - @vasco - prototyping * 0.1.0 - @vasco - linux x11 that automagically loads X11 DLL * 0.1.1 - @vasco - fixed event handling on linux - * - * TODO: demo that stress tests the whole API + * 0.1.2 - @vasco - better handling of windows, if multiple windows becomes necessary in the future + * 0.2.0 - @vasco - win32 */ #include -#include #include #include #include @@ -52,15 +51,13 @@ #else #define PEAK_MACOS #endif -#elif defined(__linux__) - #define PEAK_LINUX -#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) - #define PEAK_BSD #elif defined(__ANDROID__) #define PEAK_ANDROID #define PEAK_LINUX #elif defined(__linux__) #define PEAK_LINUX +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) + #define PEAK_BSD #endif #if defined(PEAK_LINUX) || defined(PEAK_BSD) || defined(PEAK_APPLE) @@ -77,7 +74,8 @@ #endif typedef enum { - PEAK_KEYMOD_ALT = 0, + PEAK_KEYMOD_NONE = 0, + PEAK_KEYMOD_ALT, PEAK_KEYMOD_SHIFT, PEAK_KEYMOD_CTRL, PEAK_KEYMOD_CAPS, @@ -102,7 +100,7 @@ typedef enum { PEAK_EVENT_POINTER_CONNECTED, PEAK_EVENT_POINTER_DISCONNECTED, PEAK_EVENT_LAST -} PeakEvenType; +} PeakEventType; typedef enum { PEAK_POINTER_MOVED = 0, @@ -118,7 +116,7 @@ typedef enum { } PeakPointerType; typedef struct { - PeakEvenType type; + PeakEventType type; union { struct { PeakKeyCode key; PeakKeyMod mod; } key; struct { uint32_t width, height; } resize; @@ -126,727 +124,1085 @@ typedef struct { }; } PeakEvent; +/* Implemented by the platform */ +typedef struct peak_window_internal_t PeakWindowInternal; +typedef struct PeakWindow PeakWindow; + /* Public API */ -PEAK void peak_init(void); // open a window and initialize graphics. called automatically if using peak_setup(). -PEAK void peak_quit(void); // close the window and release resources. called automatically if using peak_setup(). -PEAK bool peak_poll_events(PeakEvent *ev); // Poll the next event from the window queue. Returns true if an event was retrieved. -PEAK void peak_extensions(void); // retrieve gpu window surface extensions for native api access (e.g., vulkan/opengl). -PEAK void peak_blit(int offset_x, int offset_y, const uint32_t *rgba, size_t width, size_t height); // blit rgba pixels to the screen. -PEAK void peak_clip(int x, int y, size_t w, size_t h); // restrict all subsequent rendering operations to this bounding rectangle. -PEAK void peak_clip_reset(void); // reset clipping plane to window size. -PEAK void peak_draw_rectangle(int x, int y, size_t w, size_t h, uint32_t color); // draw a solid-color filled rectangle. -PEAK void peak_draw_rectangle_gradient(int x, int y, size_t w, size_t h, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3); // fill rectangle interpolating four corner colors in clock-wise order. -PEAK void peak_draw_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color); // draw a solid-color filled triangle. -PEAK void peak_draw_triangle_gradient(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t c0, uint32_t c1, uint32_t c2); // fill triangle with color interpolation clock-wise across its three vertices. -PEAK void peak_draw_line(int x0, int y0, int x1, int y1, size_t width, uint32_t color); // draw a line between two points. -PEAK void peak_draw_line_gradient(int x0, int y0, int x1, int y1, size_t width, uint32_t c0, uint32_t c1); // draw a line with a color gradient. +PEAK int peak_init(void); // Initialize platform context and load necessary DLLs. +PEAK void peak_quit(void); // Close platform context. +PEAK PeakWindow peak_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags); // Open a window. +PEAK void peak_window_close(PeakWindow *window); // Close a window. +PEAK void peak_window_run(PeakWindow *win, int (*peak_tick)(PeakWindow *win, void *userdata), void *userdata); // Hijacking the main loop makes life easier on platforms like web +PEAK int peak_window_epoll(PeakWindow *win, PeakEvent *ev); // Poll a window for events. +PEAK uint32_t* peak_window_backbuffer(PeakWindow *win, size_t *width, size_t *height); // Get the windows backbuffer. +PEAK void peak_window_clear(PeakWindow *win, float r, float g, float b, float a); +PEAK void peak_window_present(PeakWindow *win); -/* Implemented by the platform */ -PEAK void peak_platform_window_open(); -PEAK void peak_platform_window_close(); -PEAK uint32_t *peak_platform_window_buffer(size_t *width, size_t *height); -PEAK bool peak_platform_epoll(PeakEvent *ev); +#endif // PEAK_H -/* - * He was wipping up amazing foods, like making happiness. - * Actual happiness? - * Actual happiness and joy. - * Oh the smell, it was so divine. - * He was making happiness!? - * Happiness in the kettle! - */ -#ifdef PEAK_REPLACE_MAIN -enum PeakReturn { PEAK_CONTINUE = 0, PEAK_STOP = 1 }; -extern int peak__main(int argc, char**argv); -extern void peak_events(PeakEvent ev); -extern void peak_tick(); -static bool running = true; -static inline void peak_stop() { running = false; } -#ifndef PEAK_WEB -#define main(...)\ -main(int argc, char **argv) {\ - assert(peak_events != 0 && "Declare peak_events() to handle window events.");\ - assert(peak_tick != 0 && "Declare peak_tick() to handle each frame ticks.");\ - peak_init();\ - int ret = peak__main(argc, argv);\ - if (ret != PEAK_CONTINUE) return ret;\ - PeakEvent ev;\ - while (running) {\ - while (running && peak_poll_events(&ev)) {\ - peak_events(ev);\ - }\ - peak_tick();\ - }\ - peak_quit();\ -}\ -int peak__main(__VA_ARGS__) -#else -#include -/* - * He was wipping up agony in the kettle. - * Actual pain? - * Boiling hatred in the kettle. - * The smell, it's pain. - * Boiling anger!? - * Yes, boiling anger. - */ +#ifdef PEAK_IMPLEMENTATION +#undef PEAK_IMPLEMENTATION -static inline void -peak__emscripten_loop_step(void *arg) +#if defined(PEAK_WIN32) || defined(PEAK_WEB) +#define PEAK_Q 64 + +typedef struct { + unsigned h, n; + PeakEvent e[PEAK_Q]; +} PeakQ; + +static void +peak_q_push(PeakQ *q, PeakEvent ev) { - (void)arg; - PeakEvent ev; - while (peak_poll_events(&ev)) { - peak_events(ev); - } - - if (running) { - peak_tick(); - } else { - emscripten_cancel_main_loop(); - peak_quit(); - } + if (!q || q->n == PEAK_Q) + return; + q->e[(q->h + q->n++) % PEAK_Q] = ev; } -#define main(...)\ - main(int argc, char **argv) {\ - _Static_assert(peak_events != 0, "When using peak_setup, declare peak_events() to handle window events.");\ - _Static_assert(peak_tick != 0, "When using peak_setup, declare peak_tick() to handle frame ticks.");\ - peak_init();\ - int ret = peak__main(argc, argv);\ - if (ret != PEAK_CONTINUE) return ret;\ - emscripten_set_main_loop_arg(peak__emscripten_loop_step, NULL, 0, 1);\ - return 0;\ - }\ - int peak__main(__VA_ARGS__) +static int +peak_q_pop(PeakQ *q, PeakEvent *ev) +{ + if (!q || !q->n) + return 0; + *ev = q->e[q->h]; + q->h = (q->h + 1) % PEAK_Q; + q->n--; + return 1; +} #endif -#endif // PEAK_DONT_REPLACE_MAIN - -#endif // PEAK_H - -#ifdef PEAK_IMPLEMENTATION -#undef PEAK_IMPLEMENTATION #if defined(PEAK_WIN32) /* --- Start of p_win32.c --- */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + #include +#include +#include +#include -void -peak_platform_window_open() +#define PEAK_WIN32_USER32 "user32.dll" +#define PEAK_WIN32_GDI32 "gdi32.dll" +#define PEAK_WIN32_CLASS "PeakWindow" +#define PEAK_WIN32_PROP "Peak" + +#define PEAK_USER32_API(X) \ + X(RegisterClassExA, ATOM, WINAPI, (const WNDCLASSEXA *)) \ + X(UnregisterClassA, BOOL, WINAPI, (LPCSTR, HINSTANCE)) \ + X(CreateWindowExA, HWND, WINAPI, (DWORD, LPCSTR, LPCSTR, DWORD, int, int, int, int, HWND, HMENU, HINSTANCE, LPVOID)) \ + X(DestroyWindow, BOOL, WINAPI, (HWND)) \ + X(ShowWindow, BOOL, WINAPI, (HWND, int)) \ + X(GetDC, HDC, WINAPI, (HWND)) \ + X(ReleaseDC, int, WINAPI, (HWND, HDC)) \ + X(PeekMessageA, BOOL, WINAPI, (LPMSG, HWND, UINT, UINT, UINT)) \ + X(TranslateMessage, BOOL, WINAPI, (const MSG *)) \ + X(DispatchMessageA, LRESULT, WINAPI, (const MSG *)) \ + X(DefWindowProcA, LRESULT, WINAPI, (HWND, UINT, WPARAM, LPARAM)) \ + X(SetPropA, BOOL, WINAPI, (HWND, LPCSTR, HANDLE)) \ + X(GetPropA, HANDLE, WINAPI, (HWND, LPCSTR)) \ + X(RemovePropA, HANDLE, WINAPI, (HWND, LPCSTR)) \ + X(BeginPaint, HDC, WINAPI, (HWND, LPPAINTSTRUCT)) \ + X(EndPaint, BOOL, WINAPI, (HWND, const PAINTSTRUCT *)) \ + X(AdjustWindowRectEx, BOOL, WINAPI, (LPRECT, DWORD, BOOL, DWORD)) \ + X(LoadCursorA, HCURSOR, WINAPI, (HINSTANCE, LPCSTR)) \ + X(GetKeyState, SHORT, WINAPI, (int)) + +#define PEAK_GDI32_API(X) \ + X(StretchDIBits, int, WINAPI, (HDC, int, int, int, int, int, int, int, int, const void *, const BITMAPINFO *, UINT, DWORD)) + +typedef struct { +#define X(name, ret, conv, args) ret (conv *name) args; + PEAK_USER32_API(X) +#undef X +} PeakUser32Api; + +typedef struct { +#define X(name, ret, conv, args) ret (conv *name) args; + PEAK_GDI32_API(X) +#undef X +} PeakGdi32Api; + +typedef struct { + HMODULE user32; + HMODULE gdi32; + int class_reg; +} PeakWin32; + +struct peak_win32_win { + HWND hwnd; + HDC hdc; + uint32_t *buffer; + uint32_t width; + uint32_t height; + PeakQ q; +}; + +struct peak_window_internal_t { + struct peak_win32_win *w; +}; + +static PeakWin32 peak_win32; +static PeakUser32Api peak_user32; +static PeakGdi32Api peak_gdi32; + +static int +peak_internal_user32_load(HMODULE handle) { +#define X(name, ret, conv, args) peak_user32.name = (ret (conv *) args)(void *)GetProcAddress(handle, #name); + PEAK_USER32_API(X) +#undef X +#define X(name, ret, conv, args) || !peak_user32.name + if (0 PEAK_USER32_API(X)) + return 0; +#undef X + return 1; } -void -peak_platform_window_close() +static int +peak_internal_gdi32_load(HMODULE handle) { +#define X(name, ret, conv, args) peak_gdi32.name = (ret (conv *) args)(void *)GetProcAddress(handle, #name); + PEAK_GDI32_API(X) +#undef X +#define X(name, ret, conv, args) || !peak_gdi32.name + if (0 PEAK_GDI32_API(X)) + return 0; +#undef X + return 1; } -uint32_t* -peak_platform_window_buffer(size_t *width, size_t *height) +static PeakKeyCode +peak_internal_win32_key_map(WPARAM vk) { + if (vk >= 'A' && vk <= 'Z') + return (PeakKeyCode)(PEAK_KEY_A + (int)(vk - 'A')); + switch (vk) { + case VK_UP: return PEAK_KEY_UP; + case VK_DOWN: return PEAK_KEY_DOWN; + case VK_LEFT: return PEAK_KEY_LEFT; + case VK_RIGHT: return PEAK_KEY_RIGHT; + case VK_SPACE: return PEAK_KEY_SPACE; + case VK_ESCAPE: return PEAK_KEY_ESCAPE; + case VK_RETURN: return PEAK_KEY_ENTER; + default: return PEAK_KEY_UNKNOWN; + } } -bool -peak_platform_epoll(PeakEvent *ev) +static PeakKeyMod +peak_internal_win32_mod_map(void) +{ + if (peak_user32.GetKeyState(VK_CONTROL) & 0x8000) return PEAK_KEYMOD_CTRL; + if (peak_user32.GetKeyState(VK_MENU) & 0x8000) return PEAK_KEYMOD_ALT; + if (peak_user32.GetKeyState(VK_SHIFT) & 0x8000) return PEAK_KEYMOD_SHIFT; + if (peak_user32.GetKeyState(VK_CAPITAL) & 1) return PEAK_KEYMOD_CAPS; + return (PeakKeyMod)0; +} + +static int +peak_win32_buffer(struct peak_win32_win *w, uint32_t width, uint32_t height) +{ + uint32_t *buffer; + + buffer = calloc((size_t)width * height, sizeof *buffer); + if (!buffer) + return 0; + free(w->buffer); + w->buffer = buffer; + w->width = width; + w->height = height; + return 1; +} + +static LRESULT CALLBACK +peak_internal_win32_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + struct peak_win32_win *w; + PeakEvent ev; + + if (msg == WM_NCCREATE) { + CREATESTRUCTA *cs = (CREATESTRUCTA *)lparam; + peak_user32.SetPropA(hwnd, PEAK_WIN32_PROP, cs->lpCreateParams); + } + + w = (struct peak_win32_win *)peak_user32.GetPropA(hwnd, PEAK_WIN32_PROP); + if (!w) + return peak_user32.DefWindowProcA(hwnd, msg, wparam, lparam); + + switch (msg) { + case WM_CLOSE: + memset(&ev, 0, sizeof ev); + ev.type = PEAK_EVENT_WINDOW_CLOSE; + peak_q_push(&w->q, ev); + return 0; + case WM_SIZE: { + uint32_t width, height; + if (wparam == SIZE_MINIMIZED) + return 0; + width = (uint32_t)LOWORD(lparam); + height = (uint32_t)HIWORD(lparam); + if (!width || !height || (width == w->width && height == w->height)) + return 0; + if (!peak_win32_buffer(w, width, height)) + return 0; + memset(&ev, 0, sizeof ev); + ev.type = PEAK_EVENT_WINDOW_RESIZE; + ev.resize.width = w->width; + ev.resize.height = w->height; + peak_q_push(&w->q, ev); + return 0; + } + case WM_KEYDOWN: + case WM_KEYUP: + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + memset(&ev, 0, sizeof ev); + ev.type = (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) ? PEAK_EVENT_KEY_DOWN : PEAK_EVENT_KEY_UP; + ev.key.key = peak_internal_win32_key_map(wparam); + ev.key.mod = peak_internal_win32_mod_map(); + peak_q_push(&w->q, ev); + return 0; + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + memset(&ev, 0, sizeof ev); + ev.type = PEAK_EVENT_POINTER; + ev.pointer.x = (float)(short)LOWORD(lparam); + ev.pointer.y = (float)(short)HIWORD(lparam); + if (msg == WM_MOUSEMOVE) { + ev.pointer.state = PEAK_POINTER_MOVED; + ev.pointer.type = (wparam & MK_RBUTTON) ? PEAK_POINTER_RIGHT : + (wparam & MK_MBUTTON) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + } else if (msg == WM_LBUTTONDOWN || msg == WM_RBUTTONDOWN || msg == WM_MBUTTONDOWN) { + ev.pointer.state = PEAK_POINTER_PRESSED; + ev.pointer.type = (msg == WM_RBUTTONDOWN) ? PEAK_POINTER_RIGHT : + (msg == WM_MBUTTONDOWN) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + } else { + ev.pointer.state = PEAK_POINTER_RELEASED; + ev.pointer.type = (msg == WM_RBUTTONUP) ? PEAK_POINTER_RIGHT : + (msg == WM_MBUTTONUP) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + } + peak_q_push(&w->q, ev); + return 0; + case WM_PAINT: { + PAINTSTRUCT ps; + peak_user32.BeginPaint(hwnd, &ps); + peak_user32.EndPaint(hwnd, &ps); + return 0; + } + case WM_ERASEBKGND: + return 1; + case WM_DESTROY: + peak_user32.RemovePropA(hwnd, PEAK_WIN32_PROP); + return 0; + default: + return peak_user32.DefWindowProcA(hwnd, msg, wparam, lparam); + } +} + +static int +peak_platform_init(void) { + WNDCLASSEXA wc; + + if (!peak_user32.CreateWindowExA) { + if (!(peak_win32.user32 = LoadLibraryA(PEAK_WIN32_USER32))) { + fputs("Failed to load user32.dll. What system are you fucking using and abusing?", stderr); + return 0; + } + if (!peak_internal_user32_load(peak_win32.user32)) { + fputs("Failed to load user32 symbols", stderr); + return 0; + } + } + if (!peak_gdi32.StretchDIBits) { + if (!(peak_win32.gdi32 = LoadLibraryA(PEAK_WIN32_GDI32))) { + fputs("Failed to load gdi32.dll. What system are you fucking using and abusing?", stderr); + return 0; + } + if (!peak_internal_gdi32_load(peak_win32.gdi32)) { + fputs("Failed to load gdi32 symbols", stderr); + return 0; + } + } + if (!peak_win32.class_reg) { + memset(&wc, 0, sizeof wc); + wc.cbSize = sizeof wc; + wc.style = CS_OWNDC; + wc.lpfnWndProc = peak_internal_win32_wndproc; + wc.hInstance = GetModuleHandleA(NULL); + wc.hCursor = peak_user32.LoadCursorA(NULL, IDC_ARROW); + wc.lpszClassName = PEAK_WIN32_CLASS; + if (!peak_user32.RegisterClassExA(&wc) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) { + fputs("Failed to register window class", stderr); + return 0; + } + peak_win32.class_reg = 1; + } + return 1; +} + +static void +peak_platform_quit(void) +{ + if (!peak_win32.class_reg) + return; + peak_user32.UnregisterClassA(PEAK_WIN32_CLASS, GetModuleHandleA(NULL)); + peak_win32.class_reg = 0; +} + +static PeakWindowInternal +peak_platform_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags) +{ + PeakWindowInternal intern = {0}; + struct peak_win32_win *w; + DWORD style, ex; + RECT r; + HINSTANCE inst; + + (void)flags; + if (!peak_user32.CreateWindowExA && !peak_platform_init()) + return intern; + + w = calloc(1, sizeof *w); + if (!w) + return intern; + if (!peak_win32_buffer(w, width, height)) { + free(w); + return intern; + } + + ex = 0; + style = WS_OVERLAPPEDWINDOW; + r.left = 0; + r.top = 0; + r.right = (LONG)width; + r.bottom = (LONG)height; + peak_user32.AdjustWindowRectEx(&r, style, FALSE, ex); + inst = GetModuleHandleA(NULL); + w->hwnd = peak_user32.CreateWindowExA(ex, PEAK_WIN32_CLASS, name, style, + CW_USEDEFAULT, CW_USEDEFAULT, r.right - r.left, r.bottom - r.top, + NULL, NULL, inst, w); + if (!w->hwnd) { + free(w->buffer); + free(w); + return intern; + } + + w->hdc = peak_user32.GetDC(w->hwnd); + if (!w->hdc) { + peak_user32.DestroyWindow(w->hwnd); + free(w->buffer); + free(w); + return intern; + } + + peak_user32.ShowWindow(w->hwnd, SW_SHOWNORMAL); + intern.w = w; + return intern; +} + +static void +peak_platform_window_close(PeakWindowInternal *intern) +{ + struct peak_win32_win *w; + if (!intern || !intern->w) + return; + w = intern->w; + if (w->hdc && w->hwnd) + peak_user32.ReleaseDC(w->hwnd, w->hdc); + if (w->hwnd) { + peak_user32.RemovePropA(w->hwnd, PEAK_WIN32_PROP); + peak_user32.DestroyWindow(w->hwnd); + } + free(w->buffer); + free(w); + intern->w = NULL; +} + +static uint32_t * +peak_platform_window_buffer(PeakWindowInternal *intern, size_t *width, size_t *height) +{ + struct peak_win32_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_win32_win *w = intern ? intern->w : NULL; + BITMAPINFO bmi; + + if (!w || !w->hwnd || !w->hdc || !w->buffer) + return; + + memset(&bmi, 0, sizeof bmi); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = (LONG)w->width; + bmi.bmiHeader.biHeight = -(LONG)w->height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + peak_gdi32.StretchDIBits(w->hdc, + 0, 0, (int)w->width, (int)w->height, + 0, 0, (int)w->width, (int)w->height, + w->buffer, &bmi, DIB_RGB_COLORS, SRCCOPY); +} + +static bool +peak_platform_epoll(PeakWindowInternal *intern, PeakEvent *ev) +{ + MSG msg; + struct peak_win32_win *w = intern ? intern->w : NULL; + + if (!w || !w->hwnd) + return 0; + + while (peak_user32.PeekMessageA(&msg, w->hwnd, 0, 0, PM_REMOVE)) { + peak_user32.TranslateMessage(&msg); + peak_user32.DispatchMessageA(&msg); + if (peak_q_pop(&w->q, ev)) + return 1; + } + return peak_q_pop(&w->q, ev); } /* --- End of p_win32.c --- */ #elif defined(PEAK_LINUX) /* --- Start of p_linux.c --- */ -#include +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif + #include -#include #include +#include #include -#include +#include +#include #include -#include -#define PEAK_X11_LINUX "/usr/lib/libX11.so.6" - -#define PEAK_LINUX_DEFAULT_WIDTH 800 -#define PEAK_LINUX_DEFAULT_HEIGHT 600 - -typedef Display* (*XOpenDisplay_t)(const char*); -typedef int (*XCloseDisplay_t)(Display*); -typedef Window (*XCreateSimpleWindow_t)(Display*, Window, int, int, unsigned int, unsigned int, unsigned int, unsigned long, unsigned long); -typedef int (*XStoreName_t)(Display*, Window, const char*); -typedef Atom (*XInternAtom_t)(Display*, const char*, Bool); -typedef Status (*XSetWMProtocols_t)(Display*, Window, Atom*, int); -typedef int (*XSelectInput_t)(Display*, Window, long); -typedef GC (*XCreateGC_t)(Display*, Window, unsigned long, XGCValues*); -typedef XImage* (*XCreateImage_t)(Display*, Visual*, unsigned int, int, int, char*, unsigned int, unsigned int, int, int); -typedef int (*XMapRaised_t)(Display*, Window); -typedef int (*XFlush_t)(Display*); -typedef int (*XFreeGC_t)(Display*, GC); -typedef int (*XDestroyWindow_t)(Display*, Window); -typedef int (*XPending_t)(Display*); -typedef int (*XNextEvent_t)(Display*, XEvent*); -typedef KeySym (*XLookupKeysym_t)(XKeyEvent*, int); -typedef int (*XPutImage_t)(Display*, Drawable, GC, XImage*, int, int, int, int, unsigned int, unsigned int); -typedef int (*XDefaultScreen_t)(Display*); -typedef Window (*XRootWindow_t)(Display*, int); -typedef unsigned long (*XBlackPixel_t)(Display*, int); -typedef Visual* (*XDefaultVisual_t)(Display*, int); -typedef int (*XDefaultDepth_t)(Display*, int); +#define PEAK_X11_LINUX "libX11.so.6" + +#define PEAK_X11_API(X) \ + X(XOpenDisplay, Display *, (const char *)) \ + X(XCloseDisplay, int, (Display *)) \ + X(XCreateSimpleWindow, Window, (Display *, Window, int, int, unsigned int, unsigned int, unsigned int, unsigned long, unsigned long)) \ + X(XStoreName, int, (Display *, Window, const char *)) \ + X(XInternAtom, Atom, (Display *, const char *, Bool)) \ + X(XSetWMProtocols, Status, (Display *, Window, Atom *, int)) \ + X(XSelectInput, int, (Display *, Window, long)) \ + X(XCreateGC, GC, (Display *, Window, unsigned long, XGCValues *)) \ + X(XCreateImage, XImage *, (Display *, Visual *, unsigned int, int, int, char *, unsigned int, unsigned int, int, int)) \ + X(XMapRaised, int, (Display *, Window)) \ + X(XFlush, int, (Display *)) \ + X(XFreeGC, int, (Display *, GC)) \ + X(XDestroyWindow, int, (Display *, Window)) \ + X(XCheckIfEvent, Bool, (Display *, XEvent *, Bool (*)(Display *, XEvent *, XPointer), XPointer)) \ + X(XLookupKeysym, KeySym, (XKeyEvent *, int)) \ + X(XPutImage, int, (Display *, Drawable, GC, XImage *, int, int, int, int, unsigned int, unsigned int)) typedef struct { - XOpenDisplay_t XOpenDisplay; - XCloseDisplay_t XCloseDisplay; - XCreateSimpleWindow_t XCreateSimpleWindow; - XStoreName_t XStoreName; - XInternAtom_t XInternAtom; - XSetWMProtocols_t XSetWMProtocols; - XSelectInput_t XSelectInput; - XCreateGC_t XCreateGC; - XCreateImage_t XCreateImage; - XMapRaised_t XMapRaised; - XFlush_t XFlush; - XFreeGC_t XFreeGC; - XDestroyWindow_t XDestroyWindow; - XPending_t XPending; - XNextEvent_t XNextEvent; - XLookupKeysym_t XLookupKeysym; - XPutImage_t XPutImage; - XDefaultScreen_t XDefaultScreen; - XRootWindow_t XRootWindow; - XBlackPixel_t XBlackPixel; - XDefaultVisual_t XDefaultVisual; - XDefaultDepth_t XDefaultDepth; +#define X(name, ret, args) ret (*name) args; + PEAK_X11_API(X) +#undef X } PeakX11Api; -static PeakX11Api peak_x11; - -static int peak_load_x11(void *handle) { - peak_x11.XOpenDisplay = (XOpenDisplay_t)dlsym(handle, "XOpenDisplay"); - peak_x11.XCloseDisplay = (XCloseDisplay_t)dlsym(handle, "XCloseDisplay"); - peak_x11.XCreateSimpleWindow = (XCreateSimpleWindow_t)dlsym(handle, "XCreateSimpleWindow"); - peak_x11.XStoreName = (XStoreName_t)dlsym(handle, "XStoreName"); - peak_x11.XInternAtom = (XInternAtom_t)dlsym(handle, "XInternAtom"); - peak_x11.XSetWMProtocols = (XSetWMProtocols_t)dlsym(handle, "XSetWMProtocols"); - peak_x11.XSelectInput = (XSelectInput_t)dlsym(handle, "XSelectInput"); - peak_x11.XCreateGC = (XCreateGC_t)dlsym(handle, "XCreateGC"); - peak_x11.XCreateImage = (XCreateImage_t)dlsym(handle, "XCreateImage"); - peak_x11.XMapRaised = (XMapRaised_t)dlsym(handle, "XMapRaised"); - peak_x11.XFlush = (XFlush_t)dlsym(handle, "XFlush"); - peak_x11.XFreeGC = (XFreeGC_t)dlsym(handle, "XFreeGC"); - peak_x11.XDestroyWindow = (XDestroyWindow_t)dlsym(handle, "XDestroyWindow"); - peak_x11.XPending = (XPending_t)dlsym(handle, "XPending"); - peak_x11.XNextEvent = (XNextEvent_t)dlsym(handle, "XNextEvent"); - peak_x11.XLookupKeysym = (XLookupKeysym_t)dlsym(handle, "XLookupKeysym"); - peak_x11.XPutImage = (XPutImage_t)dlsym(handle, "XPutImage"); - peak_x11.XDefaultScreen = (XDefaultScreen_t)dlsym(handle, "XDefaultScreen"); - peak_x11.XRootWindow = (XRootWindow_t)dlsym(handle, "XRootWindow"); - peak_x11.XBlackPixel = (XBlackPixel_t)dlsym(handle, "XBlackPixel"); - peak_x11.XDefaultVisual = (XDefaultVisual_t)dlsym(handle, "XDefaultVisual"); - peak_x11.XDefaultDepth = (XDefaultDepth_t)dlsym(handle, "XDefaultDepth"); - - if (!peak_x11.XOpenDisplay || !peak_x11.XCloseDisplay || !peak_x11.XCreateSimpleWindow || - !peak_x11.XStoreName || !peak_x11.XInternAtom || !peak_x11.XSetWMProtocols || - !peak_x11.XSelectInput || !peak_x11.XCreateGC || !peak_x11.XCreateImage || - !peak_x11.XMapRaised || !peak_x11.XFlush || - !peak_x11.XFreeGC || !peak_x11.XDestroyWindow || !peak_x11.XPending || - !peak_x11.XNextEvent || !peak_x11.XLookupKeysym || !peak_x11.XPutImage || - !peak_x11.XDefaultScreen || !peak_x11.XRootWindow || !peak_x11.XBlackPixel || - !peak_x11.XDefaultVisual || !peak_x11.XDefaultDepth) { - return 0; - } - return 1; -} - - typedef struct { - Display *display; - Atom wm_delete_window; - bool initialized; + Display *display; + Atom wm_delete_window; } PeakLinux; -typedef struct { - Window window; - GC gfx_ctx; /* GRAPHICS CONTEXT, not garbage collector lol */ -} PeakLinuxWindow; +struct peak_window_internal_t { + Window window; + GC gfx_ctx; + XImage *ximage; + uint32_t *buffer; + uint32_t width; + uint32_t height; +}; -static PeakLinux peak_linux = {0}; -static PeakLinuxWindow peak_linux_window = {0}; -static XImage *peak_linux_ximage = NULL; -static uint32_t *peak_linux_buffer = NULL; -static size_t peak_linux_width = PEAK_LINUX_DEFAULT_WIDTH; -static size_t peak_linux_height = PEAK_LINUX_DEFAULT_HEIGHT; +static PeakLinux peak_linux; +static PeakX11Api peak_x11; + +static int +peak_internal_x11_load(void *handle) +{ +#define X(name, ret, args) peak_x11.name = (ret (*) args)dlsym(handle, #name); + PEAK_X11_API(X) +#undef X +#define X(name, ret, args) || !peak_x11.name + if (0 PEAK_X11_API(X)) + return 0; +#undef X + return 1; +} static PeakKeyCode -peak__x11_map_key(KeySym sym) -{ - if (sym >= XK_a && sym <= XK_z) return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_a)); - if (sym >= XK_A && sym <= XK_Z) return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_A)); - switch (sym) { - case XK_Up: return PEAK_KEY_UP; - case XK_Down: return PEAK_KEY_DOWN; - case XK_Left: return PEAK_KEY_LEFT; - case XK_Right: return PEAK_KEY_RIGHT; - case XK_space: return PEAK_KEY_SPACE; - case XK_Escape: return PEAK_KEY_ESCAPE; - case XK_Return: return PEAK_KEY_ENTER; - default: return PEAK_KEY_UNKNOWN; - } +peak_internal_x11_key_map(KeySym sym) +{ + if (sym >= XK_a && sym <= XK_z) + return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_a)); + if (sym >= XK_A && sym <= XK_Z) + return (PeakKeyCode)(PEAK_KEY_A + (int)(sym - XK_A)); + switch (sym) { + case XK_Up: return PEAK_KEY_UP; + case XK_Down: return PEAK_KEY_DOWN; + case XK_Left: return PEAK_KEY_LEFT; + case XK_Right: return PEAK_KEY_RIGHT; + case XK_space: return PEAK_KEY_SPACE; + case XK_Escape: return PEAK_KEY_ESCAPE; + case XK_Return: return PEAK_KEY_ENTER; + default: return PEAK_KEY_UNKNOWN; + } } static PeakKeyMod -peak__x11_map_mod(unsigned int state) +peak_internal_x11_mod_map(unsigned int state) { - if (state & ControlMask) return PEAK_KEYMOD_CTRL; - if (state & Mod1Mask) return PEAK_KEYMOD_ALT; - if (state & ShiftMask) return PEAK_KEYMOD_SHIFT; - if (state & LockMask) return PEAK_KEYMOD_CAPS; - return (PeakKeyMod)0; + if (state & ControlMask) return PEAK_KEYMOD_CTRL; + if (state & Mod1Mask) return PEAK_KEYMOD_ALT; + if (state & ShiftMask) return PEAK_KEYMOD_SHIFT; + if (state & LockMask) return PEAK_KEYMOD_CAPS; + return (PeakKeyMod)0; } -void -peak_platform_window_open() +static Bool +peak_internal_x11_window_match(Display *dpy, XEvent *ev, XPointer arg) { - void *x11_handle = dlopen(PEAK_X11_LINUX, RTLD_LOCAL | RTLD_NOW); - assert(x11_handle && "Failed to load X11 library. What system are you fucking using and abusing?"); - - int success = peak_load_x11(x11_handle); - assert(success && "Failed to open X11 display"); - - peak_linux.display = peak_x11.XOpenDisplay(NULL); - assert(peak_linux.display && "Failed to open X11 display"); - - /* Who design this API??? */ - int screen = DefaultScreen(peak_linux.display); - peak_linux_window.window = peak_x11.XCreateSimpleWindow(peak_linux.display, RootWindow(peak_linux.display, screen), 0, 0, peak_linux_width, peak_linux_height, 0, BlackPixel(peak_linux.display, screen), BlackPixel(peak_linux.display, screen)); - peak_x11.XStoreName(peak_linux.display, peak_linux_window.window, "Peak"); - peak_linux.wm_delete_window = peak_x11.XInternAtom(peak_linux.display, "WM_DELETE_WINDOW", False); - peak_x11.XSetWMProtocols(peak_linux.display, peak_linux_window.window, &peak_linux.wm_delete_window, 1); - peak_x11.XSelectInput(peak_linux.display, peak_linux_window.window, ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask); - peak_linux_window.gfx_ctx = peak_x11.XCreateGC(peak_linux.display, peak_linux_window.window, 0, NULL); - peak_linux_buffer = (uint32_t *)calloc(peak_linux_width * peak_linux_height, sizeof(uint32_t)); - assert(peak_linux_buffer && "Failed to allocate framebuffer"); - - /* Wrap the buffer in an XImage for XPutImage blitting */ - Visual *visual = DefaultVisual(peak_linux.display, screen); - int depth = DefaultDepth(peak_linux.display, screen); - - peak_linux_ximage = peak_x11.XCreateImage(peak_linux.display, visual, depth, ZPixmap, 0, (char *)peak_linux_buffer, peak_linux_width, peak_linux_height, 32, 0); - assert(peak_linux_ximage && "Failed to create XImage"); - - peak_x11.XMapRaised(peak_linux.display, peak_linux_window.window); - peak_x11.XFlush(peak_linux.display); + (void)dpy; + return ev->xany.window == *(Window *)arg; +} - peak_linux.initialized = true; +static int +peak_linux_buffer(PeakWindowInternal *w, uint32_t width, uint32_t height) +{ + int screen; + + if (w->ximage) { + w->ximage->data = NULL; + XDestroyImage(w->ximage); + w->ximage = NULL; + } + free(w->buffer); + w->buffer = calloc((size_t)width * height, sizeof *w->buffer); + if (!w->buffer) + return 0; + + w->width = width; + w->height = height; + screen = DefaultScreen(peak_linux.display); + w->ximage = peak_x11.XCreateImage(peak_linux.display, + DefaultVisual(peak_linux.display, screen), + DefaultDepth(peak_linux.display, screen), + ZPixmap, 0, (char *)w->buffer, width, height, 32, 0); + if (!w->ximage) { + free(w->buffer); + w->buffer = NULL; + return 0; + } + return 1; } -void -peak_platform_window_close() +static int +peak_platform_init(void) { - if (!peak_linux.initialized) return; + void *handle; + + if (!peak_x11.XOpenDisplay) { + if (!(handle = dlopen(PEAK_X11_LINUX, RTLD_LOCAL | RTLD_NOW))) { + fputs("Failed to load X11 library. What system are you fucking using and abusing?", stderr); + return 0; + } + if (!peak_internal_x11_load(handle)) { + fputs("Failed to load X11 symbols", stderr); + return 0; + } + } + if (!peak_linux.display) { + if (!(peak_linux.display = peak_x11.XOpenDisplay(NULL))) { + fputs("Failed to open X11 display", stderr); + return 0; + } + peak_linux.wm_delete_window = peak_x11.XInternAtom(peak_linux.display, "WM_DELETE_WINDOW", False); + } + return 1; +} - if (peak_linux_ximage) { - peak_linux_ximage->data = NULL; - XDestroyImage(peak_linux_ximage); - peak_linux_ximage = NULL; - } +static void +peak_platform_quit(void) +{ + if (!peak_linux.display) + return; + peak_x11.XCloseDisplay(peak_linux.display); + peak_linux.display = 0; +} - free(peak_linux_buffer); - peak_linux_buffer = NULL; +static PeakWindowInternal +peak_platform_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags) +{ + PeakWindowInternal w = {0}; + int screen; + + (void)flags; + if (!peak_linux.display && !peak_platform_init()) + return w; + + screen = DefaultScreen(peak_linux.display); + w.window = peak_x11.XCreateSimpleWindow(peak_linux.display, + RootWindow(peak_linux.display, screen), 0, 0, width, height, 0, + BlackPixel(peak_linux.display, screen), BlackPixel(peak_linux.display, screen)); + peak_x11.XStoreName(peak_linux.display, w.window, name); + peak_x11.XSetWMProtocols(peak_linux.display, w.window, &peak_linux.wm_delete_window, 1); + peak_x11.XSelectInput(peak_linux.display, w.window, + KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | + PointerMotionMask | StructureNotifyMask); + w.gfx_ctx = peak_x11.XCreateGC(peak_linux.display, w.window, 0, NULL); + + if (!peak_linux_buffer(&w, width, height)) { + peak_x11.XFreeGC(peak_linux.display, w.gfx_ctx); + peak_x11.XDestroyWindow(peak_linux.display, w.window); + return (PeakWindowInternal){0}; + } + + peak_x11.XMapRaised(peak_linux.display, w.window); + peak_x11.XFlush(peak_linux.display); + return w; +} - peak_x11.XFreeGC(peak_linux.display, peak_linux_window.gfx_ctx); - peak_x11.XDestroyWindow(peak_linux.display, peak_linux_window.window); - peak_x11.XCloseDisplay(peak_linux.display); +static void +peak_platform_window_close(PeakWindowInternal *w) +{ + if (!w || !w->window || !peak_linux.display) + return; + if (w->ximage) { + w->ximage->data = NULL; + XDestroyImage(w->ximage); + } + free(w->buffer); + if (w->gfx_ctx) + peak_x11.XFreeGC(peak_linux.display, w->gfx_ctx); + peak_x11.XDestroyWindow(peak_linux.display, w->window); + *w = (PeakWindowInternal){0}; +} - peak_linux.initialized = false; +static uint32_t * +peak_platform_window_buffer(PeakWindowInternal *w, size_t *width, size_t *height) +{ + if (!w || !w->window) { + *width = 0; + *height = 0; + return NULL; + } + *width = w->width; + *height = w->height; + return w->buffer; } -uint32_t * -peak_platform_window_buffer(size_t *width, size_t *height) -{ - *width = peak_linux_width; - *height = peak_linux_height; - return peak_linux_buffer; -} - -bool -peak_platform_epoll(PeakEvent *ev) -{ - if (!peak_linux.initialized) return false; - - Display *dpy = peak_linux.display; - - while (peak_x11.XPending(dpy) > 0) { - XEvent xev; - peak_x11.XNextEvent(dpy, &xev); - - switch (xev.type) { - - case ClientMessage: - if ((Atom)xev.xclient.data.l[0] == peak_linux.wm_delete_window) { - ev->type = PEAK_EVENT_WINDOW_CLOSE; - printf("CLLOOOOOOOSEEEE"); - return true; - } - continue; - - case KeyPress: - case KeyRelease: { - KeySym sym = peak_x11.XLookupKeysym(&xev.xkey, 0); - ev->type = (xev.type == KeyPress) ? PEAK_EVENT_KEY_DOWN : PEAK_EVENT_KEY_UP; - ev->key.key = peak__x11_map_key(sym); - ev->key.mod = peak__x11_map_mod(xev.xkey.state); - return true; - } - - case ButtonPress: - case ButtonRelease: { - ev->type = PEAK_EVENT_POINTER; - ev->pointer.state = (xev.type == ButtonPress) ? PEAK_POINTER_PRESSED : PEAK_POINTER_RELEASED; - ev->pointer.x = (float)xev.xbutton.x; - ev->pointer.y = (float)xev.xbutton.y; - switch (xev.xbutton.button) { - case Button1: ev->pointer.type = PEAK_POINTER_LEFT; break; - case Button2: ev->pointer.type = PEAK_POINTER_MIDDLE; break; - case Button3: ev->pointer.type = PEAK_POINTER_RIGHT; break; - default: ev->pointer.type = PEAK_POINTER_LEFT; break; - } - return true; - } - - case MotionNotify: - ev->type = PEAK_EVENT_POINTER; - ev->pointer.state = PEAK_POINTER_MOVED; - ev->pointer.x = (float)xev.xmotion.x; - ev->pointer.y = (float)xev.xmotion.y; - if (xev.xmotion.state & Button3Mask) - ev->pointer.type = PEAK_POINTER_RIGHT; - else if (xev.xmotion.state & Button2Mask) - ev->pointer.type = PEAK_POINTER_MIDDLE; - else - ev->pointer.type = PEAK_POINTER_LEFT; - return true; - - case ConfigureNotify: - if ((size_t)xev.xconfigure.width != peak_linux_width || - (size_t)xev.xconfigure.height != peak_linux_height) { - ev->type = PEAK_EVENT_WINDOW_RESIZE; - ev->resize.width = (uint32_t)xev.xconfigure.width; - ev->resize.height = (uint32_t)xev.xconfigure.height; - return true; - } - continue; - - case Expose: - if (xev.xexpose.count == 0) { - peak_x11.XPutImage(dpy, peak_linux_window.window, peak_linux_window.gfx_ctx, peak_linux_ximage, 0, 0, 0, 0, peak_linux_width, peak_linux_height); - } - continue; - - default: - continue; - } - } +static void +peak_platform_window_present(PeakWindowInternal *w) +{ + if (!w || !w->ximage || !peak_linux.display) + return; + peak_x11.XPutImage(peak_linux.display, w->window, w->gfx_ctx, w->ximage, + 0, 0, 0, 0, w->width, w->height); + peak_x11.XFlush(peak_linux.display); +} - peak_x11.XPutImage(dpy, peak_linux_window.window, peak_linux_window.gfx_ctx, peak_linux_ximage, 0, 0, 0, 0, peak_linux_width, peak_linux_height); - peak_x11.XFlush(dpy); - return false; +static bool +peak_platform_epoll(PeakWindowInternal *w, PeakEvent *ev) +{ + XEvent xev; + + if (!w || !w->window || !peak_linux.display) + return 0; + + while (peak_x11.XCheckIfEvent(peak_linux.display, &xev, peak_internal_x11_window_match, (XPointer)&w->window)) { + switch (xev.type) { + case ClientMessage: + if ((Atom)xev.xclient.data.l[0] == peak_linux.wm_delete_window) { + ev->type = PEAK_EVENT_WINDOW_CLOSE; + return 1; + } + continue; + case KeyPress: + case KeyRelease: + ev->type = (xev.type == KeyPress) ? PEAK_EVENT_KEY_DOWN : PEAK_EVENT_KEY_UP; + ev->key.key = peak_internal_x11_key_map(peak_x11.XLookupKeysym(&xev.xkey, 0)); + ev->key.mod = peak_internal_x11_mod_map(xev.xkey.state); + return 1; + case ButtonPress: + case ButtonRelease: + ev->type = PEAK_EVENT_POINTER; + ev->pointer.state = (xev.type == ButtonPress) ? PEAK_POINTER_PRESSED : PEAK_POINTER_RELEASED; + ev->pointer.x = (float)xev.xbutton.x; + ev->pointer.y = (float)xev.xbutton.y; + ev->pointer.type = (xev.xbutton.button == Button2) ? PEAK_POINTER_MIDDLE : + (xev.xbutton.button == Button3) ? PEAK_POINTER_RIGHT : PEAK_POINTER_LEFT; + return 1; + case MotionNotify: + ev->type = PEAK_EVENT_POINTER; + ev->pointer.state = PEAK_POINTER_MOVED; + ev->pointer.x = (float)xev.xmotion.x; + ev->pointer.y = (float)xev.xmotion.y; + ev->pointer.type = (xev.xmotion.state & Button3Mask) ? PEAK_POINTER_RIGHT : + (xev.xmotion.state & Button2Mask) ? PEAK_POINTER_MIDDLE : PEAK_POINTER_LEFT; + return 1; + case ConfigureNotify: { + uint32_t width = (uint32_t)xev.xconfigure.width; + uint32_t height = (uint32_t)xev.xconfigure.height; + if (width == w->width && height == w->height) + continue; + if (!peak_linux_buffer(w, width, height)) + continue; + ev->type = PEAK_EVENT_WINDOW_RESIZE; + ev->resize.width = w->width; + ev->resize.height = w->height; + return 1; + } + default: + continue; + } + } + return 0; } /* --- End of p_linux.c --- */ #elif defined(PEAK_WEB) /* --- Start of p_emscripten.c --- */ +#include +#include #include #include -#include - -#define PEAK_CANVAS "#PeakCanvas" +#include -static PeakKeyCode map_key(const char *code); -static PeakKeyMod map_mod(const EmscriptenKeyboardEvent *keyEvent); -static EM_BOOL key_callback(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData); -static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); -static EM_BOOL resize_callback(int eventType, const EmscriptenUiEvent *uiEvent, void *userData); +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; +} -void -peak_platform_window_open() +static PeakKeyCode +peak_web_key_map(const char *code) { - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_TRUE, key_callback); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_TRUE, key_callback); - emscripten_set_mousedown_callback(PEAK_CANVAS, NULL, EM_TRUE, mouse_callback); - emscripten_set_mouseup_callback(PEAK_CANVAS, NULL, EM_TRUE, mouse_callback); - emscripten_set_mousemove_callback(PEAK_CANVAS, NULL, EM_TRUE, mouse_callback); - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, EM_TRUE, resize_callback); + 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; } -void -peak_platform_window_close() +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; } -uint32_t* -peak_platform_window_buffer(size_t *width, size_t *height) +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; } -bool -peak_platform_epoll(PeakEvent *ev) +static void +peak_web_listen(struct peak_web_win *w, int on) { - return 0; + 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); } -/* --- End of p_emscripten.c --- */ -#endif -/* --- Start of peak.c --- */ -static inline void peak__put_pixel(int x, int y, uint32_t color); -static inline uint32_t peak__lerp_color(uint32_t c0, uint32_t c1, float t); -static inline float peak__edge_cross(float ax, float ay, float bx, float by, float cx, float cy); - -static uint32_t *peak_window_buffer; -static size_t peak_window_width; -static size_t peak_window_height; -static int peak_clip_x; -static int peak_clip_y; -static size_t peak_clip_width; -static size_t peak_clip_height; +static int +peak_platform_init(void) +{ + return 1; +} -void -peak__put_pixel(int x, int y, uint32_t color) +static void +peak_platform_quit(void) { - if (x < peak_clip_x || x >= (int)(peak_clip_x + peak_clip_width) || - y < peak_clip_y || y >= (int)(peak_clip_y + peak_clip_height)) { - return; - } - if (x < 0 || x >= (int)peak_window_width || y < 0 || y >= (int)peak_window_height) { - return; - } - peak_window_buffer[y * peak_window_width + x] = color; } -uint32_t -peak__lerp_color(uint32_t c0, uint32_t c1, float t) +static PeakWindowInternal +peak_platform_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags) { - if (t <= 0.0f) return c0; - if (t >= 1.0f) return c1; + PeakWindowInternal intern = {0}; + struct peak_web_win *w; - uint32_t a0 = (c0 >> 24) & 0xFF, a1 = (c1 >> 24) & 0xFF; - uint32_t r0 = (c0 >> 16) & 0xFF, r1 = (c1 >> 16) & 0xFF; - uint32_t g0 = (c0 >> 8) & 0xFF, g1 = (c1 >> 8) & 0xFF; - uint32_t b0 = c0 & 0xFF, b1 = c1 & 0xFF; + (void)flags; - uint32_t a = (uint32_t)(a0 + t * ((float)a1 - (float)a0)); - uint32_t r = (uint32_t)(r0 + t * ((float)r1 - (float)r0)); - uint32_t g = (uint32_t)(g0 + t * ((float)g1 - (float)g0)); - uint32_t b = (uint32_t)(b0 + t * ((float)b1 - (float)b0)); + 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; - return (a << 24) | (r << 16) | (g << 8) | b; -} + peak_web_dom_open(w->name, (int)width, (int)height); -float -peak__edge_cross(float ax, float ay, float bx, float by, float cx, float cy) -{ - return (cx - ax) * (by - ay) - (cy - ay) * (bx - ax); + peak_web_listen(w, 1); + intern.w = w; + return intern; } -void -peak_init(void) +static void +peak_platform_window_close(PeakWindowInternal *intern) { - peak_platform_window_open(); - peak_window_buffer = peak_platform_window_buffer(&peak_window_width, &peak_window_height); - peak_clip_x = 0; - peak_clip_y = 0; - peak_clip_width = peak_window_width; - peak_clip_height = peak_window_height; + struct peak_web_win *w; + if (!intern || !intern->w) + return; + w = intern->w; + peak_web_listen(w, 0); + free(w); + intern->w = NULL; } -void -peak_quit(void) +static uint32_t * +peak_platform_window_buffer(PeakWindowInternal *intern, size_t *width, size_t *height) { - peak_platform_window_close(); + 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; } -void -peak_extensions(void) +static void +peak_platform_window_present(PeakWindowInternal *intern) { - // TODO + 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); } -bool -peak_poll_events(PeakEvent *ev) +static bool +peak_platform_epoll(PeakWindowInternal *intern, PeakEvent *ev) { - return peak_platform_epoll(ev); + struct peak_web_win *w = intern ? intern->w : NULL; + return w ? peak_q_pop(&w->q, ev) : 0; } +/* --- End of p_emscripten.c --- */ +#endif -void -peak_blit(int offset_x, int offset_y, const uint32_t *rgba, size_t width, size_t height) -{ - for (size_t y = 0; y < height; ++y) { - int target_y = offset_y + (int)y; - if (target_y < peak_clip_y || target_y >= (int)(peak_clip_y + peak_clip_height) || - target_y < 0 || target_y >= (int)peak_window_height) continue; - - int start_x = offset_x < peak_clip_x ? peak_clip_x : offset_x; - int end_x = (offset_x + (int)width) > (int)(peak_clip_x + peak_clip_width) - ? (int)(peak_clip_x + peak_clip_width) - : (offset_x + (int)width); +/* We define PeakWindow only when we know the type of peak_window_internal_t */ +struct PeakWindow { + PeakWindowInternal internal; + int (*tick)(struct PeakWindow *win, void *userdata); + void *userdata; + uint32_t *buffer; + uint32_t width; + uint32_t height; + uint32_t bufsize; + int running; +}; - if (start_x >= end_x) continue; - size_t src_offset_x = (size_t)(start_x - offset_x); - size_t copy_pixels = (size_t)(end_x - start_x); +/* --- Start of peak.c --- */ +static uint32_t * +peak_window_sync(PeakWindow *win, size_t *width, size_t *height) +{ + size_t w = 0, h = 0; + win->buffer = peak_platform_window_buffer(&win->internal, &w, &h); + win->width = (uint32_t)w; + win->height = (uint32_t)h; + win->bufsize = win->width * win->height; + if (width) *width = w; + if (height) *height = h; + return win->buffer; +} - memcpy(&peak_window_buffer[target_y * peak_window_width + start_x], &rgba[y * width + src_offset_x], copy_pixels * sizeof(uint32_t)); - } +int +peak_init(void) +{ + return peak_platform_init(); } void -peak_clip(int x, int y, size_t w, size_t h) +peak_quit(void) { - peak_clip_x = x; - peak_clip_y = y; - peak_clip_width = w; - peak_clip_height = h; + peak_platform_quit(); } -void -peak_clip_reset(void) +PeakWindow +peak_window_open(const char *name, uint32_t width, uint32_t height, uint32_t flags) { - peak_clip_x = 0; - peak_clip_y = 0; - peak_clip_width = peak_window_width; - peak_clip_height = peak_window_height; + PeakWindow win = {0}; + if (!name || !name[0]) name = "Peak"; + if (!width) width = 800; + if (!height) height = 600; + win.internal = peak_platform_window_open(name, width, height, flags); + if (!peak_window_sync(&win, NULL, NULL)) return win; + win.running = 1; + return win; } void -peak_draw_rectangle(int x, int y, size_t w, size_t h, uint32_t color) -{ - int start_x = (x < peak_clip_x) ? peak_clip_x : x; - int start_y = (y < peak_clip_y) ? peak_clip_y : y; - int end_x = (x + (int)w > (int)(peak_clip_x + peak_clip_width)) ? (int)(peak_clip_x + peak_clip_width) : x + (int)w; - int end_y = (y + (int)h > (int)(peak_clip_y + peak_clip_height)) ? (int)(peak_clip_y + peak_clip_height) : y + (int)h; - - for (int py = start_y; py < end_y; ++py) { - if (py < 0 || py >= (int)peak_window_height) continue; - for (int px = start_x; px < end_x; ++px) { - if (px < 0 || px >= (int)peak_window_width) continue; - peak_window_buffer[py * peak_window_width + px] = color; - } - } +peak_window_close(PeakWindow *win) +{ + win->running = 0; + peak_platform_window_close(&win->internal); + win->buffer = NULL; + win->width = 0; + win->height = 0; + win->bufsize = 0; } -void -peak_draw_rectangle_gradient(int x, int y, size_t w, size_t h, uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) -{ - for (size_t ry = 0; ry < h; ++ry) { - float ty = (h > 1) ? (float)ry / (float)(h - 1) : 0.0f; - uint32_t left_color = peak__lerp_color(c0, c3, ty); - uint32_t right_color = peak__lerp_color(c1, c2, ty); - - for (size_t rx = 0; rx < w; ++rx) { - float tx = (w > 1) ? (float)rx / (float)(w - 1) : 0.0f; - uint32_t final_color = peak__lerp_color(left_color, right_color, tx); - peak__put_pixel(x + (int)rx, y + (int)ry, final_color); - } - } +int +peak_window_epoll(PeakWindow *win, PeakEvent *ev) +{ + int got = peak_platform_epoll(&win->internal, ev); + if (got && ev->type == PEAK_EVENT_WINDOW_RESIZE) + peak_window_sync(win, NULL, NULL); + return got; } -void -peak_draw_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) -{ - int min_x = (x0 < x1) ? ((x0 < x2) ? x0 : x2) : ((x1 < x2) ? x1 : x2); - int max_x = (x0 > x1) ? ((x0 > x2) ? x0 : x2) : ((x1 > x2) ? x1 : x2); - int min_y = (y0 < y1) ? ((y0 < y2) ? y0 : y2) : ((y1 < y2) ? y1 : y2); - int max_y = (y0 > y1) ? ((y0 > y2) ? y0 : y2) : ((y1 > y2) ? y1 : y2); - - float area = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x2, (float)y2); - if (area == 0.0f) return; - - for (int y = min_y; y <= max_y; ++y) { - for (int x = min_x; x <= max_x; ++x) { - float w0 = peak__edge_cross((float)x1, (float)y1, (float)x2, (float)y2, (float)x, (float)y); - float w1 = peak__edge_cross((float)x2, (float)y2, (float)x0, (float)y0, (float)x, (float)y); - float w2 = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x, (float)y); - - if ((w0 >= 0 && w1 >= 0 && w2 >= 0) || (w0 <= 0 && w1 <= 0 && w2 <= 0)) { - peak__put_pixel(x, y, color); - } - } - } +uint32_t * +peak_window_backbuffer(PeakWindow *win, size_t *width, size_t *height) +{ + return peak_window_sync(win, width, height); } void -peak_draw_triangle_gradient(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t c0, uint32_t c1, uint32_t c2) -{ - int min_x = (x0 < x1) ? ((x0 < x2) ? x0 : x2) : ((x1 < x2) ? x1 : x2); - int max_x = (x0 > x1) ? ((x0 > x2) ? x0 : x2) : ((x1 > x2) ? x1 : x2); - int min_y = (y0 < y1) ? ((y0 < y2) ? y0 : y2) : ((y1 < y2) ? y1 : y2); - int max_y = (y0 > y1) ? ((y0 > y2) ? y0 : y2) : ((y1 > y2) ? y1 : y2); - - float area = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x2, (float)y2); - if (area == 0.0f) return; - - for (int y = min_y; y <= max_y; ++y) { - for (int x = min_x; x <= max_x; ++x) { - float w0 = peak__edge_cross((float)x1, (float)y1, (float)x2, (float)y2, (float)x, (float)y) / area; - float w1 = peak__edge_cross((float)x2, (float)y2, (float)x0, (float)y0, (float)x, (float)y) / area; - float w2 = peak__edge_cross((float)x0, (float)y0, (float)x1, (float)y1, (float)x, (float)y) / area; - - if (w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f) { - uint32_t col_a = peak__lerp_color(c0, c1, w1 / (w0 + w1 + 1e-6f)); - uint32_t final_color = peak__lerp_color(col_a, c2, w2); - peak__put_pixel(x, y, final_color); - } - } - } +peak_window_clear(PeakWindow *win, float r, float g, float b, float a) +{ + uint32_t c, i; + if (!win || !win->buffer) return; +#if defined(PEAK_WEB) + /* ImageData RGBA bytes = LE 0xAABBGGRR */ + c = (uint32_t)(r * 255.f) + | ((uint32_t)(g * 255.f) << 8) + | ((uint32_t)(b * 255.f) << 16) + | ((uint32_t)(a * 255.f) << 24); +#else + c = ((uint32_t)(a * 255.f) << 24) + | ((uint32_t)(r * 255.f) << 16) + | ((uint32_t)(g * 255.f) << 8) + | (uint32_t)(b * 255.f); +#endif + for (i = 0; i < win->bufsize; i++) + win->buffer[i] = c; } void -peak_draw_line(int x0, int y0, int x1, int y1, size_t width, uint32_t color) -{ - int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; - int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; - int err = dx + dy, e2; - - int half_w = (int)width / 2; - - while (1) { - /* Draw square footprint for line thickness */ - for (int ox = -half_w; ox <= half_w; ++ox) { - for (int oy = -half_w; oy <= half_w; ++oy) { - peak__put_pixel(x0 + ox, y0 + oy, color); - } - } - if (x0 == x1 && y0 == y1) break; - e2 = 2 * err; - if (e2 >= dy) { err += dy; x0 += sx; } - if (e2 <= dx) { err += dx; y0 += sy; } - } +peak_window_present(PeakWindow *win) +{ + if (win) peak_platform_window_present(&win->internal); } -void -peak_draw_line_gradient(int x0, int y0, int x1, int y1, size_t width, uint32_t c0, uint32_t c1) -{ - float total_dist = sqrtf((float)((x1 - x0)*(x1 - x0) + (y1 - y0)*(y1 - y0))); - int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; - int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; - int err = dx + dy, e2; - - int orig_x0 = x0, orig_y0 = y0; - int half_w = (int)width / 2; - - while (1) { - float current_dist = sqrtf((float)((x0 - orig_x0)*(x0 - orig_x0) + (y0 - orig_y0)*(y0 - orig_y0))); - float t = (total_dist > 0.0f) ? (current_dist / total_dist) : 0.0f; - uint32_t color = peak__lerp_color(c0, c1, t); - - for (int ox = -half_w; ox <= half_w; ++ox) { - for (int oy = -half_w; oy <= half_w; ++oy) { - peak__put_pixel(x0 + ox, y0 + oy, color); - } - } - - if (x0 == x1 && y0 == y1) break; - e2 = 2 * err; - if (e2 >= dy) { err += dy; x0 += sx; } - if (e2 <= dx) { err += dx; y0 += sy; } +#ifdef PEAK_WEB +#include +static void +peak_internal_web_step(void *arg) +{ + PeakWindow *win = arg; + if (!win->tick(win, win->userdata) || !win->running) { + win->running = 0; + emscripten_cancel_main_loop(); } } +#endif +void +peak_window_run(PeakWindow *win, int (*peak_tick)(PeakWindow *win, void *userdata), void *userdata) +{ + assert(win && "peak_window_run needs a window"); + assert(peak_tick && "peak_window_run needs tick callback"); + win->tick = peak_tick; + win->userdata = userdata; + win->running = 1; +#ifdef PEAK_WEB + emscripten_set_main_loop_arg(peak_internal_web_step, win, 0, 1); +#else + while (win->running && win->tick(win, win->userdata)); +#endif +} /* --- End of peak.c --- */ #endif // PEAK_IMPLEMENTATION -- cgit v1.3.1